From 9f3b007615b6ed6251bb682a80ac9122b45faf7d Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 28 Aug 2020 13:28:23 -1000 Subject: [PATCH 001/287] Initial commit --- .babelrc | 18 + .circleci/config.yml | 97 + .env.example | 9 + .eslintrc.json | 201 + .gitignore | 33 + .haml-lint.yml | 6 + .overcommit.yml | 963 + .pairs | 15 + .postcssrc.yml | 3 + .rubocop.yml | 212 + .ruby-version | 1 + .stylelintrc | 161 + Gemfile | 94 + Gemfile.lock | 492 + Procfile | 3 + Procfile.local | 3 + README.md | 109 + Rakefile | 6 + app.json | 168 + app/assets/config/manifest.js | 3 + app/assets/images/favicon.ico | Bin 0 -> 1437 bytes app/assets/images/loader-black.svg | 12 + app/assets/images/loader-cyan.svg | 12 + app/assets/images/loader-white.svg | 12 + app/assets/images/lost.jpg | Bin 0 -> 319991 bytes app/assets/images/spinner.gif | Bin 0 -> 24772 bytes app/assets/images/svg/baseline-notes-24px.svg | 4 + app/assets/images/svg/g-learn-lockup.svg | 37 + app/assets/images/svg/galvanize-logo.svg | 48 + app/assets/images/svg/github-icon.svg | 6 + app/assets/images/svg/mobile-logo.svg | 24 + app/assets/images/svg/octicon-git-branch.svg | 1 + .../images/svg/svg-sprite-action-symbol.svg | 1 + .../images/svg/svg-sprite-av-symbol.svg | 1 + .../images/svg/svg-sprite-device-symbol.svg | 1 + .../images/svg/svg-sprite-image-symbol.svg | 1 + .../svg/svg-sprite-navigation-symbol.svg | 1 + app/assets/javascripts/application.js | 7 + app/assets/javascripts/mobile.js | 10 + app/assets/stylesheets/application.scss | 103 + app/assets/stylesheets/base.scss | 66 + app/assets/stylesheets/bootstrap-custom.scss | 13 + app/assets/stylesheets/cohorts.scss | 9 + .../components/_404-container.scss | 43 + .../stylesheets/components/_action-menu.scss | 53 + .../components/_activity-dashboard.scss | 253 + .../components/_activity-feed.scss | 69 + .../components/_api-interactions.scss | 62 + .../stylesheets/components/_api_token.scss | 37 + .../components/_auth-style-search.scss | 26 + .../stylesheets/components/_blocks-index.scss | 133 + .../stylesheets/components/_blocks-stats.scss | 70 + .../stylesheets/components/_button.scss | 57 + .../stylesheets/components/_callouts.scss | 54 + .../components/_challenge-block.scss | 698 + .../components/_challenge_status.scss | 18 + .../components/_checkbox-input.scss | 15 + .../components/_checkpoint-landing.scss | 170 + .../components/_checkpoint-submission.scss | 152 + ...heckpoint-submissions-columns-student.scss | 72 + .../_checkpoint-submissions-columns.scss | 36 + .../_checkpoint-submissions-index.scss | 5 + .../_checkpoint_student_scores.scss | 184 + .../_checkpoint_submisstion_state.scss | 29 + .../components/_checkpoint_toolbar.scss | 207 + .../stylesheets/components/_code-block.scss | 21 + .../components/_cohort-submissions-table.scss | 798 + .../components/_cohort_releases.scss | 205 + .../stylesheets/components/_cohorts.scss | 957 + .../components/_content-file-show.scss | 342 + .../components/_content-file-sidebar.scss | 308 + .../_curriculum-checkpoint-summary.scss | 46 + .../components/_curriculum-progress.scss | 239 + .../stylesheets/components/_curriculum.scss | 277 + .../components/_dropdown-component.scss | 79 + .../components/_external-link.scss | 13 + .../components/_flash-message.scss | 21 + .../stylesheets/components/_footer.scss | 66 + .../components/_grade-buttons.scss | 12 + .../components/_integer_picker.scss | 57 + .../components/_lp-style-button.scss | 127 + .../components/_mastery-table.scss | 382 + app/assets/stylesheets/components/_modal.scss | 195 + .../components/_navigation-dropdown.scss | 103 + .../components/_notifications.scss | 202 + .../stylesheets/components/_pagination.scss | 46 + app/assets/stylesheets/components/_pill.scss | 78 + .../components/_point_grade_buttons.scss | 19 + .../components/_primary-header.scss | 100 + .../components/_primary-navigation.scss | 132 + .../stylesheets/components/_progress.scss | 59 + .../components/_progress_thresholds_key.scss | 35 + .../_progress_thresholds_modal.scss | 20 + .../_progress_thresholds_slider.scss | 8 + .../stylesheets/components/_search-bar.scss | 44 + .../components/_secondary-navigation.scss | 58 + .../stylesheets/components/_slideshow.scss | 264 + .../components/_sort-dropdown-component.scss | 52 + .../components/_standard-card.scss | 234 + .../components/_standard-cards.scss | 159 + .../components/_standards-mastery-beans.scss | 35 + .../components/_status_picker.scss | 132 + .../components/_student-mastery-table.scss | 156 + .../components/_student-name-bar.scss | 92 + .../_submissions-dashboard-table.scss | 506 + .../stylesheets/components/_svg-icon.scss | 33 + .../stylesheets/components/_thresholds.scss | 33 + .../components/_universal-list.scss | 46 + .../components/_universal-row.scss | 68 + .../components/_universal-table.scss | 121 + .../stylesheets/components/_user-avatar.scss | 65 + .../stylesheets/components/_video-player.scss | 10 + app/assets/stylesheets/components/badge.scss | 26 + .../components/challenge-detail-comments.scss | 94 + .../components/challenge-detail-view.scss | 38 + .../components/challenge-timeline.scss | 69 + .../components/new-comment-form.scss | 94 + .../stylesheets/components/partnerup.scss | 249 + .../components/progress-table.scss | 355 + .../stylesheets/hopscotch-overrides.scss | 100 + app/assets/stylesheets/mixins.scss | 197 + app/assets/stylesheets/mobile.scss | 1 + .../mobile/_submissions-dashboard-table.scss | 51 + app/assets/stylesheets/typography.scss | 5 + app/assets/stylesheets/variables.scss | 136 + .../activity_feed_item_component_props.rb | 120 + .../notifications_component_props.rb | 22 + .../standard_card_component_props.rb | 9 + app/controllers/api/application_controller.rb | 114 + .../api/v1/blocks/releases_controller.rb | 28 + app/controllers/api/v1/blocks_controller.rb | 52 + .../blocks/content_files_controller.rb | 218 + .../api/v1/cohorts/blocks/units_controller.rb | 19 + .../api/v1/cohorts/cohorts_controller.rb | 118 + .../api/v1/cohorts/users_controller.rb | 163 + .../api/v1/content_files_controller.rb | 34 + .../api/v1/pineapple_controller.rb | 6 + app/controllers/api/v1/releases_controller.rb | 70 + app/controllers/api/v1/users_controller.rb | 55 + app/controllers/application_controller.rb | 147 + app/controllers/blocks/releases_controller.rb | 69 + app/controllers/blocks_controller.rb | 77 + .../blocks/content_files_controller.rb | 306 + .../activities_controller.rb | 80 + .../cohorts/cohort_releases_controller.rb | 156 + .../checkpoint_submissions_controller.rb | 338 + .../submitted_challenge_answers_controller.rb | 346 + .../cohorts/content_files_controller.rb | 260 + .../cohorts/pairings_controller.rb | 49 + .../cohorts/standards_controller.rb | 43 + .../activities_controller.rb | 84 + .../cohorts/users/challenges_controller.rb | 182 + .../cohorts/users/performances_controller.rb | 41 + app/controllers/cohorts/users_controller.rb | 154 + app/controllers/cohorts_controller.rb | 463 + .../concerns/api/content_visibility_crud.rb | 58 + .../concerns/api/exception_handler.rb | 75 + app/controllers/concerns/api/response.rb | 5 + app/controllers/home_controller.rb | 28 + app/controllers/notifications_controller.rb | 32 + app/controllers/permalinks_controller.rb | 85 + .../submitted_challenge_answers_controller.rb | 120 + .../webhooks/auth/cohorts_controller.rb | 34 + .../webhooks/auth/users_controller.rb | 24 + app/exporters/performance_exporter.rb | 55 + app/exporters/progress_exporter.rb | 107 + app/finders/block_finder.rb | 7 + app/finders/checkpoint_submission_finder.rb | 74 + app/finders/cohort_user_finder.rb | 9 + app/finders/content_file_finder.rb | 20 + app/finders/performance_finder.rb | 71 + app/finders/release_finder.rb | 24 + app/finders/standard_finder.rb | 10 + .../submitted_challenge_answer_finder.rb | 28 + app/finders/user_finder.rb | 8 + app/helpers/application_helper.rb | 34 + app/helpers/json_helper.rb | 9 + app/helpers/secondary_navigation_helper.rb | 10 + app/helpers/standard_navigation_helper.rb | 165 + app/javascript/api.d.ts | 550 + app/javascript/components/AceEditor.tsx | 133 + app/javascript/components/Badge.tsx | 18 + app/javascript/components/Button.tsx | 26 + app/javascript/components/ButtonTo.tsx | 75 + app/javascript/components/Dropdown.tsx | 40 + app/javascript/components/Icon.tsx | 20 + app/javascript/components/Loading.tsx | 59 + app/javascript/components/Marked.tsx | 25 + app/javascript/components/Menu.tsx | 45 + app/javascript/components/Notifications.tsx | 116 + app/javascript/components/ProcessingIcon.tsx | 31 + app/javascript/components/RowKebab.tsx | 36 + app/javascript/components/ScoreCircle.tsx | 23 + app/javascript/components/SidebarProgress.tsx | 28 + app/javascript/components/SortDropdown.tsx | 36 + app/javascript/components/StandardBean.tsx | 36 + app/javascript/components/SvgRenderer.tsx | 26 + app/javascript/components/Timestamp.tsx | 16 + app/javascript/components/UserAvatar.tsx | 29 + .../components/activities/NewActivityForm.tsx | 138 + .../components/api/ApiInteractions.tsx | 142 + app/javascript/components/api/ApiToken.tsx | 86 + .../components/blocks/BlockPage-v2.tsx | 331 + .../components/blocks/BlocksIndex-v2.tsx | 140 + .../components/blocks/BlocksNewModal.tsx | 187 + .../components/blocks/BlocksNewRelease.tsx | 82 + .../components/blocks/BlocksRow.tsx | 105 + .../components/blocks/BlocksShow.tsx | 131 + .../components/blocks/BlocksStats.tsx | 59 + .../components/blocks/CohortsTooltip.tsx | 22 + .../components/challenges/AvatarBar.tsx | 45 + .../challenges/ChallengeDetailView.tsx | 69 + .../components/challenges/ChallengeShow.tsx | 128 + .../challenge_block/ChallengeActionBlock.tsx | 71 + .../challenge_block/ChallengeBlock.tsx | 1042 + .../ChallengeDetailActivities.tsx | 45 + .../ChallengeExplanationBlock.tsx | 42 + .../ChallengeFeedbackBlock.tsx | 109 + .../challenge_block/ChallengeHintsBlock.tsx | 86 + .../challenge_block/ChallengeInputs.tsx | 199 + .../ChallengeLocalTestResults.tsx | 47 + .../challenge_block/ChallengeRubricBlock.tsx | 42 + .../challenge_block/ChallengeStatus.tsx | 73 + .../challenge_block/ChallengeStatusBar.tsx | 77 + .../challenge_block/ChallengeTestResults.tsx | 34 + .../challenge_block/ChallengeTests.tsx | 42 + .../challenge_block/ChallengeTimeLine.tsx | 56 + .../challenge_block/GradeIndicator.tsx | 58 + .../challenge_block/GradedTimestamp.tsx | 33 + .../challenge_block/StatusPicker.tsx | 33 + .../challenges/local/run-local-challenge.ts | 137 + .../components/challenges/local/sandbox.ts | 192 + .../challenges/local/stack-traces.ts | 48 + .../CheckpointAfterSubmitButton.tsx | 17 + .../CheckpointAfterSubmitModal.tsx | 136 + .../CheckpointAfterSubmitModalError.tsx | 46 + .../CheckpointSubmissionChallenges.tsx | 62 + .../checkpoints/CheckpointSubmissionShow.tsx | 364 + .../checkpoints/CheckpointSubmissionState.tsx | 41 + .../CheckpointSubmissionStudentNameBar.tsx | 118 + .../components/cohorts/CohortsIndex.tsx | 494 + .../activity_dashboard/ActivityDashboard.tsx | 207 + .../cohorts/activity_feed/ActivityFeed.tsx | 67 + .../cohort_releases/ReleaseVersionsTable.tsx | 81 + .../cohort_submissions/CohortSubmissions.tsx | 602 + .../CohortSubmissionsChallengeItem.tsx | 113 + .../CohortSubmissionsLessonRow.tsx | 97 + .../CohortSubmissionsPerformanceModal.tsx | 110 + .../CohortSubmissionsStandardRow.tsx | 97 + .../CohortSubmissionsStudentColumn.tsx | 226 + .../CohortSubmissionsStudentNameBar.tsx | 70 + .../CohortSubmissionsStudentStandard.tsx | 208 + .../CohortSubmissionsTable.tsx | 73 + .../CohortSubmissionsUnitPercent.tsx | 104 + .../cohorts/mastery/MasteryTable.tsx | 312 + .../components/cohorts/mastery/MetricRow.tsx | 25 + .../cohorts/mastery/MetricsBody.tsx | 79 + .../cohorts/mastery/PerformanceCell.tsx | 53 + .../cohorts/mastery/PerformanceRow.tsx | 65 + .../cohorts/mastery/ReleaseBody.tsx | 75 + .../cohorts/mastery/StandardRow.tsx | 62 + .../cohorts/mastery/StudentHeader.tsx | 30 + .../cohorts/mastery/StudentMasteryTable.tsx | 310 + .../cohorts/mastery/StudentReleaseRow.tsx | 50 + .../components/cohorts/pairing/GroupPairs.tsx | 243 + .../cohorts/pairing/InnerStudentList.tsx | 48 + .../components/cohorts/pairing/NewPairing.tsx | 294 + .../cohorts/pairing/PairingBoard.tsx | 62 + .../cohorts/pairing/StudentItem.tsx | 77 + .../cohorts/pairing/StudentList.tsx | 51 + .../progress/MasteryProgressionBar.tsx | 40 + .../progress/ProgressThresholdCellHeaders.tsx | 89 + .../cohorts/progress/StudentProgress.tsx | 409 + .../cohorts/progress/StudentProgressBar.tsx | 40 + .../cohorts/progress/StudentProgressCells.tsx | 75 + .../cohorts/progress/StudentProgressRow.tsx | 64 + .../progress/StudentProgressSortArrow.tsx | 22 + .../cohorts/settings/BranchReleaseModal.tsx | 73 + .../settings/CohortBlockReleaseRow.tsx | 374 + .../cohorts/settings/CohortContentTab.tsx | 204 + .../cohorts/settings/CohortInfo.tsx | 40 + .../cohorts/settings/CohortSettingsResync.tsx | 327 + .../cohorts/settings/CohortSettingsTabs.tsx | 176 + .../settings/CohortVisibilitySection.tsx | 197 + .../settings/CurriculumSettingsTab.tsx | 198 + .../cohorts/settings/ReleaseVersionModal.tsx | 97 + .../cohorts/settings/ReleaseVersionRow.tsx | 160 + .../cohorts/settings/UserImport.tsx | 138 + .../components/cohorts/settings/UserKebab.tsx | 71 + .../AnswerStatusRollup.tsx | 150 + .../HeaderStandardContainer.tsx | 79 + .../StandardContainer.tsx | 56 + .../SubmissionsDashboard.tsx | 389 + .../SubmissionsDashboardChallengeItem.tsx | 69 + .../SubmissionsDashboardContentFileRow.tsx | 100 + .../SubmissionsDashboardStudentColumn.tsx | 69 + .../SubmissionsDashboardStudentNameBar.tsx | 68 + .../SubmissionsDashboardTable.tsx | 61 + .../components/content_files/ActionMenus.tsx | 150 + .../components/content_files/Lesson.tsx | 158 + .../content_files/MarkdownRenderer.tsx | 43 + .../components/content_files/PDFRenderer.tsx | 57 + .../components/content_files/SideBar.tsx | 248 + .../content_files/SubmissionRenderer.tsx | 92 + .../content_files/checkpoints/Checkpoint.tsx | 680 + .../checkpoints/CheckpointActionBar.tsx | 192 + .../checkpoints/CheckpointDetails.tsx | 271 + .../checkpoints/CheckpointLanding.tsx | 159 + .../CheckpointLandingAttribute.tsx | 39 + .../checkpoints/CheckpointPairs.tsx | 172 + .../content_files/checkpoints/PairAvatars.tsx | 31 + .../student_scores/CheckpointStudentRow.tsx | 175 + .../CheckpointStudentScores.tsx | 187 + .../curriculum/CheckpointSummary.tsx | 36 + .../curriculum/CohortCurriculum.tsx | 186 + .../curriculum/CurriculumStandardCard.tsx | 126 + .../curriculum/ProgressIndicators.tsx | 134 + .../components/curriculum/StandardBeans.tsx | 28 + .../curriculum/StandardsRenderer.tsx | 123 + .../StudentOverallProgressDoughnut.tsx | 105 + app/javascript/components/lib/ace.ts | 92 + .../notifications/NotificationsIcon.tsx | 26 + .../shared/ActionMenu/ActionMenu.tsx | 32 + .../components/shared/Button/Button.tsx | 39 + .../ChallengePoints/ChallengePoints.tsx | 123 + .../ChallengePoints/PartialCreditBaton.tsx | 18 + .../shared/ChallengePoints/SpinText.tsx | 79 + .../IntegerPicker/IntegerPicker.tsx | 29 + .../components/shared/DonutRing/DonutRing.tsx | 177 + .../components/ColorDonut/ColorDonut.tsx | 74 + .../CompleteDonut/CompleteDonut.tsx | 43 + .../UngradedDonut/UngradedDonut.tsx | 24 + .../UngradedDonut/UngradedDonut.tsx | 31 + .../ErrorMessagesTable/ErrorMessagesTable.tsx | 26 + .../shared/FlashAlert/FlashAlert.tsx | 29 + .../shared/FormatNumber/FormatNumber.tsx | 20 + .../components/shared/Icons/AlertSign.tsx | 16 + .../components/shared/Icons/Clear.tsx | 18 + .../components/shared/Icons/CloseButton.tsx | 16 + .../components/shared/Icons/Done.tsx | 16 + .../shared/Icons/KeyboardArrowDown.tsx | 16 + .../shared/Icons/KeyboardArrowUp.tsx | 16 + .../components/shared/Icons/SettingsCog.tsx | 15 + .../components/shared/Icons/StatusCircle.tsx | 17 + .../MasteryProgressBar/MasteryProgressBar.tsx | 74 + .../components/shared/Modal/Modal.tsx | 77 + .../shared/Pagination/Pagination.tsx | 40 + .../PercentageProgressBar.tsx | 107 + .../components/shared/Pill/Pill.tsx | 35 + .../PointGradeButtons/PointGradeButtons.tsx | 78 + .../shared/PointGradeButtons/SpinText.tsx | 79 + .../shared/ProgressBar/ProgressBar.tsx | 43 + .../ProgressThresholdsKey.tsx | 32 + .../ProgressThresholdsModal.tsx | 70 + .../ProgressThresholdsSlider.tsx | 46 + .../components/shared/SearchBar/SearchBar.tsx | 102 + .../components/shared/Slideshow/Slideshow.tsx | 210 + .../shared/UngradedDonut/UngradedDonut.tsx | 31 + .../shared/UniversalList/UniversalList.tsx | 44 + .../shared/UniversalRow/UniversalRow.tsx | 66 + .../shared/UniversalTable/UniversalTable.tsx | 163 + .../standards/CompletionScoringBlock.tsx | 128 + .../standards/MasteryScoringBlock.tsx | 100 + .../components/standards/StandardCard.tsx | 211 + .../standards/StandardScoreButton.tsx | 41 + .../standards/StandardScoringBlock.tsx | 72 + .../standards/StandardTopicsRollups.tsx | 67 + .../components/vendor/ReactTooltip.js | 3 + app/javascript/generated/routes.ts | 548 + app/javascript/globals.ts | 145 + app/javascript/lib/http.ts | 58 + app/javascript/lib/utils.ts | 191 + app/javascript/packs/application.js | 13 + app/javascript/packs/server_rendering.js | 5 + app/jobs/application_job.rb | 2 + app/jobs/branch_release_notifier_job.rb | 16 + app/jobs/checkpoint_paired_submission_job.rb | 42 + .../content_file_default_visibility_job.rb | 42 + app/jobs/content_file_visit_job.rb | 28 + app/jobs/create_api_interaction_job.rb | 29 + app/jobs/create_release_job.rb | 110 + app/jobs/evaluate_code_snippet_job.rb | 17 + app/jobs/evaluate_custom_snippet_job.rb | 17 + app/jobs/evaluate_project_job.rb | 16 + app/jobs/grade_timed_checkpoint_job.rb | 90 + app/jobs/import_student_work_job.rb | 121 + app/jobs/progress_csv_job.rb | 16 + app/jobs/release_notifier_job.rb | 15 + app/jobs/resync_course_job.rb | 27 + app/jobs/set_block_caches_job.rb | 11 + app/jobs/slack_challenge_performance_job.rb | 29 + app/jobs/slack_dev_notify_job.rb | 5 + app/jobs/slack_ds_prep_job.rb | 5 + app/jobs/slack_job.rb | 30 + app/jobs/slack_se_prep_job.rb | 5 + app/jobs/slack_student_job.rb | 5 + app/jobs/student_progress_auth_job.rb | 20 + app/jobs/switch_to_branch_job.rb | 68 + app/mailers/application_mailer.rb | 10 + app/mailers/user_mailer.rb | 13 + app/models/activity.rb | 27 + app/models/api_interaction.rb | 12 + app/models/application_record.rb | 3 + app/models/block.rb | 59 + app/models/challenge.rb | 95 + app/models/checkpoint_submission.rb | 126 + app/models/cohort.rb | 117 + app/models/cohort_release.rb | 15 + app/models/cohort_user.rb | 13 + app/models/concerns/findable_by_uid.rb | 13 + app/models/concerns/read_only_model.rb | 8 + app/models/content_file.rb | 51 + app/models/content_visibility.rb | 19 + app/models/job_result.rb | 4 + app/models/lesson_visit.rb | 31 + app/models/notification.rb | 9 + app/models/pairing.rb | 7 + app/models/performance.rb | 22 + app/models/release.rb | 87 + app/models/resync_job_result.rb | 82 + app/models/section.rb | 15 + app/models/standard.rb | 30 + app/models/submitted_challenge_answer.rb | 107 + app/models/unit.rb | 1 + app/models/user.rb | 125 + app/models/user_last_viewed_standard_path.rb | 6 + app/policies/activity_policy.rb | 5 + app/policies/application_policy.rb | 58 + app/policies/block_policy.rb | 8 + app/policies/checkpoint_submission_policy.rb | 11 + app/policies/cohort_policy.rb | 87 + app/policies/cohort_release_policy.rb | 9 + app/policies/cohort_user_policy.rb | 9 + app/policies/content_file_policy.rb | 36 + app/policies/notification_policy.rb | 26 + app/policies/performance_policy.rb | 7 + app/policies/release_policy.rb | 4 + .../submitted_challenge_answer_policy.rb | 26 + app/policies/user_policy.rb | 45 + app/presenters/activity_presenter.rb | 14 + app/presenters/block_presenter/for_block.rb | 35 + .../block_presenter/for_cohort_releases.rb | 41 + .../for_cohort_releases_new.rb | 12 + .../block_presenter/for_releases.rb | 33 + ...h_submitted_challenge_answers_presenter.rb | 138 + .../checkpoint_submission_presenter.rb | 60 + .../for_index.rb | 103 + .../for_student_row.rb | 9 + .../for_cohort_setup.rb | 33 + app/presenters/cohort_setup/visibility.rb | 40 + .../for_curriculum_last_viewed.rb | 48 + .../content_file_presenter/for_footer.rb | 26 + .../content_file_presenter/for_show.rb | 100 + .../content_file_presenter/for_sidebar.rb | 15 + app/presenters/performance_presenter.rb | 20 + .../standard_card_component_props.rb | 9 + .../for_challenge_detail_view.rb | 15 + .../for_checkpoint_submission.rb | 15 + .../standard_presenter/for_standard_card.rb | 109 + .../for_submissions_dashboard.rb | 80 + app/presenters/stat_progress_presenter.rb | 29 + app/presenters/student_progress_presenter.rb | 117 + .../submissions_dashboard_presenter.rb | 184 + .../submitted_challenge_answer_presenter.rb | 123 + app/presenters/user_presenter/for_avatar.rb | 13 + app/services/activity_aggregator_service.rb | 135 + app/services/assessment_service.rb | 156 + app/services/auth_resolver_service.rb | 83 + app/services/auto_assign_release_service.rb | 7 + app/services/checkpoint_submission_service.rb | 56 + .../cohort_standard_progress_service.rb | 130 + .../cohort_student_progress_service.rb | 75 + .../completion_mode_percent_service.rb | 31 + app/services/course_validator.rb | 221 + ...eate_submitted_challenge_answer_service.rb | 205 + app/services/curriculum_progress_service.rb | 181 + .../download_github_repository_service.rb | 61 + .../download_gitlab_repository_service.rb | 71 + app/services/download_repository_service.rb | 40 + .../download_s3_repository_service.rb | 42 + app/services/git_url_service.rb | 53 + app/services/mastery_average_service.rb | 7 + app/services/mastery_mode_percent_service.rb | 14 + app/services/mock_mixpanel.rb | 13 + app/services/notification_service.rb | 58 + app/services/preview_content_file_service.rb | 136 + app/services/release_destroyer_service.rb | 39 + app/services/release_helper_service.rb | 182 + app/services/resync_course_service.rb | 121 + app/services/s3_asset_uploader_service.rb | 51 + app/services/segment_track_service.rb | 33 + app/services/sql_challenge_db_service.rb | 63 + app/services/standard_submissions_service.rb | 362 + app/views/api_interactions.html.haml | 5 + app/views/api_token.html.haml | 2 + app/views/apitome/docs/_headers.html.erb | 6 + app/views/apitome/docs/_params.html.erb | 30 + app/views/blocks/blockpagev2.html.haml | 3 + app/views/blocks/index.html.haml | 3 + app/views/blocks/new.html.haml | 12 + app/views/blocks/releases/new.html.haml | 7 + app/views/blocks/show.html.haml | 6 + app/views/cohorts/_cohort_info.html.haml | 23 + .../_completion_progress_donut.html.haml | 13 + app/views/cohorts/_user_table.html.haml | 30 + .../cohorts/activity_dashboard.html.haml | 3 + .../blocks/content_files/_footer.html.haml | 23 + .../blocks/content_files/show.html.haml | 223 + .../cohorts/cohort_releases/index.html.haml | 56 + app/views/cohorts/content.html.haml | 15 + .../checkpoint_submissions/show.html.haml | 28 + app/views/cohorts/course_stats.html.haml | 5 + app/views/cohorts/error.html.haml | 4 + app/views/cohorts/feed.html.haml | 24 + app/views/cohorts/index.html.haml | 23 + app/views/cohorts/partnerup.html.haml | 27 + app/views/cohorts/setup.html.haml | 20 + app/views/cohorts/show.html.haml | 29 + .../checkpoint_submissions/index.html.haml | 9 + app/views/cohorts/unit_progress.html.haml | 15 + app/views/cohorts/users.html.haml | 54 + .../cohorts/users/challenges/show.html.haml | 31 + .../cohorts/users/mastery/index.html.haml | 8 + .../users/submissions_dashboard.html.haml | 9 + app/views/error_404.html.haml | 8 + app/views/error_500.html.haml | 7 + app/views/home/index.html.haml | 3 + .../layouts/_primary_navigation.html.haml | 56 + .../layouts/_secondary_navigation.html.haml | 9 + app/views/layouts/application.html.haml | 53 + app/views/layouts/mailer.html.erb | 13 + app/views/layouts/mailer.text.erb | 1 + app/views/permalinks/permalink.html.haml | 15 + app/views/shared/_content_file_js.html.haml | 103 + .../shared/_hopscotch_callbacks_js.html.haml | 19 + app/views/shared/_intercom_js.html.haml | 42 + app/views/shared/_segment_js.html.haml | 7 + app/views/user_mailer/send_file.html | 1 + .../user_mailer/user_import_work.html.haml | 4 + bin/bundle | 3 + bin/rails | 4 + bin/rake | 4 + bin/setup | 38 + bin/update | 29 + bin/webpack | 15 + bin/webpack-dev-server | 15 + bin/yarn | 11 + config.ru | 5 + config/application.rb | 58 + config/boot.rb | 3 + config/database.yml | 16 + config/environment.rb | 5 + config/environments/development.rb | 61 + config/environments/production.rb | 105 + config/environments/test.rb | 47 + config/get-routes-audit.md | 169 + config/honeybadger.yml | 2 + config/initializers/apitome.rb | 66 + .../application_controller_renderer.rb | 8 + config/initializers/assets.rb | 14 + config/initializers/auth_api.rb | 21 + config/initializers/aws.rb | 39 + config/initializers/backtrace_silencers.rb | 7 + config/initializers/cookies_serializer.rb | 5 + .../initializers/filter_parameter_logging.rb | 4 + config/initializers/inflections.rb | 16 + config/initializers/mime_types.rb | 4 + .../new_framework_defaults_5_1.rb | 14 + config/initializers/octokit.rb | 4 + config/initializers/pagy.rb | 158 + config/initializers/pundit.rb | 15 + config/initializers/rack_attack.rb | 19 + config/initializers/segment.rb | 17 + config/initializers/sidekiq.rb | 4 + config/initializers/wrap_parameters.rb | 14 + config/locales/en.yml | 33 + config/puma.rb | 57 + config/routes.rb | 184 + config/secrets.yml | 65 + config/sidekiq.yml | 4 + config/spring.rb | 6 + config/webpack/development.js | 17 + config/webpack/environment.js | 5 + config/webpack/loaders/typescript.js | 10 + config/webpack/production.js | 5 + config/webpack/test.js | 5 + config/webpacker.yml | 70 + db/drawio_schema_with_explanations.xml | 1 + db/migrate/20171003191909_create_users.rb | 15 + .../20171005135550_add_admin_to_users.rb | 6 + ...05140042_rename_users_profile_image_url.rb | 5 + db/migrate/20171006195948_create_blocks.rb | 9 + db/migrate/20171009194124_create_releases.rb | 11 + ...20171011232501_add_sync_errors_to_block.rb | 5 + db/migrate/20171012200106_create_standards.rb | 15 + db/migrate/20171016192627_create_cohorts.rb | 20 + .../20171017205306_create_cohort_users.rb | 13 + ...s_from_label_and_pretty_name_on_cohorts.rb | 8 + .../20171019165527_create_content_files.rb | 12 + .../20171019192005_create_cohort_releases.rb | 10 + .../20171023211301_create_challenges.rb | 23 + ...024202210_create_checkpoint_submissions.rb | 13 + ...3630_create_submitted_challenge_answers.rb | 19 + .../20171025211916_create_performances.rb | 16 + ...25223250_add_autoscore_to_content_files.rb | 5 + ...171026193413_add_title_to_content_files.rb | 5 + ...71102144822_cohort_release_unique_index.rb | 5 + ...14_add_unique_constraint_to_block_title.rb | 5 + ...2171128_add_position_to_cohort_releases.rb | 5 + .../20171127223701_create_activities.rb | 19 + ...3_create_user_last_viewed_standard_path.rb | 18 + ...20171130185636_add_uid_to_content_files.rb | 5 + .../20171205211624_add_slack_data_to_user.rb | 7 + ...213519_add_standard_uid_to_performances.rb | 9 + ..._relative_display_name_to_content_files.rb | 5 + .../20180110223429_add_auth_roles_to_user.rb | 5 + ...denormalize_submitted_challenge_answers.rb | 9 + ...20180110233303_add_roles_to_cohort_user.rb | 5 + ...4504_add_last_viewed_cohort_id_to_users.rb | 5 + ...0116174519_add_github_user_name_to_user.rb | 5 + ...16181937_add_taught_in_learn_to_cohorts.rb | 5 + ...80116213927_remove_forge_admin_and_role.rb | 6 + ...cohort_id_to_submitted_challenge_answer.rb | 6 + ...1642_denormalize_checkpoint_submissions.rb | 11 + .../20180125212203_rename_taught_in_learn.rb | 5 + .../20180125222044_change_learn_v2_default.rb | 5 + .../20180131220605_create_notifications.rb | 13 + ...180202225951_add_github_sha_to_releases.rb | 13 + ...d_use_latest_release_to_cohort_releases.rb | 5 + ...add_docker_directory_path_to_challenges.rb | 5 + ...80316222140_rename_cohort_title_to_name.rb | 5 + ...0180320162849_add_deleted_at_to_cohorts.rb | 5 + ...02212114_remove_pretty_name_from_cohort.rb | 5 + ...4504_add_used_by_application_to_cohorts.rb | 5 + ...0412223312_populate_used_by_application.rb | 15 + ..._add_feature_branch_columns_to_releases.rb | 7 + ...d_pending_release_id_to_cohort_releases.rb | 6 + ...180813205413_add_readme_text_to_release.rb | 5 + .../20180813213358_add_mode_to_cohort.rb | 5 + .../20180813214453_populate_mode_cohorts.rb | 11 + ...rt_id_to_user_last_viewed_standard_path.rb | 6 + .../20180824200753_create_lesson_visits.rb | 19 + ...112215710_add_preferred_campus_to_users.rb | 5 + .../20181114190340_create_job_results.rb | 14 + db/migrate/20181120221622_create_sections.rb | 10 + ...02104_add_section_id_to_cohort_releases.rb | 6 + ...add_challenge_completion_to_performance.rb | 5 + ...1213180900_add_show_tests_to_challenges.rb | 5 + ...81214175640_create_content_visibilities.rb | 12 + ...add_default_visibility_to_content_files.rb | 5 + db/migrate/20190410171829_create_pairings.rb | 12 + ...190423171448_add_data_path_to_challenge.rb | 6 + ...10_add_last_setup_visit_to_cohort_users.rb | 5 + ...23958_add_max_attempts_to_content_files.rb | 6 + ...70059_add_allowed_attempts_to_challenge.rb | 5 + ...000_remove_max_attempts_from_challenges.rb | 6 + ...ints_and_success_criteria_to_challenges.rb | 6 + ...1_drop_learn_version_columns_on_cohorts.rb | 6 + .../20190829203949_add_settings_to_cohorts.rb | 5 + ...dd_points_to_submitted_challenge_answer.rb | 5 + ...03_add_points_to_checkpoint_submissions.rb | 6 + ...uccess_criteria_to_rubric_on_challenges.rb | 6 + ...20191001214559_add_topics_to_challenges.rb | 5 + ...1001221324_add_perf_data_to_submissions.rb | 8 + ...1008211403_add_release_id_to_challenges.rb | 5 + .../20191009214248_add_user_id_to_release.rb | 5 + ...visibility_type_to_content_visibilities.rb | 5 + .../20191031212058_add_api_token_to_user.rb | 5 + .../20191108211234_add_preview_to_releases.rb | 6 + .../20191114211938_create_api_interactions.rb | 16 + ...19225902_add_sandbox_boolean_to_cohorts.rb | 6 + .../20191206230913_add_sync_warnings.rb | 7 + ...165026_add_metadata_to_api_interactions.rb | 5 + ...add_assign_partial_credig_to_challenges.rb | 5 + ..._add_end_time_to_checkpoint_submissions.rb | 5 + ...201253_change_cohort_default_percentage.rb | 5 + ...13201649_add_time_limit_to_content_file.rb | 5 + ..._submitted_at_to_checkpoint_submissions.rb | 5 + ..._add_allow_paired_submissions_to_cohort.rb | 5 + ...ubmission_ids_to_checkpoint_submissinos.rb | 5 + ...0200430165251_add_external_to_challenge.rb | 6 + .../20200702155846_add_cache_to_blocks.rb | 7 + ...0200707164932_add_archived_at_to_blocks.rb | 5 + ...20_add_block_location_details_to_blocks.rb | 6 + ...00724195251_add_whitelabeled_to_cohorts.rb | 6 + ...4247_remove_block_uniqueness_pg_columns.rb | 6 + ...13_add_student_import_status_to_cohorts.rb | 6 + ...addd_docker_directory_zip_to_challenges.rb | 5 + ...0828165130_default_block_org_tog_school.rb | 5 + db/schema.rb | 400 + db/seeds.rb | 119 + doc/api.md | 29 + doc/api/cohorts/creating_a_new_cohort.json | 86 + .../cohorts/viewing_curriculum_details.json | 50 + .../content/update_content_visibility.json | 68 + .../creating_a_user_and_their_enrollment.json | 80 + .../unenrolling_a_user_from_a_cohort.json | 56 + .../updating_a_user's_enrollment_roles.json | 68 + .../viewing_cohort_enrollments.json | 50 + doc/api/index.json | 97 + doc/api/units/update_unit_visibility.json | 68 + doc/api/users/regenerate_user_token.json | 45 + lib/tasks/challenge_release_ids.rake | 8 + lib/tasks/checkpoint_resubmission.rake | 45 + lib/tasks/checkpoint_submission_points.rake | 30 + lib/tasks/content_visibility_update.rake | 8 + lib/tasks/course_progress.rake | 216 + lib/tasks/course_yaml_backfill.rake | 65 + lib/tasks/grade_checkpoints.rake | 9 + lib/tasks/object_space_count.rake | 11 + lib/tasks/prep_notifier.rake | 34 + lib/tasks/set_block_caches.rake | 16 + lib/tasks/spec.rake | 9 + lib/tasks/ts_routes.rake | 12 + lintspec.sh | 4 + package.json | 83 + public/404.html | 67 + public/422.html | 67 + public/500.html | 66 + public/apple-touch-icon-152x152.png | 0 .../apple-touch-icon-precomposed-152x152.png | 0 public/apple-touch-icon-precomposed.png | 0 public/apple-touch-icon.png | 0 .../assets/images/hopscotch-sprite-green.png | Bin 0 -> 5405 bytes .../assets/images/hopscotch-sprite-orange.png | Bin 0 -> 5374 bytes public/assets/images/jupyter-logo.png | Bin 0 -> 5922 bytes .../images/svg/checkpoint-is-rejected.svg | 15 + .../images/svg/checkpoint-is-scored.svg | 13 + .../images/svg/checkpoint-is-submitted.svg | 17 + public/assets/images/svg/github-icon.svg | 6 + public/assets/images/svg/gitlab-icon-rgb.svg | 1 + .../assets/images/svg/octicon-git-branch.svg | 1 + .../images/svg/redpriority_high-24px.svg | 12 + .../assets/images/svg/svg-link_off-24px.svg | 5 + .../images/svg/svg-sprite-action-symbol.svg | 1 + .../images/svg/svg-sprite-alert-symbol.svg | 1 + .../images/svg/svg-sprite-av-symbol.svg | 1 + .../images/svg/svg-sprite-content-symbol.svg | 1 + .../images/svg/svg-sprite-custom-symbol.svg | 18 + .../svg/svg-sprite-custom_material-symbol.svg | 1 + .../images/svg/svg-sprite-device-symbol.svg | 1 + .../images/svg/svg-sprite-file-symbol.svg | 1 + .../images/svg/svg-sprite-hardware-symbol.svg | 1 + .../svg/svg-sprite-navigation-symbol.svg | 1 + .../svg/svg-sprite-notification-symbol.svg | 1 + .../images/svg/svg-sprite-social-symbol.svg | 1 + public/favicon.ico | 0 public/javascripts/apitome/application.js | 31 + public/robots.txt | 1 + public/sandbox/chai.js | 5430 ++++++ public/sandbox/challenge-worker.js | 141 + public/sandbox/jasmine/boot.js | 207 + public/sandbox/jasmine/jasmine.js | 3298 ++++ public/sandbox/mocha/boot.js | 46 + public/sandbox/mocha/mocha.js | 16046 ++++++++++++++++ public/sandbox/mocha/test.html | 3 + public/sandbox/sandbox.html | 2 + public/sandbox/stacktrace.js | 489 + public/sandbox/worker.js | 2 + public/stylesheets/apitome/application.css | 269 + requirements.txt | 1 + scripts/sh/cohort_curriculum.sh | 10 + scripts/sh/content_file_mark_hidden.sh | 13 + scripts/sh/content_file_mark_visible.sh | 13 + scripts/sh/unit_mark_hidden.sh | 13 + scripts/sh/unit_mark_visible.sh | 13 + scripts/sql/checkpoint_answers.sql | 29 + .../sql/cohort_challenges_with_answers.sql | 27 + scripts/sql/cohort_prune_for_testing.sql | 194 + scripts/sql/percent_metrics.sql | 204 + scripts/sql/scrub_cohort_data.sql | 30 + tsconfig.json | 26 + .../javascripts/bootstrap-datepicker.js | 8 + vendor/assets/javascripts/hopscotch.js | 17 + vendor/assets/javascripts/react-tooltip.js | 2405 +++ .../stylesheets/bootstrap-datepicker.css | 7 + vendor/assets/stylesheets/hopscotch.css | 17 + yarn.lock | 8233 ++++++++ 778 files changed, 89450 insertions(+) create mode 100644 .babelrc create mode 100644 .circleci/config.yml create mode 100644 .env.example create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 .haml-lint.yml create mode 100644 .overcommit.yml create mode 100644 .pairs create mode 100644 .postcssrc.yml create mode 100644 .rubocop.yml create mode 100644 .ruby-version create mode 100644 .stylelintrc create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 Procfile create mode 100644 Procfile.local create mode 100644 README.md create mode 100644 Rakefile create mode 100644 app.json create mode 100644 app/assets/config/manifest.js create mode 100644 app/assets/images/favicon.ico create mode 100644 app/assets/images/loader-black.svg create mode 100644 app/assets/images/loader-cyan.svg create mode 100644 app/assets/images/loader-white.svg create mode 100644 app/assets/images/lost.jpg create mode 100644 app/assets/images/spinner.gif create mode 100644 app/assets/images/svg/baseline-notes-24px.svg create mode 100644 app/assets/images/svg/g-learn-lockup.svg create mode 100644 app/assets/images/svg/galvanize-logo.svg create mode 100644 app/assets/images/svg/github-icon.svg create mode 100644 app/assets/images/svg/mobile-logo.svg create mode 100644 app/assets/images/svg/octicon-git-branch.svg create mode 100755 app/assets/images/svg/svg-sprite-action-symbol.svg create mode 100644 app/assets/images/svg/svg-sprite-av-symbol.svg create mode 100644 app/assets/images/svg/svg-sprite-device-symbol.svg create mode 100644 app/assets/images/svg/svg-sprite-image-symbol.svg create mode 100755 app/assets/images/svg/svg-sprite-navigation-symbol.svg create mode 100644 app/assets/javascripts/application.js create mode 100644 app/assets/javascripts/mobile.js create mode 100644 app/assets/stylesheets/application.scss create mode 100644 app/assets/stylesheets/base.scss create mode 100644 app/assets/stylesheets/bootstrap-custom.scss create mode 100644 app/assets/stylesheets/cohorts.scss create mode 100644 app/assets/stylesheets/components/_404-container.scss create mode 100644 app/assets/stylesheets/components/_action-menu.scss create mode 100644 app/assets/stylesheets/components/_activity-dashboard.scss create mode 100644 app/assets/stylesheets/components/_activity-feed.scss create mode 100644 app/assets/stylesheets/components/_api-interactions.scss create mode 100644 app/assets/stylesheets/components/_api_token.scss create mode 100644 app/assets/stylesheets/components/_auth-style-search.scss create mode 100644 app/assets/stylesheets/components/_blocks-index.scss create mode 100644 app/assets/stylesheets/components/_blocks-stats.scss create mode 100644 app/assets/stylesheets/components/_button.scss create mode 100644 app/assets/stylesheets/components/_callouts.scss create mode 100644 app/assets/stylesheets/components/_challenge-block.scss create mode 100644 app/assets/stylesheets/components/_challenge_status.scss create mode 100644 app/assets/stylesheets/components/_checkbox-input.scss create mode 100644 app/assets/stylesheets/components/_checkpoint-landing.scss create mode 100644 app/assets/stylesheets/components/_checkpoint-submission.scss create mode 100644 app/assets/stylesheets/components/_checkpoint-submissions-columns-student.scss create mode 100644 app/assets/stylesheets/components/_checkpoint-submissions-columns.scss create mode 100644 app/assets/stylesheets/components/_checkpoint-submissions-index.scss create mode 100644 app/assets/stylesheets/components/_checkpoint_student_scores.scss create mode 100644 app/assets/stylesheets/components/_checkpoint_submisstion_state.scss create mode 100644 app/assets/stylesheets/components/_checkpoint_toolbar.scss create mode 100644 app/assets/stylesheets/components/_code-block.scss create mode 100644 app/assets/stylesheets/components/_cohort-submissions-table.scss create mode 100644 app/assets/stylesheets/components/_cohort_releases.scss create mode 100644 app/assets/stylesheets/components/_cohorts.scss create mode 100644 app/assets/stylesheets/components/_content-file-show.scss create mode 100644 app/assets/stylesheets/components/_content-file-sidebar.scss create mode 100644 app/assets/stylesheets/components/_curriculum-checkpoint-summary.scss create mode 100644 app/assets/stylesheets/components/_curriculum-progress.scss create mode 100644 app/assets/stylesheets/components/_curriculum.scss create mode 100644 app/assets/stylesheets/components/_dropdown-component.scss create mode 100644 app/assets/stylesheets/components/_external-link.scss create mode 100644 app/assets/stylesheets/components/_flash-message.scss create mode 100644 app/assets/stylesheets/components/_footer.scss create mode 100644 app/assets/stylesheets/components/_grade-buttons.scss create mode 100644 app/assets/stylesheets/components/_integer_picker.scss create mode 100644 app/assets/stylesheets/components/_lp-style-button.scss create mode 100644 app/assets/stylesheets/components/_mastery-table.scss create mode 100644 app/assets/stylesheets/components/_modal.scss create mode 100644 app/assets/stylesheets/components/_navigation-dropdown.scss create mode 100644 app/assets/stylesheets/components/_notifications.scss create mode 100644 app/assets/stylesheets/components/_pagination.scss create mode 100644 app/assets/stylesheets/components/_pill.scss create mode 100644 app/assets/stylesheets/components/_point_grade_buttons.scss create mode 100644 app/assets/stylesheets/components/_primary-header.scss create mode 100644 app/assets/stylesheets/components/_primary-navigation.scss create mode 100644 app/assets/stylesheets/components/_progress.scss create mode 100644 app/assets/stylesheets/components/_progress_thresholds_key.scss create mode 100644 app/assets/stylesheets/components/_progress_thresholds_modal.scss create mode 100644 app/assets/stylesheets/components/_progress_thresholds_slider.scss create mode 100644 app/assets/stylesheets/components/_search-bar.scss create mode 100644 app/assets/stylesheets/components/_secondary-navigation.scss create mode 100644 app/assets/stylesheets/components/_slideshow.scss create mode 100644 app/assets/stylesheets/components/_sort-dropdown-component.scss create mode 100644 app/assets/stylesheets/components/_standard-card.scss create mode 100644 app/assets/stylesheets/components/_standard-cards.scss create mode 100644 app/assets/stylesheets/components/_standards-mastery-beans.scss create mode 100644 app/assets/stylesheets/components/_status_picker.scss create mode 100644 app/assets/stylesheets/components/_student-mastery-table.scss create mode 100644 app/assets/stylesheets/components/_student-name-bar.scss create mode 100644 app/assets/stylesheets/components/_submissions-dashboard-table.scss create mode 100644 app/assets/stylesheets/components/_svg-icon.scss create mode 100644 app/assets/stylesheets/components/_thresholds.scss create mode 100644 app/assets/stylesheets/components/_universal-list.scss create mode 100644 app/assets/stylesheets/components/_universal-row.scss create mode 100644 app/assets/stylesheets/components/_universal-table.scss create mode 100644 app/assets/stylesheets/components/_user-avatar.scss create mode 100644 app/assets/stylesheets/components/_video-player.scss create mode 100644 app/assets/stylesheets/components/badge.scss create mode 100644 app/assets/stylesheets/components/challenge-detail-comments.scss create mode 100644 app/assets/stylesheets/components/challenge-detail-view.scss create mode 100644 app/assets/stylesheets/components/challenge-timeline.scss create mode 100644 app/assets/stylesheets/components/new-comment-form.scss create mode 100644 app/assets/stylesheets/components/partnerup.scss create mode 100644 app/assets/stylesheets/components/progress-table.scss create mode 100644 app/assets/stylesheets/hopscotch-overrides.scss create mode 100644 app/assets/stylesheets/mixins.scss create mode 100644 app/assets/stylesheets/mobile.scss create mode 100644 app/assets/stylesheets/mobile/_submissions-dashboard-table.scss create mode 100644 app/assets/stylesheets/typography.scss create mode 100644 app/assets/stylesheets/variables.scss create mode 100644 app/component_props/activity_feed_item_component_props.rb create mode 100644 app/component_props/notifications_component_props.rb create mode 100644 app/component_props/standard_card_component_props.rb create mode 100644 app/controllers/api/application_controller.rb create mode 100644 app/controllers/api/v1/blocks/releases_controller.rb create mode 100644 app/controllers/api/v1/blocks_controller.rb create mode 100644 app/controllers/api/v1/cohorts/blocks/content_files_controller.rb create mode 100644 app/controllers/api/v1/cohorts/blocks/units_controller.rb create mode 100644 app/controllers/api/v1/cohorts/cohorts_controller.rb create mode 100644 app/controllers/api/v1/cohorts/users_controller.rb create mode 100644 app/controllers/api/v1/content_files_controller.rb create mode 100644 app/controllers/api/v1/pineapple_controller.rb create mode 100644 app/controllers/api/v1/releases_controller.rb create mode 100644 app/controllers/api/v1/users_controller.rb create mode 100644 app/controllers/application_controller.rb create mode 100644 app/controllers/blocks/releases_controller.rb create mode 100644 app/controllers/blocks_controller.rb create mode 100644 app/controllers/cohorts/blocks/content_files_controller.rb create mode 100644 app/controllers/cohorts/checkpoint_submissions/activities_controller.rb create mode 100644 app/controllers/cohorts/cohort_releases_controller.rb create mode 100644 app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb create mode 100644 app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb create mode 100644 app/controllers/cohorts/content_files_controller.rb create mode 100644 app/controllers/cohorts/pairings_controller.rb create mode 100644 app/controllers/cohorts/standards_controller.rb create mode 100644 app/controllers/cohorts/submitted_challenge_answers/activities_controller.rb create mode 100644 app/controllers/cohorts/users/challenges_controller.rb create mode 100644 app/controllers/cohorts/users/performances_controller.rb create mode 100644 app/controllers/cohorts/users_controller.rb create mode 100644 app/controllers/cohorts_controller.rb create mode 100644 app/controllers/concerns/api/content_visibility_crud.rb create mode 100644 app/controllers/concerns/api/exception_handler.rb create mode 100644 app/controllers/concerns/api/response.rb create mode 100644 app/controllers/home_controller.rb create mode 100644 app/controllers/notifications_controller.rb create mode 100644 app/controllers/permalinks_controller.rb create mode 100644 app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb create mode 100644 app/controllers/webhooks/auth/cohorts_controller.rb create mode 100644 app/controllers/webhooks/auth/users_controller.rb create mode 100644 app/exporters/performance_exporter.rb create mode 100644 app/exporters/progress_exporter.rb create mode 100644 app/finders/block_finder.rb create mode 100644 app/finders/checkpoint_submission_finder.rb create mode 100644 app/finders/cohort_user_finder.rb create mode 100644 app/finders/content_file_finder.rb create mode 100644 app/finders/performance_finder.rb create mode 100644 app/finders/release_finder.rb create mode 100644 app/finders/standard_finder.rb create mode 100644 app/finders/submitted_challenge_answer_finder.rb create mode 100644 app/finders/user_finder.rb create mode 100644 app/helpers/application_helper.rb create mode 100644 app/helpers/json_helper.rb create mode 100644 app/helpers/secondary_navigation_helper.rb create mode 100644 app/helpers/standard_navigation_helper.rb create mode 100644 app/javascript/api.d.ts create mode 100644 app/javascript/components/AceEditor.tsx create mode 100644 app/javascript/components/Badge.tsx create mode 100644 app/javascript/components/Button.tsx create mode 100644 app/javascript/components/ButtonTo.tsx create mode 100644 app/javascript/components/Dropdown.tsx create mode 100644 app/javascript/components/Icon.tsx create mode 100644 app/javascript/components/Loading.tsx create mode 100644 app/javascript/components/Marked.tsx create mode 100644 app/javascript/components/Menu.tsx create mode 100644 app/javascript/components/Notifications.tsx create mode 100644 app/javascript/components/ProcessingIcon.tsx create mode 100644 app/javascript/components/RowKebab.tsx create mode 100644 app/javascript/components/ScoreCircle.tsx create mode 100644 app/javascript/components/SidebarProgress.tsx create mode 100644 app/javascript/components/SortDropdown.tsx create mode 100644 app/javascript/components/StandardBean.tsx create mode 100644 app/javascript/components/SvgRenderer.tsx create mode 100644 app/javascript/components/Timestamp.tsx create mode 100644 app/javascript/components/UserAvatar.tsx create mode 100644 app/javascript/components/activities/NewActivityForm.tsx create mode 100644 app/javascript/components/api/ApiInteractions.tsx create mode 100644 app/javascript/components/api/ApiToken.tsx create mode 100644 app/javascript/components/blocks/BlockPage-v2.tsx create mode 100644 app/javascript/components/blocks/BlocksIndex-v2.tsx create mode 100644 app/javascript/components/blocks/BlocksNewModal.tsx create mode 100644 app/javascript/components/blocks/BlocksNewRelease.tsx create mode 100644 app/javascript/components/blocks/BlocksRow.tsx create mode 100644 app/javascript/components/blocks/BlocksShow.tsx create mode 100644 app/javascript/components/blocks/BlocksStats.tsx create mode 100644 app/javascript/components/blocks/CohortsTooltip.tsx create mode 100644 app/javascript/components/challenges/AvatarBar.tsx create mode 100644 app/javascript/components/challenges/ChallengeDetailView.tsx create mode 100644 app/javascript/components/challenges/ChallengeShow.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeActionBlock.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeDetailActivities.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeExplanationBlock.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeFeedbackBlock.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeHintsBlock.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeInputs.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeLocalTestResults.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeRubricBlock.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeStatus.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeStatusBar.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeTestResults.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeTests.tsx create mode 100644 app/javascript/components/challenges/challenge_block/ChallengeTimeLine.tsx create mode 100644 app/javascript/components/challenges/challenge_block/GradeIndicator.tsx create mode 100644 app/javascript/components/challenges/challenge_block/GradedTimestamp.tsx create mode 100644 app/javascript/components/challenges/challenge_block/StatusPicker.tsx create mode 100644 app/javascript/components/challenges/local/run-local-challenge.ts create mode 100644 app/javascript/components/challenges/local/sandbox.ts create mode 100644 app/javascript/components/challenges/local/stack-traces.ts create mode 100644 app/javascript/components/checkpoints/CheckpointAfterSubmitButton.tsx create mode 100644 app/javascript/components/checkpoints/CheckpointAfterSubmitModal.tsx create mode 100644 app/javascript/components/checkpoints/CheckpointAfterSubmitModalError.tsx create mode 100644 app/javascript/components/checkpoints/CheckpointSubmissionChallenges.tsx create mode 100644 app/javascript/components/checkpoints/CheckpointSubmissionShow.tsx create mode 100644 app/javascript/components/checkpoints/CheckpointSubmissionState.tsx create mode 100644 app/javascript/components/checkpoints/CheckpointSubmissionStudentNameBar.tsx create mode 100644 app/javascript/components/cohorts/CohortsIndex.tsx create mode 100644 app/javascript/components/cohorts/activity_dashboard/ActivityDashboard.tsx create mode 100644 app/javascript/components/cohorts/activity_feed/ActivityFeed.tsx create mode 100644 app/javascript/components/cohorts/cohort_releases/ReleaseVersionsTable.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsChallengeItem.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsLessonRow.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsPerformanceModal.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStandardRow.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentColumn.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentNameBar.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentStandard.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsTable.tsx create mode 100644 app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsUnitPercent.tsx create mode 100644 app/javascript/components/cohorts/mastery/MasteryTable.tsx create mode 100644 app/javascript/components/cohorts/mastery/MetricRow.tsx create mode 100644 app/javascript/components/cohorts/mastery/MetricsBody.tsx create mode 100644 app/javascript/components/cohorts/mastery/PerformanceCell.tsx create mode 100644 app/javascript/components/cohorts/mastery/PerformanceRow.tsx create mode 100644 app/javascript/components/cohorts/mastery/ReleaseBody.tsx create mode 100644 app/javascript/components/cohorts/mastery/StandardRow.tsx create mode 100644 app/javascript/components/cohorts/mastery/StudentHeader.tsx create mode 100644 app/javascript/components/cohorts/mastery/StudentMasteryTable.tsx create mode 100644 app/javascript/components/cohorts/mastery/StudentReleaseRow.tsx create mode 100644 app/javascript/components/cohorts/pairing/GroupPairs.tsx create mode 100644 app/javascript/components/cohorts/pairing/InnerStudentList.tsx create mode 100644 app/javascript/components/cohorts/pairing/NewPairing.tsx create mode 100644 app/javascript/components/cohorts/pairing/PairingBoard.tsx create mode 100644 app/javascript/components/cohorts/pairing/StudentItem.tsx create mode 100644 app/javascript/components/cohorts/pairing/StudentList.tsx create mode 100644 app/javascript/components/cohorts/progress/MasteryProgressionBar.tsx create mode 100644 app/javascript/components/cohorts/progress/ProgressThresholdCellHeaders.tsx create mode 100644 app/javascript/components/cohorts/progress/StudentProgress.tsx create mode 100644 app/javascript/components/cohorts/progress/StudentProgressBar.tsx create mode 100644 app/javascript/components/cohorts/progress/StudentProgressCells.tsx create mode 100644 app/javascript/components/cohorts/progress/StudentProgressRow.tsx create mode 100644 app/javascript/components/cohorts/progress/StudentProgressSortArrow.tsx create mode 100644 app/javascript/components/cohorts/settings/BranchReleaseModal.tsx create mode 100644 app/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx create mode 100644 app/javascript/components/cohorts/settings/CohortContentTab.tsx create mode 100644 app/javascript/components/cohorts/settings/CohortInfo.tsx create mode 100644 app/javascript/components/cohorts/settings/CohortSettingsResync.tsx create mode 100644 app/javascript/components/cohorts/settings/CohortSettingsTabs.tsx create mode 100644 app/javascript/components/cohorts/settings/CohortVisibilitySection.tsx create mode 100644 app/javascript/components/cohorts/settings/CurriculumSettingsTab.tsx create mode 100644 app/javascript/components/cohorts/settings/ReleaseVersionModal.tsx create mode 100644 app/javascript/components/cohorts/settings/ReleaseVersionRow.tsx create mode 100644 app/javascript/components/cohorts/settings/UserImport.tsx create mode 100644 app/javascript/components/cohorts/settings/UserKebab.tsx create mode 100644 app/javascript/components/cohorts/submissions_dashboard/AnswerStatusRollup.tsx create mode 100644 app/javascript/components/cohorts/submissions_dashboard/HeaderStandardContainer.tsx create mode 100644 app/javascript/components/cohorts/submissions_dashboard/StandardContainer.tsx create mode 100644 app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx create mode 100644 app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardChallengeItem.tsx create mode 100644 app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardContentFileRow.tsx create mode 100644 app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentColumn.tsx create mode 100644 app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentNameBar.tsx create mode 100644 app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardTable.tsx create mode 100644 app/javascript/components/content_files/ActionMenus.tsx create mode 100644 app/javascript/components/content_files/Lesson.tsx create mode 100644 app/javascript/components/content_files/MarkdownRenderer.tsx create mode 100644 app/javascript/components/content_files/PDFRenderer.tsx create mode 100644 app/javascript/components/content_files/SideBar.tsx create mode 100644 app/javascript/components/content_files/SubmissionRenderer.tsx create mode 100644 app/javascript/components/content_files/checkpoints/Checkpoint.tsx create mode 100644 app/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx create mode 100644 app/javascript/components/content_files/checkpoints/CheckpointDetails.tsx create mode 100644 app/javascript/components/content_files/checkpoints/CheckpointLanding.tsx create mode 100644 app/javascript/components/content_files/checkpoints/CheckpointLandingAttribute.tsx create mode 100644 app/javascript/components/content_files/checkpoints/CheckpointPairs.tsx create mode 100644 app/javascript/components/content_files/checkpoints/PairAvatars.tsx create mode 100644 app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentRow.tsx create mode 100644 app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentScores.tsx create mode 100644 app/javascript/components/curriculum/CheckpointSummary.tsx create mode 100644 app/javascript/components/curriculum/CohortCurriculum.tsx create mode 100644 app/javascript/components/curriculum/CurriculumStandardCard.tsx create mode 100644 app/javascript/components/curriculum/ProgressIndicators.tsx create mode 100644 app/javascript/components/curriculum/StandardBeans.tsx create mode 100644 app/javascript/components/curriculum/StandardsRenderer.tsx create mode 100644 app/javascript/components/curriculum/StudentOverallProgressDoughnut.tsx create mode 100644 app/javascript/components/lib/ace.ts create mode 100644 app/javascript/components/notifications/NotificationsIcon.tsx create mode 100644 app/javascript/components/shared/ActionMenu/ActionMenu.tsx create mode 100644 app/javascript/components/shared/Button/Button.tsx create mode 100644 app/javascript/components/shared/ChallengePoints/ChallengePoints.tsx create mode 100644 app/javascript/components/shared/ChallengePoints/PartialCreditBaton.tsx create mode 100644 app/javascript/components/shared/ChallengePoints/SpinText.tsx create mode 100644 app/javascript/components/shared/ChallengePoints/components/IntegerPicker/IntegerPicker.tsx create mode 100644 app/javascript/components/shared/DonutRing/DonutRing.tsx create mode 100644 app/javascript/components/shared/DonutRing/components/ColorDonut/ColorDonut.tsx create mode 100644 app/javascript/components/shared/DonutRing/components/CompleteDonut/CompleteDonut.tsx create mode 100644 app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut.tsx create mode 100644 app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut/UngradedDonut.tsx create mode 100644 app/javascript/components/shared/ErrorMessagesTable/ErrorMessagesTable.tsx create mode 100644 app/javascript/components/shared/FlashAlert/FlashAlert.tsx create mode 100644 app/javascript/components/shared/FormatNumber/FormatNumber.tsx create mode 100644 app/javascript/components/shared/Icons/AlertSign.tsx create mode 100644 app/javascript/components/shared/Icons/Clear.tsx create mode 100644 app/javascript/components/shared/Icons/CloseButton.tsx create mode 100644 app/javascript/components/shared/Icons/Done.tsx create mode 100644 app/javascript/components/shared/Icons/KeyboardArrowDown.tsx create mode 100644 app/javascript/components/shared/Icons/KeyboardArrowUp.tsx create mode 100644 app/javascript/components/shared/Icons/SettingsCog.tsx create mode 100644 app/javascript/components/shared/Icons/StatusCircle.tsx create mode 100644 app/javascript/components/shared/MasteryProgressBar/MasteryProgressBar.tsx create mode 100644 app/javascript/components/shared/Modal/Modal.tsx create mode 100644 app/javascript/components/shared/Pagination/Pagination.tsx create mode 100644 app/javascript/components/shared/PercentageProgressBar/PercentageProgressBar.tsx create mode 100644 app/javascript/components/shared/Pill/Pill.tsx create mode 100644 app/javascript/components/shared/PointGradeButtons/PointGradeButtons.tsx create mode 100644 app/javascript/components/shared/PointGradeButtons/SpinText.tsx create mode 100644 app/javascript/components/shared/ProgressBar/ProgressBar.tsx create mode 100644 app/javascript/components/shared/ProgressThresholdsKey/ProgressThresholdsKey.tsx create mode 100644 app/javascript/components/shared/ProgressThresholdsModal/ProgressThresholdsModal.tsx create mode 100644 app/javascript/components/shared/ProgressThresholdsSlider/ProgressThresholdsSlider.tsx create mode 100644 app/javascript/components/shared/SearchBar/SearchBar.tsx create mode 100644 app/javascript/components/shared/Slideshow/Slideshow.tsx create mode 100644 app/javascript/components/shared/UngradedDonut/UngradedDonut.tsx create mode 100644 app/javascript/components/shared/UniversalList/UniversalList.tsx create mode 100644 app/javascript/components/shared/UniversalRow/UniversalRow.tsx create mode 100644 app/javascript/components/shared/UniversalTable/UniversalTable.tsx create mode 100644 app/javascript/components/standards/CompletionScoringBlock.tsx create mode 100644 app/javascript/components/standards/MasteryScoringBlock.tsx create mode 100644 app/javascript/components/standards/StandardCard.tsx create mode 100644 app/javascript/components/standards/StandardScoreButton.tsx create mode 100644 app/javascript/components/standards/StandardScoringBlock.tsx create mode 100644 app/javascript/components/standards/StandardTopicsRollups.tsx create mode 100644 app/javascript/components/vendor/ReactTooltip.js create mode 100644 app/javascript/generated/routes.ts create mode 100644 app/javascript/globals.ts create mode 100644 app/javascript/lib/http.ts create mode 100644 app/javascript/lib/utils.ts create mode 100644 app/javascript/packs/application.js create mode 100644 app/javascript/packs/server_rendering.js create mode 100644 app/jobs/application_job.rb create mode 100644 app/jobs/branch_release_notifier_job.rb create mode 100644 app/jobs/checkpoint_paired_submission_job.rb create mode 100644 app/jobs/content_file_default_visibility_job.rb create mode 100644 app/jobs/content_file_visit_job.rb create mode 100644 app/jobs/create_api_interaction_job.rb create mode 100644 app/jobs/create_release_job.rb create mode 100644 app/jobs/evaluate_code_snippet_job.rb create mode 100644 app/jobs/evaluate_custom_snippet_job.rb create mode 100644 app/jobs/evaluate_project_job.rb create mode 100644 app/jobs/grade_timed_checkpoint_job.rb create mode 100644 app/jobs/import_student_work_job.rb create mode 100644 app/jobs/progress_csv_job.rb create mode 100644 app/jobs/release_notifier_job.rb create mode 100644 app/jobs/resync_course_job.rb create mode 100644 app/jobs/set_block_caches_job.rb create mode 100644 app/jobs/slack_challenge_performance_job.rb create mode 100644 app/jobs/slack_dev_notify_job.rb create mode 100644 app/jobs/slack_ds_prep_job.rb create mode 100644 app/jobs/slack_job.rb create mode 100644 app/jobs/slack_se_prep_job.rb create mode 100644 app/jobs/slack_student_job.rb create mode 100644 app/jobs/student_progress_auth_job.rb create mode 100644 app/jobs/switch_to_branch_job.rb create mode 100644 app/mailers/application_mailer.rb create mode 100644 app/mailers/user_mailer.rb create mode 100644 app/models/activity.rb create mode 100644 app/models/api_interaction.rb create mode 100644 app/models/application_record.rb create mode 100644 app/models/block.rb create mode 100644 app/models/challenge.rb create mode 100644 app/models/checkpoint_submission.rb create mode 100644 app/models/cohort.rb create mode 100644 app/models/cohort_release.rb create mode 100644 app/models/cohort_user.rb create mode 100644 app/models/concerns/findable_by_uid.rb create mode 100644 app/models/concerns/read_only_model.rb create mode 100644 app/models/content_file.rb create mode 100644 app/models/content_visibility.rb create mode 100644 app/models/job_result.rb create mode 100644 app/models/lesson_visit.rb create mode 100644 app/models/notification.rb create mode 100644 app/models/pairing.rb create mode 100644 app/models/performance.rb create mode 100644 app/models/release.rb create mode 100644 app/models/resync_job_result.rb create mode 100644 app/models/section.rb create mode 100644 app/models/standard.rb create mode 100644 app/models/submitted_challenge_answer.rb create mode 100644 app/models/unit.rb create mode 100644 app/models/user.rb create mode 100644 app/models/user_last_viewed_standard_path.rb create mode 100644 app/policies/activity_policy.rb create mode 100644 app/policies/application_policy.rb create mode 100644 app/policies/block_policy.rb create mode 100644 app/policies/checkpoint_submission_policy.rb create mode 100644 app/policies/cohort_policy.rb create mode 100644 app/policies/cohort_release_policy.rb create mode 100644 app/policies/cohort_user_policy.rb create mode 100644 app/policies/content_file_policy.rb create mode 100644 app/policies/notification_policy.rb create mode 100644 app/policies/performance_policy.rb create mode 100644 app/policies/release_policy.rb create mode 100644 app/policies/submitted_challenge_answer_policy.rb create mode 100644 app/policies/user_policy.rb create mode 100644 app/presenters/activity_presenter.rb create mode 100644 app/presenters/block_presenter/for_block.rb create mode 100644 app/presenters/block_presenter/for_cohort_releases.rb create mode 100644 app/presenters/block_presenter/for_cohort_releases_new.rb create mode 100644 app/presenters/block_presenter/for_releases.rb create mode 100644 app/presenters/challenge_with_submitted_challenge_answers_presenter.rb create mode 100644 app/presenters/checkpoint_submission_presenter.rb create mode 100644 app/presenters/checkpoint_submission_presenter/for_index.rb create mode 100644 app/presenters/checkpoint_submission_presenter/for_student_row.rb create mode 100644 app/presenters/cohort_release_presenter/for_cohort_setup.rb create mode 100644 app/presenters/cohort_setup/visibility.rb create mode 100644 app/presenters/content_file_presenter/for_curriculum_last_viewed.rb create mode 100644 app/presenters/content_file_presenter/for_footer.rb create mode 100644 app/presenters/content_file_presenter/for_show.rb create mode 100644 app/presenters/content_file_presenter/for_sidebar.rb create mode 100644 app/presenters/performance_presenter.rb create mode 100644 app/presenters/standard_card_component_props.rb create mode 100644 app/presenters/standard_presenter/for_challenge_detail_view.rb create mode 100644 app/presenters/standard_presenter/for_checkpoint_submission.rb create mode 100644 app/presenters/standard_presenter/for_standard_card.rb create mode 100644 app/presenters/standard_presenter/for_submissions_dashboard.rb create mode 100644 app/presenters/stat_progress_presenter.rb create mode 100644 app/presenters/student_progress_presenter.rb create mode 100644 app/presenters/submissions_dashboard_presenter.rb create mode 100644 app/presenters/submitted_challenge_answer_presenter.rb create mode 100644 app/presenters/user_presenter/for_avatar.rb create mode 100644 app/services/activity_aggregator_service.rb create mode 100644 app/services/assessment_service.rb create mode 100644 app/services/auth_resolver_service.rb create mode 100644 app/services/auto_assign_release_service.rb create mode 100644 app/services/checkpoint_submission_service.rb create mode 100644 app/services/cohort_standard_progress_service.rb create mode 100644 app/services/cohort_student_progress_service.rb create mode 100644 app/services/completion_mode_percent_service.rb create mode 100644 app/services/course_validator.rb create mode 100644 app/services/create_submitted_challenge_answer_service.rb create mode 100644 app/services/curriculum_progress_service.rb create mode 100644 app/services/download_github_repository_service.rb create mode 100644 app/services/download_gitlab_repository_service.rb create mode 100644 app/services/download_repository_service.rb create mode 100644 app/services/download_s3_repository_service.rb create mode 100644 app/services/git_url_service.rb create mode 100644 app/services/mastery_average_service.rb create mode 100644 app/services/mastery_mode_percent_service.rb create mode 100644 app/services/mock_mixpanel.rb create mode 100644 app/services/notification_service.rb create mode 100644 app/services/preview_content_file_service.rb create mode 100644 app/services/release_destroyer_service.rb create mode 100644 app/services/release_helper_service.rb create mode 100644 app/services/resync_course_service.rb create mode 100644 app/services/s3_asset_uploader_service.rb create mode 100644 app/services/segment_track_service.rb create mode 100644 app/services/sql_challenge_db_service.rb create mode 100644 app/services/standard_submissions_service.rb create mode 100644 app/views/api_interactions.html.haml create mode 100644 app/views/api_token.html.haml create mode 100644 app/views/apitome/docs/_headers.html.erb create mode 100644 app/views/apitome/docs/_params.html.erb create mode 100644 app/views/blocks/blockpagev2.html.haml create mode 100644 app/views/blocks/index.html.haml create mode 100644 app/views/blocks/new.html.haml create mode 100644 app/views/blocks/releases/new.html.haml create mode 100644 app/views/blocks/show.html.haml create mode 100644 app/views/cohorts/_cohort_info.html.haml create mode 100644 app/views/cohorts/_completion_progress_donut.html.haml create mode 100644 app/views/cohorts/_user_table.html.haml create mode 100644 app/views/cohorts/activity_dashboard.html.haml create mode 100644 app/views/cohorts/blocks/content_files/_footer.html.haml create mode 100644 app/views/cohorts/blocks/content_files/show.html.haml create mode 100644 app/views/cohorts/cohort_releases/index.html.haml create mode 100644 app/views/cohorts/content.html.haml create mode 100644 app/views/cohorts/content_files/checkpoint_submissions/show.html.haml create mode 100644 app/views/cohorts/course_stats.html.haml create mode 100644 app/views/cohorts/error.html.haml create mode 100644 app/views/cohorts/feed.html.haml create mode 100644 app/views/cohorts/index.html.haml create mode 100644 app/views/cohorts/partnerup.html.haml create mode 100644 app/views/cohorts/setup.html.haml create mode 100644 app/views/cohorts/show.html.haml create mode 100644 app/views/cohorts/standards/checkpoint_submissions/index.html.haml create mode 100644 app/views/cohorts/unit_progress.html.haml create mode 100644 app/views/cohorts/users.html.haml create mode 100644 app/views/cohorts/users/challenges/show.html.haml create mode 100644 app/views/cohorts/users/mastery/index.html.haml create mode 100644 app/views/cohorts/users/submissions_dashboard.html.haml create mode 100644 app/views/error_404.html.haml create mode 100644 app/views/error_500.html.haml create mode 100644 app/views/home/index.html.haml create mode 100644 app/views/layouts/_primary_navigation.html.haml create mode 100644 app/views/layouts/_secondary_navigation.html.haml create mode 100644 app/views/layouts/application.html.haml create mode 100644 app/views/layouts/mailer.html.erb create mode 100644 app/views/layouts/mailer.text.erb create mode 100644 app/views/permalinks/permalink.html.haml create mode 100644 app/views/shared/_content_file_js.html.haml create mode 100644 app/views/shared/_hopscotch_callbacks_js.html.haml create mode 100644 app/views/shared/_intercom_js.html.haml create mode 100644 app/views/shared/_segment_js.html.haml create mode 100644 app/views/user_mailer/send_file.html create mode 100644 app/views/user_mailer/user_import_work.html.haml create mode 100755 bin/bundle create mode 100755 bin/rails create mode 100755 bin/rake create mode 100755 bin/setup create mode 100755 bin/update create mode 100755 bin/webpack create mode 100755 bin/webpack-dev-server create mode 100755 bin/yarn create mode 100644 config.ru create mode 100644 config/application.rb create mode 100644 config/boot.rb create mode 100644 config/database.yml create mode 100644 config/environment.rb create mode 100644 config/environments/development.rb create mode 100644 config/environments/production.rb create mode 100644 config/environments/test.rb create mode 100644 config/get-routes-audit.md create mode 100644 config/honeybadger.yml create mode 100644 config/initializers/apitome.rb create mode 100644 config/initializers/application_controller_renderer.rb create mode 100644 config/initializers/assets.rb create mode 100644 config/initializers/auth_api.rb create mode 100644 config/initializers/aws.rb create mode 100644 config/initializers/backtrace_silencers.rb create mode 100644 config/initializers/cookies_serializer.rb create mode 100644 config/initializers/filter_parameter_logging.rb create mode 100644 config/initializers/inflections.rb create mode 100644 config/initializers/mime_types.rb create mode 100644 config/initializers/new_framework_defaults_5_1.rb create mode 100644 config/initializers/octokit.rb create mode 100644 config/initializers/pagy.rb create mode 100644 config/initializers/pundit.rb create mode 100644 config/initializers/rack_attack.rb create mode 100644 config/initializers/segment.rb create mode 100644 config/initializers/sidekiq.rb create mode 100644 config/initializers/wrap_parameters.rb create mode 100644 config/locales/en.yml create mode 100644 config/puma.rb create mode 100644 config/routes.rb create mode 100644 config/secrets.yml create mode 100644 config/sidekiq.yml create mode 100644 config/spring.rb create mode 100644 config/webpack/development.js create mode 100644 config/webpack/environment.js create mode 100644 config/webpack/loaders/typescript.js create mode 100644 config/webpack/production.js create mode 100644 config/webpack/test.js create mode 100644 config/webpacker.yml create mode 100644 db/drawio_schema_with_explanations.xml create mode 100644 db/migrate/20171003191909_create_users.rb create mode 100644 db/migrate/20171005135550_add_admin_to_users.rb create mode 100644 db/migrate/20171005140042_rename_users_profile_image_url.rb create mode 100644 db/migrate/20171006195948_create_blocks.rb create mode 100644 db/migrate/20171009194124_create_releases.rb create mode 100644 db/migrate/20171011232501_add_sync_errors_to_block.rb create mode 100644 db/migrate/20171012200106_create_standards.rb create mode 100644 db/migrate/20171016192627_create_cohorts.rb create mode 100644 db/migrate/20171017205306_create_cohort_users.rb create mode 100644 db/migrate/20171017221501_remove_presence_indices_from_label_and_pretty_name_on_cohorts.rb create mode 100644 db/migrate/20171019165527_create_content_files.rb create mode 100644 db/migrate/20171019192005_create_cohort_releases.rb create mode 100644 db/migrate/20171023211301_create_challenges.rb create mode 100644 db/migrate/20171024202210_create_checkpoint_submissions.rb create mode 100644 db/migrate/20171024203630_create_submitted_challenge_answers.rb create mode 100644 db/migrate/20171025211916_create_performances.rb create mode 100644 db/migrate/20171025223250_add_autoscore_to_content_files.rb create mode 100644 db/migrate/20171026193413_add_title_to_content_files.rb create mode 100644 db/migrate/20171102144822_cohort_release_unique_index.rb create mode 100644 db/migrate/20171102165314_add_unique_constraint_to_block_title.rb create mode 100644 db/migrate/20171102171128_add_position_to_cohort_releases.rb create mode 100644 db/migrate/20171127223701_create_activities.rb create mode 100644 db/migrate/20171130183523_create_user_last_viewed_standard_path.rb create mode 100644 db/migrate/20171130185636_add_uid_to_content_files.rb create mode 100644 db/migrate/20171205211624_add_slack_data_to_user.rb create mode 100644 db/migrate/20180104213519_add_standard_uid_to_performances.rb create mode 100644 db/migrate/20180110174447_add_type_relative_display_name_to_content_files.rb create mode 100644 db/migrate/20180110223429_add_auth_roles_to_user.rb create mode 100644 db/migrate/20180110224150_denormalize_submitted_challenge_answers.rb create mode 100644 db/migrate/20180110233303_add_roles_to_cohort_user.rb create mode 100644 db/migrate/20180112234504_add_last_viewed_cohort_id_to_users.rb create mode 100644 db/migrate/20180116174519_add_github_user_name_to_user.rb create mode 100644 db/migrate/20180116181937_add_taught_in_learn_to_cohorts.rb create mode 100644 db/migrate/20180116213927_remove_forge_admin_and_role.rb create mode 100644 db/migrate/20180119184121_add_cohort_id_to_submitted_challenge_answer.rb create mode 100644 db/migrate/20180124221642_denormalize_checkpoint_submissions.rb create mode 100644 db/migrate/20180125212203_rename_taught_in_learn.rb create mode 100644 db/migrate/20180125222044_change_learn_v2_default.rb create mode 100644 db/migrate/20180131220605_create_notifications.rb create mode 100644 db/migrate/20180202225951_add_github_sha_to_releases.rb create mode 100644 db/migrate/20180209175941_add_use_latest_release_to_cohort_releases.rb create mode 100644 db/migrate/20180313220132_add_docker_directory_path_to_challenges.rb create mode 100644 db/migrate/20180316222140_rename_cohort_title_to_name.rb create mode 100644 db/migrate/20180320162849_add_deleted_at_to_cohorts.rb create mode 100644 db/migrate/20180402212114_remove_pretty_name_from_cohort.rb create mode 100644 db/migrate/20180412214504_add_used_by_application_to_cohorts.rb create mode 100644 db/migrate/20180412223312_populate_used_by_application.rb create mode 100644 db/migrate/20180504193033_add_feature_branch_columns_to_releases.rb create mode 100644 db/migrate/20180504200226_add_pending_release_id_to_cohort_releases.rb create mode 100644 db/migrate/20180813205413_add_readme_text_to_release.rb create mode 100644 db/migrate/20180813213358_add_mode_to_cohort.rb create mode 100644 db/migrate/20180813214453_populate_mode_cohorts.rb create mode 100644 db/migrate/20180817181129_add_cohort_id_to_user_last_viewed_standard_path.rb create mode 100644 db/migrate/20180824200753_create_lesson_visits.rb create mode 100644 db/migrate/20181112215710_add_preferred_campus_to_users.rb create mode 100644 db/migrate/20181114190340_create_job_results.rb create mode 100644 db/migrate/20181120221622_create_sections.rb create mode 100644 db/migrate/20181121202104_add_section_id_to_cohort_releases.rb create mode 100644 db/migrate/20181203215524_add_challenge_completion_to_performance.rb create mode 100644 db/migrate/20181213180900_add_show_tests_to_challenges.rb create mode 100644 db/migrate/20181214175640_create_content_visibilities.rb create mode 100644 db/migrate/20181220005015_add_default_visibility_to_content_files.rb create mode 100644 db/migrate/20190410171829_create_pairings.rb create mode 100644 db/migrate/20190423171448_add_data_path_to_challenge.rb create mode 100644 db/migrate/20190510193410_add_last_setup_visit_to_cohort_users.rb create mode 100644 db/migrate/20190520223958_add_max_attempts_to_content_files.rb create mode 100644 db/migrate/20190529170059_add_allowed_attempts_to_challenge.rb create mode 100644 db/migrate/20190624222000_remove_max_attempts_from_challenges.rb create mode 100644 db/migrate/20190625173725_add_points_and_success_criteria_to_challenges.rb create mode 100644 db/migrate/20190709170851_drop_learn_version_columns_on_cohorts.rb create mode 100644 db/migrate/20190829203949_add_settings_to_cohorts.rb create mode 100644 db/migrate/20190910161219_add_points_to_submitted_challenge_answer.rb create mode 100644 db/migrate/20190910161303_add_points_to_checkpoint_submissions.rb create mode 100644 db/migrate/20190919173956_change_success_criteria_to_rubric_on_challenges.rb create mode 100644 db/migrate/20191001214559_add_topics_to_challenges.rb create mode 100644 db/migrate/20191001221324_add_perf_data_to_submissions.rb create mode 100644 db/migrate/20191008211403_add_release_id_to_challenges.rb create mode 100644 db/migrate/20191009214248_add_user_id_to_release.rb create mode 100644 db/migrate/20191024200444_add_visibility_type_to_content_visibilities.rb create mode 100644 db/migrate/20191031212058_add_api_token_to_user.rb create mode 100644 db/migrate/20191108211234_add_preview_to_releases.rb create mode 100644 db/migrate/20191114211938_create_api_interactions.rb create mode 100644 db/migrate/20191119225902_add_sandbox_boolean_to_cohorts.rb create mode 100644 db/migrate/20191206230913_add_sync_warnings.rb create mode 100644 db/migrate/20191209165026_add_metadata_to_api_interactions.rb create mode 100644 db/migrate/20191218232717_add_assign_partial_credig_to_challenges.rb create mode 100644 db/migrate/20200108184027_add_end_time_to_checkpoint_submissions.rb create mode 100644 db/migrate/20200110201253_change_cohort_default_percentage.rb create mode 100644 db/migrate/20200213201649_add_time_limit_to_content_file.rb create mode 100644 db/migrate/20200310222235_add_submitted_at_to_checkpoint_submissions.rb create mode 100644 db/migrate/20200408212051_add_allow_paired_submissions_to_cohort.rb create mode 100644 db/migrate/20200416155234_add_pair_submission_ids_to_checkpoint_submissinos.rb create mode 100644 db/migrate/20200430165251_add_external_to_challenge.rb create mode 100644 db/migrate/20200702155846_add_cache_to_blocks.rb create mode 100644 db/migrate/20200707164932_add_archived_at_to_blocks.rb create mode 100644 db/migrate/20200720213420_add_block_location_details_to_blocks.rb create mode 100644 db/migrate/20200724195251_add_whitelabeled_to_cohorts.rb create mode 100644 db/migrate/20200729214247_remove_block_uniqueness_pg_columns.rb create mode 100644 db/migrate/20200811215713_add_student_import_status_to_cohorts.rb create mode 100644 db/migrate/20200818172419_addd_docker_directory_zip_to_challenges.rb create mode 100644 db/migrate/20200828165130_default_block_org_tog_school.rb create mode 100644 db/schema.rb create mode 100644 db/seeds.rb create mode 100644 doc/api.md create mode 100644 doc/api/cohorts/creating_a_new_cohort.json create mode 100644 doc/api/cohorts/viewing_curriculum_details.json create mode 100644 doc/api/content/update_content_visibility.json create mode 100644 doc/api/enrollments/creating_a_user_and_their_enrollment.json create mode 100644 doc/api/enrollments/unenrolling_a_user_from_a_cohort.json create mode 100644 doc/api/enrollments/updating_a_user's_enrollment_roles.json create mode 100644 doc/api/enrollments/viewing_cohort_enrollments.json create mode 100644 doc/api/index.json create mode 100644 doc/api/units/update_unit_visibility.json create mode 100644 doc/api/users/regenerate_user_token.json create mode 100644 lib/tasks/challenge_release_ids.rake create mode 100644 lib/tasks/checkpoint_resubmission.rake create mode 100644 lib/tasks/checkpoint_submission_points.rake create mode 100644 lib/tasks/content_visibility_update.rake create mode 100644 lib/tasks/course_progress.rake create mode 100644 lib/tasks/course_yaml_backfill.rake create mode 100644 lib/tasks/grade_checkpoints.rake create mode 100644 lib/tasks/object_space_count.rake create mode 100644 lib/tasks/prep_notifier.rake create mode 100644 lib/tasks/set_block_caches.rake create mode 100644 lib/tasks/spec.rake create mode 100644 lib/tasks/ts_routes.rake create mode 100755 lintspec.sh create mode 100644 package.json create mode 100644 public/404.html create mode 100644 public/422.html create mode 100644 public/500.html create mode 100644 public/apple-touch-icon-152x152.png create mode 100644 public/apple-touch-icon-precomposed-152x152.png create mode 100644 public/apple-touch-icon-precomposed.png create mode 100644 public/apple-touch-icon.png create mode 100755 public/assets/images/hopscotch-sprite-green.png create mode 100755 public/assets/images/hopscotch-sprite-orange.png create mode 100644 public/assets/images/jupyter-logo.png create mode 100644 public/assets/images/svg/checkpoint-is-rejected.svg create mode 100644 public/assets/images/svg/checkpoint-is-scored.svg create mode 100644 public/assets/images/svg/checkpoint-is-submitted.svg create mode 100644 public/assets/images/svg/github-icon.svg create mode 100644 public/assets/images/svg/gitlab-icon-rgb.svg create mode 100644 public/assets/images/svg/octicon-git-branch.svg create mode 100644 public/assets/images/svg/redpriority_high-24px.svg create mode 100644 public/assets/images/svg/svg-link_off-24px.svg create mode 100755 public/assets/images/svg/svg-sprite-action-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-alert-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-av-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-content-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-custom-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-custom_material-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-device-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-file-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-hardware-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-navigation-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-notification-symbol.svg create mode 100644 public/assets/images/svg/svg-sprite-social-symbol.svg create mode 100644 public/favicon.ico create mode 100644 public/javascripts/apitome/application.js create mode 100644 public/robots.txt create mode 100755 public/sandbox/chai.js create mode 100644 public/sandbox/challenge-worker.js create mode 100755 public/sandbox/jasmine/boot.js create mode 100755 public/sandbox/jasmine/jasmine.js create mode 100644 public/sandbox/mocha/boot.js create mode 100644 public/sandbox/mocha/mocha.js create mode 100644 public/sandbox/mocha/test.html create mode 100644 public/sandbox/sandbox.html create mode 100644 public/sandbox/stacktrace.js create mode 100644 public/sandbox/worker.js create mode 100644 public/stylesheets/apitome/application.css create mode 100644 requirements.txt create mode 100755 scripts/sh/cohort_curriculum.sh create mode 100644 scripts/sh/content_file_mark_hidden.sh create mode 100644 scripts/sh/content_file_mark_visible.sh create mode 100644 scripts/sh/unit_mark_hidden.sh create mode 100644 scripts/sh/unit_mark_visible.sh create mode 100644 scripts/sql/checkpoint_answers.sql create mode 100644 scripts/sql/cohort_challenges_with_answers.sql create mode 100644 scripts/sql/cohort_prune_for_testing.sql create mode 100644 scripts/sql/percent_metrics.sql create mode 100644 scripts/sql/scrub_cohort_data.sql create mode 100644 tsconfig.json create mode 100644 vendor/assets/javascripts/bootstrap-datepicker.js create mode 100755 vendor/assets/javascripts/hopscotch.js create mode 100644 vendor/assets/javascripts/react-tooltip.js create mode 100644 vendor/assets/stylesheets/bootstrap-datepicker.css create mode 100755 vendor/assets/stylesheets/hopscotch.css create mode 100644 yarn.lock diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..ded31c0 --- /dev/null +++ b/.babelrc @@ -0,0 +1,18 @@ +{ + "presets": [ + ["env", { + "modules": false, + "targets": { + "browsers": "> 1%", + "uglify": true + }, + "useBuiltIns": true + }] + ], + + "plugins": [ + "syntax-dynamic-import", + "transform-object-rest-spread", + ["transform-class-properties", { "spec": true }] + ] +} diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..837f2f3 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,97 @@ +version: 2.0 + +defaults: &defaults + docker: + - image: circleci/ruby:2.6.0-node-browsers + environment: + - PG_HOST: localhost + - PG_USER: ubuntu + - RAILS_ENV: test + - RACK_ENV: test + - image: circleci/postgres:9.5-alpine + environment: + POSTGRES_USER: ubuntu + POSTGRES_DB: forge_test + - image: redis + working_directory: ~/forge + +jobs: + install_dependencies: + <<: *defaults + parallelism: 1 + steps: + - checkout + - attach_workspace: + at: ~/forge + - restore_cache: + key: v1-bundle-{{ checksum "Gemfile.lock" }} + - run: bundle install --path vendor/bundle + - save_cache: + key: v1-bundle-{{ checksum "Gemfile.lock" }} + paths: + - ~/forge/vendor/bundle + - persist_to_workspace: + root: . + paths: vendor/bundle + - restore_cache: + key: v1-yarn-{{ checksum "package.json" }} + - run: + name: Yarn Install JS Dependencies + command: yarn install + - save_cache: + key: v1-yarn-{{ checksum "package.json" }} + paths: + - ~/forge/node_modules + - persist_to_workspace: + root: . + paths: node_modules + + lint: + <<: *defaults + parallelism: 1 + steps: + - checkout + - attach_workspace: + at: ~/forge + - run: bundle --path vendor/bundle + - run: + name: Run Rubocop + command: bundle exec rubocop + - run: + name: Run tsc + command: ./node_modules/.bin/tsc --version && ./node_modules/.bin/tsc + + rake_test: + <<: *defaults + parallelism: 1 + steps: + - checkout + - attach_workspace: + at: ~/forge + - run: bundle --path vendor/bundle + - run: + name: Create DB + command: bundle exec rake db:create db:schema:load + environment: + DATABASE_URL: "postgres://ubuntu@localhost:5432/forge_test" + - run: + name: Run Rspec Tests + command: | + TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) + bundle exec rspec --profile 10 --format RspecJunitFormatter --out ~/forge/test-results/rspec.xml --format progress -- ${TESTFILES} + environment: + DATABASE_URL: "postgres://ubuntu@localhost:5432/forge_test" + - store_test_results: + path: ~/forge/test-results + +workflows: + version: 2 + build-and-deploy: + jobs: + - install_dependencies + - lint: + requires: + - install_dependencies + - rake_test: + requires: + - install_dependencies diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ce45694 --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +AUTH_CLIENT_ID=forge +AUTH_CLIENT_SECRET= +AUTH_WEBHOOK_TOKEN= +GITHUB_TOKEN= +ASSESSMENTS_API_KEY= +ASSESSMENTS_CALLBACK_TOKEN= +LEARN_FIND_COHORT_URL="http://localhost:3002/cohorts/find" +DS_PREP_UID="01t0a000005He03AAC" +STANDARD_EVENT_UIDS="40f3bb48-1d23-4105-a22a-7f07b90bd1e6,cc22ef7e-d9c9-4e3d-ae9f-39f3477c5861" diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..53cbea6 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,201 @@ +{ + "parserOptions": { + "ecmaVersion": 8, + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "jsx": true + }, + "sourceType": "module" + }, + + "env": { + "es6": true, + "node": false + }, + + "plugins": [ + "import", + "promise", + "react", + "jsx-a11y", + "standard" + ], + + "globals": { + "document": false, + "navigator": false, + "window": false + }, + + "rules": { + "array-bracket-spacing": ["error", "never"], + "curly": ["error", "multi-line"], + "block-spacing": ["error", "always"], + "brace-style": ["error", "1tbs", { "allowSingleLine": true }], + "semi": ["error", "always"], + "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], + "comma-spacing": ["error", { "before": false, "after": true }], + "no-debugger": "error", + "no-extra-parens": ["error", "functions"], + "no-floating-decimal": "error", + "no-implicit-coercion": "error", + "no-irregular-whitespace": "error", + "no-multi-spaces": "error", + "no-regex-spaces": "error", + "no-undef-init": "error", + "no-unsafe-negation": "error", + "no-unused-labels": "error", + "no-useless-return": "error", + "wrap-iife": ["error", "any", { "functionPrototypeMethods": true }], + "yoda": ["error", "never"], + "eol-last": "error", + "func-call-spacing": ["error", "never"], + "indent": ["error", 2], + "key-spacing": ["error", { "beforeColon": false, "afterColon": true }], + "keyword-spacing": ["error", { "before": true, "after": true }], + "new-parens": "error", + "newline-after-var": ["error", "always"], + "newline-before-return": "error", + "no-lonely-if": "error", + "no-multiple-empty-lines": ["error", { "max": 1 }], + "no-trailing-spaces": "error", + "no-unneeded-ternary": ["error", { "defaultAssignment": false }], + "no-whitespace-before-property": "error", + "nonblock-statement-body-position": "error", + "object-curly-spacing": ["error", "always"], + "operator-assignment": "error", + "space-before-blocks": ["error", "always"], + "space-before-function-paren": ["error", "never"], + "space-in-parens": ["error", "never"], + "space-infix-ops": "error", +// "spaced-comment": ["error", "always", { +// "line": { "markers": ["*package", "!", "//", ","] }, +// "block": { "balanced": true, "markers": ["*package", "!", ",", ":", "::", "flow-include"], "exceptions": ["*", "//"] } +// }], + "wrap-regex": "error" +// "accessor-pairs": "error", +// "arrow-spacing": ["error", { "before": true, "after": true }], +// "camelcase": ["error", { "properties": "never" }], +// "comma-dangle": ["error", { +// "arrays": "never", +// "objects": "never", +// "imports": "never", +// "exports": "never", +// "functions": "never" +// }], +// "comma-style": ["error", "last"], +// "constructor-super": "error", +// "dot-location": ["error", "property"], +// "eqeqeq": ["error", "always", { "null": "ignore" }], +// "generator-star-spacing": ["error", { "before": true, "after": true }], +// "handle-callback-err": ["error", "^(err|error)$" ], +// "new-cap": ["error", { "newIsCap": true, "capIsNew": false }], +// "no-array-constructor": "error", +// "no-caller": "error", +// "no-class-assign": "error", +// "no-compare-neg-zero": "error", +// "no-cond-assign": "error", +// "no-const-assign": "error", +// "no-constant-condition": ["error", { "checkLoops": false }], +// "no-control-regex": "error", +// "no-delete-var": "error", +// "no-dupe-args": "error", +// "no-dupe-class-members": "error", +// "no-dupe-keys": "error", +// "no-duplicate-case": "error", +// "no-empty-character-class": "error", +// "no-empty-pattern": "error", +// "no-eval": "error", +// "no-ex-assign": "error", +// "no-extend-native": "error", +// "no-extra-bind": "error", +// "no-extra-boolean-cast": "error", +// "no-fallthrough": "error", +// "no-func-assign": "error", +// "no-global-assign": "error", +// "no-implied-eval": "error", +// "no-inner-declarations": ["error", "functions"], +// "no-invalid-regexp": "error", +// "no-iterator": "error", +// "no-label-var": "error", +// "no-labels": ["error", { "allowLoop": false, "allowSwitch": false }], +// "no-lone-blocks": "error", +// "no-mixed-operators": ["error", { +// "groups": [ +// ["==", "!=", "===", "!==", ">", ">=", "<", "<="], +// ["&&", "||"], +// ["in", "instanceof"] +// ], +// "allowSamePrecedence": true +// }], +// "no-mixed-spaces-and-tabs": "error", +// "no-multi-str": "error", +// "no-negated-in-lhs": "error", +// "no-new": "error", +// "no-new-func": "error", +// "no-new-object": "error", +// "no-new-require": "error", +// "no-new-symbol": "error", +// "no-new-wrappers": "error", +// "no-obj-calls": "error", +// "no-octal": "error", +// "no-octal-escape": "error", +// "no-path-concat": "error", +// "no-proto": "error", +// "no-redeclare": "error", +// "no-return-assign": ["error", "except-parens"], +// "no-return-await": "error", +// "no-self-assign": "error", +// "no-self-compare": "error", +// "no-sequences": "error", +// "no-shadow-restricted-names": "error", +// "no-sparse-arrays": "error", +// "no-tabs": "error", +// "no-template-curly-in-string": "error", +// "no-this-before-super": "error", +// "no-throw-literal": "error", +// "no-undef": "error", +// "no-unexpected-multiline": "error", +// "no-unmodified-loop-condition": "error", +// "no-unreachable": "error", +// "no-unsafe-finally": "error", +// "no-unused-expressions": ["error", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }], +// "no-unused-vars": ["error", { "vars": "all", "args": "none", "ignoreRestSiblings": true }], +// "no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }], +// "no-useless-call": "error", +// "no-useless-computed-key": "error", +// "no-useless-constructor": "error", +// "no-useless-escape": "error", +// "no-useless-rename": "error", +// "no-with": "error", +// "object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }], +// "one-var": ["error", { "initialized": "never" }], +// "operator-linebreak": ["error", "after", { "overrides": { "?": "before", ":": "before" } }], +// "padded-blocks": ["error", { "blocks": "never", "switches": "never", "classes": "never" }], +// "prefer-promise-reject-errors": "error", +// "rest-spread-spacing": ["error", "never"], +// "semi-spacing": ["error", { "before": false, "after": true }], +// "space-unary-ops": ["error", { "words": true, "nonwords": false }], +// "symbol-description": "error", +// "template-curly-spacing": ["error", "never"], +// "template-tag-spacing": ["error", "never"], +// "unicode-bom": ["error", "never"], +// "use-isnan": "error", +// "valid-typeof": ["error", { "requireStringLiterals": true }], +// "yield-star-spacing": ["error", "both"], +// "import/export": "error", +// "import/first": "error", +// "import/no-duplicates": "error", +// "import/no-webpack-loader-syntax": "error", +// +// "node/no-deprecated-api": "error", +// "node/process-exit-as-throw": "error", +// +// "promise/param-names": "error", +// +// "standard/array-bracket-even-spacing": ["error", "either"], +// "standard/computed-property-even-spacing": ["error", "even"], +// "standard/no-callback-literal": "error", +// "standard/object-curly-even-spacing": ["error", "either"] + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e644a12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* + +/node_modules + +.byebug_history +.DS_Store +.env +.idea/* +/public/packs-test +/node_modules +.rspec_status +.idea/* +.vscode/* +/public/packs +/public/packs-test +/node_modules +yarn-debug.log* +.yarn-integrity +yarn-error.log +tags +.zshrc +.solargraph.yml \ No newline at end of file diff --git a/.haml-lint.yml b/.haml-lint.yml new file mode 100644 index 0000000..c82c789 --- /dev/null +++ b/.haml-lint.yml @@ -0,0 +1,6 @@ +linters: + LineLength: + max: 200 + + InlineStyles: + enabled: false diff --git a/.overcommit.yml b/.overcommit.yml new file mode 100644 index 0000000..13aa9ce --- /dev/null +++ b/.overcommit.yml @@ -0,0 +1,963 @@ +# Default configuration that all Overcommit configurations inherit from. +# +# This is an opinionated list of which hooks are valuable to run and what their +# out-of-the-box settings should be. +#------------------------------------------------------------------------------- + +# Loads Bundler context from a Gemfile. If false, does nothing (default). +# +# Specifying a Gemfile for Bundler to load allows you to control which gems are +# available in the load path (i.e. loadable via `require`) within your hook +# runs. Note that having a Gemfile requires you to include `overcommit` itself +# in your Gemfile (otherwise Overcommit can't load itself!). +# +# This is useful if you want to: +# +# - Enforce a specific version of Overcommit to use for all hook runs +# (or to use a version from the master branch that has not been released yet) +# - Enforce a specific version or unreleased branch is used for a gem you want +# to use in your git hooks +# +# WARNING: This makes your hook runs slower, but you can work around this! +# +# Loading a Bundler context necessarily adds a startup delay to your hook runs +# as Bundler parses the Gemfile and checks that the dependencies are satisfied. +# Thus for projects with many gems this can introduce a noticeable delay. +# +# The recommended workaround is to create a separate Gemfile in the root of your +# repository (call it `.overcommit_gems.rb`), and include only the gems that +# your Overcommit hooks need in order to run. This significantly reduces the +# startup delay in your hook runs. Make sure to commit both +# `.overcommit_gems.rb` and the resulting `.overcommit_gems.rb.lock` file to +# your repository, and then set the `gemfile` option below to the name you gave +# the file. +# (Generate lock file by running `bundle install --gemfile=.overcommit_gems.rb`) +gemfile: false + +# Where to store hook plugins specific to a repository. These are loaded in +# addition to the default hooks Overcommit comes with. The location is relative +# to the root of the repository. +plugin_directory: '.git-hooks' + +# Whether to hide hook output by default. This results in completely silent hook +# runs except in the case of warning or failure. +quiet: false + +# Number of hooks that can be run concurrently. Typically this won't need to be +# adjusted, but if you know that some of your hooks themselves use multiple +# processors you can lower this value accordingly. You can define +# single-operator mathematical expressions, e.g. '%{processors} * 2', or +# '%{processors} / 2'. +concurrency: '%{processors}' + +# Whether to check if a hook plugin has changed since Overcommit last ran it. +# This is a defense mechanism when working with repositories which can contain +# untrusted code (e.g. when you fetch a pull request from a third party). +# See https://github.com/brigade/overcommit#security for more information. +verify_signatures: false + +# Hooks that are run against every commit message after a user has written it. +# These hooks are useful for enforcing policies on commit messages written for a +# project. +CommitMsg: + ALL: + requires_files: false + quiet: false + + CapitalizedSubject: + enabled: false + description: 'Check subject capitalization' + + EmptyMessage: + enabled: false + description: 'Check for empty commit message' + quiet: true + + GerritChangeId: + enabled: false + description: 'Ensure Gerrit Change-Id is present' + required: true + + HardTabs: + enabled: false + description: 'Check for hard tabs' + + MessageFormat: + enabled: false + description: 'Check commit message matches expected pattern' + pattern: '(.+)[|](.+)[|](.+)' + expected_pattern_message: ' | | ' + sample_message: 'DEFECT-1234 | Refactored Onboarding flow | John Doe' + + RussianNovel: + enabled: false + description: 'Check length of commit message' + quiet: true + + SingleLineSubject: + enabled: false + description: 'Check subject line' + + SpellCheck: + enabled: false + description: 'Check for misspelled words' + required_executable: 'hunspell' + flags: ['-a'] + + TextWidth: + enabled: false + description: 'Check text width' + max_subject_width: 60 + max_body_width: 72 + + TrailingPeriod: + enabled: false + description: 'Check for trailing periods in subject' + +# Hooks that are run after `git commit` is executed, before the commit message +# editor is displayed. These hooks are ideal for syntax checkers, linters, and +# other checks that you want to run before you allow a commit object to be +# created. +PreCommit: + ALL: + problem_on_unmodified_line: report + requires_files: true + required: false + quiet: false + + AuthorEmail: + enabled: false + description: 'Check author email' + requires_files: false + required: true + quiet: true + pattern: '^[^@]+@.*$' + + AuthorName: + enabled: false + description: 'Check for author name' + requires_files: false + required: true + quiet: true + + BerksfileCheck: + enabled: false + description: 'Check Berksfile lock' + required_executable: 'berks' + flags: ['list', '--quiet'] + install_command: 'gem install berks' + include: + - 'Berksfile' + - 'Berksfile.lock' + + BrokenSymlinks: + enabled: false + description: 'Check for broken symlinks' + quiet: true + + BundleAudit: + enabled: false + description: 'Check for vulnerable versions of gems' + required_executable: 'bundle-audit' + install_command: 'gem install bundler-audit' + + BundleCheck: + enabled: false + description: 'Check Gemfile dependencies' + required_executable: 'bundle' + flags: ['check'] + install_command: 'gem install bundler' + include: + - 'Gemfile' + - 'Gemfile.lock' + - '*.gemspec' + + BundleOutdated: + enabled: false + description: 'List installed gems with newer versions available' + required_executable: 'bundle' + flags: ['outdated', '--strict', '--parseable'] + install_command: 'gem install bundler' + + CaseConflicts: + enabled: false + description: 'Check for case-insensitivity conflicts' + quiet: true + + ChamberSecurity: + enabled: false + description: 'Check that settings have been secured with Chamber' + required_executable: 'chamber' + flags: ['secure', '--files'] + install_command: 'gem install chamber' + include: + - 'config/settings.yml' + - 'config/settings/**/*.yml' + + CoffeeLint: + enabled: false + description: 'Analyze with coffeelint' + required_executable: 'coffeelint' + flags: ['--reporter=csv'] + install_command: 'npm install -g coffeelint' + include: '**/*.coffee' + + Credo: + enabled: false + description: 'Analyze with credo' + required_executable: 'mix' + flags: ['credo', '--all', '--strict', '--format', 'flycheck'] + include: + - '**/*.ex' + - '**/*.exs' + + CssLint: + enabled: false + description: 'Analyze with csslint' + required_executable: 'csslint' + flags: ['--quiet', '--format=compact'] + install_command: 'npm install -g csslint' + include: '**/*.css' + + Dogma: + enabled: false + description: 'Analyze with dogma' + required_executable: 'mix' + flags: ['dogma'] + include: + - '**/*.ex' + - '**/*.exs' + + EsLint: + enabled: true + on_warn: fail + required_executable: './node_modules/.bin/eslint' + include: + - 'app/**/*.js' + - 'app/**/*.jsx' + + ExecutePermissions: + enabled: false + description: 'Check for file execute permissions' + quiet: true + + Fasterer: + enabled: false + description: 'Analyzing for potential speed improvements' + required_executable: 'fasterer' + install_command: 'gem install fasterer' + include: '**/*.rb' + + FixMe: + enabled: false + description: 'Check for "token" strings' + required_executable: 'grep' + flags: ['-IEHnw'] + keywords: ['BROKEN', 'BUG', 'ERROR', 'FIXME', 'HACK', 'NOTE', 'OPTIMIZE', 'REVIEW', 'TODO', 'WTF', 'XXX'] + + Foodcritic: + enabled: false + description: 'Analyze with Foodcritic' + required_executable: 'foodcritic' + flags: ['--epic-fail=any'] + install_command: 'gem install foodcritic' + + ForbiddenBranches: + enabled: false + description: 'Check for commit to forbidden branch' + quiet: true + branch_patterns: ['master'] + + GoLint: + enabled: false + description: 'Analyze with golint' + required_executable: 'golint' + install_command: 'go get github.com/golang/lint/golint' + include: '**/*.go' + + GoVet: + enabled: false + description: 'Analyze with go vet' + required_executable: 'go' + flags: ['tool', 'vet'] + install_command: 'go get golang.org/x/tools/cmd/vet' + include: '**/*.go' + + HamlLint: + enabled: false + description: 'Analyze with haml-lint' + required_executable: 'haml-lint' + install_command: 'gem install haml-lint' + include: '**/*.haml' + on_warn: 'fail' + + HardTabs: + enabled: false + description: 'Check for hard tabs' + quiet: true + required_executable: 'grep' + flags: ['-IHn', "\t"] + + Hlint: + enabled: false + description: 'Analyze with hlint' + required_executable: 'hlint' + install_command: 'cabal install hlint' + include: '**/*.hs' + + HtmlHint: + enabled: false + description: 'Analyze with HTMLHint' + required_executable: 'htmlhint' + install_command: 'npm install -g htmlhint' + include: '**/*.html' + + HtmlTidy: + enabled: false + description: 'Analyze HTML with tidy' + required_executable: 'tidy' + flags: ['-errors', '-quiet', '-utf8'] + include: '**/*.html' + + ImageOptim: + enabled: false + description: 'Check for optimizable images' + required_executable: 'image_optim' + install_command: 'gem install image_optim' + include: + - '**/*.gif' + - '**/*.jpeg' + - '**/*.jpg' + - '**/*.png' + - '**/*.svg' + + JavaCheckstyle: + enabled: false + description: 'Analyze with checkstyle' + required_executable: 'checkstyle' + flags: ['-c', '/sun_checks.xml'] + include: '**/*.java' + + Jscs: + enabled: false + description: 'Analyze with JSCS' + required_executable: 'jscs' + flags: ['--reporter=inline'] + install_command: 'npm install -g jscs' + include: '**/*.js' + + JsHint: + enabled: false + description: 'Analyze with JSHint' + required_executable: 'jshint' + flags: ['--verbose'] + install_command: 'npm install -g jshint' + include: '**/*.js' + + JsLint: + enabled: false + description: 'Analyze with JSLint' + required_executable: 'jslint' + flags: ['--terse'] + install_command: 'npm install -g jslint' + include: '**/*.js' + + Jsl: + enabled: false + description: 'Analyze with JSL' + required_executable: 'jsl' + flags: ['-nologo', '-nofilelisting', '-nocontext', '-nosummary'] + include: '**/*.js' + + JsonSyntax: + enabled: false + description: 'Validate JSON syntax' + required_library: 'json' + install_command: 'gem install json' + include: '**/*.json' + + LicenseHeader: + enabled: false + license_file: 'LICENSE.txt' + description: 'Check source files for license headers' + + LocalPathsInGemfile: + enabled: false + description: 'Check for local paths in Gemfile' + required_executable: 'grep' + flags: ['-IHnE', "^[^#]*((\\bpath:)|(:path[ \t]*=>))"] + include: '**/Gemfile' + + Mdl: + enabled: false + description: 'Analyze with mdl' + required_executable: 'mdl' + install_command: 'gem install mdl' + include: '**/*.md' + + MergeConflicts: + enabled: false + description: 'Check for merge conflicts' + quiet: true + required_executable: 'grep' + flags: ['-IHn', "^<<<<<<<[ \t]"] + + NginxTest: + enabled: false + description: 'Test nginx configs' + required_executable: 'nginx' + flags: ['-t'] + include: '**/nginx.conf' + + Pep257: + enabled: false + description: 'Analyze docstrings with pep257' + required_executable: 'pep257' + install_command: 'pip install pep257' + include: '**/*.py' + + Pep8: + enabled: false + description: 'Analyze with pep8' + required_executable: 'pep8' + install_command: 'pip install pep8' + include: '**/*.py' + + PuppetLint: + enabled: false + description: 'Analyze with puppet-lint' + required_executable: 'puppet-lint' + install_command: 'gem install puppet-lint' + flags: + - '--log-format="%{fullpath}:%{line}:%{column}:%{KIND}: %{message} (%{check})"' + - '--fail-on-warnings' + - '--error-level=all' + include: '**/*.pp' + + Pyflakes: + enabled: false + description: 'Analyze with pyflakes' + required_executable: 'pyflakes' + install_command: 'pip install pyflakes' + include: '**/*.py' + + Pylint: + enabled: false + description: 'Analyze with Pylint' + required_executable: 'pylint' + install_command: 'pip install pylint' + flags: + - '--msg-template="{path}:{line}:{C}: {msg} ({symbol})"' + - '--reports=n' + - '--persistent=n' + include: '**/*.py' + + PythonFlake8: + enabled: false + description: 'Analyze with flake8' + required_executable: 'flake8' + install_command: 'pip install flake8' + include: '**/*.py' + + RakeTarget: + enabled: false + description: 'Run rake targets' + # targets: + # - 'lint' + # - 'validate' + # - '...' + required_executable: 'rake' + install_command: 'gem install rake' + + RailsBestPractices: + enabled: false + description: 'Analyze with RailsBestPractices' + required_executable: 'rails_best_practices' + flags: ['--without-color'] + install_command: 'gem install rails_best_practices' + + RailsSchemaUpToDate: + enabled: false + description: 'Check if database schema is up to date' + include: + - 'db/migrate/*.rb' + - 'db/schema.rb' + - 'db/structure.sql' + + Reek: + enabled: false + description: 'Analyze with Reek' + required_executable: 'reek' + flags: ['--single-line', '--no-color'] + install_command: 'gem install reek' + include: + - '**/*.gemspec' + - '**/*.rake' + - '**/*.rb' + - '**/Gemfile' + - '**/Rakefile' + + RuboCop: + enabled: true + description: 'Analyze with RuboCop' + required_executable: 'rubocop' + flags: ['--format=emacs', '--force-exclusion', '--display-cop-names'] + install_command: 'gem install rubocop' + on_warn: 'fail' + include: + - '**/*.gemspec' + - '**/*.rake' + - '**/*.rb' + - '**/*.ru' + - '**/Gemfile' + - '**/Rakefile' + + RubyLint: + enabled: false + description: 'Analyze with ruby-lint' + required_executable: 'ruby-lint' + flags: ['--presenter=syntastic', '--levels=error,warning'] + install_command: 'gem install ruby-lint' + include: + - '**/*.gemspec' + - '**/*.rb' + + Scalariform: + enabled: false + description: 'Check formatting with Scalariform' + required_executable: 'scalariform' + flags: ['--test'] + include: '**/*.scala' + + Scalastyle: + enabled: false + description: 'Analyze with Scalastyle' + required_executable: 'scalastyle' + include: '**/*.scala' + + StyleLint: + enabled: false + description: 'Analyze with style-lint' + required_library: 'json' + required_executable: 'stylelint' + flags: ['app/**/*.css', 'app/**/*.scss'] + install_command: 'npm install -g stylelint' + include: + - '**/*.scss' + - '**/*.css' + + SemiStandard: + enabled: false + description: 'Analyze with semistandard' + required_executable: 'semistandard' + flags: ['--verbose'] + install_command: 'npm install -g semistandard' + include: '**/*.js' + + ShellCheck: + enabled: false + description: 'Analyze with ShellCheck' + required_executable: 'shellcheck' + flags: ['--format=gcc'] + include: '**/*.sh' + + SlimLint: + enabled: false + description: 'Analyze with slim-lint' + required_executable: 'slim-lint' + install_command: 'gem install slim_lint' + include: '**/*.slim' + + Sqlint: + enabled: false + description: 'Analyze with sqlint' + required_executable: 'sqlint' + install_command: 'gem install sqlint' + include: '**/*.sql' + + Standard: + enabled: false + description: 'Analyze with standard' + required_executable: 'standard' + flags: ['--verbose'] + install_command: 'npm install -g standard' + include: '**/*.js' + + TsLint: + enabled: false + description: 'Analyze with TSLint' + required_executable: 'tslint' + install_command: 'npm install -g tslint typescript' + flags: ['--t=prose'] + include: '**/*.ts' + + TrailingWhitespace: + enabled: false + description: 'Check for trailing whitespace' + required_executable: 'grep' + flags: ['-IHn', "[ \t]$"] + + TravisLint: + enabled: false + description: 'Check Travis CI configuration' + required_executable: 'travis' + flags: ['lint'] + install_command: 'gem install travis' + include: '.travis.yml' + + Vint: + enabled: false + description: 'Analyze with Vint' + required_executable: 'vint' + install_command: 'pip install vim-vint' + include: + - '**/*.vim' + - '**/*.vimrc' + + W3cCss: + enabled: false + description: 'Analyze with W3C CSS validation service' + required_library: 'w3c_validators' + install_command: 'gem install w3c_validators' + validator_uri: 'http://jigsaw.w3.org/css-validator/validator' + language: 'en' + profile: 'css3' + warn_level: 2 + include: + - '**/*.css' + + W3cHtml: + enabled: false + description: 'Analyze with W3C HTML validation service' + required_library: 'w3c_validators' + install_command: 'gem install w3c_validators' + validator_uri: 'http://validator.w3.org/check' + charset: 'utf-8' + doctype: 'HTML5' + include: + - '**/*.html' + + LineEndings: + description: 'Check line endings' + enabled: false + eol: "\n" # or "\r\n" for Windows-style newlines + + XmlLint: + enabled: false + description: 'Analyze with xmllint' + required_executable: 'xmllint' + flags: ['--noout'] + include: + - '**/*.xml' + - '**/*.svg' + + XmlSyntax: + enabled: false + description: 'Check XML syntax' + required_library: 'rexml/document' + include: + - '**/*.xml' + - '**/*.svg' + + YamlLint: + enabled: false + description: 'Analyze with YAMLlint' + required_executable: 'yamllint' + flags: ['--format=parsable'] + install_command: 'pip install yamllint' + include: + - '**/*.yaml' + - '**/*.yml' + + YamlSyntax: + enabled: false + description: 'Check YAML syntax' + required_library: 'yaml' + include: + - '**/*.yaml' + - '**/*.yml' + +# Hooks that run after HEAD changes or a file is explicitly checked out. +PostCheckout: + ALL: + required: false + quiet: false + skip_file_checkout: true + + BowerInstall: + enabled: false + description: 'Install bower dependencies' + requires_files: true + required_executable: 'bower' + install_command: 'npm install -g bower' + flags: ['install'] + include: 'bower.json' + + BundleInstall: + enabled: false + description: 'Install Bundler dependencies' + requires_files: true + required_executable: 'bundle' + install_command: 'gem install bundler' + flags: ['install'] + include: + - 'Gemfile' + - 'Gemfile.lock' + - '*.gemspec' + + IndexTags: + enabled: false + description: 'Generate tags file from source' + quiet: true + required_executable: 'ctags' + + NpmInstall: + enabled: false + description: 'Install NPM dependencies' + requires_files: true + required_executable: 'npm' + flags: ['install'] + include: + - 'package.json' + - 'npm-shrinkwrap.json' + + SubmoduleStatus: + enabled: false + description: 'Check submodule status' + quiet: true + recursive: false + +# Hooks that run after a commit is created. +PostCommit: + ALL: + requires_files: false + required: false + quiet: false + + BowerInstall: + enabled: false + description: 'Install bower dependencies' + requires_files: true + required_executable: 'bower' + install_command: 'npm install -g bower' + flags: ['install'] + include: 'bower.json' + + BundleInstall: + enabled: false + description: 'Install Bundler dependencies' + requires_files: true + required_executable: 'bundle' + install_command: 'gem install bundler' + flags: ['install'] + include: + - 'Gemfile' + - 'Gemfile.lock' + - '*.gemspec' + + Commitplease: + enabled: false + description: 'Analyze with Commitplease' + required_executable: './node_modules/.bin/commitplease' + install_command: 'npm install --save-dev commitplease' + flags: ['-1'] + + GitGuilt: + enabled: false + description: 'Calculate changes in blame since last commit' + requires_files: true + required_executable: 'git-guilt' + flags: ['HEAD~', 'HEAD'] + install_command: 'npm install -g git-guilt' + + IndexTags: + enabled: false + description: 'Generate tags file from source' + quiet: true + required_executable: 'ctags' + + NpmInstall: + enabled: false + description: 'Install NPM dependencies' + requires_files: true + required_executable: 'npm' + flags: ['install'] + include: + - 'package.json' + - 'npm-shrinkwrap.json' + + SubmoduleStatus: + enabled: false + description: 'Check submodule status' + quiet: true + recursive: false + +# Hooks that run after `git merge` executes successfully (no merge conflicts). +PostMerge: + ALL: + requires_files: false + quiet: false + + BowerInstall: + enabled: false + description: 'Install bower dependencies' + requires_files: true + required_executable: 'bower' + install_command: 'npm install -g bower' + flags: ['install'] + include: 'bower.json' + + BundleInstall: + enabled: false + description: 'Install Bundler dependencies' + requires_files: true + required_executable: 'bundle' + install_command: 'gem install bundler' + flags: ['install'] + include: + - 'Gemfile' + - 'Gemfile.lock' + - '*.gemspec' + + IndexTags: + enabled: false + description: 'Generate tags file from source' + quiet: true + required_executable: 'ctags' + + NpmInstall: + enabled: false + description: 'Install NPM dependencies' + requires_files: true + required_executable: 'npm' + flags: ['install'] + include: + - 'package.json' + - 'npm-shrinkwrap.json' + + SubmoduleStatus: + enabled: false + description: 'Check submodule status' + quiet: true + recursive: false + +# Hooks that run after a commit is modified by an amend or rebase. +PostRewrite: + ALL: + requires_files: false + quiet: false + + BowerInstall: + enabled: false + description: 'Install bower dependencies' + requires_files: true + required_executable: 'bower' + install_command: 'npm install -g bower' + flags: ['install'] + include: 'bower.json' + + BundleInstall: + enabled: false + description: 'Install Bundler dependencies' + requires_files: true + required_executable: 'bundle' + install_command: 'gem install bundler' + flags: ['install'] + include: + - 'Gemfile' + - 'Gemfile.lock' + - '*.gemspec' + + IndexTags: + enabled: false + description: 'Generate tags file from source' + quiet: true + required_executable: 'ctags' + + NpmInstall: + enabled: false + description: 'Install NPM dependencies' + requires_files: true + required_executable: 'npm' + flags: ['install'] + include: + - 'package.json' + - 'npm-shrinkwrap.json' + + SubmoduleStatus: + enabled: false + description: 'Check submodule status' + quiet: true + recursive: false + +# Hooks that run during `git push`, after remote refs have been updated but +# before any objects have been transferred. +PrePush: + ALL: + requires_files: false + required: false + quiet: false + + ProtectedBranches: + enabled: false + description: 'Check for illegal pushes to protected branches' + destructive_only: true + branches: ['master'] + + Pytest: + enabled: false + description: 'Run pytest test suite' + required_executable: 'pytest' + install_command: 'pip install -U pytest' + + PythonNose: + enabled: false + description: 'Run nose test suite' + required_executable: 'nosetests' + install_command: 'pip install -U nose' + + RSpec: + enabled: false + description: 'Run RSpec test suite' + required_executable: 'rspec' + + RakeTarget: + enabled: false + description: 'Run rake targets' + # targets: + # - 'lint' + # - 'validate' + # - '...' + required_executable: 'rake' + install_command: 'gem install rake' + + Minitest: + enabled: false + description: 'Run Minitest test suite' + command: ['ruby', '-Ilib:test', '-rminitest', "-e 'exit! Minitest.run'"] + include: 'test/**/*_test.rb' + + TestUnit: + enabled: false + description: 'Run Test::Unit test suite' + command: ['ruby', '-Ilib:test', '-rtest/unit', "-e 'exit! Test::Unit::AutoRunner.run'"] + + Brakeman: + enabled: false + description: 'Check for security vulnerabilities' + required_executable: 'brakeman' + flags: ['--exit-on-warn', '--quiet', '--summary'] + install_command: 'gem install brakeman' + +# Hooks that run during `git rebase`, before any commits are rebased. +# If a hook fails, the rebase is aborted. +PreRebase: + ALL: + requires_files: false + required: false + quiet: false + + MergedCommits: + enabled: false + description: 'Check for commits that have already been merged' + branches: ['master'] diff --git a/.pairs b/.pairs new file mode 100644 index 0000000..8add478 --- /dev/null +++ b/.pairs @@ -0,0 +1,15 @@ +authors: + jj: Jeremy Jackson; jejacks0n + mb: Martha Berner; martha + pg: Peter Grunde; peter.a.grunde + dl: Derik Linch; derik.linch + cl: Chad Lillquist; chad.lillquist + jh: Joel Hawksley; joel + +email: + domain: galvanize.com + +email_addresses: + jj: jejacks0n@gmail.com + pg: peter.a.grunde@gmail.com + jh: joel@hawksley.org diff --git a/.postcssrc.yml b/.postcssrc.yml new file mode 100644 index 0000000..150dac3 --- /dev/null +++ b/.postcssrc.yml @@ -0,0 +1,3 @@ +plugins: + postcss-import: {} + postcss-cssnext: {} diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..267ce07 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,212 @@ +AllCops: + Exclude: + - 'bin/**/*' + - 'vendor/**/*' + - 'db/schema.rb' + - 'node_modules/**/*' + - 'spec/**/*' + - 'db/seeds.rb' + +Bundler/OrderedGems: + Enabled: false + +Style/Semicolon: + Enabled: true + +Lint/AssignmentInCondition: + Enabled: true + +Style/FloatDivision: + Enabled: false + +Naming/MemoizedInstanceVariableName: + Enabled: false + +Layout/EndAlignment: + Enabled: true + +Lint/HandleExceptions: + Enabled: true + +Lint/IneffectiveAccessModifier: + Enabled: true + +Lint/UselessAccessModifier: + Enabled: true + +Lint/RescueException: + Enabled: true + +Lint/ParenthesesAsGroupedExpression: + Enabled: false + +Lint/NonLocalExitFromIterator: + Enabled: false + +Lint/UriEscapeUnescape: + Enabled: false + +Style/DoubleNegation: + Enabled: false + +Style/IfUnlessModifier: + Enabled: false + +Style/MultilineBlockChain: + Enabled: false + +Naming/PredicateName: + Enabled: true + Exclude: + - app/controllers/application_controller.rb + +Naming/VariableNumber: + Enabled: true + Exclude: + - spec/**/* + +Metrics/AbcSize: + Enabled: false + +Metrics/BlockLength: + Enabled: true + Max: 1200 + +Metrics/BlockNesting: + Enabled: true + Max: 6 + +Metrics/ClassLength: + Enabled: true + Max: 500 + +Metrics/CyclomaticComplexity: + Enabled: true + Max: 30 + +Metrics/LineLength: + Enabled: true + Max: 140 + Exclude: + - app/controllers/cohorts_controller.rb + - app/jobs/create_release_job.rb + - app/services/standard_submissions_service.rb + - app/services/sql_challenge_db_service.rb + - spec/**/* + +Metrics/MethodLength: + Enabled: true + Max: 200 + +Metrics/ModuleLength: + Enabled: true + Max: 500 + +Metrics/ParameterLists: + Enabled: true + Max: 8 + +Metrics/PerceivedComplexity: + Enabled: false + +Layout/DotPosition: + EnforcedStyle: trailing + +Layout/EmptyLinesAroundArguments: + Enabled: false + +Layout/SpaceInsideBlockBraces: + Enabled: false + +Layout/MultilineMethodCallIndentation: + Enabled: false + +Style/SpecialGlobalVars: + EnforcedStyle: use_perl_names + +Style/StringLiterals: + EnforcedStyle: double_quotes + +Style/TrivialAccessors: + Enabled: true + +Security/YAMLLoad: + Enabled: false + +Style/AndOr: + Enabled: false + +Layout/CaseIndentation: + Enabled: false + +Style/Documentation: + Enabled: false + +Style/EmptyMethod: + Enabled: false + +Style/FrozenStringLiteralComment: + Enabled: false + +Style/MutableConstant: + Enabled: false + +Style/NumericLiteralPrefix: + Enabled: false + +Style/NumericLiterals: + Enabled: false + +Style/NumericPredicate: + Enabled: false + +Style/RedundantParentheses: + Enabled: false + +Style/RegexpLiteral: + Enabled: false + +Style/UnneededPercentQ: + Enabled: false + +Naming/VariableName: + Enabled: true + +Style/WordArray: + Enabled: false + +Style/ClassAndModuleChildren: + Enabled: false + +Style/ClassVars: + Enabled: false + +Style/DateTime: + Enabled: false + +Style/Next: + Enabled: false + +Style/GuardClause: + Enabled: false + +Style/SignalException: + Enabled: false + +Style/SymbolProc: + Enabled: true + Exclude: + - app/controllers/cohorts_controller.rb + +Style/FormatStringToken: + Enabled: false + +Layout/AlignHash: + Enabled: false + +Layout/AlignParameters: + Enabled: false + EnforcedStyle: with_fixed_indentation + +Layout/EmptyLinesAroundBlockBody: + Enabled: true diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000..e70b452 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.6.0 diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 0000000..2c85a99 --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,161 @@ +{ + "rules": { + # "at-rule-empty-line-before": "always", + # "at-rule-blacklist": string|[], + # "at-rule-name-case": "lower"|"upper", + # "at-rule-name-newline-after": "always"|"always-multi-line", + # "at-rule-name-space-after": "always"|"always-single-line", + # "at-rule-no-unknown": true, + # "at-rule-no-vendor-prefix": true, + # "at-rule-semicolon-newline-after": "always", + # "at-rule-semicolon-space-before": "always"|"never", + # "at-rule-whitelist": string|[], + # "block-closing-brace-empty-line-before": "always-multi-line"|"never", + "block-closing-brace-newline-after": "always", + "block-closing-brace-newline-before": "always", + # "block-closing-brace-space-after": "always"|"always-single-line"|"never-single-line"|"always-multi-line"|"never-multi-line", + # "block-closing-brace-space-before": "always"|"never"|"always-single-line"|"never-single-line"|"always-multi-line"|"never-multi-line", + # "block-no-empty": true, + # "color-hex-case": "lower"|"upper", + # "color-hex-length": "short"|"long", + # "color-named": "always-where-possible"|"never", + # "color-no-hex": true, + # "color-no-invalid-hex": true, + # "comment-empty-line-before": "always"|"never", + # "comment-no-empty": true, + # "comment-whitespace-inside": "always"|"never", + # "comment-word-blacklist": string|[], + # "custom-media-pattern": string, + # "custom-property-empty-line-before": "always"|"never", + # "custom-property-pattern": string, + # "declaration-bang-space-after": "always"|"never", + # "declaration-bang-space-before": "always"|"never", + # "declaration-block-no-duplicate-properties": true, + # "declaration-block-no-redundant-longhand-properties": true, + # "declaration-block-no-shorthand-property-overrides": true, + # "declaration-block-semicolon-newline-after": "always"|"always-multi-line"|"never-multi-line", + # "declaration-block-semicolon-newline-before": "always"|"always-multi-line"|"never-multi-line", + # "declaration-block-semicolon-space-after": "always"|"never"|"always-single-line"|"never-single-line", + # "declaration-block-semicolon-space-before": "always"|"never"|"always-single-line"|"never-single-line", + # "declaration-block-single-line-max-declarations": int, + # "declaration-block-trailing-semicolon": "always"|"never", + # "declaration-colon-newline-after": "always"|"always-multi-line", + "declaration-colon-space-after": "always", + "declaration-colon-space-before": "never", + "declaration-empty-line-before": "never", + # "declaration-no-important": true, + # "declaration-property-unit-blacklist": {}, + # "declaration-property-unit-whitelist": {}, + # "declaration-property-value-blacklist": {}, + # "declaration-property-value-whitelist": {}, + # "font-family-name-quotes": "always-where-required"|"always-where-recommended"|"always-unless-keyword", + # "font-family-no-duplicate-names": true, + # "font-weight-notation": "numeric"|"named", + # "function-blacklist": string|[], + # "function-calc-no-unspaced-operator": true, + # "function-comma-newline-after": "always"|"always-multi-line"|"never-multi-line", + # "function-comma-newline-before": "always"|"always-multi-line"|"never-multi-line", + # "function-comma-space-after": "always"|"never"|"always-single-line"|"never-single-line", + # "function-comma-space-before": "always"|"never"|"always-single-line"|"never-single-line", + # "function-linear-gradient-no-nonstandard-direction": true, + # "function-max-empty-lines": int, + # "function-name-case": "lower"|"upper", + # "function-parentheses-newline-inside": "always"|"always-multi-line"|"never-multi-line", + # "function-parentheses-space-inside": "always"|"never"|"always-single-line"|"never-single-line", + # "function-url-data-uris": "always"|"never", + # "function-url-no-scheme-relative": true, + # "function-url-quotes": "always"|"never", + # "function-url-scheme-whitelist": string|[], + # "function-whitelist": string|[], + # "function-whitespace-after": "always"|"never", + "indentation": 2, + # "keyframe-declaration-no-important": true, + # "length-zero-no-unit": true, + # "max-empty-lines": int, + # "max-line-length": int, + # "max-nesting-depth": int, + # "media-feature-colon-space-after": "always"|"never", + # "media-feature-colon-space-before": "always"|"never", + # "media-feature-name-blacklist": string|[], + # "media-feature-name-case": "lower"|"upper", + # "media-feature-name-no-unknown": true, + # "media-feature-name-no-vendor-prefix": true, + # "media-feature-name-whitelist": string|[], + # "media-feature-parentheses-space-inside": "always"|"never", + # "media-feature-range-operator-space-after": "always"|"never", + # "media-feature-range-operator-space-before": "always"|"never", + # "media-query-list-comma-newline-after": "always"|"always-multi-line"|"never-multi-line", + # "media-query-list-comma-newline-before": "always"|"always-multi-line"|"never-multi-line", + # "media-query-list-comma-space-after": "always"|"never"|"always-single-line"|"never-single-line", + # "media-query-list-comma-space-before": "always"|"never"|"always-single-line"|"never-single-line", + # "no-descending-specificity": true, + # "no-duplicate-selectors": true, + # "no-empty-source": true, + # "no-eol-whitespace": true, + # "no-extra-semicolons": true, + # "no-invalid-double-slash-comments": true, + # "no-missing-end-of-source-newline": true, + # "no-unknown-animations": true, + "number-leading-zero": "always", + # "number-max-precision": int, + "number-no-trailing-zeros": true, + # "property-blacklist": string|[], + # "property-case": "lower"|"upper", + # "property-no-unknown": true, + # "property-no-vendor-prefix": true, + # "property-whitelist": string|[], + # "rule-empty-line-before": "always"|"never"|"always-multi-line"|"never-multi-line", + # "selector-attribute-brackets-space-inside": "always"|"never", + # "selector-attribute-operator-blacklist": string|[], + # "selector-attribute-operator-space-after": "always"|"never", + # "selector-attribute-operator-space-before": "always"|"never", + # "selector-attribute-operator-whitelist": string|[], + # "selector-attribute-quotes": "always"|"never", + # "selector-class-pattern": string, + "selector-combinator-space-after": "always", + "selector-combinator-space-before": "always", + # "selector-descendant-combinator-no-non-space": true, + # "selector-id-pattern": string, + # "selector-list-comma-newline-after": "always"|"always-multi-line"|"never-multi-line", + # "selector-list-comma-newline-before": "always"|"always-multi-line"|"never-multi-line", + # "selector-list-comma-space-after": "always"|"never"|"always-single-line"|"never-single-line", + "selector-list-comma-space-before": "never", + # "selector-max-empty-lines": int, + # "selector-max-class": int, + # "selector-max-compound-selectors": int, + # "selector-max-specificity": string, + # "selector-nested-pattern": string, + # "selector-no-attribute": true, + # "selector-no-combinator": true, + # "selector-no-id": true, + # "selector-no-qualifying-type": true, + # "selector-no-type": true, + # "selector-no-universal": true, + # "selector-no-vendor-prefix": true, + # "selector-pseudo-class-blacklist": string|[], + # "selector-pseudo-class-case": "lower"|"upper", + # "selector-pseudo-class-no-unknown": true, + # "selector-pseudo-class-parentheses-space-inside": "always"|"never", + # "selector-pseudo-class-whitelist": string|[], + # "selector-pseudo-element-case": "lower"|"upper", + # "selector-pseudo-element-colon-notation": "single"|"double", + # "selector-pseudo-element-no-unknown": true, + # "selector-type-case": "lower"|"upper", + # "selector-type-no-unknown": true, + "shorthand-property-no-redundant-values": true, + # "string-no-newline": true, + "string-quotes": "double", + # "time-min-milliseconds": int, + # "unit-blacklist": string|[], + # "unit-case": "lower"|"upper", + # "unit-no-unknown": true, + # "unit-whitelist": string|[], + # "value-keyword-case": "lower"|"upper", + # "value-list-comma-newline-after": "always"|"always-multi-line"|"never-multi-line", + # "value-list-comma-newline-before": "always"|"always-multi-line"|"never-multi-line", + # "value-list-comma-space-after": "always"|"never"|"always-single-line"|"never-single-line", + # "value-list-comma-space-before": "always"|"never"|"always-single-line"|"never-single-line", + # "value-list-max-empty-lines": int, + # "value-no-vendor-prefix": true + } +} diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..581729e --- /dev/null +++ b/Gemfile @@ -0,0 +1,94 @@ +source "https://rubygems.org" +ruby "2.6.0" + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + +# architecture +gem "aws-sdk", "~> 2.6.5" +gem "pg", "~> 0.18" +gem "redis", "~> 3.0" +gem "puma", "~> 3.12.0" +gem "pundit" +gem "httparty" +gem "pagy" +gem "rails", "~> 5.2.1" +gem "sidekiq" +gem "dotenv-rails" +gem "rubyzip", ">= 1.0.0" +gem "zip-zip" +gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby] +gem "rack-attack" + +# assets +gem "ts_routes" +gem "webpacker", "~> 3.5" +gem "bootstrap", "4.0.0" +gem "font-awesome-rails" +gem "sass-rails", "~> 5.0" +gem "uglifier", ">= 1.3.0" + +# views +gem "haml" +gem "jquery-rails" +gem "mathjax-rails" +gem "react-rails", "2.4.5" +gem "underscore-rails", "1.8.3" +gem "js-routes" +gem "browser" +gem "apitome" + +# services +gem "auth-api", github: "Galvanize-IT/auth-api", ref: "81cd1ec61544746ee71d03287dc9d23126fdcf27" +gem "honeybadger", "~> 3.1" +gem "github_url", "0.2.1" +gem "gitlab" +gem "analytics-ruby", "~> 2.0.0", require: "segment/analytics" +gem "octokit" +gem "barnes" +gem "scout_apm" +gem "solid_use_case", "~> 2.2.0" +gem "dry-validation", "~> 0.12.2" + +# file processing +gem "block_parser", github: "Galvanize-IT/block_parser", ref: "6d0fc6c3f2d2d7d06b9ad51d52f57430b7e438bc" + +# Reduces boot times through caching; required in config/boot.rb +gem "bootsnap", ">= 1.1.0" + +# performance +gem "rack-mini-profiler" +gem "memory_profiler" +gem "flamegraph" +gem "stackprof" + +group :development, :test do + gem "json_spec" + gem "byebug" + gem "factory_bot_rails" + gem "rspec-rails" +end + +group :development do + gem "foreman" + gem "overcommit" + gem "rubocop" + gem "better_errors" + gem "binding_of_caller" + gem "letter_opener" +end + +group :test do + gem "capybara" + gem "database_cleaner" + gem "rspec_junit_formatter" + gem "selenium-webdriver" + gem "shoulda-matchers" + gem "timecop" + gem "vcr" + gem "webmock" + gem "rails-controller-testing" + gem "rspec_api_documentation" +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..efc3766 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,492 @@ +GIT + remote: https://github.com/Galvanize-IT/auth-api.git + revision: 81cd1ec61544746ee71d03287dc9d23126fdcf27 + ref: 81cd1ec61544746ee71d03287dc9d23126fdcf27 + specs: + auth-api (0.0.2) + omniauth-oauth2 (< 1.4.0) + railties (< 6) + +GIT + remote: https://github.com/Galvanize-IT/block_parser.git + revision: 6d0fc6c3f2d2d7d06b9ad51d52f57430b7e438bc + ref: 6d0fc6c3f2d2d7d06b9ad51d52f57430b7e438bc + specs: + block_parser (0.1.0) + activemodel (> 4.2) + github-markdown (= 0.6.9) + github-markup (= 1.6.1) + github_url (= 0.2.1) + gitlab (= 4.14.1) + nokogiri (= 1.8.0) + octokit (= 4.3.0) + psych (= 2.2.4) + redcarpet (= 3.3.4) + rspec_junit_formatter (> 0.2.3) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.1) + actionpack (= 5.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.1) + actionview (= 5.2.1) + activesupport (= 5.2.1) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.1) + activesupport (= 5.2.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.1) + activesupport (= 5.2.1) + globalid (>= 0.3.6) + activemodel (5.2.1) + activesupport (= 5.2.1) + activerecord (5.2.1) + activemodel (= 5.2.1) + activesupport (= 5.2.1) + arel (>= 9.0) + activestorage (5.2.1) + actionpack (= 5.2.1) + activerecord (= 5.2.1) + marcel (~> 0.3.1) + activesupport (5.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.4.0) + analytics-ruby (2.0.13) + apitome (0.3.0) + kramdown + railties + arel (9.0.0) + ast (2.4.0) + autoprefixer-rails (7.2.5) + execjs + aws-sdk (2.6.50) + aws-sdk-resources (= 2.6.50) + aws-sdk-core (2.6.50) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-resources (2.6.50) + aws-sdk-core (= 2.6.50) + aws-sigv4 (1.0.2) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) + barnes (0.0.7) + multi_json (~> 1) + statsd-ruby (~> 1.1) + better_errors (2.5.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.3.1) + msgpack (~> 1.0) + bootstrap (4.0.0) + autoprefixer-rails (>= 6.0.3) + popper_js (>= 1.12.9, < 2) + sass (>= 3.5.2) + browser (2.5.2) + builder (3.2.3) + byebug (9.1.0) + capybara (2.16.1) + addressable + mini_mime (>= 0.1.3) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.8.0) + ffi (~> 1.0, >= 1.0.11) + coderay (1.1.2) + concurrent-ruby (1.1.5) + connection_pool (2.2.1) + crack (0.4.3) + safe_yaml (~> 1.0.0) + crass (1.0.4) + database_cleaner (1.6.2) + debug_inspector (0.0.3) + deterministic (0.6.0) + diff-lcs (1.3) + dotenv (2.5.0) + dotenv-rails (2.5.0) + dotenv (= 2.5.0) + railties (>= 3.2, < 6.0) + dry-configurable (0.7.0) + concurrent-ruby (~> 1.0) + dry-container (0.6.0) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (0.4.7) + concurrent-ruby (~> 1.0) + dry-equalizer (0.2.1) + dry-inflector (0.1.2) + dry-logic (0.4.2) + dry-container (~> 0.2, >= 0.2.6) + dry-core (~> 0.2) + dry-equalizer (~> 0.2) + dry-types (0.13.2) + concurrent-ruby (~> 1.0) + dry-container (~> 0.3) + dry-core (~> 0.4, >= 0.4.4) + dry-equalizer (~> 0.2) + dry-inflector (~> 0.1, >= 0.1.2) + dry-logic (~> 0.4, >= 0.4.2) + dry-validation (0.12.2) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (~> 0.2, >= 0.2.1) + dry-equalizer (~> 0.2) + dry-logic (~> 0.4, >= 0.4.0) + dry-types (~> 0.13.1) + erubi (1.7.1) + execjs (2.7.0) + factory_bot (4.8.2) + activesupport (>= 3.0.0) + factory_bot_rails (4.8.2) + factory_bot (~> 4.8.2) + railties (>= 3.0.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + ffi (1.9.18) + flamegraph (0.9.5) + font-awesome-rails (4.7.0.4) + railties (>= 3.2, < 6.0) + foreman (0.84.0) + thor (~> 0.19.1) + github-markdown (0.6.9) + github-markup (1.6.1) + github_url (0.2.1) + gitlab (4.14.1) + httparty (~> 0.14, >= 0.14.0) + terminal-table (~> 1.5, >= 1.5.1) + globalid (0.4.1) + activesupport (>= 4.2.0) + haml (5.0.4) + temple (>= 0.8.0) + tilt + hashdiff (0.4.0) + hashie (4.1.0) + honeybadger (3.2.0) + httparty (0.15.6) + multi_xml (>= 0.5.2) + i18n (1.6.0) + concurrent-ruby (~> 1.0) + iniparse (1.4.4) + jaro_winkler (1.5.3) + jmespath (1.3.1) + jquery-rails (4.3.1) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + js-routes (1.4.2) + railties (>= 3.2) + sprockets-rails + json_spec (1.1.5) + multi_json (~> 1.0) + rspec (>= 2.0, < 4.0) + jwt (2.2.2) + kramdown (2.1.0) + launchy (2.4.3) + addressable (~> 2.3) + letter_opener (1.7.0) + launchy (~> 2.2) + loofah (2.2.2) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.0) + mini_mime (>= 0.1.1) + marcel (0.3.2) + mimemagic (~> 0.3.2) + mathjax-rails (2.6.1) + railties (>= 3.0) + memory_profiler (0.9.12) + method_source (0.9.0) + mimemagic (0.3.2) + mini_mime (1.0.0) + mini_portile2 (2.2.0) + minitest (5.11.3) + msgpack (1.2.4) + multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + mustache (1.1.0) + nio4r (2.3.1) + nokogiri (1.8.0) + mini_portile2 (~> 2.2.0) + oauth2 (1.4.4) + faraday (>= 0.8, < 2.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + octokit (4.3.0) + sawyer (~> 0.7.0, >= 0.5.3) + omniauth (1.9.1) + hashie (>= 3.4.6) + rack (>= 1.6.2, < 3) + omniauth-oauth2 (1.3.1) + oauth2 (~> 1.0) + omniauth (~> 1.2) + overcommit (0.41.0) + childprocess (~> 0.6, >= 0.6.3) + iniparse (~> 1.4) + pagy (3.7.1) + parallel (1.17.0) + parser (2.6.3.0) + ast (~> 2.4.0) + pg (0.21.0) + popper_js (1.12.9) + psych (2.2.4) + puma (3.12.1) + pundit (1.1.0) + activesupport (>= 3.0.0) + rack (2.0.3) + rack-attack (6.2.1) + rack (>= 1.0, < 3) + rack-mini-profiler (1.0.0) + rack (>= 1.2.0) + rack-protection (2.0.0) + rack + rack-proxy (0.6.4) + rack + rack-test (0.8.2) + rack (>= 1.0, < 3) + rails (5.2.1) + actioncable (= 5.2.1) + actionmailer (= 5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + activemodel (= 5.2.1) + activerecord (= 5.2.1) + activestorage (= 5.2.1) + activesupport (= 5.2.1) + bundler (>= 1.3.0) + railties (= 5.2.1) + sprockets-rails (>= 2.0.0) + rails-controller-testing (1.0.2) + actionpack (~> 5.x, >= 5.0.1) + actionview (~> 5.x, >= 5.0.1) + activesupport (~> 5.x) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.1) + actionpack (= 5.2.1) + activesupport (= 5.2.1) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rainbow (3.0.0) + rake (12.3.1) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + react-rails (2.4.5) + babel-transpiler (>= 0.7.0) + connection_pool + execjs + railties (>= 3.2) + tilt + redcarpet (3.3.4) + redis (3.3.5) + rspec (3.7.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-core (3.7.0) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-mocks (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-rails (3.7.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-support (~> 3.7.0) + rspec-support (3.7.0) + rspec_api_documentation (6.1.0) + activesupport (>= 3.0.0) + mustache (~> 1.0, >= 0.99.4) + rspec (~> 3.0) + rspec_junit_formatter (0.3.0) + rspec-core (>= 2, < 4, != 2.12.0) + rubocop (0.72.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.6) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 1.7) + ruby-progressbar (1.10.1) + rubyzip (1.2.1) + safe_yaml (1.0.5) + sass (3.5.3) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.0.7) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sawyer (0.7.0) + addressable (>= 2.3.5, < 2.5) + faraday (~> 0.8, < 0.10) + scout_apm (2.4.19) + selenium-webdriver (3.8.0) + childprocess (~> 0.5) + rubyzip (~> 1.0) + shoulda-matchers (3.1.2) + activesupport (>= 4.0.0) + sidekiq (5.0.5) + concurrent-ruby (~> 1.0) + connection_pool (~> 2.2, >= 2.2.0) + rack-protection (>= 1.5.0) + redis (>= 3.3.4, < 5) + solid_use_case (2.2.0) + deterministic (~> 0.6.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + stackprof (0.2.12) + statsd-ruby (1.4.0) + temple (0.8.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thor (0.19.4) + thread_safe (0.3.6) + tilt (2.0.8) + timecop (0.9.1) + ts_routes (1.0.1) + railties (>= 5.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uglifier (4.0.2) + execjs (>= 0.3.0, < 3) + underscore-rails (1.8.3) + unicode-display_width (1.6.0) + vcr (4.0.0) + webmock (3.6.0) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + webpacker (3.5.5) + activesupport (>= 4.2) + rack-proxy (>= 0.6.1) + railties (>= 4.2) + websocket-driver (0.7.0) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + xpath (2.1.0) + nokogiri (~> 1.3) + zip-zip (0.3) + rubyzip (>= 1.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + analytics-ruby (~> 2.0.0) + apitome + auth-api! + aws-sdk (~> 2.6.5) + barnes + better_errors + binding_of_caller + block_parser! + bootsnap (>= 1.1.0) + bootstrap (= 4.0.0) + browser + byebug + capybara + database_cleaner + dotenv-rails + dry-validation (~> 0.12.2) + factory_bot_rails + flamegraph + font-awesome-rails + foreman + github_url (= 0.2.1) + gitlab + haml + honeybadger (~> 3.1) + httparty + jquery-rails + js-routes + json_spec + letter_opener + mathjax-rails + memory_profiler + octokit + overcommit + pagy + pg (~> 0.18) + puma (~> 3.12.0) + pundit + rack-attack + rack-mini-profiler + rails (~> 5.2.1) + rails-controller-testing + react-rails (= 2.4.5) + redis (~> 3.0) + rspec-rails + rspec_api_documentation + rspec_junit_formatter + rubocop + rubyzip (>= 1.0.0) + sass-rails (~> 5.0) + scout_apm + selenium-webdriver + shoulda-matchers + sidekiq + solid_use_case (~> 2.2.0) + stackprof + timecop + ts_routes + tzinfo-data + uglifier (>= 1.3.0) + underscore-rails (= 1.8.3) + vcr + webmock + webpacker (~> 3.5) + zip-zip + +RUBY VERSION + ruby 2.6.0p0 + +BUNDLED WITH + 1.17.3 diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..ed57e30 --- /dev/null +++ b/Procfile @@ -0,0 +1,3 @@ +web: bundle exec rails server -p $PORT +worker: sidekiq +release: rake db:migrate diff --git a/Procfile.local b/Procfile.local new file mode 100644 index 0000000..0e0c11f --- /dev/null +++ b/Procfile.local @@ -0,0 +1,3 @@ +web: bundle exec rails server -p 3003 +worker: bundle exec sidekiq +webpack: ./bin/webpack-dev-server diff --git a/README.md b/README.md new file mode 100644 index 0000000..fc701f1 --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +Forge +===== + +## Standards +See the [software-team-standards](https://www.github.com/Galvanize-IT/software-team-standards) for git aliases. + +## Dependency Setup + +[install Homebrew](http://brew.sh/) if you don't already have it. + +Install rbenv and get the right version of ruby (the version here may change, so check the Gemfile for the correct +version. ) + +- `brew install rbenv` +- `rbenv install 2.6.0` + +Install our data stores: + +- `brew install postgresql` +- `brew install redis` +- `brew services start redis` + +To simplify your postgres installation, once you've installed it with homebrew, it's recommended that you use the +[Postgres.app](http://postgresapp.com/), which is an easy download and install. This allows you to not need to specify a +username/password in your `database.yml`. + + +## Installation + +You will need a [Github access token](https://github.com/settings/tokens). Replace the `YOUR_GENERATED_PERSONAL_ACCESS_TOKEN` with your token. + +The token will need **Full control of private repositories** + +- `git clone git@github.com:Galvanize-IT/forge.git && cd forge` +- `rbenv local 2.6.0` +- `gem install bundler` +- `bundle config --local GITHUB__COM YOUR_GENERATED_PERSONAL_ACCESS_TOKEN:x-oauth-basic` +- `bundle` +- `yarn install` + +## Data + +- `rake db:create` +- `rake db:migrate` + +## Auth + +Set up [Auth](https://github.com/Galvanize-IT/auth) before running Forge. + +## Configuration + +Ask a co-developer for their .env example. + +### Sidekiq + +As an admin, you can view Sidekiq jobs at `/admin/sidekiq`. + +## Starting the server + +`heroku local -p 3003` + +or: +`rails s -p 3003` +`sidekiq` +`bin/webpack-dev-server` + +## Deployment + +PRs are automatically deployed to their own one-off environments. To test webhook-related Auth features, be sure to update the webhooks for Forge at `auth-staging.herokuapp.com` to point to your review enviroment. + +Sign in to your review environment using your credentials for `auth-staging.herokuapp.com`, or `dev+auth@galvanize.com:password`. + +`master` is automatically deployed to `staging` once CI passes. + +Migrations are run as part of the deployment release phase. + +Note: `galvanize-forge` will need to have access to any repos used for project challenges. + +## Mailer previews + +To preview our mailers, go to `http://localhost:3003/rails/mailers`. + +## End-to-end Tests (e2e) + +The [Forge e2e suite](https://github.com/Galvanize-IT/forge-e2e) is run on every deploy to staging, and the results are posted to `#forge_devops`. + +## Code Generators + +The `ts_routes` gem generates typesafe routes for use in TypeScript. To update them: + +`rake ts:routes` + +### API Documentation + +The specs for the API use a specific DSL that allows the tests to generate our API documentation. You can then run the +integration specs and generate the documentation. The documentation can be committed if you’ve made changes to the API, +otherwise they can be disregard. + +Run `rails spec:api:docs` to generate the documentation files and then browse to `/api/docs` to see what the +documentation looks like. + +## Linters + +We use `rubocop` and `eslint`. + +You can use overcommit to automatically run our linters in a pre-commit hook: (`gem install overcommit; overcommit --install`). +Note: If you want to disable it inline (i.e. for a WIP commit), use `OVERCOMMIT_DISABLE=1 git commit -m "wip"`. + +You can also run the linters manually with auto-correction, along with the test suite, by running `bash lintspec.sh`. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..9a5ea73 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative "config/application" + +Rails.application.load_tasks diff --git a/app.json b/app.json new file mode 100644 index 0000000..ac9eda1 --- /dev/null +++ b/app.json @@ -0,0 +1,168 @@ +{ + "name": "forge", + "scripts": { + "postdeploy": "bundle exec rake db:seed" + }, + "env": { + "ACTIONMAILER_HOST": { + "required": true + }, + "ASSESSMENTS_API_KEY": { + "required": true + }, + "ASSESSMENTS_CALLBACK_TOKEN": { + "required": true + }, + "ASSESSMENTS_SERVICE_DOMAIN": { + "required": true + }, + "AUTH_CLIENT_ID": { + "required": true + }, + "AUTH_CLIENT_SECRET": { + "required": true + }, + "AUTH_URL": { + "required": true + }, + "AUTH_WEBHOOK_TOKEN": { + "required": true + }, + "AWS_ACCESS_KEY_ID": { + "required": true + }, + "AWS_SECRET_ACCESS_KEY": { + "required": true + }, + "BUNDLE_GITHUB__COM": { + "required": true + }, + "LEARN_FIND_COHORT_URL": { + "required": true + }, + "GITHUB_TOKEN": { + "required": true + }, + "GITHUB_COM_TOKEN": { + "required": true + }, + "GITLAB_COM_TOKEN": { + "required": true + }, + "GLEARN_ACCESS_KEY_ID": { + "required": true + }, + "GLEARN_BUCKET_NAME": { + "required": true + }, + "GLEARN_KEY_PREFIX": { + "required": true + }, + "GLEARN_SECRET_ACCESS_KEY": { + "required": true + }, + "HEROKU_APP_NAME": { + "required": true + }, + "HONEYBADGER_API_KEY": { + "required": true + }, + "HONEYBADGER_ENV": { + "value": "review" + }, + "INTERCOM_APP_ID": { + "required": true + }, + "LANG": { + "required": true + }, + "MIXPANEL_PROJECT_TOKEN": { + "required": true + }, + "PROTOCOL": { + "required": true + }, + "RACK_ENV": { + "required": true + }, + "RAILS_ENV": { + "required": true + }, + "RAILS_LOG_TO_STDOUT": { + "required": true + }, + "RAILS_SERVE_STATIC_FILES": { + "required": true + }, + "REDIS_PROVIDER": { + "required": true + }, + "REDIS_URL": { + "required": true + }, + "SECRET_KEY_BASE": { + "required": true + }, + "SENDGRID_PASSWORD": { + "required": true + }, + "SENDGRID_USERNAME": { + "required": true + }, + "S3_BUCKET_NAME": { + "required": true + }, + "DS_PREP_UID": { + "required": true + }, + "SE_PREP_UID": { + "required": true + }, + "STANDARD_EVENT_UIDS": { + "required": true + }, + "SEGMENT_WRITE_KEY_SERVER": { + "required": true + }, + "SEGMENT_WRITE_KEY_CLIENT": { + "required": true + }, + "AWS_SQLSNIPPETS_PASSWORD": { + "required": true + }, + "AWS_SQLSNIPPETS_USER": { + "required": true + }, + "AWS_SQLSNIPPETS_HOST": { + "required": true + }, + "GITLAB_GALVANIZE_COM_TOKEN": { + "required": true + } + }, + "formation": { + "web": { + "quantity": 1, + "size": "hobby" + }, + "worker": { + "quantity": 1, + "size": "hobby" + } + }, + "addons": [ + "heroku-postgresql", + "heroku-redis" + ], + "buildpacks": [ + { + "url": "heroku/python" + }, + { + "url": "heroku/nodejs" + }, + { + "url": "heroku/ruby" + } + ] +} diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 0000000..b16e53d --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d44fabf799ec51b278a628f5185ac4ed1bce7b90 GIT binary patch literal 1437 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzwj^(N7a$D;Kb?2i11Zh|kH}&m z?E%JaC$sH9f@KAc=|EZmjN5Ipk zKxk2SWaa~BG0~F4*Oy$#GU!k=SftWAV-fe)1o1VBJY6iEOR7GuY&Yoj+T9qIBF=th z5$D=Hfsaj$Z&{{)-*D~X-GXy-Y9Cx+pS}5f)a&Ti`~Tf9Rgcn0X0|O5JJ;*1Y{Bt% zb$iyUEn7c4PzusHs_e0WIs1dZ?cF;JX8AGfn+vtKJub+PumAdi<$DriUt(a$Z_{&E zr>03-$bV0i=Sx2({@C!$!TuEs1SDsMaQQOb=zjX6>d+7W3f~PsQ+xC7iZD<7RcxRw zSF&RJ`u|sIe!o%#+U)+(W|_NM5%Y{GjTJva_B8Mpdao_Lc=&XMu=A`5Hg*$|Bpw&m z9)H&RTJH#>Oe>GFv&0cr-9&}F8lnD2Rd0@T|KP|^c*ioe@J)VOW1HWN+t=4`Uth0s zuYzkD$G-w*y>$zVwn#GY`UdzK9eu2S{??_PS!<^F+?*9~DD2Iug+{lG*d9IHuw_l| z&22AF|34<|WK<<|IP`b^%NW;Lla#*|8&0sSQJv?#WcE7A`t{QMixyi+D!Y1pI@i-~ zcE9q^(rqT1+s?3rKg=vyoE?=~k~T|4yD+YI>(fP_&be4EmgJJMOl046h<9@3XRm7a zqp!l8>t@gDd*?4&)Se*6uslhYTYT;fOY_hli_YEhUnf`p-$QHWeJ=*~kG39@-X%pv z8LHZT^g4BI)$xg5H_I<=`FeNhLe)u6E`>2!H0CiQfiFhzzV;8Tf7hn&dAj3O!PBaHGEcp9dfwYVE`C)% z#e`+T#@A<^>~|#QEJ)Ybotbvlwc=E!@Sdf!T-7J;lAC?=hg;5pnwkd2n(qCN{>=P% zExRyQ=C-!Vx`S*zPrI5HU7jCi&Z&D--cNkxO(&0S$NyJ<*l=Nv*%X_VGX%c(bJr$> z^X@e<`m{2k?XFMW?-&NPh79qbk6WD~Z>x=hjR5x_X!N&w1#0UH$H&aorT8?1KME|7+*ZczdqBa9ZQ! zSw@DneiCa+MD?X4>WMDL6n5AhUXap*1kW7Xu4$n*}$v{!NR975SG$|pe z_07ypEvYO3`}j1-$5-`Fc(XGIRx?+`Te?3&Gubx;s2pPCHj_;TW(EevtPI?$Ol<5= zo`wR|N+6jWoLL1?dfJbPf$=UQOVeXB3!oYqBsHNyo*>h_!C`s*Iy(c;ET)Vp4uPwH P>KHs-{an^LB{Ts5c-mw6 literal 0 HcmV?d00001 diff --git a/app/assets/images/loader-black.svg b/app/assets/images/loader-black.svg new file mode 100644 index 0000000..a33da18 --- /dev/null +++ b/app/assets/images/loader-black.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/app/assets/images/loader-cyan.svg b/app/assets/images/loader-cyan.svg new file mode 100644 index 0000000..50f34dd --- /dev/null +++ b/app/assets/images/loader-cyan.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/app/assets/images/loader-white.svg b/app/assets/images/loader-white.svg new file mode 100644 index 0000000..0e73c3c --- /dev/null +++ b/app/assets/images/loader-white.svg @@ -0,0 +1,12 @@ + + + + + diff --git a/app/assets/images/lost.jpg b/app/assets/images/lost.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a6c5f225f7816ed95b5a4ecf5f7675adddda6a43 GIT binary patch literal 319991 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

T(aw?JniBw#d@o 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

q=Pg-E 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®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 N^F!rBE%%q7|34(qFxdbA literal 0 HcmV?d00001 diff --git a/app/assets/images/spinner.gif b/app/assets/images/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..f83606ab0f82a916187f525f8e998eca7b11f899 GIT binary patch literal 24772 zcmeI)|6f!0-T(3PEy+m;$w^2sAi@Cx0-_CmMMZl80V5&?OBI#2L9s>WyrQ!mx_&wN zf`s}lAX@6I3rJhqxn5|!R@d5ICx9(&T^F6}T<5xAX^Wj}(b`r!=RVKwT$i_bU4H(7 z%l4<<`cqGCPMn*Xd#U@++O`KC{w9tZ^1uV24^|9*$Vk(`{o zc=6)={QTnL;`;jf0|yR-!{M&3uKVx5|GDR$>*?vadiCn%%a<=+ym^ zv--(l`zmK&Get8N_Pj)<(pkkRHH24hPEdz>gT-MVH!a_E)*|*d#U(L2i(E&9UAl1_ z4+Uz(Hy7yQ9BtNUjCjvHRx!!5nRJLx=TMULD{GJR4AbeTStbp>8($qRi+Jzx&(BQM zBy*8Y#U4uY;PFjWm!zGB!kNx?Pk&iL1~b89$@jf!9)Fjr&^;KV-eZ;3{9*gkOhmye z_3zg?2)3C{I(nAOP_G{Atgg+qh{3pDr=+H6`Hzq880+dy)cV4VN}nFDe4#FL>(*FG zR=M^_*y(Rx^ti2t?|zk2kecHb>Ghw)4fpA95SB$Z#DUL8RN`Gd;_;sl##f$S6#iq_ zr~iV?f7-$aegDv~c9PalbTyfYJZ@kM=?IA`>!BH(g3B2a9%GUSpDJaGL~LbPG7{t} z+-Ca{HrlD0>=Fs4nCli8hsDLTa87TnVcdbR*Q>Vn`Z=S6cRWPdIESflvE4O8p89NB9O zYg#EqXuc-Gs7^zv%9++F5Sd)>eQ)V2Z8}lxb&}hLP+=nKV>|D8<+)R zAgipb3~E(ZSA$}h1~W}26L9x7&B@7;T?MBg5u5@{Xb;lC zJCFyhk|e=Ws184sN+lpYcI+6`g{7cX<`kCVrhvP~ci*1x|M@@t3Eb*p&Pwb2?_4xl z&K@jQkTlJRg}sDg%w5xs(YnJyL6KEfH00zZF@Ee`*19ZG?~POGws)|HMMDRDe0%GG@;`#?|*0 z3;LwJbpws*``lvw#6rEtV-xnAu?2F5){L?W6KxD$oI{nF*#6mTYu_M9fAjJ}>oVan z(x@<&if7-u&vE&~^tm^JX%E;lzWI`WV@%QHH~!vyal{^sJ?642zDb+BPU~@aeK+DN zVy@z)Ba-yQyp+yXyTaivq2@&Ag{a9y*k^EB>qWhKQ-|~;RW2z735PvmP^5Qyo7IPG z5r(ZV3{mCo);^V$5{hXiJvOAa5n!AxWV#xd@eG zCFdn5hmG8wXyQcBJ6d8^toAlJ@8LF=7$4aBD`Gb{@H?Y&LRPn^cVtBQ4g+UX>|W5* z%qbjShY9lG#4Q8HiX(3?);sdjUES0VBi#w*tLvDAj1765V$o<%`(8TN-m{oZ$rXnW zrYM-((5TgF<*o!gGL48w$N~9)*8iXpy2w!`>xjApA@Bz5p(|kwfV^3kXi3B!v;#{Z z4SK;vzy|QZ-s6uy4&GoMJOg`ZSV)YtL_bcNGzoAP78ZIu9w-P_p`g)d1ZnUOf&xyk zinN62P#GM`bt!WQ=O7}$cDY<|8gMo=G=Oa=j>bd_4-O7;90v%2E-b`N0CkC)hT=dG zb^=A6PWOLWgzqozzdnImX~frYflTAbM}0SGq!K4hHy*3I)GR1wx7U0}BcV@CWv!bd z^(3j%jetgi>!1_AqcPI7KexshyL`HT7cuo``vzYqX((n5*@?Zx?tr(a?7;9z&txPAc$-9uvn5m?=dBAWvy-zzVq}#jqNS;qxjqGTGC}(|k$Zkf*qNzx4T$!c7bL~q zXOw&E>il{n<5}+fD&=ZR!TsWUH76V~DPE_vZA@0SuxgqY3c^iJe@VMk(>CKj z@O7iTW;qn<&My5Ca%WSO&V7Bo+!}}L#s^8v1R1? zGE)n)g-wrrq&I!rvVvG*x-e$J-B($!@T8_*442=RE-Ewb&5JTF+w;}-nzb|Y9QGq@ z)$KYFtpp4~3XqX8L@^=0AR7i>IT8T{=m2*zGJpjD0!Abl6as)C5#|6N6e7F=L1;aI zhC+mLpmNEQCBP7!2yl>oFc9v+D5wYn(TN}t#6d+UC&wTFOiWA!iLex|0!g3;8)XCK z=^t}H9D{!7R+xq!1jQiHVzFdoWS|p4H^hc-U=YaT@BsLu5TP_AMAbrC*odygDFP(K zK#y@8U?OI}_u@|Y1a84FdqYf`Cu5i+1cGtjb)wVLqUOe`FZR~h-Mhxj;5}AO>KDlp zW~Zs@h}6Uy)>jOV#8#^n8S{-b68X^1J&fsCO1P8Iw~_8x3OTit%$nVNynU!UVOg{e>DcIX$UG@ce?j9y$=>*9v9wkeWRtmy`_q&-c& zHl^dZKTN6CkOwWku&>`czp{`Ut-I_^R?mHi*+|)93Kea!J;pb+1;~JP)Rxs9A!g|6 zJ7Pa|lRJ`k)XcveB3rGRt_dTjR-NC@oPGH>IX7mPG=CABal?7Ca&DwDuvGf_7yP9y z*HS*!e;w<6!ynv{{O`4D&xt|$cdDdNKQ(9Mm9!r-CT>92bz+`NpB9Z3E9O|Q^ee{K zH&P;P<`nV#Y0hUp_Z9EmI{xISU|LXJ$kBI+;SyDP>LqF&J+ZS+TVnAx5mZgoPbzan znjo~7{Ho=F!V{b44|s|Vy=IXJaTbxF?DgVsLK!nksHQsW1iRMOdk-yac`l1m77>cJ zDHSG($;;YwLLq4E(odC4RMEB~HrUDu_q)==C2PFh8a8#{dE%iPzgqol0pBuUo=_k( zG3f>6L|~%HkUOvI$fnQAmvV3C&Z9{3Hid{ke4IAI8{`{MgowZix}e~o3&v(xf{c<; zgd)%d)B!3Gh(H5}Pzn%2N3aK60T{{-!!QP8^dRPN_=h^fEDdRZ5Ku%UB0u3JP((rk zFn9?Op(7{-SBONQ2tk1&LK1^L+yYvl3qpfo=!?pOwZIssoG=muVpa!=pc-JqOLG9nK~?Cb%oKnrM=#h{!rWQ1^Z3aqZt9hN_zH-7oI&Y z(&-!fqRtZv#-L^M)b_#wdXmEZh)c4))x3YnrtTooc|_?`k0yQ|9UdenX3)dE8wh{k z9WEmrU2KzfCN-7h6k5W>S~lgSQENwOc?-AV5&gghx-V!QyJA7@0piL2!0ac7ox(G- zbS=r{4S(KgFZ@EQ?7Ok;H=!))>o2p8>&N`&Lt(RaWWlB!7xe^9)dIpqFOnUX=p63BBmY{EyvUqD-4!Wz^2};;)e( zEon92&SOeybd{2diM^szYr7&Q=DA;>%nnm7v2?zzGnFhHm7*r{s9IDn>u^~UU#{E3 znYEW}UFw>BoJi%HlKP%uE^HRu%+(7G#B@`VV;fEt1J6)vW*Kxwi$p)m%%AFFbu}5$ zs*UAIOg-z&F>|*85(jhu61>T)89<1%f-#7>n?Rz>U?kK8Gq49b!Y5D!FR_S0O@hXo zyBM&CY6O!o53nIdVII5$gg_BGB1WMvCU$U$76gHS6D+|_m;@bx6AVVc!CPD4 zFaH)GKku;BmCIWf07>xlvrYghwZ`9-ZDdz&@CoC_yRLHAY+_KGwEdqz68xA+VfWLc z*%gesY_GMKc2ufU6xlZNN5#axgzj^LGx-LGKVDOzbu*u^dFCHX9tkD9{kCeZCJ&nw zVh=HErF7Jo7!2+oKXtC!&`Hrg{dD$24{s+sCOv687s(iJ%A1to+$nByQ@nr1vC&Hh zI|qr`hbq{C-ZB^yIklPeGN$ra$W9h{BU>%Cy#bx#g4@w# zEW|9lNVJ@yk_N;Rnv|6xFO^?l@6s1v^hl&WA4wS3VC&cFb3#5%%HHu&Q=)UUUuT@t z`;n2wpCC0(n`Ea*+YqTNH@P@-QShj=ldIj}+cUnXU?5bu?yC~B+Q^^K&n`Ad`-tIb zeWA*n=&2Id#jAAi(Veg{Pun6q!whYfC@L$j)m-vHIK!dg4=;XpN#2Wmypr!Qk;eGy zkN1qTYYckD-IxErTNk(pa$uMoj&KOr{BIXQrhJfy3Iv=$2ta}>8AzxJ4q+sSMb806 zG#SbdgE>${Sz^PC@fj$BNrWTf5*&g?zzLEeB@Ba|U==PxO)!a}8%)ATcnLV=O$+J} z4T|X-2@BHxH7Fq^*oKiX6+ogF!66XENh$ONfiMze0#4iwfF+7nmJ*1{QUb_3lZQL@ zs&8>o*~+K?^JtN=G{*XyGq8<5S@Z0hyIPq1n*BV-QVENMxmql=sSK+0aXu+LD3aOL z*@rG%cKAY>YR10d%Sf45U3sjX6Fy<`sr8RvO*+NX$^^P-LsGH#9_57f*|`^n=i9AQ zSGrvs;nY1fHt+F}=lp{$M2+U?pj5}&GYY7;#E|HHcWm6)H;ye6$Q|0y6w9~{!WXm5 zXt%b874;%-w$@qzTv7+pM*=xeIa*H~ukK zmoO>UX!U-{l!?wFKK$xe^zk3=4oa`jR=2$0e{^5ZsX!M4YKEVjdMa-xdk%3Td! zCO*LTQwJtS`{K=K!<}R`-?^AHr4mgBjv&OixB%Bj85(2W1WkbOZJpKAeS%CZtV`#V zBOP{j_C>CbD1OrIAnpyhg@kxhH?e|YJpP@F1+TD&tv=-sy|(9RhiTXlmJG8VcN>37 z_VD!y&ibRf)w5C$Gvp6G7X?CN;jP+-9}J8qW>mC~bdfA?O-L;^5z17D@VjRdavMqv zt;Q>V7Hri?#bufo=2{)BjoonDl{AjtfFmMM-n*a=K?#U~XoyC6ki}q)NP~tqS1iC0 zP=Y*EA4J1O1VM;U1Y0tKfDg=pJU9sZFg;^vmWxq7TLhZ$5e~|&h_gRL9khkK2t}|c zQwS5GEfADX7(pTY0(>A32Z%r%6FD@5uo#HJD2@SOBCrF6NL)CGh0Xt%q%lLw3usK_ zh+(jJ)Au{ph&%SeZ^2QwdDQ)1Q+1={Q>1-|qot{7M+=iuQ^dPeTlJN|(M8kB@l@$@ zJ}DLv$+YThZLI&BI2yteF| zV{x0h!l!9Cc{P$L?n#+6zWV5>aAKj>_-w}@=O-iCQ>fqC+QQOn~Z3XUu_KYdh z>pLFZtnI*nyQe_N9i_2e2{U?2 zZ7f}vmo+Mp3mba;H zt4QYFH6O&3cMZH!^)T%fvsi6XmF5)1i_%EhZ4Vu>i2;wmN8ZGsrEacaJai^4Fi|pB!omb0w~x5XE0yOM~^5-v?HjLmoc)Oa1S*J_rNCLmCqevCj3NDqTygB zrgCH}v;><-MtLg(C!s5lgpgRwVBUwH805h+{DhWJ5mZ7sNC*`HJNgx1LT@N4uVlbG zs6;qIeo%>gWLXvniH(&bub!SD>SW$+1`YlkaB^Xi?>e_UF1-cO%>c}Bs3OAIzKi@w{$ zeE3cOgxGI?6gt~|-TR#WiEh_#(wmkHlP_5>oQ@4qaVxHSPKZmdf9)q05N=7&{=JuC z##Kn+bqhs8Y9dUYCOc^(BF$X8>%>+j*XA=>@~UXf_|ZhU;t1ILB5I`PWB^6PaTd?BV%>r~dx2 zidf&S!ip=O9yBIxu;vw5N<)5e@#gE0Vd%J23wFLmm0i;lmHTR;g}HC$|E+sL{4ICm-jRX zNuK91!cU$&88bD=gro2hB7#Y|E@3396FS001SxO@jSv)E!S9lv^O zn9-6T7JOm=hu^>wYGS^|m=6>|Blwrk4Umwy56Be>HRUFS+`t~Ug*(KC?=KUdz%4-D z-@?^lxK@onRAZ=fdnrzx92=!Gy_$satk$z?VE1-Lvx(z{Cxy@ihcOu9BWyd<-g_|| zTjiu?yU#epdm`mzIqPDV5#_%&%t@H+wz)L##>eelT<_`nq^)&frSTj|QcdF+%8eY# zDCMU82w}u;WzE$;-#hU&hh4vyd&G9mc8=C7x&4t>MLC>?VP~(o#DTH)< zbL+b*+G-JA-{K?4eLqlzKdKWyi>Yq7XB-|Una&+~zWMHg-+!}n+|oyEgZ7W>{Asg8 ztMsu`mzu`dqtW{|Wi+@bDr%?}m?UP%%`FZu4Ux&!J>IxQ&KyoZy~x5CW(-Kh_frM7 zK7}RV?o<}!3!GM;+F7hm2njxJ&LB_I=}takj5`zaHl=V^1`PH_&rfwZXM=vND#hiG zoBKgnAeDIPo{P^35=Q!dcV@!k0dBSS-ul?VM9LgZ};#H@(3cr6_lAQA{r582KImxL`0MQ>qFj<1{6UTU<3J}4r~J?kP?-K z=XCycB?H*tB18m)nAm|F#%~~pK^oNv`M@NOBta33l*17^f=rxBA{@aaEQEHj5GRPB z4o8w;Ql8tfn}NQt93!{9p#f&dM6ic;1eQP=%ws(R@DQx<8!kdY7>QB@ib!F=k2?TR zmtzrLe(%H`^$FY}qOp~;BNQC_X1%FDd-88)0Tx@t#=v09y@ zLsswjyaH`{Y_fHC!jxTSt3!*069*Lh$?Gv&ok2;z(9_);9#0#m{46+iWgBl`Icq-8 z5x(;A##~KDZnr?t(`L1c$>#i;%o0maK(ZDy88aSCt_gMehjhg%rETSyVsn03@|IuN zhMe2_$JwT49dj42%234>O0^%^i%-qtd8Z}jXl52s7OlqDP+&JxERS$1v4?+-{5*JoD*^W8*Xb+O8v z$Uc=ytcnoS%2==A@##;-XQpg;#;$ONNgaEhD19RNhq0J)Qhp6bPqhYV)!dFK9^NkS z_y}ikG^WhnJ}snCPda6%S&wAb#h>$(&>1!ENW!wKT%X^<)ejg8*Kvv;^2Z8`<5ejh zf86x<;NX(uy>|U`%QlrNey@&o>0FP7y46dD-I+$p%y~_WuDwY$sqCTRwlv1=+mYVU zQAJJKLhN};b>=9cpZ=};&n1f9x9yze^*=l>j5&Lj_OoV}H@a)g|RvvPcHcc8_rW_o&80mIVNB4C(V>Me1F&u(ZFxjin5^DHy4 zx~CJ?<~gqkgOwpgXz*lbtZuJrmfBUdCc-XKPOnckUmLL6*?S3B5P50bV{BX*S~k>0 zrm8D!y&Z&jUv5^4&DBz$&hS+IteR2YJH(7rO}FqTCW)!jx(e6K;+$Te@98joMJKirf;=*MA=6)FAdl;h*Jw|A0jk!+9o_t{LL+L zXsxa`RloB+7k=CWJG<5bhxbPO(3Xb1Go*@dJZMny5=bRn+TH*5$K;$- zk@(O4@Ma&8?Q-#UhbJ2GkE=M$lX|Uth_E_qLR@Z)7$T{w0!1@x_LLeX)b{l0<2XhU zxa%0+Wmcu|6fInrBu$FP1`&HabxNl6Qn6~5BQ{n)?YU7}X(3vLUD<{8goYl7CEMBj zp?2l84{RrNu5aCfeokV#i!_AyMyau9f_*=9eePMYi$2>2= zYBTpKnd}}ed4{?#dQg*)e00E&SNK&!IOA8YJ|;MKU_TD|)Pd5@53C_hAU2$sfBVO} zZ~Lwb#%0g~sBZ2r&{K#(c?<>_0VPO@_L+b%TPzMF!ACSZN3;$&5 zU=qp>ZHQz8dvf<-bt0bt!cLe6>cA>IMwtRFU_jJw|G|3NeCXn1=L#9H_%-xQ@F4&P;**oyNnRdA+xw z_@2$}E|WJb4Fu^^at39`C><=J=I18|dlb#>+nEQeNrG>ZLTs7Q#tVe5oy_YkDyF-g z)XerXN4;&tj?8>JBhGA_zc3{s;lt?U0#UQKENJ^Uqchtd)UGvIHwKy%R&ryCdyO}E zLRq?YZMutRb-JuBZ9dV|^g!OY#B=VXE>(ZhthJ{{HXLM{->PI!TZa1Yiqm9;#Tvn* zNywO9UG(OP;g2Mu_@$Z$>%vkb=itoPNQdP;kH>#Olq4U*;<6etTtZ5Cu_KjIV{kP8qaoNtl zeY2*5`)TOSy(z}|)^na&wNU;GgGn-)t&+pXq_=w2rhO6-VDep&t!9s_#8$!{DW;x{7RS-im`GPWYLkfX-gVhNgF7RU+53fnBlY4j zFKuwd2z5zzEM(5D=BXdj4_|UrvljnhFS_5+N-ZLRQK96QBbt5poDN zScWFVunYmAD)@snuoA!lJ50^+QC^{-01=Ha2&7_I2A7zT!6-b%St5!NSqL{FEYOsv zW+Wo&PJXdP&R6-h7a$0}VKjOVwt_RD3E;56!2}IM;W#!h&=A-`I_L=^;VjUE>97)# zLQm8qW^hmoh#}*h3B;Xt)wh61&D_xS&mW0kENAf>J4pahkXFspd$aOi4O$pq|ModA zjzqmwx6`ZScrnx}hzS#Z?n&p2f{Jnk29Gb^E$R2nn#GL#p;IzF&1Gk6yXsdGVZBr>&-TPtxk>YU>PI7^nSr9Dj8>@&=m8HNukC+?d@ZRDEcR^0(Vw_J-KMEQOhg*pWxRY#!lUs+~*5uxJD;c z{2vi2fhlU>fASxW7Dr-z!)R2Mm@#MPf}_7062JP(*hkayLy^EYUnFU6Fvluyh}8kb z8$X|s{>GNghEHGl@dI}cuh%QCUb;(=iiUhS8pb7wCw`$Ml?E;M=e224sLvE(p+4;n*=u-B|p4+|-jg1Nvgk znq9@I#096MGL4Ryp6x)6X-)B9$5o8gdIZX#d?~a1ian);QfpH;<_gEJ4eUtMI)8S<{!N5PbT<6?e#iMMUnmd# z?fySi9taGK#2#kuvPkyLM&0ypg|_hHzO?^L%L}%-5B}q;8b^p`a)Pg{&}Mk}Chx_C zLlnam6X)zg(A~*WjNzO<+ripZ358t0K1DI=r%TwX{-%taR>E#^I7yYpxgnxR&k-2I z-GgDTQepKF+ZJ+XL<6_o)#OM%*FYI)t>Dn#y)jlo2u|Lo)^H{%p|~S4vSTKGRHt2+ zZ|mFDSXwP?ycj=lf|#KZPdrTz%!uq_j}NvHGkmtO2XtJBh`W2z;SbmI=PYDtKyzkb zS4RFj2e>J{?&aNvR(GFi@8{2%+83`it(~{*;>gl@oK}0|Tw-AV^ zB?O`@BIJNK;1J$`84w78z$C&EK!Qdn2(ge^Kn|4#*gz4&4#*)5VH?U1GcmjblPEK}3rDo+@Iy2qG zdhsh_zU(>KMo-=0CPATUtI3fHBvO}>!&-mUUr!hb!fLqIOT>2+w*T{|a$5J_Rj8>A z(Rf8=>I(L7>y&px&82%Q@&)oaJw2{`uD9*C0f}-Xnx=}Y-W2l`CdQl8b$%yv_r}z- zh22gCb07O*hut!7o1)Zxqd^Jz5Qt;=54UU{`>Fbo2k!O?WV+KdveZ^z{G&jL%j+N%rl5;h z9>7CJms;j|+3cEAJh^0$k2D!=wz2lOV62lJ!yQg1mj=a7hIeI4_tN)y1;ah(Mmt$U zju2H?)_VH6d)M`PpmMXoAR?pz6#xVN!9_R(jldGh5A_D^z@fZAfojkc!eQEldGZ1U z?14i_DL+vIAORkrf{OqTcH-avRt@(+ivm@LAW|U z?kC2r#31uqB;(vi_KvQJj@M-r7)-DKVb#EUX&R$aSQ)_VT(HnNvu@+tyPuhIJTX$` z(HQ8IF2)&3|Ag3P8Gq4qI#b}XRH@aMoc&@Qo>}2r-uBy@w`LPodt3NzKiv`6SRFbK zRH(V#Ij1KJ_zgnMh?H{dZKrQ~hjz49d_nlnL;9Rwwc5P2MJXO%|KzaPs`zE*2Q}+6 zy}k+B)NHr(=*(XSQ}~8Izvn&pK5f8HL)|@e`ECx z(-%p9zUEEQD^5A6spgJaqRbp3V2$6%m*!Fwg~)QLl+^55@Eu8L2qh~z#t zcaHcmo%+nT(RhXZ^82?PoaMJyfDn34e)Abk1|g7ca1k2G=ZH`QQ3#W;1ObYG5Sa*0 z&}t9`vnl`sSD+CM2d*$I!z!Q%DZwXDgo21dCLmcyOf_r;i5QUjMAgE zonNW;uCVqN3Wlzo8l~333a9HG6Q{51ZMbF=wsjbNXNEm(Z#}u6ZJb-pr+?B+WLJDP z5}NXs&O0?eZ+>xs5a^hf?8wrrc2;Ul2{n!3c05KiPQy5fjOW;GmKno`!nD(MT=!OUo*6t2_LKNrUxHQ)+#s!n3n8%ddCYIt|J5Z>=Tu+9=l2pzZz?48-zo|s!GzkX<@S3|ioWIy&K^)Kjjt{>RJaUeOM(@1 zw5~DO$7mjjMpUy+RSt8)@zFj?o#Q22{P9N#E!{4zAhOt8znIL45GcN&*e z3Eo;`@pEp)Q|#5Spe--={*Gc32Y#D4p1mLto^LtPQ2tIYa7^Kc)%S8-4yisFwk{o~ xh(4rhpIa`N$~D5!<5k + + + diff --git a/app/assets/images/svg/g-learn-lockup.svg b/app/assets/images/svg/g-learn-lockup.svg new file mode 100644 index 0000000..745a726 --- /dev/null +++ b/app/assets/images/svg/g-learn-lockup.svg @@ -0,0 +1,37 @@ + + + + g-learn-lockup + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/assets/images/svg/galvanize-logo.svg b/app/assets/images/svg/galvanize-logo.svg new file mode 100644 index 0000000..30d6af4 --- /dev/null +++ b/app/assets/images/svg/galvanize-logo.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/svg/github-icon.svg b/app/assets/images/svg/github-icon.svg new file mode 100644 index 0000000..1c6c9ec --- /dev/null +++ b/app/assets/images/svg/github-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/assets/images/svg/mobile-logo.svg b/app/assets/images/svg/mobile-logo.svg new file mode 100644 index 0000000..d51b17a --- /dev/null +++ b/app/assets/images/svg/mobile-logo.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/app/assets/images/svg/octicon-git-branch.svg b/app/assets/images/svg/octicon-git-branch.svg new file mode 100644 index 0000000..4a41892 --- /dev/null +++ b/app/assets/images/svg/octicon-git-branch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/svg/svg-sprite-action-symbol.svg b/app/assets/images/svg/svg-sprite-action-symbol.svg new file mode 100755 index 0000000..c8abe24 --- /dev/null +++ b/app/assets/images/svg/svg-sprite-action-symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/svg/svg-sprite-av-symbol.svg b/app/assets/images/svg/svg-sprite-av-symbol.svg new file mode 100644 index 0000000..84aaa88 --- /dev/null +++ b/app/assets/images/svg/svg-sprite-av-symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/svg/svg-sprite-device-symbol.svg b/app/assets/images/svg/svg-sprite-device-symbol.svg new file mode 100644 index 0000000..ad4bc20 --- /dev/null +++ b/app/assets/images/svg/svg-sprite-device-symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/svg/svg-sprite-image-symbol.svg b/app/assets/images/svg/svg-sprite-image-symbol.svg new file mode 100644 index 0000000..5431358 --- /dev/null +++ b/app/assets/images/svg/svg-sprite-image-symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/svg/svg-sprite-navigation-symbol.svg b/app/assets/images/svg/svg-sprite-navigation-symbol.svg new file mode 100755 index 0000000..af09449 --- /dev/null +++ b/app/assets/images/svg/svg-sprite-navigation-symbol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 0000000..8cc897f --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,7 @@ +//= require jquery +//= require rails-ujs +//= require popper +//= require bootstrap-sprockets +//= require bootstrap-datepicker +//= require hopscotch +//= require gsap diff --git a/app/assets/javascripts/mobile.js b/app/assets/javascripts/mobile.js new file mode 100644 index 0000000..b3b4c3d --- /dev/null +++ b/app/assets/javascripts/mobile.js @@ -0,0 +1,10 @@ +window.onDocumentReady(function() { + $('body').on('touchmove', function() { + // + // What does the :hover selector do?? + // + if ($('.navigation-dropdown:hover').is(':visible')) { + $('.navigation-dropdown').unbind('hover'); + } + }); +}); diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss new file mode 100644 index 0000000..5cf0878 --- /dev/null +++ b/app/assets/stylesheets/application.scss @@ -0,0 +1,103 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's + * vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + *= require font-awesome + */ + +@import "font-awesome"; +@import "bootstrap-custom"; +@import "bootstrap-datepicker"; +@import "hopscotch"; + +@import "variables"; +@import "mixins"; +@import "base"; +@import "typography"; +@import "hopscotch-overrides"; + +@import "rc-slider/assets/index"; +@import "rc-tooltip/assets/bootstrap"; + +@import "components/activity-feed"; +@import "components/auth-style-search"; +@import "components/action-menu"; +@import "components/badge"; +@import "components/blocks-index"; +@import "components/blocks-stats"; +@import "components/callouts"; +@import "components/challenge-block"; +@import "components/challenge-detail-comments"; +@import "components/challenge-detail-view"; +@import "components/challenge-timeline"; +@import "components/checkpoint-landing"; +@import "components/checkpoint-submission"; +@import "components/checkpoint-submissions-columns"; +@import "components/checkpoint-submissions-columns-student"; +@import "components/checkpoint-submissions-index"; +@import "components/checkpoint_submisstion_state"; +@import "components/checkpoint_student_scores"; +@import "components/checkpoint_toolbar"; +@import "components/code-block"; +@import "components/cohorts"; +@import "cohorts"; +@import "components/content-file-show"; +@import "components/content-file-sidebar"; +@import "components/curriculum"; +@import "components/curriculum-progress"; +@import "components/curriculum-checkpoint-summary"; +@import "components/dropdown-component"; +@import "components/footer"; +@import "components/flash-message"; +@import "components/activity-dashboard"; +@import "components/external-link"; +@import "components/grade-buttons"; +@import "components/lp-style-button"; +@import "components/mastery-table"; +@import "components/navigation-dropdown"; +@import "components/notifications"; +@import "components/new-comment-form"; +@import "components/primary-header"; +@import "components/primary-navigation"; +@import "components/progress"; +@import "components/thresholds"; +@import "components/progress-table"; +@import "components/search-bar"; +@import "components/secondary-navigation"; +@import "components/sort-dropdown-component"; +@import "components/standard-card"; +@import "components/standards-mastery-beans"; +@import "components/student-mastery-table"; +@import "components/student-name-bar"; +@import "components/submissions-dashboard-table"; +@import "components/cohort-submissions-table"; +@import "components/svg-icon"; +@import "components/user-avatar"; +@import "components/universal-list"; +@import "components/universal-row"; +@import "components/universal-table"; +@import "components/404-container"; +@import "components/video-player.scss"; +@import "components/cohort_releases"; +@import "components/checkbox-input"; +@import "components/partnerup"; +@import "components/integer_picker"; +@import "components/challenge_status"; +@import "components/status_picker"; +@import "components/slideshow"; +@import "components/progress_thresholds_slider"; +@import "components/progress_thresholds_key"; +@import "components/progress_thresholds_modal"; +@import "components/modal"; +@import "components/pill"; +@import "components/api-interactions"; +@import "components/pagination"; +@import "components/api_token"; +@import "components/button"; diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss new file mode 100644 index 0000000..1e9d53d --- /dev/null +++ b/app/assets/stylesheets/base.scss @@ -0,0 +1,66 @@ +html, +body { + overflow-x: hidden; + overflow-y: auto; +} + +body { + margin: 0; + width: 100%; + max-width: 100%; + background-color: $color-black-98-2; +} + +main { + min-height: calc(100vh - 6.5rem); + > .with-margins { + margin: 0 auto; + padding: 1rem ; + } +} + +body > main > .container-404 { + background-color: #e9eaec; + text-align: center; + font-family: sans-serif; + background: url("#{image_path("lost.jpg")}") no-repeat center center fixed; + background-size: cover; + -webkit-font-smoothing: antialiased; + margin: 0; + vertical-align: middle; + width: 100%; + + .logo { + padding-top: 2rem; + } + + h1, h2, h3 { + color: #fff; + } + + h1 { + font-size: 4rem; + font-weight: 200; + margin: 3rem 0 3rem; + } + + h2 { + font-size: 2rem; + font-weight: 200; + margin-bottom: 3rem; + padding-left: 20%; + padding-right: 20%; + } + + h3 { + font-size: 1.5rem; + text-transform: uppercase; + letter-spacing: .1rem; + font-weight: 600; + } + + a { + color: rgb(247, 144, 30); + text-decoration: none; + } +} diff --git a/app/assets/stylesheets/bootstrap-custom.scss b/app/assets/stylesheets/bootstrap-custom.scss new file mode 100644 index 0000000..7859210 --- /dev/null +++ b/app/assets/stylesheets/bootstrap-custom.scss @@ -0,0 +1,13 @@ +@import "bootstrap"; + +.alert { + margin: 1.5rem 2rem 0; +} + +.table p:last-child { + margin-bottom: 0; +} + +.card .table { + margin-bottom: 0; +} diff --git a/app/assets/stylesheets/cohorts.scss b/app/assets/stylesheets/cohorts.scss new file mode 100644 index 0000000..aaf876c --- /dev/null +++ b/app/assets/stylesheets/cohorts.scss @@ -0,0 +1,9 @@ +.center { + text-align: center; +} + +.alert-danger { + a { + color: $color-lightened-black; + } +} diff --git a/app/assets/stylesheets/components/_404-container.scss b/app/assets/stylesheets/components/_404-container.scss new file mode 100644 index 0000000..8638f2b --- /dev/null +++ b/app/assets/stylesheets/components/_404-container.scss @@ -0,0 +1,43 @@ +.container-404 { + position: absolute; + width: 100%; + height: 100%; + text-align: center; + background: url("#{image_path("lost.jpg")}") no-repeat center center fixed; + background-size: cover; + margin: 0; + + .logo { + padding-top: 2rem; + } + + h1, h2, h3 { + color: #fff; + } + + h1 { + font-size: 4rem; + font-weight: 200; + margin: 3rem 0 3rem; + } + + h2 { + font-size: 2rem; + font-weight: 200; + margin-bottom: 3rem; + padding-left: 20%; + padding-right: 20%; + } + + h3 { + font-size: 1.5rem; + text-transform: uppercase; + letter-spacing: .1rem; + font-weight: 600; + } + + a { + color: rgb(247, 144, 30); + text-decoration: none; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_action-menu.scss b/app/assets/stylesheets/components/_action-menu.scss new file mode 100644 index 0000000..87ebbd7 --- /dev/null +++ b/app/assets/stylesheets/components/_action-menu.scss @@ -0,0 +1,53 @@ +.action-menu-list, #action-menu-list { + position: absolute; + margin: 0; + right: 0; + top: 40px; + z-index: 10; + white-space: nowrap; + + &#action-menu { + margin: 48px 48px 0 0; + top: unset; + } + + &.lesson { + margin: -6px 156px 0 0 + } + + ul { + background: white; + border: 1px solid $color-black-77; + list-style-type: none; + margin: 0; + padding: 0; + border-radius: 8px; + box-shadow: 0 1px 10px 0 rgba(0,0,0,0.05); + border-color: rgb(238,238,238); + + li { + margin: 0; + + a, p { + color: $color-tundora; + text-decoration: none; + font-size: 16px; + font-weight: 400; + cursor: pointer; + line-height: 48px; + padding-left: 16px; + padding-right: 16px; + display: flex; + &:hover { + background: rgb(223,243,245); + } + + &.remove { + color: $color-valencia; + } + } + } + } + + +} diff --git a/app/assets/stylesheets/components/_activity-dashboard.scss b/app/assets/stylesheets/components/_activity-dashboard.scss new file mode 100644 index 0000000..85de563 --- /dev/null +++ b/app/assets/stylesheets/components/_activity-dashboard.scss @@ -0,0 +1,253 @@ +.activity-dashboard { + & { + background-color: $color-white; + overflow-x: scroll; + overflow-y: hidden; + white-space: nowrap; + } + + > .headerrow { + height: 67px; + position: relative; + z-index: 6; + + &.-is-sticky { + position: fixed; + top: 0; + } + } + + > .headerspacer { + height: 67px; + border-bottom: 1px solid $color-black-83-1; + position: relative; + z-index: 9; + left: 0; + box-shadow: 1px 0px 0px 0px $color-black-83-1; + + &.-is-sticky { + position: fixed; + top: 0; + } + } + + > .headerspacer, + > .headerrow > .headermonths { + background-color: $color-black-97-3; + display: inline-block; + height: 67px; + vertical-align: top; + } + + > .headerrow > .headermonths { + position: relative; + z-index: 6; + } + + > .headerspacer, + > .studentrow > .studentnameplate { + width: 225px; + left: 0; + position: absolute; + } + + > .headerrow > .headermonths, > .studentrow > .studentdata { + margin-left: 225px; + } + + > .headerrow > .headermonths > .headermonth { + display: inline-block; + + &:last-child { + border-right: 1px solid $color-black-76-5; + } + } + + > .headerrow > .headermonths > .headermonth > .monthname { + border-left: 1px solid $color-black-76-5; + border-bottom: 1px solid $color-black-83-1; + font-size: 12px; + font-weight: bold; + padding: 4px 12px; + overflow: hidden; + text-overflow: ellipsis; + } + + > .headerrow > .headermonths > .headermonth > .dates > .dateitem { + border-bottom: 1px solid $color-black-83-1; + border-left: 1px solid $color-black-83-1; + width: 40px; + height: 40px; + display: inline-block; + font-size: 12px; + text-align: center; + position: relative; + + &.-is-first-of-month { + border-left-color: $color-black-76-5; + } + + &.-is-highlighted { + background-color: rgba(0,165,181,0.1); + border-left-color: $color-cyan; + box-shadow: 1px 0px 0px 0px $color-cyan, 0px -1px 0px 0px $color-cyan; + position: relative; + z-index: 7; + border-bottom: 1px solid #00A5B5; + + &::after { + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + content: " "; + border-top: 5px solid $color-cyan; + position: absolute; + left: 14px; + bottom: -5px; + } + } + } + + > .headerrow > .headermonths > .headermonth > .dates > .dateitem > .dayofmonth { + font-weight: bold; + padding-top: 6px; + line-height: 1; + } + + > .headerrow > .headermonths > .headermonth > .dates > .dateitem > .dayofweek { + color: $color-black-66-7; + text-transform: uppercase; + padding-top: 3px; + font-size: 10px; + } + + > .studentrow { + height: 40px; + } + + > .studentrow > .studentnameplate { + display: flex; + flex-flow: row nowrap; + align-items: center; + height: 40px; + padding: 0 1rem; + border-bottom: 1px solid $color-black-83-1; + background: $color-white; + box-shadow: 1px 0px 0px 0px #d4d4d4; + overflow: hidden; + z-index: 8; + } + + > .studentrow > .studentnameplate > .avatarfullname { + font-weight: 500; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + > .studentrow > .studentnameplate > .user-avatar { + flex: 0 0 auto; + margin-right: .5rem; + } + + > .studentrow > .studentdata { + position: relative; + margin-left: 225px; + height: 40px; + } + + > .studentrow > .studentdata > .chart { + height: 40px; + position: absolute; + top: 0; + left: 0; + z-index: 1; + } + + > .studentrow > .studentdata > .studentdataitems { + position: absolute; + z-index: 2; + border-right: 1px solid $color-black-76-5; + } + + > .studentrow > .studentdata > .studentdataitems > .studentdataitem { + height: 40px; + width: 40px; + border-bottom: 1px solid $color-black-83-1; + border-left: 1px solid $color-black-83-1; + line-height: 40px; + text-align: center; + display: inline-block; + overflow: hidden; + + &.-is-weekend { + background-color: rgba(0,0,0,0.02); + } + + &.-is-first-of-month { + border-left-color: $color-black-76-5; + } + + &.-is-highlighted { + background-color: rgba(0,165,181,0.1); + border-left-color: $color-cyan; + box-shadow: 1px 0px 0px 0px $color-cyan; + position: relative; + z-index: 99; + } + } +} + +.activity-loadmore { + margin: 0; + margin-left: 1rem; + align-items: center; + display: flex; + + a { + margin-left: 0.5rem; + } +} + +.activity-dashboard-legendrow { + & { + position: relative; + display: flex; + flex-flow: row; + justify-content: flex-end; + background: $color-black-97-3; + border-bottom: 1px solid $color-black-90-2; + } + + > .svg { + opacity: .6; + margin: .5rem; + cursor: help; + + &:hover + .infomodal { + display: block; + } + } + + > .infomodal { + font-size: 14px; + max-width: 360px; + margin: .5rem; + padding: 1rem 1.5rem; + border-radius: .5rem; + color: $color-white; + background-color: $color-black-10-2; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.3); + cursor: help; + display: none; + position: absolute; + top: 0; + right: 0; + z-index: 10; + + &:hover { + display: block; + } + } +} diff --git a/app/assets/stylesheets/components/_activity-feed.scss b/app/assets/stylesheets/components/_activity-feed.scss new file mode 100644 index 0000000..c32a868 --- /dev/null +++ b/app/assets/stylesheets/components/_activity-feed.scss @@ -0,0 +1,69 @@ +.activityfeed { + > .table > tbody > .activityitem > .message * { + vertical-align: middle; + } + + > .table > tbody > .activityitem > .message > .user-avatar { + display: inline-block; + margin-right: 8px; + } +} + +.activitylegend { + display: inline-block; + list-style-type: none; +} + +.activitylegend > .activitylegenditem { + display: inline-block; + vertical-align: middle; + margin-left: 16px; +} + +.activitylegend > .activitylegenditem * { + vertical-align: middle; +} + +.activitylegend > .activitylegenditem > .activitylabel { + margin-right: 4px; +} + +.activitylabel { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 100%; + margin-right: 8px; + + &.-primary { + background-color: $color-cyan; + } + + &.-warning { + background-color: $color-banana-yellow; + } + + &.-default { + background-color: $color-black-80; + } + + &.-danger { + background-color: $color-valencia; + } + + &.-success { + background-color: $color-med-green; + } + + &.-score-1 { + background-color: $color-tangerine-orange; + } + + &.-score-2 { + background-color: $color-banana-yellow; + } + + &.-score-3 { + background-color: $color-lime-green; + } +} diff --git a/app/assets/stylesheets/components/_api-interactions.scss b/app/assets/stylesheets/components/_api-interactions.scss new file mode 100644 index 0000000..7603cf1 --- /dev/null +++ b/app/assets/stylesheets/components/_api-interactions.scss @@ -0,0 +1,62 @@ +.api-interactions-wrapper { + margin: 60px 100px 30px 100px; + + h3 { + margin-bottom: 15px; + } + + .api-interactions-display { + box-shadow: 0px 0px 10px 1px rgba(0, 0, 0, 0.2); + border-radius: 5px; + + .api-interactions-table { + text-align: center; + font-family: "Trebuchet MS", Arial, Helvetica, sans-serif; + border-collapse: collapse; + width: 100%; + + th { + font-size: 13px; + } + + .td-metadata { + display: flex; + justify-content: flex-end; + padding-right: 45px; + } + } + + .api-interactions-table td, .api-interactions-table th { + padding: 14px; + text-align: left; + } + + .tr { + cursor: pointer; + border-bottom: 0.5px solid #e6e6e6; + background-color: white; + } + + .tr:hover { + background-color: #e6e6e6; + } + + .api-interactions-table th { + padding-top: 12px; + padding-bottom: 12px; + background-color: $color-galvanize-orange; + color: white; + } + } + + .metadata-wrapper { + p { + color: white; + margin-bottom: 0px; + } + } + + .pagination-wrapper { + margin-bottom: 100px; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_api_token.scss b/app/assets/stylesheets/components/_api_token.scss new file mode 100644 index 0000000..67554ab --- /dev/null +++ b/app/assets/stylesheets/components/_api_token.scss @@ -0,0 +1,37 @@ +.api_token-wrapper { + + h3 { + @include font-h3; + } + + p { + @include font-paragraph; + margin-bottom: 0; + } + + .one-column { + @include one-column; + } + + .container-large { + @include container-large; + } + + .caption { + @include font-caption; + margin-bottom: 0; + margin-top: $font-default-line-height; + } + + .token-row { + margin: 20px -$input-spacing 0 -$input-spacing; + } + + input { + @include input; + + &.disabled { + @include input-disabled; + } + } +} diff --git a/app/assets/stylesheets/components/_auth-style-search.scss b/app/assets/stylesheets/components/_auth-style-search.scss new file mode 100644 index 0000000..a246cb3 --- /dev/null +++ b/app/assets/stylesheets/components/_auth-style-search.scss @@ -0,0 +1,26 @@ +.auth-style-search { + width: 100%; + box-shadow: rgba(0, 0, 0, 0.13) 1px 1px 2px 0px; + border-bottom-color: rgba(0, 0, 0, 0.15); + border-radius: 40px 40px 40px 40px; + border-bottom-style: solid; + border-bottom-width: 1px; + border-image-outset: 0px; + border-image-repeat: stretch; + border-image-slice: 100%; + border-image-source: none; + border-image-width: 1; + border-left-color: rgba(0, 0, 0, 0.15); + border-left-style: solid; + border-left-width: 1px; + border-right-color: rgba(0, 0, 0, 0.15); + border-right-style: solid; + border-right-width: 1px; + border-top-color: rgba(0, 0, 0, 0.15); + border-top-style: solid; + border-top-width: 1px; + padding-bottom: 8px; + padding-left: 28px; + padding-right: 12px; + padding-top: 8px; +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_blocks-index.scss b/app/assets/stylesheets/components/_blocks-index.scss new file mode 100644 index 0000000..ba7ff61 --- /dev/null +++ b/app/assets/stylesheets/components/_blocks-index.scss @@ -0,0 +1,133 @@ +.blocks-table{ + overflow: scroll; + max-height: 616px; + width: 100%; + margin: 0 auto; + z-index: 1000000; + background-color: $color-black-98-2; + + .createdcolumn { + padding-left: 24px; + } + + thead { + background-color: #FFFFFF; + border-top: 1px solid $color-black-77; + border-bottom: 1px solid $color-black-77; + height: 40px; + + tr { + font-size: 14px; + line-height: 16px; + text-transform: uppercase; + color: #999999; + } + + tr > th { + font-weight: 500; + padding: 12px; + } + } + + tbody { + background-color: #FFFFFF; + + .releaserow { + position: relative; + border-bottom: 1px solid #e7e7e7; + + .readme-text { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + } + + &.current-release { + background-color: rgba(0,165,181,0.05); + } + } + + .releaserow > td { + position: relative; + padding: 16px; + font-size: 16px; + vertical-align: top; + + &:nth-child(2) { + max-width: 360px; + } + + &.createdcolumn { + padding-left: 24px; + } + } + + .releaserow > td > h5 { + font-weight: bold; + line-height: 16px; + font-size: 16px; + } + + .releaserow > td > p { + line-height: 16px; + margin-bottom: 8px; + } + + .releaserow > td > a.gitlink { + line-height: 16px; + font-weight: 300; + margin-right: 24px; + position: relative; + } + + .releaserow > td > a.gitlink > svg { + width: 16px; + position: absolute; + margin-left: 3px; + + } + + .releaserow > td > span.currentlabel { + line-height: 16px; + color: $color-cyan; + text-transform: uppercase; + font-size: 12px; + font-weight: bold; + letter-spacing: 1px; + text-align: center; + padding: 0 24px; + } + + .releaserow > td .cohorts-tooltip { + position: absolute; + right: calc(100% - 2rem); + top: 1rem; + display: flex; + flex-direction: column; + width: 360px; + border-radius: .5rem; + background-color: $color-black-10-2; + color: $color-white; + overflow: hidden; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.3); + z-index: 9; + } + + .releaserow > td .cohorts-tooltip > .body > div { + padding: 24px; + } + + .releaserow > td .cohorts-tooltip > .body > div > p > a { + text-decoration: none; + color: white; + + } + + .releaserow > td .cohorts-tooltip > .body > div > p > a > svg { + height: 16px; + margin-left: 8px; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_blocks-stats.scss b/app/assets/stylesheets/components/_blocks-stats.scss new file mode 100644 index 0000000..46c8a0b --- /dev/null +++ b/app/assets/stylesheets/components/_blocks-stats.scss @@ -0,0 +1,70 @@ +.block-stats { + display: flex; + flex-direction: column; + flex-basis: 380px; + flex-shrink: 0; + + .block-stats__user-row { + display: flex; + justify-content: flex-start; + align-items: center; + margin-bottom: 15px; + flex-wrap: wrap; + @include font-paragraph; + font-size: 15px; + line-height: 20px; + + .block-stats__user-name { + margin-left: 8px; + margin-right: 8px; + } + + .block-stats__latest_version { + color: $color-silver-chalice; + font-weight: 500; + } + } + + .block-stats__stats-row { + display: flex; + justify-content: flex-start; + font-size: 14px; + color: $color-tundora; + font-weight: 400; + + a { + width: fit-content; + } + + &.vertical { + flex-direction: column; + + a { + margin-bottom: 10px; + font-weight: 500; + + &:last-child { + margin-bottom: 0; + } + } + } + } + + .block-stats__stats-icon { + margin-right: 15px; + display: flex; + align-items: center; + } + + .stats--verbose { + margin-left: 5px;; + } + + &.--vertical { + flex-basis: auto; + } + + svg { + margin-right: 3px; + } +} diff --git a/app/assets/stylesheets/components/_button.scss b/app/assets/stylesheets/components/_button.scss new file mode 100644 index 0000000..f6c709f --- /dev/null +++ b/app/assets/stylesheets/components/_button.scss @@ -0,0 +1,57 @@ +.button-wrapper { + @include button; + + &.button-solid-primary { + @include button-theme($color-primary-teal, primary); + + &:hover { + background-color: lighten( $color-primary-teal, $lighten ); + .submit-with-avatars .user-avatar { + border: 2px solid lighten( $color-primary-teal, $lighten ); + } + } + + &:active { + background-color: darken( $color-primary-teal, $darken ); + .submit-with-avatars .user-avatar { + border: 2px solid darken( $color-primary-teal, $darken ); + } + } + } + + &.button-outline-primary { + @include button-theme($color-primary-teal, outline); + + &:hover { + background-color: adjust-color($color-primary-teal, $alpha: -0.9); + } + + &:active { + background-color: adjust-color($color-primary-teal, $alpha: -0.7); + } + } + + &.button-solid-red { + @include button-theme($color-red, primary); + + &:hover { + background-color: lighten( $color-red, $lighten ); + } + + &:active { + background-color: darken( $color-red, $darken ); + } + } + + &.button-outline-red { + @include button-theme($color-red, outline); + + &:hover { + background-color: adjust-color($color-red, $alpha: -0.9); + } + + &:active { + background-color: adjust-color($color-red, $alpha: -0.7); + } + } +} diff --git a/app/assets/stylesheets/components/_callouts.scss b/app/assets/stylesheets/components/_callouts.scss new file mode 100644 index 0000000..71efbba --- /dev/null +++ b/app/assets/stylesheets/components/_callouts.scss @@ -0,0 +1,54 @@ +.callout { + border-left: 4px solid rgb(77, 169, 179); + border-radius: 4px; + padding: 25px 30px; + margin: 25px auto; + background: rgb(245, 245, 245); + max-width: 700px; + + h1,h2,h3,h4,h5,h6 { + font-size: 20px!important; + font-weight: 500!important; + letter-spacing: -0.75px!important; + margin-bottom: 0.5rem!important; + margin-top: 0!important; + color: rgb(77, 169, 179)!important; + } + + svg + h1,svg + h2,svg + h3,svg + h4,svg + h5,svg + h6 { + display: inline-block; + position: relative; + top: -5px; + padding-left: 10px; + } + + p { + margin: 0px!important; + } + + &.callout-info, &.callout-attention, &.callout-note, &.callout-tip, &.callout-hint, &.callout-primary { + border-left: 4px solid rgb(77, 169, 179); + h1,h2,h3,h4,h5,h6 { color: rgb(77, 169, 179)!important; } + svg { fill: rgb(77, 169, 179)!important; } + } + &.callout-warning, &.callout-caution { + border-left: 4px solid rgb(214, 146, 28); + h1,h2,h3,h4,h5,h6 { color: rgb(214, 146, 28)!important; } + svg { fill: rgb(214, 146, 28)!important; } + } + &.callout-danger, &.callout-error { + border-left: 4px solid rgb(199, 76, 76); + h1,h2,h3,h4,h5,h6 { color: rgb(199, 76, 76)!important; } + svg { fill: rgb(199, 76, 76)!important; } + } + &.callout-success, &.callout-important { + border-left: 4px solid rgb(109, 179, 104); + h1,h2,h3,h4,h5,h6 { color: rgb(109, 179, 104)!important; } + svg { fill: rgb(109, 179, 104)!important; } + } + &.callout-secondary, &.callout-light, &.callout-dark, &.callout-admonition { + border-left: 4px solid rgb(128, 128, 128); + h1,h2,h3,h4,h5,h6 { color: rgb(128, 128, 128)!important; } + svg { fill: rgb(128, 128, 128)!important; } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_challenge-block.scss b/app/assets/stylesheets/components/_challenge-block.scss new file mode 100644 index 0000000..1307045 --- /dev/null +++ b/app/assets/stylesheets/components/_challenge-block.scss @@ -0,0 +1,698 @@ +form.challenge-block { + border-width: 0; + + .topics { + background-color: $color-black-97-3; + display: flex; + justify-content: flex-start; + padding-bottom: 30px; + flex-direction: row; + flex-wrap: wrap; + } +} + +.challenge-legendrow { + margin-left: 14px !important; +} + +form[data-challenge-type="code-snippet"] .challenge-legendrow, +form[data-challenge-type="paragraph"] .challenge-legendrow, +form[data-challenge-type="multiple-choice"] .challenge-legendrow, +form[data-challenge-type="checkbox"] .challenge-legendrow, +form[data-challenge-type="short-answer"] .challenge-legendrow, +form[data-challenge-type="custom-snippet"] .challenge-legendrow, +form[data-challenge-type="testable-project"] .challenge-legendrow { + display: inline-block !important; + top: 5px; +} + +form[data-challenge-type="code-snippet"] .actions.checkpoint, +form[data-challenge-type="custom-snippet"] .actions.checkpoint, +form[data-challenge-type="testable-project"] .actions.checkpoint { + padding: 5px 16px 24px; +} + +.body-container .mdown-container .challenge-block, .challenge-block { + $em: 8; + + & { + position: relative; + background: $color-white; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + margin: ($em * 3 + px) 0; + } + + > .challenge-header { + position: relative; + background: $color-white; + border-bottom: 1px solid $color-black-90-2; + padding: ($em * 2 + px); + display: flex; + flex-flow: row nowrap; + align-items: center; + min-height: ($em * 9 + px); + } + + > .challenge-header .vertical-dots { + position: relative; + height: ($em * 3 + px); + width: ($em * 3 + px); + display: flex; + flex: 0 0 auto; + margin-left: auto; + margin-right: 0; + color: $color-black-60; + + &:hover { + cursor: pointer; + color: $color-cyan; + } + } + + > .challenge-header > .textcontainer { + flex: 1 1 auto; + margin-left: ($em * 2 + px); + margin-right: ($em * 2 + px); + display: flex; + flex-flow: column; + justify-content: center; + } + + > .challenge-header > .textcontainer > .title { + height: ($em * 3 + px); + color: $color-darker-gray-blue; + font-size: ($em * 2 + px); + font-weight: bold; + line-height: ($em * 3 + px); + } + + > .challenge-header > .textcontainer > .info { + height: ($em * 2 + px); + color: $color-black-60; + font-size: ($em * 1.5 + px); + line-height: ($em * 2 + px); + } + + > .challenge-header > .icon-container { + position: absolute; + top: 0; + bottom: 0; + left: -($em * 2 + px); + right: auto; + margin: auto; + background: $color-darkest-blue-gray; + height: ($em * 4 + px); + width: ($em * 4 + px); + overflow: hidden; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 0.8rem; + font-weight: 400; + } + + > .challenge-header > .icon-container > svg { + height: 100%; + width: 100%; + } + + > .problem { + background: $color-white; + border-bottom: 1px solid $color-black-90-2; + padding: ($em * 3 + px); + + p { + color: $color-black-46-7; + } + + blockquote { + border-left: 3px solid $color-black-46-7; + font-style: italic; + padding-left: 20px; + } + + p, ul, ol, li { + margin-top: 12px; + margin-bottom: 0px; + &:first-child, &.first-child > img { + margin-top: 0px; + } + } + + h1 + p, h2 + p, h3 + p, h4 + p, h5 + p, h6 + p { + margin-top: 8px; + } + + h1, h1:first-of-type, h2, h3, h4, h5, h6 { + margin-top: 24px; + margin-bottom: 0px; + &:first-child { + margin-top: 0px; + } + } + + table, pre, blockquote { + margin-top: 24px; + margin-bottom: 24px; + &:first-child { + margin-top: 0px; + } + } + + p > img { + margin-top: 24px; + margin-bottom: 24px; + } + + img { + max-width: 100%; + } + + &.-truncate { + height: 200px; + overflow: hidden; + } + } + + .show-all { + background: white; + box-shadow: 0px -20px 20px 0px rgba(255,255,255,1); + color: $color-cyan; + text-align: center; + position: relative; + padding: 8px; + cursor: pointer; + } + + > .answer { + background: $color-black-97-3; + border-bottom: 1px solid $color-black-90-2; + padding: ($em * 3 + px); + + &.-codeeditor { + position: relative; + min-height: 400px; + width: 100%; + padding: 0; + overflow: scroll; + } + } + + > .answer > .checkbox-challenge-label > .renderedmdown > ul, + > .answer > .checkbox-challenge-label > .renderedmdown > h3 { + margin-top: 12px; + margin-bottom: 12px; + } + + > .answer .checkbox-challenge-info { + font-size: 14px; + color: $color-black-60; + font-weight: 400; + text-transform: uppercase; + } + + .input-lang { + background: #25282c; + text-align: right; + font-size: 12px; + text-transform: uppercase; + padding: 2px 4px; + color: #C5C8C6; + } + + > .answer > input { + height: ($em * 5 + px); + width: 100%; + padding: 0 ($em * 2 + px); + border: 1px solid $color-black-83-1; + border-radius: 4px; + background-color: $color-white; + box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.1); + } + + > .answer > input::placeholder, > .answer > textarea::placeholder { + color: $color-black-83-1; + font-style: italic; + } + + > .answer > textarea { + width: 100%; + padding: ($em * 2 + px); + border: 1px solid $color-black-83-1; + border-radius: 4px; + background-color: $color-white; + box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.1); + resize: vertical; + } + + > .actions { + min-height: ($em * 9 + px); + padding: ($em * 2 + px) ($em * 3 + px); + background-color: $color-black-97-3; + display: flex; + flex-flow: column-reverse nowrap; + align-items: center; + + .draft-timestamp { + font-size: 14px; + color: $color-black-60; + font-weight: 400; + } + + @media screen and (min-width: $tablet-breakpoint) { + flex-flow: row nowrap; + } + } + + > .actions .feedback { + display: flex; + flex-flow: row; + align-items: center; + font-size: ($em * 2.25 + px); + line-height: ($em * 3 + px); + margin-right: 10px; + } + + > .actions .feedback > .icon-container { + flex: 0 0 auto; + height: ($em * 3 + px); + width: ($em * 3 + px); + display: flex; + align-items: center; + justify-content: center; + margin-right: ($em * 1 + px); + } + + > .actions > .buttons { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + margin-bottom: 1rem; + + @media screen and (min-width: $tablet-breakpoint) { + margin: 0 0 0 auto; + } + } + + > .actions > .buttons > .resetbutton { + background-color: inherit; + color: $color-cyan; + width: 135px; + } + + .multi-and-checkbox { + & { + display: flex; + flex-flow: row nowrap; + align-items: center; + margin: ($em * 1 + px) 0; + } + + &:hover { + background: rgba($color-cyan, 0.05); + border-radius: 8px; + user-select: none; + cursor: pointer; + } + + > .renderedmdown { + flex: 1; + padding: 0.5rem 1rem; + border: 1px solid rgba(0,0,0, 0.1); + border-radius: 0.5rem; + overflow: hidden; + pointer-events: none; + } + + > .renderedmdown > pre, + > .renderedmdown > pre > code { + background: none; + margin: 0; + white-space: pre-wrap; + } + } + + .checkbox-challenge-label { + @extend .multi-and-checkbox; + + > input[type="checkbox"]:checked + .renderedmdown { + border-color: $color-cyan; + background: rgba($color-cyan, 0.05); + } + + > input[type="checkbox"] { + box-sizing: border-box; + padding: 0; + display: inline-block; + vertical-align: middle; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + cursor: pointer; + height: ($em * 3 + px); + width: ($em * 3 + px); + background: none center no-repeat; + background-size: cover; + margin: ($em * 1 + px); + margin-right: ($em * 2 + px); + flex: 0 0 auto; + } + + > input[type="checkbox"] { + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20style%3D%22fill%3A%23cccccc%3B%22%3E%3Cpath%20d%3D%22M19%205v14H5V5h14m0-2H5c-1.1%200-2%20.9-2%202v14c0%201.1.9%202%202%202h14c1.1%200%202-.9%202-2V5c0-1.1-.9-2-2-2z%22%2F%3E%3C%2Fsvg%3E"); + } + + > input[type="checkbox"]:checked { + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20style%3D%22fill%3A%2369A2D7%3B%22%3E%3Cpath%20d%3D%22M19%203H5c-1.11%200-2%20.9-2%202v14c0%201.1.89%202%202%202h14c1.11%200%202-.9%202-2V5c0-1.1-.89-2-2-2zm-9%2014l-5-5%201.41-1.41L10%2014.17l7.59-7.59L19%208l-9%209z%22%2F%3E%3C%2Fsvg%3E"); + } + + > .renderedmdown > p { + margin-top: 12px !important; + margin-bottom: 12px !important; + font-size: 18px !important; + } + } + + .multi-choice-challenge-label { + @extend .multi-and-checkbox; + + > input[type="radio"]:checked + .renderedmdown { + border-color: $color-cyan; + background: rgba($color-cyan, 0.05); + } + + > input[type="radio"] { + box-sizing: border-box; + padding: 0; + display: inline-block; + vertical-align: middle; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + cursor: pointer; + height: ($em * 3 + px); + width: ($em * 3 + px); + background: none center no-repeat; + background-size: cover; + margin: ($em * 1 + px); + margin-right: ($em * 2 + px); + flex: 0 0 auto; + } + + > input[type="radio"] { + background-image: url("data:image/svg+xml, %3Csvg%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2020%2020%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M10%200C4.48%200%200%204.48%200%2010s4.48%2010%2010%2010%2010-4.48%2010-10S15.52%200%2010%200z%22%20fill%3D%22%23FFF%22%20fill-rule%3D%22nonzero%22%2F%3E%3Cpath%20d%3D%22M10%200C4.48%200%200%204.48%200%2010s4.48%2010%2010%2010%2010-4.48%2010-10S15.52%200%2010%200zm0%2018c-4.42%200-8-3.58-8-8s3.58-8%208-8%208%203.58%208%208-3.58%208-8%208z%22%20fill%3D%22%23D4D4D4%22%20fill-rule%3D%22nonzero%22%2F%3E%3Cpath%20d%3D%22M-2-2h24v24H-2z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + } + + > input[type="radio"]:checked { + background-image: url("data:image/svg+xml, %3Csvg%20width%3D%2220%22%20height%3D%2220%22%20viewBox%3D%220%200%2020%2020%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill%3D%22none%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M10%205c-2.76%200-5%202.24-5%205s2.24%205%205%205%205-2.24%205-5-2.24-5-5-5zm0-5C4.48%200%200%204.48%200%2010s4.48%2010%2010%2010%2010-4.48%2010-10S15.52%200%2010%200zm0%2018c-4.42%200-8-3.58-8-8s3.58-8%208-8%208%203.58%208%208-3.58%208-8%208z%22%20fill%3D%22%2300A5B5%22%20fill-rule%3D%22nonzero%22%2F%3E%3Cpath%20d%3D%22M-2-2h24v24H-2z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E"); + } + + > .renderedmdown > p { + margin-top: 12px !important; + margin-bottom: 12px !important; + font-size: 18px !important; + } + } + + + .challenge-grading-buttons { + & { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + min-height: 72px; + background-color: $color-black-97-3; + padding: 0 24px; + + border: solid $color-black-90-2; + border-width: 1px 0; + } + + .grading-details { + & { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-end; + margin-left: auto; + } + .timestamp { + flex: 0 1 auto; + font-size: 16px; + color: $color-black-60-8; + font-weight: 500; + margin-right: 18px; + } + + .buttons { + display: flex; + flex-flow: row nowrap; + margin-left: 16px; + } + + .points-ratio { + font-size: 24px; + } + + .points-section { + display: flex; + align-items: center; + position: relative; + + .points { + margin-left: 5px; + font-weight: 500; + color: $color-black-60-8; + font-weight: 500; + margin-left: 15px; + } + + .correct-check { + color: $color-cyan; + width: 32px; + height: 32px; + } + + .incorrect-check { + color: $color-valencia; + width: 32px; + height: 32px; + } + } + } + } + + .topics { + padding: 10px 16px 16px; + } + +} + +//challenge-accordion +.challenge-accordion { + $em: 8; + + & { + position: relative; + background: $color-white; + } + + &.-is-expanded > .heading > .icon-container { + transform: rotate(0deg); + } + + &.-is-expanded > .body > .hint-container { + margin-left: 1em; + margin-right: 0.5em; + + p { + margin-bottom: 1.5em; + } + } + + &.-is-expanded > .body > .text-center > p.get-hint-text { + color: $color-cyan; + cursor: pointer; + } + + > .heading { + user-select: none; + height: ($em * 5 + px); + padding: 0 ($em * 2 + px); + border: solid $color-black-90-2; + border-width: 1px 0; + display: flex; + flex-flow: row nowrap; + align-items: center; + cursor: pointer; + } + + > .heading > .headingtitle { + flex: 1 1 100%; + height: ($em * 1.5 + px); + color: $color-cyan; + font-size: ($em * 1.5 + px); + font-weight: bold; + letter-spacing: 1px; + line-height: ($em * 1.5 + px); + margin: 0 ($em * 1 + px); + text-transform: uppercase; + } + + > .heading > .icon-container { + flex: 0 0 auto; + height: ($em * 3 + px); + width: ($em * 3 + px); + display: flex; + align-items: center; + justify-content: center; + transform: rotate(-90deg); + } + + > .body { + padding: ($em * 3 + px); + overflow-x: scroll; + + pre { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + margin: 0 0 10px; + padding: 9.5px; + background-color: $color-black-97-3; + font-size: 13px; + word-wrap: normal; + margin-top: 0px !important; + } + + p, h1, h2, h3, h4, h5, h6, table { + margin-top: 0px !important; + } + + .problem pre { + word-break: none; + word-wrap: normal; + color: $color-black-20; + background-color: $color-black-97-3; + border: none; + border-radius: 0px; + } + } +} + +.challenge-legendrow { + & { + position: relative; + display: flex; + flex-flow: row; + background: $color-black-97-3; + } + + > .svg { + opacity: .6; + margin: .5rem; + cursor: help; + margin-right: 10px; + margin-left: auto; + + &:hover + .infomodal { + display: block; + } + } + + > .infomodal { + font-size: 14px; + width: 260px; + margin: .5rem; + padding: 1rem 1.5rem; + border-radius: .5rem; + color: $color-white; + background-color: $color-black-10-2; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.3); + cursor: help; + display: none; + position: absolute; + top: 0; + right: 0; + z-index: 10; + + &:hover { + display: block; + } + } +} + +// menu.js.jsx +.menu-default { + $em: 8; + + & { + position: absolute; + top: 0; + right: 0; + background: $color-white; + border-radius: 4px; + border: 1px solid $color-black-83-1; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + overflow: hidden; + width: 100%; + min-width: 200px; + } + + > ul { + margin: 0; + padding: 0; + list-style: none; + } + + > ul > li { + margin: 0; + padding: 0; + height: ($em * 5 + px); + line-height: ($em * 5 + px); + background-color: $color-white; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + cursor: pointer; + user-select: none; + font-size: ($em * 2 + px); + color: black; + + &:hover { + color: $color-cyan; + background-color: rgba($color-cyan, 0.02); + } + } + + a { + display: block; + height: inherit; + line-height: inherit; + font-style: inherit; + color: inherit; + text-decoration: none; + padding: 0 ($em * 2 + px); + } +} + +// icon.js.jsx +.svg { + &.primary-color { + color: $color-cyan; + } + + &.error-color { + color: #E36875; + } +} + +.ace_editor { + display: block; + position: relative; + min-height: 400px; + min-width: 500px; + width: 100%; +} diff --git a/app/assets/stylesheets/components/_challenge_status.scss b/app/assets/stylesheets/components/_challenge_status.scss new file mode 100644 index 0000000..05fae01 --- /dev/null +++ b/app/assets/stylesheets/components/_challenge_status.scss @@ -0,0 +1,18 @@ +.status-section { + display: flex; + align-items: center; + position: relative; + margin-left: 25px; + + .incorrect-check { + color: $color-valencia; + width: 40px; + height: 45px; + } + + .correct-check { + color: $color-cyan; + width: 40px; + height: 45px; + } +} diff --git a/app/assets/stylesheets/components/_checkbox-input.scss b/app/assets/stylesheets/components/_checkbox-input.scss new file mode 100644 index 0000000..47f92e9 --- /dev/null +++ b/app/assets/stylesheets/components/_checkbox-input.scss @@ -0,0 +1,15 @@ +input[type="checkbox"].checkbox-input { + display: inline-block; + vertical-align: middle; + appearance: none; + cursor: pointer; + height: 24px; + width: 24px; + background: none center no-repeat; + background-size: cover; + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20style%3D%22fill%3A%23cccccc%3B%22%3E%3Cpath%20d%3D%22M19%205v14H5V5h14m0-2H5c-1.1%200-2%20.9-2%202v14c0%201.1.9%202%202%202h14c1.1%200%202-.9%202-2V5c0-1.1-.9-2-2-2z%22%2F%3E%3C%2Fsvg%3E"); + + &:checked { + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20style%3D%22fill%3A%2369A2D7%3B%22%3E%3Cpath%20d%3D%22M19%203H5c-1.11%200-2%20.9-2%202v14c0%201.1.89%202%202%202h14c1.11%200%202-.9%202-2V5c0-1.1-.89-2-2-2zm-9%2014l-5-5%201.41-1.41L10%2014.17l7.59-7.59L19%208l-9%209z%22%2F%3E%3C%2Fsvg%3E"); + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_checkpoint-landing.scss b/app/assets/stylesheets/components/_checkpoint-landing.scss new file mode 100644 index 0000000..c13e740 --- /dev/null +++ b/app/assets/stylesheets/components/_checkpoint-landing.scss @@ -0,0 +1,170 @@ +.instructor-actions { + color: $color-dove-gray; + display: flex; + flex-flow: row nowrap; + width: 170px; + justify-content: flex-end; + position: relative; + + a { + text-decoration: none; + } + div { + display: flex; + flex-direction: row; + margin-left: 2rem; + } + span { + font-size: 16px; + } +} + +.checkpoint-landing { + @include font-base; + margin-bottom: 50px; +} + +.checkpoint-attributes, .checkpoint-attributes .checkpoint-attribute { + display: flex; + flex-flow: row nowrap; + background-color: white; +} + +.checkpoint-landing-title { + @include font-h1; + margin: 66px 0 60px 0; +} + +.checkpoint-attributes { + margin-bottom: 1rem; +} + +.checkpoint-attributes .checkpoint-attribute:not(:last-child) { + margin-right: 5rem; +} + +.checkpoint-attribute svg { + margin-right: 10px; + position: relative; + top: -2px; +} + +.checkpoint-points-earned svg, .checkpoint-scoring svg { + margin-right: 5px; + position: relative; + top: 4px; + height: 21px; +} + +.checkpoint-attribute .quantity-attribute { + @include font-base; + line-height: 20px; + color: $color-mine-shaft; + font-weight: 500; +} + +.checkpoint-attribute .quantity-attribute { + b { + @include font-base; + font-weight: 500; + color: $color-mine-shaft; + + text-align: right; + line-height: normal; + } + + .subtext { + @include font-small; + color: $color-gray; + margin-left: 8px; + line-height: normal; + } +} + +.checkpoint-attribute .text-attribute, .checkpoint-scored-at { + @include font-small; + color: $color-gray; +} + +.checkpoint-scoring { + @include font-small; +} + +.checkpoint-scored-at { + line-height: 1.7; +} + +.checkpoint-scoring > .view-checkpoint { + line-height: 1.4; +} + +.checkpoint-scoring .actioncircle { + --actioncircle-size: 24px; + line-height: 0; +} + +.checkpoint-landing-summary { + margin-bottom: 30px; +} + +.checkpoint-centered { + margin: 0 auto 1rem; +} + +.checkpoint-points-earned { + @include font-base; + line-height: 1; + color: $color-mine-shaft; + display: inline; +} + +.checkpoint-started-at { + @include font-small; + font-weight: 400; + margin: 0 auto 1rem; +} + +.checkpoint-end-message { + text-align: center; + font-size: 18px; + color: $color-mine-shaft; + margin: 2rem 0 4rem; +} + +.checkpoint-details { + display: flex; + flex-flow: column nowrap; + padding: 2rem; + background-color: #FBFBFB; + border-radius: 5px; + border: 1px solid #E1E1E1; + margin-top: 2rem; + + .checkpoint-scoring { + margin-bottom: 32px; + } + + .button-wrapper.checkpoint-landing-button { + margin: 0 auto; + max-width: 275px; + position: relative; + } + + &.timed-checkpoint { + background-color: #FFFCF0; + + .checkpoint-started-at { + @include font-paragraph; + } + + .time-remaining svg{ + position: relative; + top: 6px; + margin-right: 8px; + } + } +} + +.primary-teal { + color: $color-primary-teal; +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_checkpoint-submission.scss b/app/assets/stylesheets/components/_checkpoint-submission.scss new file mode 100644 index 0000000..f7b309a --- /dev/null +++ b/app/assets/stylesheets/components/_checkpoint-submission.scss @@ -0,0 +1,152 @@ +.checkpoint-submission { + .header { + background: $color-darkest-blue-gray; + height: 112px; + padding: 32px; + display: flex; + justify-content: space-between; + } + + .header > div > .label { + color: $color-black-60-8; + font-size: 12px; + letter-spacing: 2px; + margin-bottom: 8px; + font-weight: 300; + padding-left: 0; + } + + .header > div > .title { + color: $color-white; + font-size: 24px; + } + + .header > .header-right .lp-badge { + margin-bottom: 8px; + } + + .header > .header-right > .title { + text-align: center; + } + + .challenge-block > .problem { + h3 { + font-size: 24px; + font-weight: 500; + line-height: 1.1; + color: $color-black-20; + margin: 20px 0 10px; + } + + p { + margin: 0 0 10px; + } + + pre, + pre > code, + pre > code > span { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + } + } +} + +.checkpoint-submission .body-container { + & { + display: flex; + flex-flow: row wrap; + height: auto; + + @media screen and (min-width: $tablet-breakpoint) { + min-height: 100vh; + } + } + + > .challenges { + flex: 1 1 auto; + width: 60%; + padding: 0 1.5rem; + overflow: hidden; + background: $color-white; + } + + > .standards-wrapper { + flex: 0 0 auto; + width: 480px; + max-width: 100%; + } + + > .standards-wrapper > .standards { + background: $color-black-97-3; + padding: 1.5rem 1rem 0; + + &.-is-sticky { + flex: 0 0 auto; + width: 480px; + overflow: scroll; + position: fixed; + z-index: 9999; + height: calc(100% - 4rem); + top: 4rem; + right: 0; + } + &.-is-student { + height: 100%; + top: 0; + } + } + + > .standards-wrapper > .standards .standard-score-info { + flex: 1 1 100%; + } + + > .standards-wrapper > .standards .standard-score-info > .label { + height: 20px; + font-size: 12px; + font-weight: bold; + letter-spacing: 1px; + line-height: 12px; + text-transform: uppercase; + padding-left: 0; + color: $color-dark-blue-gray; + + &.gray-label { + color: $color-black-60-8; + } + + &.cyan-label { + color: $color-cyan; + } + + > .svg { + flex: 0 0 auto; + position: absolute; + left: auto; + margin: auto; + pointer-events: none; + right: 60px; + } + } + + .standards .standard-score-info > p { + margin: 0; + height: 16px; + font-size: 12px; + font-weight: 400; + line-height: 16px; + color: $color-black-60-8; + } + + .standard-topics-rollups { + display: flex; + flex-flow: row wrap; + margin-top: 10px; + + > .pill-wrapper.small { + margin: 0 10px 10px 0; + } + + &.hide-rollups { + display: none; + } + } +} diff --git a/app/assets/stylesheets/components/_checkpoint-submissions-columns-student.scss b/app/assets/stylesheets/components/_checkpoint-submissions-columns-student.scss new file mode 100644 index 0000000..4ab3c53 --- /dev/null +++ b/app/assets/stylesheets/components/_checkpoint-submissions-columns-student.scss @@ -0,0 +1,72 @@ +.checkpoint-submissions-columns-student { + & { + position: relative; + display: flex; + flex-flow: row nowrap; + background: white; + height: 56px; + border: 1px solid transparent; + border-bottom-color: $color-black-83-1; + padding: 8px 16px; + } + + &.-is-not-submitted { + align-items: center; + background: none; + + &:hover { + border-color: transparent; + border-bottom: 1px solid $color-black-83-1; + box-shadow: none; + cursor: auto; + } + + > .usercel > .usertimestamp { + display: none; + } + + > .lp-badge { + display: none; + } + } + + &:hover { + border-color: $color-cyan; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2); + cursor: pointer; + } + + > .user-avatar { + padding: 0; + flex: 0 0 auto; + } + + > .usercel { + margin: 0 8px; + flex: 1 1 auto; + min-width: 0; + } + + > .usercel > .username { + color: $color-darker-gray-blue; + font-size: 18px; + line-height: 24px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + > .usercel > .usertimestamp { + color: $color-black-60-8; + font-size: 12px; + line-height: 16px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + > .lp-badge { + flex: 0 0 auto; + margin: 2px 0; + } +} diff --git a/app/assets/stylesheets/components/_checkpoint-submissions-columns.scss b/app/assets/stylesheets/components/_checkpoint-submissions-columns.scss new file mode 100644 index 0000000..4013512 --- /dev/null +++ b/app/assets/stylesheets/components/_checkpoint-submissions-columns.scss @@ -0,0 +1,36 @@ +.checkpoint-submissions-columns { + & { + display: flex; + flex-flow: row nowrap; + } + + > .column { + flex: 1 1 auto; + width: calc(100%/3 - 64px); + + &.-no-shadow > .colbody { + box-shadow: none; + } + } + > .column:not(:last-of-type) { + margin-right: 32px; + } + + > .column > .colbody { + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.1); + } + + > .column > .colbody > a { + text-decoration: none; + } + + > .column > .colheading { + height: 56px; + border-bottom: 2px solid $color-black-83-1; + color: $color-black; + font-size: 20px; + display: flex; + align-items: center; + justify-content: center; + } +} diff --git a/app/assets/stylesheets/components/_checkpoint-submissions-index.scss b/app/assets/stylesheets/components/_checkpoint-submissions-index.scss new file mode 100644 index 0000000..5d102a6 --- /dev/null +++ b/app/assets/stylesheets/components/_checkpoint-submissions-index.scss @@ -0,0 +1,5 @@ +.checkpoint-submissions-index { + background: $color-black-97-3; + overflow: hidden; + padding: 0 32px 40px; +} diff --git a/app/assets/stylesheets/components/_checkpoint_student_scores.scss b/app/assets/stylesheets/components/_checkpoint_student_scores.scss new file mode 100644 index 0000000..c31efc4 --- /dev/null +++ b/app/assets/stylesheets/components/_checkpoint_student_scores.scss @@ -0,0 +1,184 @@ +.lesson-layout .body-container .checkpoint-student-scores { + background: $color-alabaster; + + .mdown-container .checkpoint-results { + h1:first-of-type { + margin-top: 45px; + margin-bottom: 40px; + } + h2:first-of-type { + margin-top: 45px; + margin-bottom: 40px; + } + } + + table.checkpoints-scores { + font-size: 16px; + width: 100%; + margin-top: 24px; + + tbody { + color: $color-tundora; + border: 1px solid $color-mercury; + box-shadow: 0 1px 10px 0 rgba(0,0,0,0.06); + background: white; + + tr { + border-bottom: 1px solid $color-black-93-3; + height: 56px; + + &:hover, &.never-signed-in:hover { + border-bottom: 1px solid #d0d0d0; + box-shadow: 0 0 10px 0 rgba(0,0,0,0.08); + } + + &.never-signed-in { + background: $color-alabaster; + span, div { + color: $color-black-60; + } + } + + &.row-selected td { + background: rgba(77, 169, 179, 0.1); + } + + &:last-of-type { + td { + border-bottom: none; + } + } + } + } + + th, td { + white-space: nowrap; + + &.checkbox-col { + width: 5%; + text-align: center; + } + + &.name-col > span, &.name-col > a { + padding: 0 20px 0 40px; + } + + &.score-col { + > a, > span { + padding: 0 20px; + } + } + + &.status-col { + width: 170px; + + > a, > span { + padding: 0 40px 0 20px; + } + } + + > input[type="checkbox"] { + box-sizing: border-box; + padding: 0; + display: inline-block; + vertical-align: middle; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + cursor: pointer; + height: (8 * 3 + px); + width: (8 * 3 + px); + background: none center no-repeat; + background-size: cover; + margin: (8 * 1 + px); + margin-right: (8 * 2 + px); + flex: 0 0 auto; + } + + > input[type="checkbox"] { + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20style%3D%22fill%3A%23cccccc%3B%22%3E%3Cpath%20d%3D%22M19%205v14H5V5h14m0-2H5c-1.1%200-2%20.9-2%202v14c0%201.1.9%202%202%202h14c1.1%200%202-.9%202-2V5c0-1.1-.9-2-2-2z%22%2F%3E%3C%2Fsvg%3E"); + } + + } + + th { + height: 36px; + > span { + color: $color-dove-gray; + font-weight: 400; + } + + > input[type="checkbox"]:checked { + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%2224%22%20height=%2224%22%20viewBox=%220%200%2024%2024%22%3E%3Cpath%20stroke=%22666%22%20strok-width=%221%22%20d=%22M19%206.41L17.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%22/%3E%3C/svg%3E"); + } + } + + td { + height: inherit; + + > input[type="checkbox"]:checked { + border-color: $color-cyan; + background: rgba($color-cyan, 0.05); + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20style%3D%22fill%3A%234DA9B3%3B%22%3E%3Cpath%20d%3D%22M19%203H5c-1.11%200-2%20.9-2%202v14c0%201.1.89%202%202%202h14c1.11%200%202-.9%202-2V5c0-1.1-.89-2-2-2zm-9%2014l-5-5%201.41-1.41L10%2014.17l7.59-7.59L19%208l-9%209z%22%2F%3E%3C%2Fsvg%3E"); + } + + > a, > span { + color: $color-tundora; + text-decoration: none; + height: 100%; + display: flex; + align-items: center; + } + + > a:hover { + color: $color-link-blue; + } + + &.name-col { + width: 100px; + height: inherit; + + .user-avatar { + margin-right: 10px; + } + + div { + max-width: 170px; + overflow: hidden; + text-overflow: ellipsis; + } + } + + &.score-col { + .checkpoint-score-text { + width: 18%; + } + + .checkpoint-score-bar { + width: 82%; + + div { + border-radius: 4px; + height: 12px; + } + } + } + + &.status-col { + svg { + margin-left: -2px; + margin-right: 5px; + } + + div.pending-submission { + border-radius: 100%; + height: 6px; + width: 6px; + background-color: #DC3D48; + margin-left: 2px; + margin-right: 8px; + } + } + } + } +} diff --git a/app/assets/stylesheets/components/_checkpoint_submisstion_state.scss b/app/assets/stylesheets/components/_checkpoint_submisstion_state.scss new file mode 100644 index 0000000..8edd6dc --- /dev/null +++ b/app/assets/stylesheets/components/_checkpoint_submisstion_state.scss @@ -0,0 +1,29 @@ +.checkpoint-submission-state-wrapper { + & { + text-align: right; + } + + >.badgerow { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-end; + height: auto; + } + + > .badgerow > .lp-badge { + flex: 0 0 auto; + } + + > .badgerow > a { + color: $color-cyan; + text-transform: uppercase; + margin-left: 8px; + } + + > .timestamp { + color: $color-darkest-blue-gray; + font-size: 14px; + line-height: 24px; + } +} diff --git a/app/assets/stylesheets/components/_checkpoint_toolbar.scss b/app/assets/stylesheets/components/_checkpoint_toolbar.scss new file mode 100644 index 0000000..84d7a6d --- /dev/null +++ b/app/assets/stylesheets/components/_checkpoint_toolbar.scss @@ -0,0 +1,207 @@ +$sidebar-width: 280px; +$max-content-width: 1280px; + +.checkpoint-submit { + & { + width: 100%; + height: auto; + text-align: center; + z-index: 9; + + .lesson-layout.-is-collapsed & { + padding-left: 0; + } + } + + > .checkpoint-toolbar { + position: relative; + display: flex; + flex-flow: column; + justify-content: center; + width: 100%; + max-width: $max-content-width; + margin: 0 auto; + padding: 0 1rem; + color: $color-white; + height: auto; + min-height: 4.5rem; + @media screen and (min-width: $tablet-breakpoint) { + padding: 0 3rem; + } + } + + .bodyrow { + flex: 0 0 auto; + margin-top: 40px; + max-width: 78%; + color: $color-white; + font-size: 20px; + font-weight: 500; + line-height: 24px; + display: none; + @media screen and (min-width: $tablet-breakpoint) { + display: block; + } + } + + .actionrow { + flex: 0 0 auto; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + padding: 1rem 0; + } + + .actionrow > .-submitbttn { + margin: 1rem; + } + + .actionrow > .-savebttn { + margin: 1rem; + } + + .closebttn { + position: absolute; + top: 0; + right: 80px; + display: none; + align-items: center; + justify-content: center; + padding: 20px 8px; + cursor: pointer; + + &:hover { + color: $color-white; + } + @media screen and (min-width: $tablet-breakpoint) { + display: flex; + } + + } + + .afterSubmitModal { + position: fixed; + top: 0; + left: 0; + z-index: 1000000; + background: rgba(0, 0, 0, 0.4); + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + + .error-wrapper { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + } + + .error-svg { + height: 24px; + width: 24px; + + svg { + fill: red; + } + } + + .error-header { + font-size: 22px; + font-weight: 600; + margin: 0 !important; + text-transform: uppercase; + color: #616161; + } + + .error-header-wrapper { + display: flex; + align-items: center; + height: 47px; + margin-left: 5px; + } + + .error-subheader { + margin: 16px 0 25px 0 !important; + text-align: left; + font-weight: 500; + color: #616161 !important; + } + + .error-message-wrapper { + margin-bottom: 20px; + } + + .error-challenge-number { + text-align: left; + text-transform: uppercase; + color: #b7b7b7; + font-size: 17px; + margin: 0 !important; + } + + .error-message { + text-align: left; + margin: 0 !important; + font-weight: 500; + color: #616161 !important; + } + } + + .afterSubmitModal .theModal .results-link { + color: $color-cyan; + font-size: 12px; + font-weight: bold; + text-decoration: none; + } + + .afterSubmitModal .theModal { + width: 500px; + border: 1px solid $color-black-60; + background: $color-white; + border-radius: 3px; + padding: 3rem; + position: relative; + } + + .afterSubmitModal .theModal h3 { + color: $color-dark-gray; + text-transform: uppercase; + } + + .afterSubmitModal .theModal h3 svg { + position: relative; + top: 6px; + margin-right: 0.5rem; + color: $color-black-60; + height: 30px; + width: 30px; + } + + .afterSubmitModal .theModal h3 img { + position: relative; + top: -3px; + margin-right: 0.5rem + } + + .afterSubmitModal .theModal p { + color: $color-black-60; + margin: 2rem 0; + } + + .afterSubmitModal .theModal p span { + font-size: 2rem; + + span { + color: $color-cyan; + font-weight: bold; + } + } + + .afterSubmitModal .theModal .closebttn { + right: 15px; + color: $color-black-60; + } +} diff --git a/app/assets/stylesheets/components/_code-block.scss b/app/assets/stylesheets/components/_code-block.scss new file mode 100644 index 0000000..3762468 --- /dev/null +++ b/app/assets/stylesheets/components/_code-block.scss @@ -0,0 +1,21 @@ +@import "highlightjs/styles/atom-one-light"; + +.hljs { + background: #f6f8fa; + padding: 16px; +} + +pre { + background: $color-white; + border-color: $color-black-83-1; +} + +pre.text { + background: $color-black-97-3; + padding: 0.5em; +} + +code { + color: black; + background-color: #f8f8f8; +} diff --git a/app/assets/stylesheets/components/_cohort-submissions-table.scss b/app/assets/stylesheets/components/_cohort-submissions-table.scss new file mode 100644 index 0000000..da27f81 --- /dev/null +++ b/app/assets/stylesheets/components/_cohort-submissions-table.scss @@ -0,0 +1,798 @@ +$submissions-cell-height: 56px; +.cohort-submissions { + %student-column { + & { + flex: 0 0 auto; + width: 40px; + } + + > .block-title-spacer { + background: $color-black-70-2; + height: 56px; + } + + > .student { + background: $color-white; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + height: $submissions-cell-height; + padding: 8px; + cursor: pointer; + + &:hover { + background: $color-black-97-3; + } + } + + > .standard > .standarditem { + position: relative; + width: 100%; + border-right: 1px solid $color-black-94-3; + border-bottom: 1px solid $color-black-94-3; + + &:not(:last-of-type) { + border-bottom: 2px solid $color-black-83-1; + } + + &.checkpoint { + border-bottom: 21px solid $color-black-84-7; + } + } + + > .standard > .standarditem > .itemheading { + position: relative; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + height: $submissions-cell-height; + background: $color-black-97-3; + padding: 8px; + } + + > .standard > .standarditem > .itemheading.performance { + background: $color-white; + } + + > .standard > .standarditem > .itemheading .tinybang { + position: absolute; + bottom: 26px; + right: 28px; + height: 12px; + } + + > .standard > .standarditem > .itemheading .actioncircle.tinyscore { + position: absolute; + bottom: 38px; + right: 24px; + height: 12px; + width: 12px; + } + + > .standard > .standarditem > .itemheading .actioncircle.pending-submission { + position: absolute; + bottom: 40px; + left: 27px; + height: 8px; + width: 8px; + background-color: #DC3D48; + } + + > .standard > .standarditem > .itemheading a { + text-decoration: none; + } + + > .standard > .standarditem > .itemheading .emdash { + &::before { + content: "—" + } + color: $color-dark-gray; + } + + > .standard > .standarditem > .itemheading .percent-correctness { + font-size: 13px; + font-weight: 300; + color: $color-dark-gray; + } + + > .standard > .standarditem > .itemheading .actioncircle { + &{ + border-radius: 100%; + color: $color-white; + display: flex; + align-items: center; + justify-content: center; + height: 23px; + width: 23px; + font-size: 10px; + line-height: 15px; + font-weight: 200; + } + + &.-score-3 { + background-color: $color-lime-green; + } + + &.-score-2 { + background-color: $color-banana-yellow; + } + + &.-score-1 { + background-color: $color-tangerine-orange; + } + + &.-ungraded { + border: 2px solid $color-black-20; + } + } + + > .standard > .standarditem > .itembody { + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + background: $color-black-99-3; + height: $submissions-cell-height; + border-top: 1px solid $color-black-94-3; + } + + > .standard > .standarditem > .itembody > a { + flex: 1 1 100%; + display: inherit; + align-items: inherit; + justify-content: inherit; + padding: 8px; + + &:hover { + background: $color-black-92-2; + } + } + + > .standard > .emptyrow { + height: 56px; + background: $color-black-92-2; + } + + > .standard > .standarditem > .itemheading > svg.donut, + > .standard > .standarditem > .itembody > svg.donut { + height: 100%; + width: 100%; + } + } + + %standard-item { + & { + } + + > .standardtitle { + display: flex; + flex-flow: row nowrap; + align-items: center; + background: $color-white; + color: $color-dark-blue-gray; + font-size: 12px; + font-weight: bold; + letter-spacing: 2px; + height: 56px; + line-height: 56px; + text-transform: uppercase; + user-select: none; + cursor: pointer; + } + + > .standardtitle .action-kebab { + position: relative; + top: 8px; + cursor: pointer; + color: $color-black-46-7; + } + + > .standardtitle .action-menu, .standarditem .action-menu { + position: absolute; + top: -1px; + left: 8px; + width: 220px; + z-index: 10; + + ul { + background: white; + border: 1px solid $color-black-60-8; + border-radius: 8px; + list-style-type: none; + margin: 0; + padding: 0; + line-height: 56px; + + li { + padding: 0px 16px; + box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.05); + + a { + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0px; + color: $color-black; + text-decoration: none; + text-transform: none; + } + } + + li:last-of-type { + border-bottom: 0px solid transparent; + } + } + } + .standarditem .action-menu { + top: -9px; + } + + > .standardtitle > .iconwrapper.kebab { + position: relative; + } + + > .standardtitle > .iconwrapper.arrow { + transform: rotate(-90deg); + color: inherit; + cursor: pointer; + } + + .standardtitle.-open > .iconwrapper.arrow { + transform: rotate(0); + color: $color-cyan; + } + + > .standardtitle > .title { + flex: 1; + height: 100%; + padding: 0 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + > .standarditem { + position: relative; + + &:not(:last-of-type) { + border-bottom: 2px solid $color-black-83-1; + } + + &.-open > .itemheading > .iconwrapper.arrow { + transform: rotate(0); + color: $color-cyan; + } + + &.checkpoint { + border-bottom: 20px solid $color-black-84-7; + } + } + + > .standarditem .kebab { + position: absolute; + right: 0; + top: 15px; + cursor: pointer; + color: $color-black-46-7; + } + + > .standarditem > .itemheading { + color: $color-dark-blue-gray; + background: $color-black-97-3; + position: relative; + display: flex; + flex-flow: column; + justify-content: center; + height: $submissions-cell-height; + + &:hover { + cursor: pointer; + color: $color-cyan; + } + } + + > .standarditem > .itemheading > .iconwrapper { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: auto; + margin: auto; + margin-left: 8px; + height: 24px; + width: 24px; + } + + > .standarditem > .itemheading > .iconwrapper { + &.arrow { + transform: rotate(-90deg); + color: inherit; + } + &.star { + color: $color-galvanize-orange + } + } + + > .standarditem > .itemheading > .challenge-table-label { + text-transform: uppercase; + color: $color-black-60-8; + font-size: 10px; + letter-spacing: 2px; + line-height: 16px; + margin-left: 40px; + } + + > .standarditem > .itemheading > .title { + //color: $dark-blue-gray; + color: inherit; + font-size: 16px; + line-height: 24px; + margin-left: 40px; + padding-right: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + > .standarditem > .itemheading > .title > a { + text-decoration: none; + color: inherit; + } + + > .standarditem > .itembody { + position: relative; + height: $submissions-cell-height; + background: $color-black-99-3; + border-left: 4px solid $color-cyan; + padding-left: 32px; + } + + > .standarditem > .itembody > .short-border-box { + display: flex; + flex-flow: column; + justify-content: center; + border-top: 1px solid $color-black-94-3; + height: 100%; + } + + > .standarditem > .itembody > .short-border-box > .challenge-table-label { + color: $color-black-60-8; + font-size: 10px; + letter-spacing: 2px; + line-height: 16px; + text-transform: uppercase; + margin-left: 16px; + } + + > .standarditem > .itembody > .short-border-box > .title { + color: $color-dark-blue-gray; + font-size: 16px; + line-height: 24px; + margin-left: 16px; + margin-right: 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + .submissions-dashboard-table-wrapper { + .flex-row { + > .nameplate { + margin-bottom: 1rem; + } + + > .nameplate > .avatar-wrapper { + width: 40px; + display: inline-block; + line-height: 56px; + margin-right: 10px; + vertical-align: middle; + } + + > .nameplate > span { + display: inline-block; + line-height: 56px; + font-size: 1.1rem; + vertical-align: middle; + color: $color-darker-gray-blue; + } + } + } + + .flex-row { + .sort-dropdown-component { + margin-bottom: 15px; + } + + .expand-collapse { + font-size: 10px; + color: $color-sky-blue; + align-self: center; + } + } + + .manual-score-performance { + width: 188px; + height: 40px; + background-color: white; + border: 1px solid $color-black-90-2; + position: absolute; + z-index: 10; + top: 8px; + left: 107%; + box-shadow: 0px 0px 4px #ccc; + + .scores { + display: flex; + flex-flow: row; + align-items: center; + height: 100%; + } + + &.show-success-criteria { + width: 270px; + height: auto; + + .scores .toggle-success-criteria { + margin-right: 12px; + margin-left: auto; + } + + .scores { + height: 38px; + } + + .success-criteria h1 { + color: #272e39; + font-size: 16px; + font-weight: bold; + letter-spacing: 2px; + margin-bottom: 12px; + } + + .success-criteria h4 { + color: #a8a8a8; + text-transform: uppercase; + font-size: 12px; + font-weight: 600; + margin-bottom: 10px; + } + + .success-criteria ul { + padding-left: 1.2rem; + } + } + + .scores > .actioncircle { + border-radius: 100%; + color: $color-white; + display: flex; + align-items: center; + justify-content: center; + height: 23px; + width: 23px; + font-size: 10px; + line-height: 15px; + font-weight: 200; + background: $color-black-84-7; + margin-left: 12px; + cursor: pointer; + + &.score1 { + &.active { + background-color: $color-tangerine-orange; + } + &:hover { + background-color: $color-tangerine-orange; + } + } + &.score2 { + &.active { + background-color: $color-banana-yellow; + } + &:hover { + background-color: $color-banana-yellow; + } + } + &.score3 { + &.active { + background-color: $color-lime-green; + } + &:hover { + background-color: $color-lime-green; + } + } + } + + .scores > .triangle-thing { + width: 15px; + height: 15px; + z-index: 9; + position: absolute; + background: $color-white; + border-left: 1px solid $color-black-90-2; + border-bottom: 1px solid $color-black-90-2; + -webkit-transform: rotate(45deg); + transform: rotate(45deg); + left: -8px; + } + + .scores > .toggle-success-criteria { + color: $color-black-84-7; + height: 23px; + width: 23px; + display: flex; + align-items: center; + justify-content: center; + margin-left: 12px; + cursor: pointer; + } + + > .success-criteria { + padding: 12px; + width: 100%; + border-top: 1px solid $color-black-90-2; + } + } + + .cohort-submission-legend { + & { + position: relative; + display: flex; + flex-flow: row; + justify-content: flex-end; + } + + > .svg { + opacity: .6; + margin-bottom: .5rem; + cursor: help; + + &:hover + .infomodal { + display: block; + } + } + + > .infomodal { + font-size: 14px; + max-width: 360px; + padding: 1rem 1.5rem; + border-radius: .5rem; + color: $color-white; + background-color: $color-black-10-2; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.3); + cursor: help; + display: none; + position: absolute; + top: 0; + right: 0; + z-index: 10; + + &:hover { + display: block; + } + } + } + + .challengeindex-table { + & { + position: relative; + display: flex; + flex-flow: row nowrap; + overflow: hidden; + background-color: $color-white; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + } + + &:after { + content: ""; + position: absolute; + left: 100%; + top: 0; + bottom: 0; + box-shadow: -4px 0 6px 1px rgba(black, 0.1); + background: white; + width: 10px; + } + + &.single-student { + &:after { + box-shadow: none; + } + } + + > .studentspanel { + position: relative; + z-index: 0; + flex: 1 1 100%; + overflow: hidden; + background: #e8e8e8; + } + + > .studentspanel .studentcolumn { + @extend %student-column; + } + + > .curriculumpanel { + position: relative; + z-index: 1; + flex: 0 0 auto; + width: 320px; + padding-top: $submissions-cell-height; + box-shadow: 4px 0 6px 1px rgba(black, 0.1); + } + + > .curriculumpanel > .block-title { + background: $color-black-70-2; + height: 56px; + padding: 0 1rem; + color: $color-dark-blue-gray; + font-size: 12px; + font-weight: bold; + letter-spacing: 2px; + line-height: 56px; + text-transform: uppercase; + user-select: none; + cursor: default; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + > .curriculumpanel > .standard { + @extend %standard-item; + border-bottom: 1px solid $color-black-83-1; + } + + > .curriculumpanel > .standard > .firstcontentfile { + border-top: 2px solid $color-black-83-1; + } + + > .curriculumpanel { + &.single-student { + padding-top: 0; + box-shadow: none; + + > .standard > .standarditem { + border-right: 2px solid $color-black-83-1; + } + } + } + + > .studentspanel { + &.single-student { + overflow-x: auto; + + .studentcolumn { + flex: 1 0 auto; + } + + > .studentcolumn > .standard > .standarditem > .itemheading { + flex-flow: row; + justify-content: flex-end; + } + + > .studentcolumn > .standard > .standarditem > .itembody { + flex-flow: row; + justify-content: flex-end; + } + + > .studentcolumn > .standard > .standarditem > .itemheading svg.donut { + width: 24px; + } + } + } + } + + .columnwrapper { + width: 100%; + overflow: auto; + display: flex; + flex-flow: row nowrap; + } + + .studentnamebar { + position: fixed; + top: 0; + z-index: 999; + width: calc(100% - 32px - 320px); + overflow: auto; + + &.-is-hidden { + visibility: hidden; + pointer-events: none; + } + + > .studentavatars { + display: flex; + flex-flow: row nowrap; + } + + > .studentavatars > .student { + background: $color-white; + flex: 0 0 auto; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + height: $submissions-cell-height; + width: 40px; + padding: 8px; + border-bottom: 1px solid $color-black-83-1; + + &:hover { + background: $color-black-97-3; + } + } + } + + .dropdown-component > select { + background-color: $color-white; + } + + .icon-legend { + & { + height: 48px; + background: none; + border: 1px solid $color-black-83-1; + border-radius: 4px; + display: inline-flex; + flex-flow: row nowrap; + vertical-align: bottom; + align-items: center; + margin-bottom: 32px; + user-select: none; + overflow: hidden; + position: relative; + } + + > .item { + flex: 1 0 auto; + display: flex; + flex-flow: row nowrap; + align-items: center; + margin: 0 16px; + } + + > .item > .challenge-table-label { + color: $color-darker-gray-blue; + font-size: 16px; + line-height: 22px; + } + + > .item > .svg { + margin-right: 8px; + } + } + + .flex-row { + & { + position: relative; + display: flex; + flex-flow: row; + } + &.-align-middle { + align-items: center; + } + + > .-flex-alignright { + margin-right: 0; + margin-left: auto; + } + + > .-margin-right { + margin-right: 8px; + } + } + + // other flex-row styles + .flex-row > .pullright { + margin-right: 0; + margin-left: auto; + } +} diff --git a/app/assets/stylesheets/components/_cohort_releases.scss b/app/assets/stylesheets/components/_cohort_releases.scss new file mode 100644 index 0000000..e95a7f4 --- /dev/null +++ b/app/assets/stylesheets/components/_cohort_releases.scss @@ -0,0 +1,205 @@ +.available-blocks { + h2 { + font-weight: bold; + } + + table.block-table { + color: $color-black-46-7; + + tr:nth-of-type(odd) { + background: rgba(0, 0, 0, 0.05); + + td:first-of-type { + border-top-left-radius: 5px; + border-bottom-left-radius: 5px; + } + td:last-of-type { + border-top-right-radius: 5px; + border-bottom-right-radius: 5px; + } + } + + th { + background: white; + } + + th, td { + padding: .5rem; + border-bottom: none; + border-top: none; + } + + .center { + text-align: center; + } + } + + .action-cell { + position: relative; + + .action-menu { + display: none; + position: absolute; + top: 0; + right: 0; + width: 420px; + z-index: 10; + + ul { + background: white; + border: 1px solid $color-black-46-7; + list-style-type: none; + margin: 0; + padding: 0; + + li { + font-size: 14px; + padding: .5rem 1rem; + + a { + svg.svg { + height: 14px; + width: 14px; + margin-left: 4px; + vertical-align: middle; + } + + div { + display: inline; + } + } + } + + li:last-of-type { + border-bottom: 0px solid transparent; + } + } + } + + &:hover { + .action-menu { + display: block; + } + } + } +} + +.cohort-releases-show, .releases-show { + h2 { + padding: 16px 32px; + } + + .pick-version-table { + width: 100%; + } + + .pick-version-table .createdcolumn { + padding-left: 24px; + } + + .pick-version-table > thead { + background-color: #FFFFFF; + border-top: 1px solid $color-black-77; + border-bottom: 1px solid $color-black-77; + height: 40px; + + tr { + font-size: 14px; + line-height: 16px; + text-transform: uppercase; + color: #999999; + } + + tr > th { + font-weight: 500; + padding: 12px; + } + } + + .pick-version-table > tbody { + background-color: #FFFFFF; + + .releaserow { + border-bottom: 1px solid #e7e7e7; + + &.current-release { + background-color: rgba(0,165,181,0.05); + } + } + + .releaserow > td { + padding: 16px; + font-size: 16px; + vertical-align: top; + position: relative; + + &.createdcolumn { + padding-left: 24px; + } + } + + .releaserow > td > h5 { + font-weight: bold; + line-height: 16px; + font-size: 16px; + } + + .releaserow > td > p { + line-height: 16px; + margin-bottom: 8px; + } + + .releaserow > td > a.gitlink { + line-height: 16px; + font-weight: 300; + margin-right: 24px; + } + + .releaserow > td > span.currentlabel { + line-height: 16px; + color: $color-cyan; + text-transform: uppercase; + font-size: 12px; + font-weight: bold; + letter-spacing: 1px; + text-align: center; + padding: 0 24px; + } + } +} + +.cohorts-tooltip { + position: absolute; + top: 14%; + right: 80%; + display: flex; + flex-direction: column; + width: 220px; + border-radius: .5rem; + background-color: $color-black-10-2; + color: $color-white; + overflow: hidden; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.3); + margin-top: -22px; + margin-left: -10px; +} + +.cohorts-tooltip > .body > div { + padding: 16px; +} + +.cohorts-tooltip > .body > div > p { + margin: 0; +} + +.cohorts-tooltip > .body > div > p > a { + text-decoration: none; + color: white; +} + +.cohorts-tooltip > .body > div > p > a > svg { + height: 14px; + margin-left: 5px; + position: absolute; + margin-top: 5px; +} diff --git a/app/assets/stylesheets/components/_cohorts.scss b/app/assets/stylesheets/components/_cohorts.scss new file mode 100644 index 0000000..5694369 --- /dev/null +++ b/app/assets/stylesheets/components/_cohorts.scss @@ -0,0 +1,957 @@ +.cohorts { + padding: 36px 0; + + .cohortstable { + margin: 20px 0; + } + + .cohortsetuptable .cohorts-list .cohortsetuprow > td, th { + line-height: 1.5rem; + padding: 12px; + vertical-align: middle; + font-weight: normal; + } + + .cohortsetuptable .cohortsetupheader .sortdirection { + position: absolute; + display: inline-flex; + align-items: center; + justify-content: center; + } + + .filters { + margin-bottom: 20px; + } + + a { + font-size: 14px; + margin-top: 7px; + text-decoration: none; + color: $color-sky-blue; + } + + .filterGroup { + margin-bottom: 14px; + } + + .filter-types { + display: -webkit-flex; /* Safari */ + -webkit-flex-wrap: wrap; /* Safari 6.1+ */ + display: flex; + flex-wrap: wrap; + + label { + color: black; + background: linear-gradient(to bottom, #fff 0%, #eceeef 100%); + border-radius: 4px; + margin: 2px 5px 2px 0; + padding: 3px 5px 3px 5px; + border: 1px solid #e6e6e6; + font-size: 12px; + font-family: Arial, sans-serif; + font-weight: lighter; + line-height: 16px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + i { + padding-right: 5px; + font-size: 10px; + color: black; + cursor: pointer; + font-weight: bold; + } + } + + .new-cohort-btn { + float: right; + + .help-text { + display: none; + } + &.disabled { + a.disabled { + background-color: grey; + border-color: grey; + } + &:hover { + .help-text { + display: block; + font-size: .6rem; + margin-top: 1px; + padding-right: 5px; + } + } + } + } +} + +.cohortinfo { + background-color: $color-black-94-9; + height: 87px; + padding: 0px 36px 0px 36px; + display: flex; + + p { + font-weight: 400; + font-size: 14px; + } + + h6 { + font-weight: bold; + margin-bottom: 4px; + } + + .cohorttitle{ + flex: 1 0 auto; + justify-content: flex-start; + margin-top: 24px; + padding-left: 7px; + } + .cohortdetails{ + display: flex; + justify-content: flex-end; + flex: 0 0 auto; + margin-top: 24px; + } + .cohortdetails > a { + color: $color-cyan; + font-size: 16px; + line-height: 20px; + display: flex; + justify-content: space-between; + } + .cohortdetails > .cohortdetail { + margin-right: 32px; + position: relative; + } +} + +.settingscontainer { + margin: 0px 36px 0px 36px; + + .settingsrow { + display: flex; + border-bottom: 1px solid $color-black-84-7; + } + + .settingsrow > .selectedsettingstab { + flex: 1 0 auto; + justify-content: flex-start; + display: flex; + } + + .settingsrow > .selectedsettingstab > div > p { + font-size: 18px; + line-height: 16px; + font-weight: 500; + padding: 16px 24px 16px 8px; + margin-bottom: 0; + } + .settingsrow > .selectedsettingstab > div > p > a { + text-decoration: none; + color: inherit; + } + + .settingsrow > .selectedsettingstab > .selected { + color: $color-cyan; + border-bottom: 4px solid $color-cyan; + } + + .settingsrow > .managebutton { + flex: 0 0 auto; + align-self: center; + } + + .settingsrow > .managebutton > a { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + font-weight: 200; + color: white; + } + + .resync-ui { + padding: 1rem 0; + .submit { margin-left: 1rem; } + #resync-url { text-overflow: ellipsis; } + .github-icon { + height: 1.4rem; + position: relative; top: -1px; + } + .non-success-label { padding-right: 0.5rem; } + } + .resync-success-icon { + display: inline-block; + height: 24px; + vertical-align: bottom; + width: 24px; + background: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20style%3D%22fill%3A%2369A2D7%3B%22%3E%3Cpath%20d%3D%22M19%203H5c-1.11%200-2%20.9-2%202v14c0%201.1.89%202%202%202h14c1.11%200%202-.9%202-2V5c0-1.1-.89-2-2-2zm-9%2014l-5-5%201.41-1.41L10%2014.17l7.59-7.59L19%208l-9%209z%22%2F%3E%3C%2Fsvg%3E"); + } + .successful-resync { + margin-top: 8px; + font-size: 80%; + } + + .import-work { + margin-right: 2rem; + display: flex; + height: 100%; + align-items: center; + + span { + margin: 0 10px; + color: $color-black-60; + } + + svg.success { + color: $color-cyan; + } + + svg.failure { + color: $color-valencia + } + + a { + color: $color-cyan; + text-decoration: none; + } + } +} + +.branch-modal { + position: fixed; + top: 0; + right: 0; + left: 0; + margin: 0 auto; + z-index: 1000000; + box-sizing: border-box; + width: 482px; + height: 202px; + background-color: #FFFFFF; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.15); + + .modal-header { + padding: 0px; + padding-top: 10px; + padding-left: 24px; + padding-right: 24px; + background-color: #FAFAFA; + } + + .modal-header > h1 { + font-size: 16px; + font-weight: bold; + line-height: 24px; + } + + .modal-header > div > svg { + color: $color-black-60; + cursor: pointer; + } + + .modal-body { + ::-webkit-input-placeholder { /* Chrome/Opera/Safari */ + color: gray; + opacity: .5; + font-weight: italic; + font-style: italic; + padding: 1rem; + border-top: 1px solid #D4D4D4; + } + } + + .modal-body > div > label { + padding-left: 10px; + padding-right: 24px; + } + + .modal-footer { + padding: 6px 0 0 0px; + text-align: right; + width: 100%; + + } + + .modal-footer > .lp-style-button { + margin-right: 22px; + } + + .modal-body > div > label { + font-size: 16px; + font-weight: bold; + line-height: 16px; + } + + .modal-body > div { + margin-top: 8px; + } + + .modal-body > div > span { + margin-left: 67px; + } + + .branch-name { + width: 300px; + height: 32px; + border: 2px solid #D4D4D4; + border-radius: 8px; + background-color: #FFFFFF; + margin-left: 8px; + padding: 8px; + } + +} + +.pick-version-modal .modal-header { + padding: 24px 24px 0 24px; +} + +.settingscontainer > .blocksmodal .modal-header { + padding: 24px 24px 12px 24px; +} + +.pick-version-modal, +.settingscontainer > .blocksmodal, +.cohortinfo > .switch-cohort-mode-modal { + max-width: 1200px; + width: 100%; + position: fixed; + top: 0; + right: 0; + left: 0; + margin: 0 auto; + z-index: 9999999; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.2); + background-color: $color-black-98-2; + + .actionbar { + left: 0; + bottom: 0; + width: 100%; + height: 80px; + padding: 16px; + background-color: white; + text-align: right; + border-top: 1px solid $color-black-90-2; + } + + .actionbar .lp-style-button { + margin-left: 16px; + } + + .actionbar label { + margin-left: 12px; + } + + .modal-header { + background-color: $color-black-98-2; + flex-wrap: wrap; + } + + .modal-header > h1 { + font-size: 24px; + font-weight: bold; + line-height: 24px; + width: 90%; + } + + .modal-header > .manual-warning { + width: 100%; + padding: 0.5rem 1rem; + margin: 0 0 1rem; + } + + .modal-header > div > svg { + color: $color-black-60; + cursor: pointer; + } + + .loading-spinner { + text-align: center; + padding: 2rem; + } + + .tablewrapper { + overflow-y: auto; + } + + .tablewrapper > .pick-version-table { + width: 100%; + } + + .tablewrapper > .pick-version-table .createdcolumn { + padding-left: 24px; + } + + .tablewrapper > .pick-version-table .checkboxcell { + width: 45px; + } + + .tablewrapper > .pick-version-table > thead { + background-color: #FFFFFF; + border-top: 1px solid $color-black-77; + border-bottom: 1px solid $color-black-77; + height: 40px; + + tr { + font-size: 14px; + line-height: 16px; + text-transform: uppercase; + color: #999999; + } + + tr > th { + font-weight: 500; + padding: 12px; + } + } + + .tablewrapper > .pick-version-table > tbody { + background-color: #FFFFFF; + + .releaserow { + border-bottom: 1px solid #e7e7e7; + + &.current-release { + background-color: rgba(0,165,181,0.05); + } + } + + .releaserow > td { + padding: 16px; + font-size: 16px; + vertical-align: top; + position: relative; + + &.createdcolumn { + padding-left: 24px; + } + } + + .releaserow > td > h5 { + font-weight: bold; + line-height: 16px; + font-size: 16px; + } + + .releaserow > td > p { + line-height: 16px; + margin-bottom: 8px; + } + + .releaserow > td > a.gitlink { + line-height: 16px; + font-weight: 300; + margin-right: 24px; + } + + .releaserow > td > a.gitlink > svg { + width: 16px; + vertical-align: bottom; + margin-left: 0.5rem; + } + + .releaserow > td > span.currentlabel { + line-height: 16px; + color: $color-cyan; + text-transform: uppercase; + font-size: 12px; + font-weight: bold; + letter-spacing: 1px; + text-align: center; + padding: 0 24px; + } + + .releaserow > td .cohorts-tooltip { + z-index: 99999999; + position: absolute; + top: 15%; + right: 80%; + display: flex; + flex-direction: column; + width: 220px; + border-radius: .5rem; + background-color: $color-black-10-2; + color: $color-white; + overflow: hidden; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.3); + } + + .releaserow > td .cohorts-tooltip > .body > div { + padding: 16px; + } + + .releaserow > td .cohorts-tooltip > .body > div > p { + margin: 0; + } + + .releaserow > td .cohorts-tooltip > .body > div > p > a { + text-decoration: none; + color: white; + } + + .releaserow > td .cohorts-tooltip > .body > div > p > a > svg { + height: 14px; + margin-left: 5px; + position: absolute; + margin-top: 5px; + } + } + + .loadingcontainer { + width: 100%; + height: 15rem; + display: flex; + align-items: center; + justify-content:center; + } + + .loadingcontainer:first-child { + position: relative; + right: 1rem; + } +} + +.course-scoring-info{ + & { + position: absolute; + top: -11px; + left: -38px; + display: flex; + } + + > .svg { + opacity: .6; + margin: .5rem; + cursor: help; + + &:hover + .infomodal { + display: block; + } + } + + > .infomodal { + font-size: 14px; + width: 310px; + margin: .5rem; + padding: 1rem 1.5rem; + border-radius: .5rem; + color: $color-white; + background-color: $color-black-10-2; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.3); + cursor: help; + display: none; + position: absolute; + top: 0; + right: 0; + z-index: 10; + + &:hover { + display: block; + } + } +} + +.cohortinfo > .switch-cohort-mode-modal { + position: fixed; + top: 0; + right: 0; + left: 0; + margin: 0 auto; + z-index: 1000000; + box-sizing: border-box; + width: 482px; + height: 165px; + background-color: #FFFFFF; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.15); + + .modal-header { + padding: 0px; + padding-top: 10px; + padding-left: 24px; + padding-right: 24px; + background-color: #FAFAFA; + } + + .modal-header > h1 { + font-size: 16px; + font-weight: bold; + line-height: 24px; + } + + .modal-header > div > svg { + color: $color-black-60; + cursor: pointer; + } + + .modal-body > div > label { + padding-left: 10px; + padding-right: 24px; + } + + .modal-footer { + padding: 6px 0 0 0px; + text-align: right; + width: 100%; + + } + + .modal-footer > .lp-style-button { + margin-right: 22px; + } + + .modal-body > div > label { + font-size: 16px; + font-weight: bold; + line-height: 16px; + } + + .modal-body > div { + margin-top: 8px; + } + + .modal-body > div > span { + margin-left: 67px; + } +} + +.api-documentation { + margin: 2rem auto; + max-width: 800px; +} + +.curriclum-visibility { + + .settingscontainer { + display: flex; + flex-flow: row wrap; + + .settingsrow { + margin-bottom: 2rem; + width: 100%; + } + } + + .visibility { + flex: 0 0 auto; + width: 40%; + margin-right: 1rem; + + .section { + border-bottom: 2px solid $color-black-92-2; + } + + .section-header { + padding: 1rem 0 1rem 2rem; + font-weight: bold; + background: $color-black-94-9; + border-top: 2px solid $color-black-92-2; + } + + .standard { + padding: 1rem 1rem 1rem 1.6rem; + font-weight: 400; + background: white; + border-top: 2px solid $color-black-92-2; + + > .header { + display: flex; + align-items: center; + &.hidden { color: #bfbfbf; } + } + + .chevron { + cursor: pointer; + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + svg { + color: #ccc; + top: 7px; + position: relative; + margin-right: 0.2rem; + } + } + + .content-file-row { + cursor: pointer; + padding: 0.5rem 0 0.5rem 3rem; + display: flex; + justify-content: space-between; + &.hidden { color: #bfbfbf; } + &.previewed-contentfile { + background: $color-cyan; + color: white; + + svg { + color: white!important; + } + } + } + .content-file-row > p { + font-weight: 400; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 450px; + margin-bottom: 0px; + + &.previewed-contentfile { + background: $color-cyan; + color: white; + + svg { + color: white; + } + } + } + .content-file-row > svg { + position: relative; + cursor: pointer; + } + } + } + + .lesson-preview { + padding: 2rem; + flex: 1 1 auto; + width: 55%; + background: $color-black-88-2; + margin-left: 1rem; + color: #ccc; + + &.has-content { + background: white; + overflow: scroll; + color: #333; + padding: 0; + + div[id^="challenge"] { + height: 200px; + margin-bottom: 2rem; + background: #ccc; + display: flex; + justify-content: center; + align-items: center; + } + } + } + + .lesson-preview h3.placeholder { + margin: 2rem 0; + width: 100%; + text-align: center; + } + + .status-header { + padding: 6px 3rem 0; + display: flex; + background: $color-black-88-2; + color: $color-black-60; + + svg { + color: $color-black-60; + fill: $color-black-60; + } + + .content-file-visibility { + text-transform: uppercase; + } + + .content-file-links { + margin-right: 0; + margin-left: auto; + + .github-icon { + height: 19px; + width: 19px; + position: relative; + top: -9px; + margin-left: 1rem; + } + } + } +} + +.cohortsetuptable { + font-size: 14px; + width: 100%; + + > .cohortsetupheader { + border-bottom: 2px solid $color-black-77; + color: $color-black-60; + text-transform: uppercase; + line-height: 40px; + } + + > .cohortsetupheader > tr > th { + font-weight: 500; + } + + > tbody > .cohortsetuprow > td { + line-height: 48px; + background-color: #FFFFFF; + border-bottom: 1px solid #E7E7E7; + font-size: 16px; + font-weight: 500; + } + + > tbody > .sectiontitleseparator > td { + line-height: 48px; + background-color: $color-black-94-9; + border-bottom: 1px solid #E7E7E7; + font-size: 16px; + font-weight: 600; + padding-left: 16px; + border-top: 2px solid $color-black-77; + } + + td.cohortusername, th.cohortusername { + padding-left: 20px; + } + + > tbody > .cohortsetuprow > td > select { + border: 1px solid #fff; + background-color: transparent; + margin-left: -8px; + } + + .sortcolumn { + padding-left: 24px; + width: 8em; + } + + .sortcolumn > a > svg { + margin-left: -4px; + margin-top: 16px; + cursor: pointer; + } + + > tbody > .cohortsetuprow > td > div > a > .versionlabel { + height: 24px; + background-color: rgba(74,144,226,0.1); + color: $color-sky-blue; + border-radius: 4px; + padding: 4px 10px; + cursor: pointer; + } + + > tbody > .cohortsetuprow > td > div > a > .versionlabel { + > .arrow-svg > svg { + vertical-align: text-bottom; + margin: 0px 8px; + } + } + + > tbody > .cohortsetuprow > td.action-cell { + position: relative; + } + + > tbody > .cohortsetuprow > td.action-cell > div { + display: inline-block; + } + + > tbody > .cohortsetuprow > td.action-cell .action-kebab { + float: right; + color: $color-black-46-7; + + &.active { + color: $color-cyan; + } + + &.disabled { + opacity: 0.25; + + svg { + cursor: not-allowed; + } + } + } + + > tbody > .cohortsetuprow > td.action-cell .user-action { + float: right; + } + > tbody > .cohortsetuprow > td.action-cell .user-action .action-kebab { + padding-top: 7px; + height: 40px; + } + > tbody > .cohortsetuprow > td.action-cell .user-action .action-menu { + margin: 18px 48px 0 0; + } + + > tbody > .cohortsetuprow > td.action-cell > a > svg { + margin: 12px 8px 0 0; + cursor: pointer; + } + + > tbody > .cohortsetuprow > td.updatecolumn > div > svg { + margin-top: 12px; + cursor: pointer; + } + + > tbody > .cohortsetuprow .error-sync { + color: $color-valencia; + } + + > tbody > .cohortsetuprow .error-sync > .errorsyncicon { + cursor:pointer; + } + + > tbody > .cohortsetuprow .error-sync > .errorsyncicon > svg { + vertical-align: middle; + margin-left: 8px; + } + + > tbody > .cohortsetuprow .error-sync > .errors-tooltip { + position: absolute; + display: flex; + flex-direction: column; + width: 400px; + border-radius: 0.5rem; + background-color: #FFFFFF; + color: $color-valencia; + overflow: hidden; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.3); + padding: 1rem; + margin-top: -8px; + + li { + line-height: 1.5rem; + } + } + + > tbody > .cohortsetuprow .error-sync > a { + //vertical-align: super; + margin-left: 8px; + text-decoration: underline; + color: $color-valencia; + } + + .blockcolumn { + padding-left: 1rem; + } + + > tbody > .cohortsetuprow > td.blockcolumn > svg { + vertical-align: text-bottom; + margin-right: 1rem; + } + + > tbody > .cohortsetuprow > td.blockcolumn > span.managereleases { + font-size: 10px; + background-color: #4e92df; + padding: 1px 8px; + border: none; + border-radius: 12px; + margin-left: 10px; + line-height: 16px; + text-transform: uppercase; + cursor: default; + height: 18px; + } +} diff --git a/app/assets/stylesheets/components/_content-file-show.scss b/app/assets/stylesheets/components/_content-file-show.scss new file mode 100644 index 0000000..29a161e --- /dev/null +++ b/app/assets/stylesheets/components/_content-file-show.scss @@ -0,0 +1,342 @@ + +$sidebar-width: 260px; +$max-content-width: 1280px; +$max-text-content-width: 700px; + +.lesson-layout, #content-file-html { + & { + position: relative; + display: flex; + width: 100%; + height: auto; + min-height: calc(100vh - 104px); + } + + &.-collapsed { + .left-container { + width: 55px; + } + + .content-file-sidebar { + overflow-x: hidden; + width: 55px; + + .toggleaction { + transform: rotate(-90deg); + margin-right: 4px; + } + + .standardtitle, .lessoncel, .blockslink { + display: none; + } + + .standardscore { + margin-left: 0; + } + + .itemrow > svg { + margin-right: 0px; + } + } + } + + .left-container { + position: relative; + flex: 0 0 auto; + width: 0; + background: $color-white; + margin-left: 0; + + @media screen and (min-width: $tablet-breakpoint) { + width: $sidebar-width; + } + } + + .body-container { + flex: 1 1 auto; + display: flex; + flex-flow: column; + justify-content: space-between; + width: 100%; + background: white; + overflow-x: auto; + } + + .body-container .mdown-container { + width: 100%; + margin: 0 auto; + padding: 1rem 50px; + color: $color-tundora; + font-size: 19px; + font-weight: 300; + + @media screen and (min-width: $tablet-breakpoint) { + padding: 2rem 3rem; + } + + pre { + font-size: 14px; + } + + h1 { + @include font-h1; + } + + .checkpoint-info h1 { + margin-bottom: 50px; + margin-top: 30px; + } + + h2 { font-size: 28px; } + h3 { font-size: 22px; } + h4 { font-size: 20px; } + h5 { font-size: 18px; } + h6 { font-size: 18px; } + + h2, h3, h4, h5, h6 { + font-weight: 500; + letter-spacing: -0.3px; + line-height: 1.1; + // margin-bottom: -28px; + } + + h1, h2 { + margin-top: 66px; + } + + h3, h4, h5, h6 { + margin-top: 48px; + } + + h1:first-of-type { + margin-bottom: 60px; + } + + iframe { + display: block; + margin-left: auto; + margin-right: auto; + border: 1px solid #ddd; + margin-top: 42px; + margin-bottom: 6px; + } + + .fluidvids > iframe { + margin-top: 0px; + margin-bottom: 0px; + } + + .fluidvids { + margin-top: 42px; + margin-bottom: 6px; + } + + img { + margin-top: 6px; + margin-bottom: 6px; + } + + pre, table, blockquote, .challenge-block { + margin-top: 42px; + margin-bottom: 6px; + } + + p { + line-height: 1.7; + margin-top: 36px; + margin-bottom: 0px; + } + + blockquote { + font-style: italic; + border-left: 3px solid $color-tundora; + padding-left: 20px; + } + + b, strong { + font-weight: 500; + } + + ul, ol, li { + margin-top: 18px; + margin-bottom: 0px; + } + + h1 + p, h2 + p, h3 + p, h4 + p, h5 + p, h6 + p { + margin-top: 8px; + } + + table, + pre, code, + p, ul, ol, + blockquote, + h1,h2,h3,h4,h5,h6, + .challenge-block, + .checkbox-container, .checkpoint-landing, + .checkpoint-results { + max-width: $max-text-content-width; + margin-left: auto; + margin-right: auto; + } + } + + .body-container .pdf-viewer { + background: $color-black-98-2; + + .react-pdf__Page__canvas { + margin-bottom: 20px; + border: 1px solid #E3E3E3; + } + } + + .body-container .mdown-container img { + max-width: 100%; + } + + .body-container .checkpoint-spacer { + margin-bottom: 72px; + } + + .body-container .content-arrow-container { + display: flex; + flex-direction: column; + border-top: 1px solid $color-black-94-9; + } + + .body-container .content-arrow-container > .content-arrows { + display: flex; + flex-direction: row; + justify-content: space-between; + height: 4rem; + } + + .body-container .content-arrow-container > .content-arrows > .standard-arrow { + flex: 0 1 auto; + max-width: 50%; + padding: 0 1.5rem; + font-weight: 500; + text-decoration: none; + text-transform: uppercase; + + @media screen and (min-width: $tablet-breakpoint) { + padding: 0 3rem; + } + + &.-prev { + margin-right: auto; + } + + &.-next { + margin-left: auto; + } + + &.-prev span { + margin-left: 1rem; + } + + &.-next span { + margin-right: 1rem; + } + } + + .body-container .content-arrow-container > .content-arrows > .standard-arrow > div { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; + color: $color-black-60; + + white-space: nowrap; + } + + .body-container .content-arrow-container > .content-arrows > .standard-arrow > div > span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + color: $color-cyan; + display: none; + @media screen and (min-width: $tablet-breakpoint) { + display: inline-block; + } + } + + .body-container .content-arrow-container > .content-arrows > .standard-arrow > div > svg { + flex: 0 0 24px; + } + +} + +.react-pdf__Page__canvas { + margin: 0 auto; +} + +.checkpoint-info { + $border-width: 10px; + + .checkpoint-info--header { + background: #666; + color: white; + font-size: 18px; + line-height: 36px; + margin: 0 auto; + max-width: $max-text-content-width; + padding: 2rem 4rem; + + border-top: none; + border-right: none; + border-bottom: none; + border-left: $border-width solid transparent; + + &.-state-complete-all-correct { border-left-color: $color-cyan; } + &.-state-complete-some-incorrect { border-left-color: $color-tangerine-orange; } + + &.-state-incomplete { border-left-color: $color-banana-yellow; } + &.-state-rejected { border-left-color: #E36875; } + &.-state-pending { border-left-color: #AAA; } + + &.-state-score-1 { border-left-color: $color-tangerine-orange; } + &.-state-score-2 { border-left-color: $color-banana-yellow; } + &.-state-score-3 { border-left-color: $color-lime-green; } + + > p:last-child { + margin-bottom: 0; + } + + > ul { + list-style-position: inside; + margin-bottom: 0; + padding-left: 4px; + } + + > .view-results { + background: white; + border-radius: 999px; + border: none; + color: #666666; + display: inline-block; + font-size: 90%; + font-weight: 500; + padding: 6px 35px 4px (35px - $border-width); + text-transform: uppercase; + } + } +} + +ul.task-list { + padding-left: 2em; + + ul { + margin-bottom: 0; + margin-top: 0 + } + + li.task-list-item { + list-style-type: none; + + input.md-task { + margin: 0 0.2em 0.25em -1.2em; + vertical-align: middle; + } + } +} diff --git a/app/assets/stylesheets/components/_content-file-sidebar.scss b/app/assets/stylesheets/components/_content-file-sidebar.scss new file mode 100644 index 0000000..4308332 --- /dev/null +++ b/app/assets/stylesheets/components/_content-file-sidebar.scss @@ -0,0 +1,308 @@ +$sidebar-width: 260px; + +.navigation-move-right { + margin-left: auto; + margin-right: 0; +} + +.content-file-sidebar { + & { + position: fixed; + top: 0; + left: 0; + z-index: 99; + height: 100vh; + width: $sidebar-width; + max-width: calc(100vw - 40px); + transform: translateX(-100%); + transition: transform 200ms ease-out; + flex-shrink: 0; + background: #3D424A; + overflow-x: hidden; + transform: translateX(0); + + &.-is-sticky { + position: fixed; + } + } + + .lesson-layout { + @media screen and (min-width: $tablet-breakpoint) { + transform: translateX(0); + transform: translateX(-100%); + } + } + + .lesson-layout.-is-collapsed & { + transform: translateX(0); + @media screen and (min-width: $tablet-breakpoint) { + transform: translateX(-100%); + } + } + + > .actionsrow { + flex: 0 0 auto; + background: #343940; + padding: 1rem 0.5rem; + display: flex; + flex-flow: row nowrap; + align-items: center; + height: 60px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + + .blockslink { + margin-left: 5px; + font-size: 0px; + + svg { + fill: rgba(255, 255, 255, 0.6); + } + } + + .toggleaction { + margin-left: auto; + margin-right: 15px; + transform: rotate(90deg); + cursor: pointer; + + @media screen and (max-width: $tablet-breakpoint) { + display: none; + } + + svg { + fill: rgba(255, 255, 255, 0.6); + } + } + } + + > .standardrow { + flex: 0 0 auto; + background: #343940; + padding: 1rem 1.25rem 1rem 1rem; + display: flex; + flex-flow: row nowrap; + align-items: center; + + &.-status-in-progress.-mode-percentage { + padding-bottom: 0.3rem + } + + > .standardtitle { + font-size: 14px; + font-weight: bold; + color: rgba(255,255,255,0.55); + line-height: 24px; + flex: 1 1 auto; + max-width: 208px; + } + + > .actioncircle { + --actioncircle-size: 24px; + } + > .actioncircle > .svg { + color: $color-cyan; + line-height: 1; + } + > .actioncircle.-awaiting-grade > .svg { + color: #A3A7AE; + line-height: 1; + } + } + + .standardprogressbar { + background: #343940; + padding: 0 1.25rem 1rem 1rem; + > .progressbar { --progressbar-height: 3px } + } + + > .bodyrow { + flex: 1 1 auto; + padding-bottom: 7.25rem; + background: #3D424A; + overflow-y: auto; // Always show scrollbar to avoid alignment issues + scrollbar-width: none; + + &::-webkit-scrollbar { + width: 4px; + } + /* Track */ + &::-webkit-scrollbar-track { + background: $color-medium-blue-gray; + } + /* Handle */ + &::-webkit-scrollbar-thumb { + background: #3D424A; + } + /* Handle on hover */ + &::-webkit-scrollbar-thumb:hover { + background: #3D424A; + } + } + + > .bodyrow > a { + display: block; + color: #3A4551; + text-decoration: none; + } + + > .bodyrow > a.checkpoint svg { + + } + + > .bodyrow > a > .itemrow.-is-active, + > .bodyrow > a:visited > .itemrow.-is-active { + background-color: #3A6371; + + > .iconcel { + color: $color-cyan; + opacity: 1; + } + + > .lessoncel > .lessontitle { + color: rgba(255,255,255,1); + } + } + + .itemrow { + display: flex; + flex-flow: row nowrap; + align-items: center; + height: 60px; + width: 100%; + color: #A3A7AE; + background: #3D424A; + + &:not(.-is-active) { + border-bottom: 1px solid #3b3b3b; + } + } + + .itemrow > .iconcel { + visibility: hidden; + flex: 0 0 auto; + height: 18px; + width: 0; + margin-left: 16px; + margin-right: 0; + opacity: 0.5; + color: $color-black; + } + + .itemrow > .iconcel .svg { + height: 18px; + width: 18px; + color: rgba(255,255,255,0.9); + } + + .itemrow > .lessoncel { + flex: 1 1 auto; + margin-right: 16px; + overflow: hidden; + white-space: nowrap; + } + + .itemrow > .lessoncel > .lessontitle { + color: rgba(255,255,255,0.6); + text-overflow: ellipsis; + overflow: hidden; + font-size: 16px; + line-height: 16px; + } + + .itemrow > svg, .itemrow img { + margin-right: 16px; + flex-shrink: 0; + } + + .itemrow .cant-complete-icon { + position: relative; + + .infomodal { + font-size: 14px; + max-width: 360px; + margin: .5rem; + padding: .5rem 1rem; + border-radius: .5rem; + color: $color-white; + background-color: $color-black-10-2; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.3); + cursor: help; + display: none; + position: absolute; + top: -13px; + right: 100%; + z-index: 10; + width: 174px; + } + + &:hover { + .infomodal { + display: block; + } + } + } + + .sidebar-checkpoint-percentage { + margin: 0; + font-weight: 500; + padding-right: 5%; + color: white; + } + + .checkpoint-complete-sidebar-title { + height: 60px; + display: flex; + justify-content: space-between; + align-items: center; + color: rgba(255, 255, 255, 0.6); + font-weight: 500; + padding-left: 16px; + padding-right: 16px; + background-color: #343940; + + p { + margin-bottom: 0; + } + } +} + + +.showhidecircle { + position: fixed; + z-index: 999999; + bottom: 3.5rem; + left: 0; + transform: translateX(-.5rem); + height: 3rem; + width: 3rem; + background: $color-white; + border-radius: 100%; + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.5); + user-select: none; + cursor: pointer; + + @media screen and (min-width: $tablet-breakpoint) { + height: 2.5rem; + width: 2.5rem; + position: absolute; + top: 1.5rem; + bottom: auto; + transform: translateX( calc(280px - 50%) ); + + .-is-sticky + & { + position: fixed; + transform: translateX(calc(280px - 50%)); + } + .-is-collapsed & { + transform: translateX( 1rem ); + } + } + + > div { + display: flex; + justify-content: center; + align-items: center; + height: inherit; + width: inherit; + } +} diff --git a/app/assets/stylesheets/components/_curriculum-checkpoint-summary.scss b/app/assets/stylesheets/components/_curriculum-checkpoint-summary.scss new file mode 100644 index 0000000..aab88a1 --- /dev/null +++ b/app/assets/stylesheets/components/_curriculum-checkpoint-summary.scss @@ -0,0 +1,46 @@ +.checkpoint-summary-wrapper { + display: flex; + flex-flow: column; + align-items: center; + justify-content: space-between; + margin-right: 1.5rem; + height: 100%; +} + +.checkpoint-summary { + display: flex; + flex-flow: row nowrap; + align-items: center; + line-height: 1rem; + + .avg { + color: $color-cyan; + font-size: 2.75rem; + position: relative; + line-height: 32px; + } + + .indicators { + display: flex; + flex-flow: column nowrap; + margin-left: 1rem; + height: 41px; + + .indicator { + width: 25px; + height: 5px; + margin-top: 3px; + border-radius: 8px; + flex: 0 0 auto; + background-color: $color-black-84-7; + + &:first-of-type { + margin-top: 0px; + } + + &.fill { + background-color: $color-cyan; + } + } + } +} diff --git a/app/assets/stylesheets/components/_curriculum-progress.scss b/app/assets/stylesheets/components/_curriculum-progress.scss new file mode 100644 index 0000000..6bf94b5 --- /dev/null +++ b/app/assets/stylesheets/components/_curriculum-progress.scss @@ -0,0 +1,239 @@ +.curriculumprogress { + & { + display: flex; + flex-flow: column; + align-items: center; + justify-content: space-between; + margin: 64px auto 0px auto; + height: auto; + + @media screen and (min-width: $tablet-breakpoint) { + flex-flow: row nowrap; + padding: 0 4rem; + height: 48px; + } + } + + .progress-indicators { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + } + + .curriculumprogressheader { + line-height: 2.5rem; + font-size: 40px; + font-weight: 300; + margin-bottom: 1rem; + letter-spacing: -1px; + + @media screen and (min-width: $tablet-breakpoint) { + font-size: 40px; + margin-bottom: 0; + } + } + + .masterywrapper { + display: flex; + flex-flow: row nowrap; + align-items: center; + + &.-percentage { + flex-flow: column nowrap; + justify-content: space-between; + height: 100%; + + .progressdonut { + width: auto; + height: auto; + } + } + } + + .masterywrapper .masteryaverage { + flex: 0 0 auto; + text-align: center; + } + + .masterywrapper .masteryaverage .masteryaveragevalue { + height: 24px; + color: $black; + font-size: 24px; + font-weight: 500; + line-height: 24px; + } + + .masterywrapper .masteryaverage .masteryaveragelabel { + height: 24px; + color: $color-black-50-2; + font-size: 16px; + font-weight: 500; + } + + .masterywrapper .progressdonut { + position: relative; + flex: 0 0 auto; + height: 96px; + width: 96px; + } + + .masterywrapper .progressdonut.-percentage { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: space-between; + + .donut { + width: 60px; + } + + .donutscore { + position: relative; + color: $color-cyan; + font-size: 2.75rem; + } + } + + .masterywrapper .progressdonut .donutscore { + position: absolute; + width: 100%; + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: $color-lime-green; + font-size: 32px; + line-height: 32px; + + &.-percentage { + color: $color-cyan; + } + } + + .masterywrapper .progressdonut .donutscore .percent { + font-size: 18px; + font-weight: bold; + line-height: 16px; + margin-top: -8px; + } + + .masterywrapper .progressdonut .donut { + width: 100%; + height: 100%; + transform: rotate(90deg); + } + + .masterywrapper .progressdonut .donut .donutring { + stroke: $color-black-84-7; + } + + .masterywrapper .progressdonut .donut .donutsegment { + stroke-linecap: round; + &.-score-3 { + stroke: $color-lime-green; + } + + &.-score-2 { + stroke: $color-banana-yellow; + } + + &.-score-1 { + stroke: $color-tangerine-orange; + } + + &.-completed { + stroke: $color-cyan; + } + + &.-pending { + stroke: $color-black-50-2; + } + } + + .masterywrapper .progresspercentages { + flex: 0 0 auto; + flex-flow: row nowrap; + } + + .masterywrapper .progresspercentages .progresspercentage { + display: flex; + flex-flow: row nowrap; + align-items: center; + flex: 0 0 auto; + } + + .masterywrapper .progresspercentages .progresspercentage .standardbean { + margin-right: 0.75rem; + } + + .masterywrapper .progresspercentages .progresspercentage { + height: 16px; + color: $color-black-20; + font-size: 16px; + font-weight: 500; + line-height: 16px; + margin-bottom: 8px; + } + .masterywrapper .progresspercentages .progresspercentage.percentunscored { + margin-bottom: 0; + } + + .progress-indicators { + position: relative; + height: 60px; + + .infomodal { + font-size: 14px; + padding: .5rem 1rem; + border-radius: .5rem; + color: $color-white; + background-color: $color-black-10-2; + box-shadow: 0 2px 8px 0 rgba(0,0,0,0.3); + cursor: help; + display: none; + position: absolute; + margin-top: -5px; + margin-right: 1rem; + right: 100%; + z-index: 10; + width: 360px; + } + + &:hover { + .infomodal { + display: block; + } + } + } +} + +.curriculum-last-viewed { + width: 100%; + display: flex; + flex-flow: row; + align-items: center; + justify-content: space-between; + padding: 0 4rem; + margin-top: 12px; + + .last-viewed-content { + font-weight: 400; + color: $color-dove-gray; + font-size: 16px; + + a { + color: $color-primary-teal; + } + } +} + +.curriculum-subset-notice.flash-message { + display: block; + position: fixed; top: 0; right: 0; left: 0; + text-align: center; + z-index: $z-primary-navigation + 1; +} +body.-with-curriculum-subset-notice { + padding-top: 50px; +} diff --git a/app/assets/stylesheets/components/_curriculum.scss b/app/assets/stylesheets/components/_curriculum.scss new file mode 100644 index 0000000..b43e20b --- /dev/null +++ b/app/assets/stylesheets/components/_curriculum.scss @@ -0,0 +1,277 @@ +.cohort-curriculum { + > .no-curriculum { + padding: 2.5rem; + } + + > .block-container { + padding: 0; + border-bottom: 6px solid #EAEAEA; + + &:last-of-type { + border-bottom: 0px solid transparent; + } + @media screen and (min-width: 800px) { + padding: 0 2.5rem 0; + } + + &.block-hidden { + .block-info { + margin-bottom: 20px; + } + + .standards-container { + display: none; + } + } + } + + > .block-container > .standards-container { + display: flex; + flex-flow: row wrap; + padding: 16px; + margin-bottom: 26px; + } +} + +.slideshow { + display: flex; + justify-content: center; + padding-top: 25px; +} + +.standardsprogress { + display: flex; + flex-flow: row nowrap; + align-items: center; + margin: 0 0 0 12px; +} + +.standardsprogress > .standardscount { + margin-left: 8px; + + opacity: 0.5; + color: #000000; + + font-size: 14px; + font-weight: 500; + line-height: 16px; + + margin-bottom: -2px; +} + +.block-info { + & { + display: flex; + flex-flow: column; + justify-content: center; + margin: 80px 24px 20px; + } + + > .blocktitle { + line-height: 24px; + font-weight: 500; + font-size: 24px; + display: flex; + align-items: center; + + > span { + margin-left: -5px; + margin-right: 12px; + } + } + + > .blocktitle .svg { + position: relative; + top: 2px; + color: #757575; + cursor: pointer; + } +} + +.non-sidebar-standard-complete { + position: relative; + margin-left: 220px; + margin-bottom: 256px; + + .standard-complete { + width: 21px; + } +} + +.standard-card, a.standard-card{ + .checkpoint-data { + position: absolute; + display: flex; + margin-top: 7.5%; + + .donut { + height: 20px; + width: 20px; + margin-top: 2px; + } + + .checkpoint-complete { + margin-top: 2px; + height: 20px; + } + + .grade-percent { + color: $color-cyan; + font-weight: 600; + padding-left: 5px; + } + } + + & { + position: relative; + display: flex; + flex-flow: column; + flex: 1 1 100%; + height: 180px; + max-width: 274px; + min-width: 274px; + margin: 1rem 0.5rem; + background-color: $color-white; + box-shadow: 0 1px 10px 0 rgba(0,0,0,0.06); + border: 1px solid #E3E3E3; + text-decoration: none; + color: inherit; + border-radius: 4px; + } + + &.-placeholder { + h5, p { + background-color: #333333; + border-radius: 3px; + opacity: 0.1; + } + h5 { width: 50%; } + .action p { + background-color: transparent; + opacity: 1; + } + } + + @media screen and (min-width: 800px) { + flex: 0 0 calc((100% / 4) - 1rem); + max-width: 288px; + } + + &.-completed-standard { + background-color: #f4f4f4; + border: 1px solid #E3E3E3; + box-shadow: none; + + h5, p { + color: #808080; + } + } + + &:hover { + &.-score-3 { + box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px $color-lime-green; + } + + &.-score-2 { + box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px $color-banana-yellow; + } + + &.-score-1 { + box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2), 0 0 0 1px $color-tangerine-orange; + } + } + + &.-score-3 > .action .percentage-badge-container > .percentage-badge.actioncircle { + background-color: $color-lime-green; + } + + &.-score-2 > .action .percentage-badge-container > .percentage-badge.actioncircle { + background-color: $color-banana-yellow; + } + + &.-score-1 > .action .percentage-badge-container > .percentage-badge.actioncircle { + background-color: $color-tangerine-orange; + } + + > h5 { + font-size: 18px; + font-weight: 500; + line-height: 26px; + margin: 24px 24px 4px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: $color-black-20; + } + + > p { + font-size: 14px; + line-height: 19px; + margin: 0px 24px 32px; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + color: $color-black-20; + } + + > .action { + font-size: 12px; + font-weight: 600; + line-height: 16px; + height: 40px; + padding: 0 24px; + display: flex; + position: relative; + flex-flow: row nowrap; + align-items: center; + margin-top: auto; + + &.-is-visited { + color: $color-black-20; + } + + .actioncircle { + position: absolute; right: 16px; bottom: 22px; + } + + p { + font-size: 12px; + font-weight: 400; + color: $color-black-50-2; + text-align: right; + margin-top: 4px; + } + + .percentage-badge-container { + p.pending-score { + margin-left: 45px; + text-align: left; + margin-top: -20px; + } + + .percentage-badge.actioncircle { + right: auto; + left: 19px; + background-color: $color-cyan; + + &.-awaiting-grade { + background-color: transparent; + color: $color-black-50-2; + } + } + } + + .full-width { + width: 100%; + } + } + + > span svg { + height: 16px; + width: 16px; + display: inline-block; + margin-right: 6px; + } + +} diff --git a/app/assets/stylesheets/components/_dropdown-component.scss b/app/assets/stylesheets/components/_dropdown-component.scss new file mode 100644 index 0000000..863abf5 --- /dev/null +++ b/app/assets/stylesheets/components/_dropdown-component.scss @@ -0,0 +1,79 @@ +%dropdown-select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + height: 100%; + min-width: 100%; + margin: 0; + padding: 0 40px 0 16px; + border: 0; + font-size: inherit; + color: inherit; + cursor: inherit; + display: flex; + flex-flow: row; + align-items: center; +} + +.dropdown-component { + & { + display: inline-block; + position: relative; + height: 48px; + border: 1px solid $color-black-92-2; + border-radius: 4px; + background-color: $color-white; + box-shadow: 0 1px 2px 0 rgba(155, 155, 155, 0.2); + color: rgba($color-darkest-blue-gray, 0.8); + cursor: pointer; + user-select: none; + width: auto; + min-width: 200px; + max-width: 400px; + margin-bottom: 32px; + font-size: 18px; + margin-right: 1rem; + } + + &:hover { + color: $color-darkest-blue-gray; + + > .svg { + fill: $color-cyan; + } + } + + > select { + @extend %dropdown-select; + } + + > .svg { + pointer-events: none; + position: absolute; + top: 0; + bottom: 0; + left: auto; + right: 8px; + margin: auto; + fill: $color-black-83-1; + } +} + +.faux-dropdown { + @extend .dropdown-component; + background-color: white; + + > .fauxselect { + @extend %dropdown-select; + } + + &.-is-pressed { + color: $color-dark-blue-gray; + background: $color-black-94-9; + box-shadow: none; + } + + &:hover > .svg { + fill: $color-black-83-1; + } +} diff --git a/app/assets/stylesheets/components/_external-link.scss b/app/assets/stylesheets/components/_external-link.scss new file mode 100644 index 0000000..cc24d52 --- /dev/null +++ b/app/assets/stylesheets/components/_external-link.scss @@ -0,0 +1,13 @@ +a[target="_blank"].external-link { + > svg.svg { + display: inline; + height: 12px; + width: 12px; + margin-left: 4px; + vertical-align: middle; + } + + &.view-in-github { + font-size: 11px; + } +} diff --git a/app/assets/stylesheets/components/_flash-message.scss b/app/assets/stylesheets/components/_flash-message.scss new file mode 100644 index 0000000..b63f831 --- /dev/null +++ b/app/assets/stylesheets/components/_flash-message.scss @@ -0,0 +1,21 @@ +.flash-message { + color: rgba(0, 0, 0, 0.6); + display: flex; + justify-content: space-between; + margin: 0; + border-radius: 0; + line-height: 24px; + font-size: 16px; + font-weight: 400; + strong { + font-weight: 500; + } + + .flash-closebtn { + height: 24px; + } + .flash-closebtn > svg { + color: rgb(153, 153, 153); + cursor: pointer; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_footer.scss b/app/assets/stylesheets/components/_footer.scss new file mode 100644 index 0000000..6ca2484 --- /dev/null +++ b/app/assets/stylesheets/components/_footer.scss @@ -0,0 +1,66 @@ +body > footer { + & { + display: flex; + flex-flow: column-reverse; + width: 100%; + min-height: 3.5rem; + padding: 0.5rem 2rem; + border-top: 1px solid $color-black-90-2; + font-size: 12px; + text-align: center; + + @media screen and (min-width: $tablet-breakpoint) { + flex-direction: row; + align-items: center; + padding: 0 2rem; + } + } + + > ul { + list-style: none; + padding: 0; + margin: 0 0 0 auto; + text-transform: uppercase; + + @media screen and (min-width: $tablet-breakpoint) { + display: flex; + flex-flow: row nowrap; + } + } + + > ul > li:not(:last-child) { + @media screen and (min-width: $tablet-breakpoint) { + margin-right: 1rem; + } + } + + > ul > li > a { + font-weight: 400; + line-height: 1.5rem; + color: $color-cyan; + text-decoration: none; + + &:hover { + color: $color-navy-blue; + } + } + + > span { + font-weight: 400; + line-height: 1.5rem; + } + + > .logo { + background: none left center no-repeat; + background-image: image-url("svg/mobile-logo.svg"); + background-size: contain; + height: 34px; + margin: 0 1rem 0 0; + min-width: 40px; + @media screen and (min-width: $tablet-breakpoint) { + background-image: image-url("svg/g-learn-lockup.svg"); + margin: 0 1.5rem 0 0; + min-width: 150px; + } + } +} diff --git a/app/assets/stylesheets/components/_grade-buttons.scss b/app/assets/stylesheets/components/_grade-buttons.scss new file mode 100644 index 0000000..12c3318 --- /dev/null +++ b/app/assets/stylesheets/components/_grade-buttons.scss @@ -0,0 +1,12 @@ +.grade-buttons { + & { + right: 0; + position: absolute; + margin-right: 32px; + } + + form { + display: inline-block; + margin-left: 8px; + } +} diff --git a/app/assets/stylesheets/components/_integer_picker.scss b/app/assets/stylesheets/components/_integer_picker.scss new file mode 100644 index 0000000..6af8843 --- /dev/null +++ b/app/assets/stylesheets/components/_integer_picker.scss @@ -0,0 +1,57 @@ +.integer-container { + display: flex; + border-radius: 25px; + border: 2px solid $color-black-93-3; + background-color: $color-white; + height: 45px; + justify-content: center; + align-items: center; + padding: 0 16px 0 16px; + + &.vertical { + flex-direction: column; + height: auto; + position: absolute; + top: 33px; + right: 23px; + border-radius: 10px; + z-index: 1; + width: 70px; + padding: 0; + box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1); + + .integer { + padding: 5px 0 5px 0; + width: 100%; + } + + .integer:first-child { + border-radius: 8px 8px 0 0; + } + + .integer:last-child { + border-radius: 0 0 8px 8px; + } + + .integer:not(:last-child) { + border-bottom: 1px solid $color-black-93-3; + } + } + + .integer { + color: $color-black-60-8; + text-align: center; + width: 25px; + font-size: 1.2rem; + font-weight: 500; + cursor: pointer; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + + &:hover { + background-color: $color-cyan-very-light; + } + } +} diff --git a/app/assets/stylesheets/components/_lp-style-button.scss b/app/assets/stylesheets/components/_lp-style-button.scss new file mode 100644 index 0000000..ae02900 --- /dev/null +++ b/app/assets/stylesheets/components/_lp-style-button.scss @@ -0,0 +1,127 @@ +%submit-button { + $color-em: 8; + $color-height: ($color-em * 5 + px); + + & { + display: inline-block; + height: $color-height; + width: auto; + min-width: 120px; + padding: 0 ($color-em * 3 + px); + border: none; + border-radius: 100px; + background-color: $color-cyan; + cursor: pointer; + line-height: $color-height; + color: white; + font-size: ($color-em * 1.5 + px); + font-weight: bold; + letter-spacing: 1px; + text-align: center; + text-transform: uppercase; + vertical-align: middle; + + } + + &:focus { + outline: 0; + } + + &:hover { + color: white; + text-decoration: none; + } + + &.-cancel { + background: none; + border: 1px solid rgba(220, 53, 73, 1); + color: rgba(220, 53, 73, 1); + } + + &.-cancel:hover { + background: rgba(220, 53, 73, 1); + border: 1px solid rgba(220, 53, 73, 1); + color: white; + } + + &.-disabled { + opacity: 0.3; + cursor: not-allowed; + } + + &.-outline { + background: none; + border: 1px solid $color-cyan; + color: $color-cyan; + } + + &.-outline:hover { + background: $color-cyan; + border: 1px solid $color-cyan; + color: white; + } + + &.-reject { + background: $color-valencia; + border: 1px solid $color-valencia; + color: $color-white; + } + + &.-white { + background: $color-white; + color: $color-cyan; + + &:hover { + background-color: rgba(255, 255, 255, 0.7); + } + } + + &.-white-grey-border { + background: $color-white; + color: $color-cyan; + font-weight: bold; + border: 1px solid $color-black-90-2; + height: auto; + padding: 2px 18px; + min-width: 80px; + line-height: 24px; + } + + &.-white-alt { + background: $color-white; + color: $color-darkest-blue-gray; + } + + &.-white-outline { + background: none; + border: 1px solid white; + color: white; + } + + &.-red-outline { + background: none; + border: 1px solid red; + color: red; + } + + &.-nowrap { + white-space: nowrap; + } + + &.-small { + font-weight: 200; + height: auto; + padding: 2px; + min-width: 80px; + line-height: 24px; + color: white; + } + + &.max-attempts { + line-height: 1; + } +} + +.lp-style-button { + @extend %submit-button; +} diff --git a/app/assets/stylesheets/components/_mastery-table.scss b/app/assets/stylesheets/components/_mastery-table.scss new file mode 100644 index 0000000..d90fcde --- /dev/null +++ b/app/assets/stylesheets/components/_mastery-table.scss @@ -0,0 +1,382 @@ +.masterytable { + overflow-x: scroll; +} + +.table-performance-grid { + & { + width: 100%; + margin: 0 auto; + background: $color-white; + margin-top: 50px; + } + + th.student-label { + width: 30px; + + &:last-child { + padding-right: 15px; + } + } + + th.vertical-text { + height: 100px; + white-space: nowrap; + font-weight: normal; + } + + th.vertical-text > div { + width: 30px; + transform: translate(19px, 35px) rotate(315deg); + } + + th.vertical-text > div > span { + border-bottom: 1px solid $color-black-80; + } + + th.vertical-text > div > span.selected { + font-weight: bold; + background: yellow; + } + + .tr-summary { + background: $color-light-gray-blue; + } + + .tr-standard > th { + font-weight: normal; + } + + tbody th { + padding: 0 3px; + } + + tbody th, + tbody td { + border: 1px solid $color-black-80; + } + + tbody td.score { + text-align: center; + } + + tbody td.score-0 { + background: white; + } + + tbody td.score-1 { + background: $color-soft-red; + } + + tbody td.score-2 { + background: $color-soft-orange; + } + + tbody td.score-3 { + background: $color-sage; + } + + tbody td.selected { + box-shadow: 0 0 0 2px black inset; + font-weight: bold; + } + + .clear-filters { + position: relative; + } + + .standard-legend { + position: absolute; + bottom: 3rem; + border: 1px solid $color-gray-light; + color: gray; + border-radius: 0.25rem; + padding: 0.5rem 2rem; + margin-right: 0.5rem; + margin-top: 0.5rem; + + &.elective { + left: 80px; + background-color: $color-pale-yellow; + } + } +} + +// print overwrites? +@media print { + table.table-performance-grid { + td { + &.score-selected { + box-shadow: 0 0 0 2px black inset; + } + + &.score-1, + &.score-2, + &.score-3 { + color: $color-black-80 !important; + } + + &.score-0 { + color: white !important; + } + } + } +} + +// used in: +// app/views/instructor/performances/index.html.haml +// app/views/instructor/performances/active_performances.html.haml +.workbook-performance-table { + .student-header { + position: absolute; + width: 100%; + left: 0; + height: 200px; + background: -moz-linear-gradient(top, rgba(0, 0, 0, 0.65) 0%, rgba(0, 0, 0, 0) 100%); + background: -webkit-linear-gradient(top, rgba(0, 0, 0, 0.65) 0%, rgba(0, 0, 0, 0) 100%); + background: linear-gradient(to bottom, rgba(0, 0, 0, 0.65) 0%, rgba(0, 0, 0, 0) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr="#a6000000", endColorstr="#00000000", GradientType=0); + } + + .student-selected-view { + &.card { + background: rgb(125, 125, 125); + vertical-align: top; + overflow: hidden; + position: fixed; + height: 120px; + width: 300px; + z-index: 2; + + .prop { + text-shadow: 1px 1px 0.5px rgba(65, 65, 65, 0.3); + padding-left: 15px; + display: block; + width: 175px; + float: left; + color: $color-white; + + &.avatar { + height: 119px; + width: 123px; + object-position: center; + object-fit: cover; + } + + &.first-name { + margin: 15px 0 0; + font-weight: 600; + font-size: 20px; + } + + &.last-name { + margin: 2px 0 0; + font-weight: 100; + font-size: 16px; + } + + &.student-performance-gauge { + margin: 20px 0 0; + font-size: 15px; + color: white; + bottom: 10px; + } + } + } + } + + .student-map-wrapper { + background: rgb(255, 255, 255); + position: fixed; + overflow: auto; + height: 120px; + right: 15px; + left: 315px; + z-index: 1; + + .card { + font-size: 0; + padding: 0; + margin: 0; + + li { + box-shadow: inset 0 0 1px rgba(119, 119, 119, 0.75); + transition: ease-in 0.33s; + display: inline-block; + background: white; + width: 20%; + margin: 0; + + .prop { + width: calc(100% - 45px); + padding-left: 5px; + float: left; + + &.avatar { + padding-left: 0; + height: 40px; + width: 40px; + object-position: center; + object-fit: cover; + } + + &.first-name { + padding-top: 4px; + font-size: 16px; + font-weight: 300; + } + + &.last-name { + font-size: 11px; + font-weight: 600; + } + } + + &.active { + box-shadow: inset 0 0 1px $black; + background: $color-galvanize-orange; + } + } + } + } + + .performance-spinner { + position: relative; + text-align: center; + margin: 33% auto; + width: 100%; + } + + .table-performance-grid { + position: relative; + width: 100%; + top: 135px; + + .clear-filters { + vertical-align: bottom; + } + + th.current-name { + vertical-align: bottom; + padding-bottom: 1em; + } + + th.vertical-text { + height: 180px; + white-space: nowrap; + font-weight: normal; + } + + th.vertical-text > div { + transform: translate(19px, 75px) rotate(315deg); + width: 30px; + // hide + display: none; + } + + th.vertical-text > div > span { + border-bottom: 1px solid $color-black-80; + } + + .selected { + background: yellow; + font-weight: bold; + } + + tbody { + tr.tr-summary { + th, + td { + background: $color-black-80; + padding: 10px 0; + + a { + color: $color-black-20; + font-size: 15px; + } + } + } + + th { + padding: 0 3px; + } + + th, + td { + border: 1px solid $color-black-80; + } + + td { + text-align: center; + padding: 8px; + + &.score-0 { + background: white; + } + + &.score-1 { + background: $color-soft-red; + } + + &.score-2 { + background: $color-soft-orange; + } + + &.score-3 { + background: $color-sage; + } + + &.selected { + box-shadow: 0 0 0 2px black inset; + font-weight: bold; + border-bottom: 1px; + border-right: 1px; + } + } + + .tr-success-criterion > th { + font-weight: normal; + max-width: 300px; + background: white; + } + } + } + + table.table { + tr.tr-summary, + tr.tr-standard { + th, + td { + background: $color-light-gray-blue; + } + } + + td { + &.score-selected { + box-shadow: 0 0 0 2px black inset; + } + + &.score-0 { + text-align: center; + vertical-align: middle; + } + + &.score-1 { + background: $color-soft-red; + text-align: center; + vertical-align: middle; + } + + &.score-2 { + background: $color-soft-orange; + text-align: center; + vertical-align: middle; + } + + &.score-3 { + background: $color-sage; + text-align: center; + vertical-align: middle; + } + } + } +} diff --git a/app/assets/stylesheets/components/_modal.scss b/app/assets/stylesheets/components/_modal.scss new file mode 100644 index 0000000..56d47f4 --- /dev/null +++ b/app/assets/stylesheets/components/_modal.scss @@ -0,0 +1,195 @@ +.modal-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.6); + z-index: 1000000; +} + +.modal { + position: fixed; + top: 0; + left: 0; +} + +.modal-main, .mdown-container .modal-container .modal-main { + color: $color-darker-gray-blue; + position: relative; + background: white; + height: auto; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 730px; + border-radius: 5px; + box-shadow: 0 2px 20px 0 rgba(0,0,0,0.07); + overflow: hidden; + + .close-button-wrapper { + display: flex; + justify-content: flex-end; + width: 100%; + padding-top: 5px; + padding-right: 5px; + + svg { + fill: lightgrey; + cursor: pointer; + width: 100%; + height: 100%; + } + + .close-button { + height: 40px; + width: 40px; + + &:hover { + svg { + fill: #AFADAD; + } + } + } + } + + .modal-contents { + padding: 40px 35px 15px; + max-height: 450px; + overflow-y: auto; + + // h1, h3, h4 { + // @include font-h1-settings; + // font-size: 28px; + // font-weight: 400; + // margin-bottom: 25px; + // color: $color-emperor; + // margin-top: 0; + // } + + h4, h1, h3 { + @include font-h1-settings; + display: flex; + font-size: 28px; + font-weight: 400; + margin-bottom: 25px; + color: $color-emperor; + margin-top: 0; + + img { + margin-right: 10px; + } + } + + p, div { + @include font-paragraph; + } + + label { + @include font-paragraph; + margin: 0; + color: $color-silver-chalice; + } + + input[type="checkbox"] { + box-sizing: border-box; + padding: 0; + display: inline-block; + vertical-align: middle; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + cursor: pointer; + height: 24px; + width: 24px; + background: none center no-repeat; + background-size: cover; + margin: 8px; + flex: 0 0 auto; + } + + input[type="checkbox"] { + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20style%3D%22fill%3A%23cccccc%3B%22%3E%3Cpath%20d%3D%22M19%205v14H5V5h14m0-2H5c-1.1%200-2%20.9-2%202v14c0%201.1.9%202%202%202h14c1.1%200%202-.9%202-2V5c0-1.1-.9-2-2-2z%22%2F%3E%3C%2Fsvg%3E"); + } + + input[type="checkbox"]:checked { + border-color: $color-cyan; + background: rgba($color-cyan, 0.05); + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%20style%3D%22fill%3A%234DA9B3%3B%22%3E%3Cpath%20d%3D%22M19%203H5c-1.11%200-2%20.9-2%202v14c0%201.1.89%202%202%202h14c1.11%200%202-.9%202-2V5c0-1.1-.89-2-2-2zm-9%2014l-5-5%201.41-1.41L10%2014.17l7.59-7.59L19%208l-9%209z%22%2F%3E%3C%2Fsvg%3E"); + } + + input[type="text"] { + @include modal-text-input; + } + + textarea { + resize: none; + @extend input[type="text"]; + padding: 8px 16px; + + &.modal__input--large { + height: 180px; + } + } + + .modal__input-wrapper { + margin-bottom: 30px; + } + + .modal__input--error, .new-block__input--error { + border: 1px solid $color-red !important; + } + + .modal__input--error-msg { + color: $color-red; + } + } + + .modal-help-text { + margin-right: 22px; + } + + .modal-actions { + padding: 0 22px 0 0; + border-top: 1px solid #E0E0E0; + width: 100%; + height: 90px; + background-color: #FCFCFC; + display: flex; + align-items: center; + justify-content: flex-end; + } + + .helper-text { + display: flex; + height: 100%; + } + + .helper-text > svg { + margin-right: 10px; + } + + .helper-text > p { + margin: 0 0 2px 0; + } + + // removed this temporarily because it was overriding button hover styles. Leaving it short-term in case it turns out it was needed for something else + // .modal-action { + // &:hover { + // background-color: #F5F3F3; + // } + + // h3 { + // text-transform: uppercase; + // margin-bottom: 0; + // } + // } +} + +.display-block { + display: block; +} + +.display-none { + display: none; +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_navigation-dropdown.scss b/app/assets/stylesheets/components/_navigation-dropdown.scss new file mode 100644 index 0000000..a0ddcee --- /dev/null +++ b/app/assets/stylesheets/components/_navigation-dropdown.scss @@ -0,0 +1,103 @@ +.navigation-dropdown { + & { + position: relative; + display: flex; + flex-flow: row; + align-items: center; + height: inherit; + user-select: none; + cursor: pointer; + } + + .cohort-label { + max-width: 320px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &:hover { + background-color: $color-black-97-3; + box-shadow: 0 2px 4px 0 rgba(0,0,0,0.1); + } + + &:hover > .list { + display: block; + } + + &:hover > .label > .sprite-icon { + color: $color-black; + } + + > .label { + display: flex; + flex-flow: row nowrap; + flex: 1 0 auto; + align-items: center; + padding: 0 1rem; + } + + > .label > .user-avatar { + margin-right: 0.5rem; + } + + > .label > .sprite-icon { + color: $color-black-83-1; + margin: 0 0 0 auto; + } + + > .list { + display: none; + position: fixed; + left: 0; + right: 0; + top: 3.5rem; + z-index: 100; + min-width: 100%; + margin: 0; + padding: 0; + background-color: $color-black-97-3; + box-shadow: 0 2px 4px 0 rgba(0,0,0,0.1); + list-style: none; + + @media screen and (min-width: $tablet-breakpoint) { + position: absolute; + top: 100%; + left: auto; + } + } + + > .list > .item { + color: inherit; + border: 0 solid rgba(0,0,0,0.1); + + &:not(:first-child) { + border-top-width: 1px; + } + + &:hover { + background-color: $color-white; + color: $color-black; + } + } + + > .list > .item > a { + color: $color-black-60; + display: block; + line-height: 2.5rem; + padding: 0 1rem; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; + max-width: 400px; + overflow: hidden; + text-overflow: ellipsis; + } + + .account-email { + padding: 0 1rem; + font-size: 14px; + position: relative; + top: -8px; + } +} diff --git a/app/assets/stylesheets/components/_notifications.scss b/app/assets/stylesheets/components/_notifications.scss new file mode 100644 index 0000000..0497645 --- /dev/null +++ b/app/assets/stylesheets/components/_notifications.scss @@ -0,0 +1,202 @@ +.notifications { + & { + position: relative; + font-size: 12px; + width: 40px; + user-select: none; + } + + > .notificationsicon { + position: relative; + display: flex; + align-items: center; + justify-content: center; + height: 40px; + width: 40px; + cursor: pointer; + } + + > .notificationsicon > svg { + fill: $color-black-80; + } + + &.open > .notificationsicon svg { + fill: $black; + } + + > .notificationsicon > .notificationscount { + top: 8px; + right: 22px; + color: $white; + background-color: $color-valencia; + height: 14px; + min-width: 14px; + font-size: 12px; + font-weight: 500; + position: absolute; + border-radius: 14px; + padding: 0 6px; + display: flex; + align-items: center; + justify-content: center; + } + + > .notificationspanel { + position: fixed; + z-index: 500; + left: 0.5rem; + right: 0.5rem; + top: 3.5rem; + display: flex; + flex-flow: column; + max-height: 448px; + width: auto; + margin: auto; + border: 1px solid $color-black-97-3; + background-color: $white; + box-shadow: 0 2px 16px 0 rgba(0,0,0,0.25); + + @media screen and (min-width: $tablet-breakpoint) { + position: absolute; + top: 100%; + left: -140px; + width: 320px; + } + + &:after { + content: ""; + display: none; + position: absolute; + left: 0; + right: 0; + bottom: 100%; + width: 0; + height: 0; + margin: auto; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid $white; + background-color: transparent; + @media screen and (min-width: $tablet-breakpoint) { + display: inherit; + } + } + } + + > .notificationspanel > .headerrow { + background-color: $white; + border-bottom: 1px solid $color-black-90-2; + color: $color-black-50-2; + display: flex; + flex-flow: row nowrap; + font-size: 12px; + font-weight: bold; + align-items: center; + height: 32px; + padding: 0 16px; + flex: 0 0 auto; + text-transform: uppercase; + } + + > .notificationspanel > .bodyrow { + background-color: $color-black-96-1; + display: flex; + flex-flow: column; + flex: 0 1 auto; + overflow-y: auto; + } + + > .notificationspanel > .bodyrow > .notificationitem { + background-color: $white; + border-bottom: 1px solid $color-black-90-2; + text-decoration: none; + padding: 8px 16px 16px 32px; + position: relative; + + &:hover { + > .notificationtitle { + text-decoration: underline; + } + } + + &.unread { + background-color: $color-cyan-opacity-4; + + > .notificationtitle { + font-weight: bold; + } + + &:after { + content: ""; + height: 8px; + width: 8px; + background-color: $color-cyan; + border-radius: 100%; + position: absolute; + top: 12px; + left: 12px; + } + } + } + + > .notificationspanel > .bodyrow > .notificationitem > .notificationheader { + height: 16px; + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + } + + > .notificationspanel > .bodyrow > .notificationitem > .notificationheader > .notificationtagline { + font-size: 12px; + font-weight: bold; + color: $black; + line-height: 16px; + flex: 0 1 auto; + } + + > .notificationspanel > .bodyrow > .notificationitem > .notificationheader > .notificationtimestamp { + font-size: 12px; + font-weight: 500; + color: $color-black-50-2; + line-height: 16px; + text-align: right; + flex: 0 0 auto; + } + + > .notificationspanel > .bodyrow > .notificationitem > .notificationtitle { + font-size: 14px; + color: $color-cyan; + line-height: 16px; + margin-top: 8px; + } + + > .notificationspanel > .bodyrow > .notificationitem > .notificationdescription { + font-size: 14px; + color: $color-black-20; + line-height: 16px; + margin-top: 8px; + } + + > .notificationspanel > .footerrow { + background-color: $white; + border-top: 1px solid $color-black-90-2; + color: $color-cyan; + display: flex; + flex-flow: row nowrap; + font-size: 12px; + font-weight: bold; + align-items: center; + height: 40px; + padding: 0 16px; + flex: 0 0 auto; + text-transform: uppercase; + } + + > .notificationspanel > .footerrow > .markread { + cursor: pointer; + } + + > .notificationspanel > .notificationitem { + margin: 10px 0; + } +} diff --git a/app/assets/stylesheets/components/_pagination.scss b/app/assets/stylesheets/components/_pagination.scss new file mode 100644 index 0000000..4e8ea67 --- /dev/null +++ b/app/assets/stylesheets/components/_pagination.scss @@ -0,0 +1,46 @@ +.react-paginate-container { + margin: 0px 0px 30px 0px; + display: block; + float: left; + list-style: none; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + padding: 0; +} + +.react-paginate-page { + background-color: blue; + display: block; + float: left; + width: 40px; + padding: 5px; + cursor: pointer; + border: 1px solid grey; + background-color: white; + border-radius: 1px; + text-align: center; + padding: 4px; + + &:hover { + background-color: #ffd4a8; + text-decoration: none; + } +} + +.react-paginate-link { + display: block; + width: 100%; + height: 100%; + text-decoration: none !important; + color: black; +} + +.react-paginate-break { + display: none; +} + +.react-paginate-active { + background-color: #ffd4a8; +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_pill.scss b/app/assets/stylesheets/components/_pill.scss new file mode 100644 index 0000000..e2e88e4 --- /dev/null +++ b/app/assets/stylesheets/components/_pill.scss @@ -0,0 +1,78 @@ +.pill-wrapper { + border-radius: 6px; + display: inline-block; + text-transform: uppercase; + color: #333; + font-weight: 300; + + p { + margin-top: 0px !important; + margin-bottom: 0px !important; + font-family: $font-family-proxima-nova; + } + + &.small { + padding: 2px 10px 2px 10px; + height: 18px; + + .p-text { + font-size: 10px; + line-height: 17px; + + } + + .a-text { + font-size: 10px; + line-height: 17px; + } + } + + &.medium { + padding: 2px 10px 2px 10px; + height: 24px; + + .p-text { + font-size: 12px; + line-height: 20px; + } + + .a-text { + font-size: 12px; + line-height: 20px; + } + } + + &.large { + padding: 5px 20px 5px 20px; + height: 30px; + + .p-text { + font-size: 14px; + line-height: 23px; + } + + .a-text { + font-size: 14px; + line-height: 23px; + } + } + + .p-text { + margin: 0; + text-transform: inherit; + color: inherit; + letter-spacing: 0.1rem; + margin-right: -0.1rem !important; + font-weight: inherit; + } + + .a-text { + margin: 0; + text-transform: inherit; + color: inherit; + letter-spacing: 0.1rem; + margin-right: -0.1rem !important; + font-weight: inherit; + vertical-align: top; + } +} diff --git a/app/assets/stylesheets/components/_point_grade_buttons.scss b/app/assets/stylesheets/components/_point_grade_buttons.scss new file mode 100644 index 0000000..499c4f8 --- /dev/null +++ b/app/assets/stylesheets/components/_point_grade_buttons.scss @@ -0,0 +1,19 @@ +.points-container { + display: flex; + border-radius: 25px; + border: 2px solid $color-black-93-3; + background-color: $color-white; + height: 45px; + justify-content: center; + align-items: center; + padding: 0 10px 0 10px; + + .point { + color: $color-black-60-8; + text-align: center; + width: 25px; + font-size: 1.2rem; + font-weight: 500; + cursor: pointer; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_primary-header.scss b/app/assets/stylesheets/components/_primary-header.scss new file mode 100644 index 0000000..7ec922d --- /dev/null +++ b/app/assets/stylesheets/components/_primary-header.scss @@ -0,0 +1,100 @@ +.primary-header { + & { + display: flex; + align-items: center; + justify-content: space-between; + padding: 2rem 1.5rem; + } + + &.-blue { + background: $color-darkest-blue-gray; + } + + &.-blue > div > .label { + color: $color-black-60-8; + font-weight: 300; + } + + &.-blue > div > .title { + color: $color-white; + font-size: 1.5rem; + line-height: 1.5rem; + } + + &.-white { + border-bottom: 0.125rem solid $color-black-83-1; + padding: 1.5rem 0; + } + + &.-white > div > .label { + color: $color-black-60; + font-weight: bold; + } + + > .header-left { + max-width: 50%; + } + + > .header-left > .label, + > .header-left > .title { + height: auto; + overflow: hidden; + text-overflow: ellipsis; + } + + > .header-left > .title > span > a { + padding-left: 7px; + color: white; + text-decoration: none; + } + + > .header-left > .title > span > a.disabled { + color: $color-black-50-2; + } + + > .header-left > .title > span > a:hover { + color: $color-black-50-2; + } + + > .header-left > .title > span > a > div { + display: inline; + } + + .header-left > .label > a { + color: $color-black-60-8; + text-decoration: underline; + } + + .header-left > .label > a:hover { + color: $color-black-50-2; + text-decoration: none; + } + + > div > .label { + font-size: 0.75rem; + margin-bottom: 0.5rem; + text-transform: uppercase; + line-height: 1rem; + } + + > div > .title { + line-height: 1.5rem; + font-size: 1.5rem; + } + + > div > .title > .lp-badge { + vertical-align: middle; + } + + > .header-right .lp-badge { + margin-bottom: 0.5rem; + } + + > .header-right a { + text-decoration: none; + } + + > .header-right > .title { + text-align: center; + } +} diff --git a/app/assets/stylesheets/components/_primary-navigation.scss b/app/assets/stylesheets/components/_primary-navigation.scss new file mode 100644 index 0000000..42fc382 --- /dev/null +++ b/app/assets/stylesheets/components/_primary-navigation.scss @@ -0,0 +1,132 @@ +.primary-navigation { + & { + position: relative; + z-index: $z-primary-navigation; + display: flex; + flex-flow: row nowrap; + align-items: center; + height: 3.5rem; + width: 100%; + border-bottom: 1px solid #f2f2f2; + background-color: $color-white; + @media screen and (min-width: $tablet-breakpoint) { + padding-right: 1rem; + } + } + + &.navshadow { + box-shadow: 0 1px 1px 0 rgba(0,0,0, 0.2); + border-bottom: 0; + } + &.checkpoint-navshadow { + box-shadow: 0 2px 20px 0 rgba(0,0,0, 0.07); + border-bottom: 1px solid $color-black-96-1; + } + + > .logo { + background: none left center no-repeat; + background-image: image-url("svg/mobile-logo.svg"); + background-size: contain; + height: 34px; + margin: 0 1rem; + min-width: 40px; + @media screen and (min-width: $tablet-breakpoint) { + background-image: image-url("svg/g-learn-lockup.svg"); + margin: 0 1.5rem; + min-width: 236px; + } + } + + > a .custom-logo { + height: 3rem; + padding: 0.25rem 0.5rem; + } + + > .item { + flex: 0 0 auto; + height: inherit; + margin: 0; + + &:first-of-type { + margin-left: auto; + } + } + + &.checkpoint-navbar { + position: fixed; + justify-content: inherit; + + .checkpoint-navbar-details { + display: flex; + justify-content: right; + white-space: nowrap; + + .checkpoint-timer { + @include font-base; + display: flex; + align-items: center; + margin-right: 4rem; + font-weight: 500; + color: $color-mine-shaft; + min-width: 92px; + + .final-countdown { + } + + svg { + margin-right: 10px; + + &.under-minute { + color: $color-valencia; + } + } + } + .final-countdown > b { + @include font-base; + font-weight: 500; + color: $color-mine-shaft; + display: inline-block; + } + + .final-countdown > .subtext { + margin-right: 10px; + } + + div.its-the.final-countdown > .subtext, div.its-the.final-countdown > b { + color: $color-valencia; + } + + .subtext { + @include font-small; + color: $color-gray; + margin-left: 8px; + } + + .checkpoint-challenges-attempted { + @include font-base; + font-weight: 500; + color: $color-mine-shaft; + display: flex; + align-items: baseline; + margin: auto 4rem auto 0; + white-space: nowrap; + } + + .checkpoint-pair-menu { + margin: auto 0 auto 10px; + display: flex; + position: relative; + + svg { + color: $color-gray; + } + + .pair-menu { + position: absolute; + z-index: 10; + bottom: 0; + } + } + } + } +} diff --git a/app/assets/stylesheets/components/_progress.scss b/app/assets/stylesheets/components/_progress.scss new file mode 100644 index 0000000..7fbb166 --- /dev/null +++ b/app/assets/stylesheets/components/_progress.scss @@ -0,0 +1,59 @@ +// Named this way to avoid conflict with bootstrap's .progress-bar styling +.progressbar { + --progressbar-height: 4px; + width: 100%; + height: var(--progressbar-height); + position: relative; + + &.-theme-light { + background: $color-black-92-2; + } + &.-theme-dark { + background: #4B5157; + } + + .progress-percentage { + height: var(--progressbar-height); + position: absolute; + z-index: 15; + background: $color-cyan; + } + + .progress-pending { + height: var(--progressbar-height); + position: absolute; + z-index: 10; + background: $color-black-50-2; + } +} + +.actioncircle { + --actioncircle-size: 32px; + border-radius: 100%; + color: $color-white; + display: flex; + align-items: center; + justify-content: center; + height: var(--actioncircle-size); + width: var(--actioncircle-size); + font-size: 14px; + line-height: 15px; + font-weight: 200; + flex-shrink: 0; + + svg { + height: var(--actioncircle-size); + width: var(--actioncircle-size); + } + + &.-score-3 { + background-color: $color-lime-green; + } + &.-score-2 { + background-color: $color-banana-yellow; + } + &.-score-1 { + background-color: $color-tangerine-orange; + } +} + diff --git a/app/assets/stylesheets/components/_progress_thresholds_key.scss b/app/assets/stylesheets/components/_progress_thresholds_key.scss new file mode 100644 index 0000000..770e1c3 --- /dev/null +++ b/app/assets/stylesheets/components/_progress_thresholds_key.scss @@ -0,0 +1,35 @@ +.thresholds-key-wrapper { + display: flex; + align-items: center; + + .thresholds-key-square { + width: 15px; + height: 15px; + margin-right: 8px; + border-radius: 2px; + } + + .thresholds-key-content { + margin-top: 1px; + margin-right: 30px; + margin-bottom: 0; + } + + .unscored-key-wrapper { + display: flex; + align-items: center; + + .unscored-key { + height: 10px; + width: 10px; + border-radius: 50%; + background-color: #D23C44; + } + + p { + margin-top: 0; + margin-left: 8px; + margin-right: 30px; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_progress_thresholds_modal.scss b/app/assets/stylesheets/components/_progress_thresholds_modal.scss new file mode 100644 index 0000000..27c7687 --- /dev/null +++ b/app/assets/stylesheets/components/_progress_thresholds_modal.scss @@ -0,0 +1,20 @@ +.progress-thresholds-modal { + .title { + color: #999999; + text-align: center; + margin-bottom: 50px; + } + + .slider-wrapper { + width: 500px; + margin: 0 auto; + } + + .description { + color: #999999; + width: 80%; + margin: 0 auto; + text-align: center; + margin-top: 63px; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_progress_thresholds_slider.scss b/app/assets/stylesheets/components/_progress_thresholds_slider.scss new file mode 100644 index 0000000..ef1cdc9 --- /dev/null +++ b/app/assets/stylesheets/components/_progress_thresholds_slider.scss @@ -0,0 +1,8 @@ +.ranger-rick { + width: 100%; + position: relative; +} + +.rc-slider-tooltip { + z-index: 1000001; +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_search-bar.scss b/app/assets/stylesheets/components/_search-bar.scss new file mode 100644 index 0000000..bcd4176 --- /dev/null +++ b/app/assets/stylesheets/components/_search-bar.scss @@ -0,0 +1,44 @@ +.search-bar__wrapper { + display: flex; + width: 100%; + + .search-bar { + display: flex; + padding-left: 5px; + align-items: center; + height: 40px; + transition: flex-grow 0.1s, background-color 0.1s; + + &.search-bar--expanded { + @include input; + border-radius: 40px; + padding-left: 4px; + padding-right: 3px; + background-color: $color-white; + border: 1px solid $color-mercury; + margin: 0; + margin-right: 8px; + flex-grow: 1; + max-width: 400px; + } + + .search-bar__icon { + height: 32px; + padding: 4px 8px; + border-radius: 24px; + &:hover { + background-color: #E8E8E8; + } + } + + input { + border: none; + flex-grow: inherit; + margin-left: 4px; + + &:focus { /* WARNING: accessibility depends on focus outline */ + outline: none; + } + } + } +} diff --git a/app/assets/stylesheets/components/_secondary-navigation.scss b/app/assets/stylesheets/components/_secondary-navigation.scss new file mode 100644 index 0000000..983231b --- /dev/null +++ b/app/assets/stylesheets/components/_secondary-navigation.scss @@ -0,0 +1,58 @@ +.secondary-navigation { + & { + position: relative; + z-index: 9999; + display: flex; + flex-flow: row nowrap; + height: 3rem; + width: 100%; + background-color: $color-white; + box-shadow: 0 1px 1px 0 rgba(0,0,0, 0.2); + overflow-x: scroll; + @media screen and (min-width: $tablet-breakpoint) { + overflow-x: auto; + } + } + + > .item { + flex: 1; + margin: 0; + height: inherit; + border: 0 solid transparent; + border-width: 0 0 2px 0; + color: $color-black-60; + @media screen and (min-width: $tablet-breakpoint) { + flex: 0 0 auto; + } + } + + > .item.-is-active { + color: black; + border-color: $color-cyan; + } + + > .item:hover { + color: black; + background-color: $color-black-97-3; + border-color: $color-black-83-1; + } + + > .item:active { + border-color: $color-dark-cyan; + } + + > .item > a { + position: relative; + display: flex; + flex-flow: row; + align-items: center; + justify-content: center; + height: inherit; + padding: 0 1rem; + text-transform: uppercase; + color: inherit; + text-decoration: none; + user-select: none; + cursor: pointer; + } +} diff --git a/app/assets/stylesheets/components/_slideshow.scss b/app/assets/stylesheets/components/_slideshow.scss new file mode 100644 index 0000000..52e1bcd --- /dev/null +++ b/app/assets/stylesheets/components/_slideshow.scss @@ -0,0 +1,264 @@ +.slideshow-container { + position: absolute; + height: 100%; + width: 100%; + box-shadow: 0 1px 10px 0 rgba(0, 0, 0, 0.2); +} + +.slides { + position: relative; + height: 100%; + padding: 0px; + margin: 0px; + list-style-type: none; + overflow: hidden; +} + +.slide { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + opacity: 0; + z-index: 1; + padding: 0; + margin: 0; + background-repeat: no-repeat; + background-size: cover; + background-position: center; +} + +.showing-none { + opacity: 1; + z-index: 2; +} + +.showing-fade { + opacity: 1; + z-index: 2; + animation-duration: 1s; + animation-name: slide-fade; +} + +@keyframes slide-fade { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.showing-right { + opacity: 1; + animation-duration: 1s; + animation-name: slide-right; +} + +@keyframes slide-right { + from { + left: -50%; + } + + to { + left: 0; + } +} + +.showing-left { + opacity: 1; + animation-duration: 1s; + animation-name: slide-left; +} + +@keyframes slide-left { + from { + left: 100%; + } + + to { + left: 0; + } +} + +.showing-top { + opacity: 1; + animation-duration: 1s; + animation-name: slide-top; +} + +@keyframes slide-top { + from { + top: 100%; + } + + to { + top: 0; + } +} + +.showing-bottom { + opacity: 1; + animation-duration: 1s; + animation-name: slide-bottom; +} + +@keyframes slide-bottom { + from { + top: -100%; + } + + to { + top: 0; + } +} + +.showing-bounce-left { + opacity: 1; + animation-duration: 1s; + animation-name: slide-bounce-left; +} + +@keyframes slide-bounce-left { + 0% { + left: 100%; + } + 50% { + left: -20%; + } + 100% { + left: 0; + } +} + +.showing-bounce-right { + opacity: 1; + animation-duration: 1s; + animation-name: slide-bounce-right; +} + +@keyframes slide-bounce-right { + 0% { + left: -100%; + } + 50% { + left: 20%; + } + 100% { + left: 0; + } +} + +.show-index.is-text { + color: black; + position: absolute; + bottom: 0; + z-index: 100; + left: 50%; + font-size: 28px; + font-family: 'Slabo 27px', serif; +} + +.show-index.is-dot { + color: black; + position: absolute; + bottom: 0; + z-index: 100; + left: 50%; + font-size: 28px; + font-family: 'Slabo 27px', serif; + display: flex; + transform: translate(-50%); +} + +.show-index.is-dot .dot { + width: 10px; + height: 10px; + margin: 0 3px 3rem 3px; + background-color: black; + border-radius: 50%; + opacity: 0.5; +} + +.show-index.is-dot .dot.is-active { + opacity: 1; +} + +.slideshow-container button:focus { + outline: none; +} + +.btn-arrow { + display: block; + font-size: 5px; + line-height: 0; + -moz-transition: all 0.3s; + -o-transition: all 0.3s; + -webkit-transition: all 0.3s; + transition: all 0.3s; + position: absolute; + top: 50%; + z-index: 10; + cursor: pointer; + height: 50px; + width: 50px; + border-radius: 50%; + padding: 5px; + z-index: 10000; +} + +.btn-arrow:hover, +.btn-arrow:focus { + background: $color-black-92-2; +} + +.btn-arrow:hover::before, +.btn-arrow:hover::after, +.btn-arrow:focus::before, +.btn-arrow:focus::after { + background: rgba(256, 256, 256, 0.5); +} + +.btn-arrow::before { + content: ''; + display: block; + background: rgba(256, 256, 256, 0.6); + position: absolute; + top: 0; + left: 0; + width: 6em; + height: 2em; +} + +.btn-arrow::after { + content: ''; + display: block; + background: rgba(256, 256, 256, 0.6); + position: absolute; + top: 0; + left: 0; + width: 2em; + height: 6em; +} + +.btn-arrow.btn-arrow-left { + left: -75px; +} + +.btn-arrow.btn-arrow-left::before, +.btn-arrow.btn-arrow-left::after { + top: 18px; + left: 18px; +} + +.btn-arrow.btn-arrow-right { + right: -75px; +} + +.btn-arrow.btn-arrow-right::before, +.btn-arrow.btn-arrow-right::after { + top: 18px; + left: 18px; +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_sort-dropdown-component.scss b/app/assets/stylesheets/components/_sort-dropdown-component.scss new file mode 100644 index 0000000..1912fbd --- /dev/null +++ b/app/assets/stylesheets/components/_sort-dropdown-component.scss @@ -0,0 +1,52 @@ +.sort-dropdown-component { + & { + position: relative; + display: flex; + flex-flow: row nowrap; + align-items: center; + margin-bottom: 32px; + user-select: none; + } + + > .label { + flex: 1 0 auto; + color: $color-black-60-8; + font-size: 18px; + line-height: 24px; + margin-right: 8px; + } + + > .svg { + flex: 0 0 auto; + fill: $color-black-83-1; + position: absolute; + left: auto; + right: 0; + top: 0; + bottom: 0; + margin: auto; + pointer-events: none; + } + + > select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + flex: 0 0 auto; + border: none; + background: none; + margin: 0; + padding: 0; + width: auto; + color: $color-darkest-blue-gray; + font-size: 18px; + font-weight: bold; + line-height: 24px; + cursor: pointer; + padding-right: (24 + 4 + px); + + &:hover + .svg { + fill: $color-cyan; + } + } +} diff --git a/app/assets/stylesheets/components/_standard-card.scss b/app/assets/stylesheets/components/_standard-card.scss new file mode 100644 index 0000000..08364f4 --- /dev/null +++ b/app/assets/stylesheets/components/_standard-card.scss @@ -0,0 +1,234 @@ +.standardcard { + background-color: $color-white; + margin-bottom: 1.5rem; + border-radius: 5px; + border: 1px solid $color-black-90-2; + display: flex; + flex-flow: row wrap; + align-items: flex-start; + justify-content: space-between; + padding: 20px; + + &.Percentage { + .standard-scoring-block { + border-bottom: 0px solid transparent; + height: auto; + padding: 0; + flex-flow: column; + flex: 0; + } + + .student-details { + width: 50%; + flex: 1; + font-size: 18px; + + span { + min-width: 250px; + } + } + + .info { + color: $color-black-60; + font-size: 14px; + margin-top: 10px; + } + + button { + margin: 25px auto 0; + width: 100%; + } + } + + > .standard-current-score { + background-color: $color-black-92-2; + width: 100%; + padding: 0.3125rem 1.5rem; + font-size: 0.75rem; + font-weight: bold; + letter-spacing: 0.0625rem; + text-transform: uppercase; + } + + > .standard-current-score > .label { + color: $color-black; + opacity: 0.5; + } + + > .standard-description { + padding-top: 1rem; + padding-bottom: 1rem; + width: 100%; + border-top: 1px solid #e6e6e6; + margin-left: 20px; + margin-right: 20px; + } + + > .standard-description > h4 { + margin: 0 0 1.25rem; + font-size: 1.25rem; + font-weight: normal; + line-height: 1.75rem; + color: $color-black; + } + + > .standard-description > .success-criteria { + color: $color-black; + font-size: 1rem; + line-height: 1.5rem; + } + + > .standard-description > .success-criteria > ul { + padding: 0 1rem 0 1.25rem; + margin: 1rem 0 0; + } + + > .standard-description > .success-criteria > ul > li { + padding-left: 0.5rem; + } + + > .standard-description > .toggle-success-criteria { + cursor: pointer; + position: relative; + } + + > .standard-description > .toggle-success-criteria > .label.gray-label { + color: $color-black-60-8; + text-transform: uppercase; + font-size: 0.75rem; + font-weight: bold; + letter-spacing: 0.0625rem; + } + + > .standard-description > .toggle-success-criteria > .svg { + position: absolute; + left: auto; + margin: auto; + pointer-events: none; + right: 0; + color: $color-black-60-8; + top: 0; + bottom: 0; + font-size: 0.875rem; + } + + > .standard-description > .null-success-criteria { + font-style: italic; + color: $color-black-60-8; + } + + > .standard-scoring-block { + display: flex; + height: 4.5rem; + align-items: center; + justify-content: space-between; + padding: 0.3125rem 0; + margin-left: 20px; + margin-right: 20px; + } + + > .standard-scoring-block > .label { + width: 50%; + color: $color-black; + } + + > .standard-scoring-block > .standard-scoring-buttons { + display: flex; + } + + > .standard-scoring-block > .standard-scoring-buttons > .standard-scoring-button { + height: 2.5rem; + width: 2.5rem; + background-color: $color-black-90-2; + color: $color-white; + cursor: pointer; + line-height: 2.5rem; + text-align: center; + margin-left: 0.5rem; + border-radius: 100%; + user-select: none; + font-weight: bolder; + font-size: 1rem; + + &.score-1:hover, + &.score-1.-is-selected { + background-color: $color-valencia; + cursor: default; + } + + &.score-2:hover, + &.score-2.-is-selected { + background-color: $color-banana-yellow; + cursor: default; + } + + &.score-3:hover, + &.score-3.-is-selected { + background-color: $color-lime-green; + cursor: default; + } + + &.-is-suggested:hover, + &.-is-suggested { + border: 0.25rem solid $color-darkest-blue-gray; + background-color: $color-white; + color: $color-darkest-blue-gray; + line-height: 2rem; + } + } + + > .standard-scoring-block > .standard-scoring-buttons > .standard-scoring-button > .suggested-score-tooltip { + background: $color-darkest-blue-gray !important; + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + margin-top: 0.25rem; + + &:after { + display: inline-block !important; + border-left-color: $color-darkest-blue-gray; + height: 0.75rem; + margin-top: -0.375rem; + } + } + + > .standard-scoring-block.-percentage-mode > .percentage-score { + display: flex; + align-items: center; + // padding-right: 20px; + flex-flow: column nowrap; + width: 100px; + + > .overall { + color: #333; + font-size: 24px; + display: flex; + flex-flow: column; + align-items: center; + + .scoring { + padding-top: 5px; + + p { + margin: 0; + padding: 0; + text-align: center; + line-height: 23px; + + &:first-of-type { + font-size: 30px; + font-weight: 400; + } + &:last-of-type { + font-size: 14px; + color: rgb(54,54,54) + } + + } + } + } + .p-text { + color: #DD3D48; + font-weight: 400; + } + } +} diff --git a/app/assets/stylesheets/components/_standard-cards.scss b/app/assets/stylesheets/components/_standard-cards.scss new file mode 100644 index 0000000..5ca5681 --- /dev/null +++ b/app/assets/stylesheets/components/_standard-cards.scss @@ -0,0 +1,159 @@ +.standards-cards { + .standardcard { + & { + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + background-color: $color-white; + margin: 24px; + } + + > .standard-current-score { + background-color: $color-black-92-2; + width: 100%; + padding: 5px 24px; + font-size: 12px; + font-weight: bold; + letter-spacing: 1px; + text-transform: uppercase; + + > .label { + color: $color-black; + opacity: 0.5; + } + } + + > .standard-description { + padding: 16px 24px 24px; + + > h4 { + margin: 0 0 20px; + font-size: 24px; + font-weight: normal; + line-height: 28px; + color: $color-black; + } + + > .success-criteria { + color: $color-black; + font-size: 16px; + line-height: 24px; + + > ul { + padding: 0px 16px 0px 20px; + margin: 16px 0px 0px; + + > li { + padding-left: 8px; + } + } + } + + > .toggle-success-criteria { + cursor: pointer; + position: relative; + } + + > .toggle-success-criteria > .label.gray-label { + color: $color-black-60-8; + text-transform: uppercase; + font-size: 12px; + font-weight: bold; + letter-spacing: 1px; + } + + > .toggle-success-criteria > .svg { + position: absolute; + left: auto; + margin: auto; + pointer-events: none; + right: 0; + color: $color-black-60-8; + top: 0px; + bottom: 0px; + font-size: 14px; + } + + > .null-success-criteria { + font-style: italic; + color: $color-black-60-8; + } + } + + > .standard-scoring-block { + display: flex; + height: 72px; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid $color-black-90-2; + padding: 5px 24px; + + > .label { + width: 50%; + color: $color-black; + } + + > .standard-scoring-buttons { + display: flex; + + > .standard-scoring-button { + height: 40px; + width: 40px; + background-color: $color-black-90-2; + color: $color-white; + cursor: pointer; + line-height: 40px; + text-align: center; + margin-left: 8px; + border-radius: 100%; + user-select: none; + font-weight: bolder; + font-size: 16px; + + &.score-1:hover, + &.score-1.-is-selected { + background-color: $color-valencia; + } + + &.score-2:hover, + &.score-2.-is-selected { + background-color: $color-navy-blue; + } + + &.score-3:hover, + &.score-3.-is-selected { + background-color: $color-lime-green; + } + + &.-is-suggested:hover, + &.-is-suggested { + border: 4px solid $color-darkest-blue-gray; + background-color: $color-white; + color: $color-darkest-blue-gray; + line-height: 32px; + } + } + + > .standard-scoring-button > .suggested-score-tooltip { + background: $color-darkest-blue-gray !important; + padding-top: 8px !important; + padding-bottom: 8px !important; + margin-top: 4px; + + &:after { + display: inline-block !important; + border-left-color: $color-darkest-blue-gray; + height: 12px; + margin-top: -6px; + } + } + } + } + } + + .nullstandardcard { + & { + font-size: 16px; + font-style: italic; + padding: 24px; + } + } +} diff --git a/app/assets/stylesheets/components/_standards-mastery-beans.scss b/app/assets/stylesheets/components/_standards-mastery-beans.scss new file mode 100644 index 0000000..5452867 --- /dev/null +++ b/app/assets/stylesheets/components/_standards-mastery-beans.scss @@ -0,0 +1,35 @@ +.standardbeans { + display: flex; + flex-flow: row nowrap; + align-items: center; + margin-right: 16px; +} + +.standardbean { + width: 8px; + height: 8px; + margin-right: 6px; + border-radius: 8px; + flex: 0 0 auto; + + + &.-unscored { + background-color: $color-black-84-7; + } + + &.-is-completed { + background-color: $color-cyan; + } + + &.-score-3 { + background-color: $color-lime-green; + } + + &.-score-2 { + background-color: $color-banana-yellow; + } + + &.-score-1 { + background-color: $color-tangerine-orange; + } +} diff --git a/app/assets/stylesheets/components/_status_picker.scss b/app/assets/stylesheets/components/_status_picker.scss new file mode 100644 index 0000000..c26f154 --- /dev/null +++ b/app/assets/stylesheets/components/_status_picker.scss @@ -0,0 +1,132 @@ +.status-container { + display: flex; + border-radius: 25px; + border: 2px solid $color-black-93-3; + background-color: $color-white; + height: 45px; + justify-content: space-between; + align-items: center; + padding: 0; + width: 120px; + background: linear-gradient( + to right, + transparent 0%, + transparent calc(50% - 0.81px), + $color-black-93-3 calc(50% - 0.8px), + $color-black-93-3 calc(50% + 0.8px), + transparent calc(50% + 0.81px), + transparent 100% + ); + background-color: white; + cursor: pointer; + + .incorrect-status { + height: 100%; + width: 60px; + display: flex; + justify-content: center; + border-radius: 25px 0 0 25px; + + .svg-div { + color: $color-black-60-8; + text-align: center; + font-size: 1.2rem; + font-weight: 500; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + width: 35px; + } + + .incorrect-check { + color: $color-valencia; + width: 40px; + height: 40px; + } + + &:hover { + background-color: $color-cyan-very-light; + } + } + + .correct-status { + height: 100%; + width: 60px; + display: flex; + justify-content: center; + border-radius: 0 25px 25px 0; + + .svg-div { + color: $color-black-60-8; + text-align: center; + font-size: 1.2rem; + font-weight: 500; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + width: 35px; + } + + .correct-check { + color: $color-cyan; + width: 40px; + height: 40px; + } + + &:hover { + background-color: $color-cyan-very-light; + } + } + + &.vertical { + flex-direction: column; + justify-content: center; + height: auto; + position: absolute; + top: 43px; + right: -18px; + border-radius: 10px; + z-index: 1; + width: 80px; + padding: 0; + box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1); + background: white; + + .status { + width: 100%; + } + + .incorrect-status { + width: 100%; + border-bottom: 1px solid $color-black-93-3; + border-radius: 8px 8px 0 0; + + &:hover { + background-color: $color-cyan-very-light; + } + } + + .correct-status { + width: 100%; + border-radius: 0 0 8px 8px; + + &:hover { + background-color: $color-cyan-very-light; + } + } + + .correct-check { + margin: 0; + } + + .incorrect-check { + margin: 0; + } + + .svg-div { + width: 100%; + } + } +} diff --git a/app/assets/stylesheets/components/_student-mastery-table.scss b/app/assets/stylesheets/components/_student-mastery-table.scss new file mode 100644 index 0000000..c516592 --- /dev/null +++ b/app/assets/stylesheets/components/_student-mastery-table.scss @@ -0,0 +1,156 @@ +.student-performances-table { + & { + background: $color-black-97-3 + } + > .page-header > .container { + > .performance-breadcrumb { + margin: 32px; + padding: 0; + list-style: none; + color: $color-too-gray; + font-size: 16px; + > .item { + color: inherit; + display: inline-block; + } + > .item:not(:first-of-type):before { + content: ""; + display: inline-block; + margin: 0 3px; + height: 18px; + width: 24px; + background: none center no-repeat; + background-image: url("data:image/svg+xml;charset=utf-8, %3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%0A%20%20%3Cpath%20fill%3D%22grey%22%20d%3D%22M10%206L8.59%207.41%2013.17%2012l-4.58%204.59L10%2018l6-6z%22%2F%3E%0A%3C%2Fsvg%3E"); + background-size: contain; + transform: translateY(4px); + } + > .item > a { + font-weight: 300; + text-decoration: none; + outline: 0; + transition: all 300ms ease; + color: inherit; + opacity: 0.5; + } + > .item a:hover { + opacity: 1; + } + } + + > .performance-heading { + margin: 2rem 0 0; + font-size: 48px; + } + } + + .scores > .score > .score-label { + font-size: 12px; + color: $color-white; + position: absolute; + bottom: 1rem; + left: 0.25rem; + right: 0; + text-align: center; + } + + .scores > .score > .value { + font-size: 64px; + font-weight: normal; + color: white; + line-height: 144px; + } + + .scores > .score { + display: inline-block; + position: relative; + background: $color-blue-dark; + border: 1px solid $color-gray-light; + text-align: center; + width: 160px; + height: 160px; + flex: 1 1 auto; + } + + .scores { + position: relative; + padding: 2rem 0; + width: 100%; + display: flex; + margin: 0 auto; + text-align: right; + } + + .performances-container { + & { + padding: 16px; + background: $color-white; + } + + > .legend-div { + display: flex; + margin-bottom: 18px; + align-items: center; + } + + > .legend-div > .standard-legend { + border: 1px solid $color-gray-light; + bottom: 3rem; + border-radius: 0.25rem; + padding: 0.5rem 2rem; + margin-right: 0.5rem; + margin-top: 0.5rem; + } + + > .legend-div > .standard-legend.elective { + background-color: $color-pale-yellow; + } + + > .legend-div > a { + flex-grow: 1; + text-align: right; + } + + .standard-elective { + background-color: $color-pale-yellow; + } + } +} + +table.table { + tr.tr-summary, + tr.tr-standard { + th, + td { + background: $color-light-gray-blue; + } + } + + td { + &.score-selected { + box-shadow: 0 0 0 2px black inset; + } + + &.score-0 { + text-align: center; + vertical-align: middle; + } + + &.score-1 { + background: $color-soft-red; + text-align: center; + vertical-align: middle; + } + + &.score-2 { + background: $color-soft-orange; + text-align: center; + vertical-align: middle; + } + + &.score-3 { + background: $color-sage; + text-align: center; + vertical-align: middle; + } + } +} diff --git a/app/assets/stylesheets/components/_student-name-bar.scss b/app/assets/stylesheets/components/_student-name-bar.scss new file mode 100644 index 0000000..a857e6a --- /dev/null +++ b/app/assets/stylesheets/components/_student-name-bar.scss @@ -0,0 +1,92 @@ +.student-name-bar { + & { + height: 4rem; + background-color: $color-darkester-blue-gray; + position: relative; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + } + + &.-is-sticky { + position: fixed; + z-index: 9999; + top: 0; + left: 0; + } + + > .avatarname { + color: $color-white; + display: flex; + align-items: center; + font-size: 1.125rem; + } + + > .avatarname > div > .user-avatar, + > .avatarname > .user-avatar { + padding: 0; + margin-right: 10px; + height: 32px; + width: 32px; + } + + > .avatarname > .submit-with-avatars > .user-avatar { + background-color: $color-black-60; + background-size: contain; + border-radius: 100%; + position: relative; + color: $color-white; + margin-right: auto; + border: 2px solid #262A2E; + } + + > .avatarname > .submit-with-avatars.showing-one > .user-avatar { + margin-right: 10px; + } + + > .avatarname > .submit-with-avatars.others .user-avatar { + margin-right: 10px; + } + + > .backtochallengeslink { + margin-left: 2rem; + color: $color-cyan; + font-size: 0.75rem; + cursor: pointer; + text-transform: uppercase; + text-decoration: none; + font-weight: 400; + } + + > .nextsubmissionlink { + margin-right: 2rem; + color: $color-cyan; + font-size: 0.75em; + cursor: pointer; + text-transform: uppercase; + text-decoration: none; + font-weight: 400; + } + + > .allgraded { + display: flex; + align-items: center; + margin-right: 17px; + &:hover { + text-decoration: none; + } + } + + > .avatarname > a { + color: $color-white; + } + + > .avatarname > a.-disabled { + opacity: 0.3; + } + + > .avatarname > a svg { + margin: 1.5625rem 1rem 1rem; + } +} diff --git a/app/assets/stylesheets/components/_submissions-dashboard-table.scss b/app/assets/stylesheets/components/_submissions-dashboard-table.scss new file mode 100644 index 0000000..4af63c3 --- /dev/null +++ b/app/assets/stylesheets/components/_submissions-dashboard-table.scss @@ -0,0 +1,506 @@ +$cell-height: 56px; + +%student-column { + & { + flex: 0 0 auto; + width: 40px; + } + + > .block-title-spacer { + background: $color-black-70-2; + height: 40px; + } + + > .student { + background: $color-white; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + height: $cell-height; + padding: 8px; + cursor: pointer; + + &:hover { + background: $color-black-97-3; + } + } + + > .standard > .standarditem { + position: relative; + width: 100%; + border-right: 1px solid $color-black-93-3; + + &:not(:last-of-type) { + border-bottom: 2px solid $color-black-83-1; + } + } + + > .standard > .standarditem > .itemheading { + position: relative; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + height: $cell-height; + background: white; + padding: 8px; + } + + > .standard > .standarditem > .itemheading > a { + text-decoration: none; + } + + > .standard > .standarditem > .itemheading > a > .actioncircle { + &{ + border-radius: 100%; + color: $color-white; + display: flex; + align-items: center; + justify-content: center; + height: 23px; + width: 23px; + font-size: 10px; + line-height: 15px; + font-weight: 200; + } + + &.-score-3 { + background-color: $color-lime-green; + } + + &.-score-2 { + background-color: $color-banana-yellow; + } + + &.-score-1 { + background-color: $color-tangerine-orange; + } + } + + > .standard > .standarditem > .itembody { + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + background: $color-black-97-3; + height: $cell-height; + border-top: 1px solid $color-black-93-3; + } + + > .standard > .standarditem > .itembody > a { + flex: 1 1 100%; + display: inherit; + align-items: inherit; + justify-content: inherit; + padding: 8px; + + &:hover { + background: $color-black-92-2; + } + } + + > .standard > .emptyrow { + height: 40px; + background: $color-black-92-2; + } + + > .standard > .standarditem > .itemheading > svg.donut, + > .standard > .standarditem > .itembody > svg.donut { + height: 100%; + width: 100%; + } +} + +%standard-item { + & { + } + + .standarditem.hidden .itemheading .title { + opacity: 0.4; + } + + > .standardtitle { + display: flex; + flex-flow: row nowrap; + align-items: center; + background: $color-black-92-2; + color: $color-dark-blue-gray; + font-size: 12px; + font-weight: bold; + letter-spacing: 2px; + height: 40px; + line-height: 40px; + text-transform: uppercase; + user-select: none; + cursor: default; + } + + > .standardtitle > .challenge-table-label { + flex: 0 0 auto; + height: 100%; + padding: 0 16px; + background: $color-black-83-1; + } + + > .standardtitle > .title { + flex: 1; + height: 100%; + padding: 0 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + > .standarditem { + position: relative; + background: white; + + &:not(:last-of-type) { + border-bottom: 2px solid $color-black-83-1; + } + + &.-is-expanded > .itemheading > .iconwrapper.arrow { + transform: rotate(0); + color: $color-cyan; + } + } + + > .standarditem > .itemheading { + color: $color-dark-blue-gray; + background: white; + position: relative; + display: flex; + flex-flow: column; + justify-content: center; + height: $cell-height; + + &:hover { + cursor: pointer; + color: $color-cyan; + } + } + + > .standarditem > .itemheading > .iconwrapper { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: auto; + margin: auto; + margin-left: 8px; + height: 24px; + width: 24px; + } + + > .standarditem > .itemheading > .iconwrapper { + &.arrow { + transform: rotate(-90deg); + color: inherit; + } + &.star { + color: $color-galvanize-orange + } + } + + > .standarditem > .itemheading > .challenge-table-label { + text-transform: uppercase; + color: $color-black-60-8; + font-size: 10px; + letter-spacing: 2px; + line-height: 16px; + margin-left: 40px; + } + + > .standarditem > .itemheading > .title { + //color: $dark-blue-gray; + color: inherit; + font-size: 16px; + line-height: 24px; + margin-left: 40px; + padding-right: 16px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + > .standarditem > .itemheading > .title > a { + text-decoration: none; + color: inherit; + } + + > .standarditem > .itembody { + position: relative; + height: $cell-height; + background: $color-black-97-3; + border-left: 4px solid $color-cyan; + padding-left: 32px; + } + + > .standarditem > .itembody > .short-border-box { + display: flex; + flex-flow: column; + justify-content: center; + border-top: 1px solid $color-black-93-3; + height: 100%; + } + + > .standarditem > .itembody > .short-border-box > .challenge-table-label { + color: $color-black-60-8; + font-size: 10px; + letter-spacing: 2px; + line-height: 16px; + text-transform: uppercase; + margin-left: 16px; + } + + > .standarditem > .itembody > .short-border-box > .title { + color: $color-dark-blue-gray; + font-size: 16px; + line-height: 24px; + margin-left: 16px; + margin-right: 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +} + +.submissions-dashboard-table-wrapper { + + .flex-row { + > .nameplate { + margin-bottom: 1rem; + } + + > .nameplate > .avatar-wrapper { + width: 40px; + display: inline-block; + line-height: 40px; + margin-right: 10px; + vertical-align: middle; + } + + > .nameplate > span { + display: inline-block; + line-height: 40px; + font-size: 1.1rem; + vertical-align: middle; + color: $color-darker-gray-blue; + } + } +} + +.challengeindex-table { + & { + position: relative; + display: flex; + flex-flow: row nowrap; + overflow: hidden; + background-color: $color-white; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + } + + &:after { + content: ""; + position: absolute; + left: 100%; + top: 0; + bottom: 0; + box-shadow: -4px 0 6px 1px rgba(black, 0.1); + background: white; + width: 10px; + } + + &.single-student { + &:after { + box-shadow: none; + } + } + + > .studentspanel { + position: relative; + z-index: 0; + flex: 1 1 100%; + overflow: hidden; + background: $color-black-83-1; + } + + > .studentspanel .studentcolumn { + @extend %student-column; + } + + > .curriculumpanel { + position: relative; + z-index: 1; + flex: 0 0 auto; + width: 320px; + padding-top: $cell-height; + box-shadow: 4px 0 6px 1px rgba(black, 0.1); + } + + > .curriculumpanel > .block-title { + background: $color-black-70-2; + height: 40px; + padding: 0 1rem; + color: $color-dark-blue-gray; + font-size: 12px; + font-weight: bold; + letter-spacing: 2px; + line-height: 40px; + text-transform: uppercase; + user-select: none; + cursor: default; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + > .curriculumpanel > .standard { + @extend %standard-item; + } + + > .curriculumpanel { + &.single-student { + padding-top: 0; + box-shadow: none; + + > .standard > .standarditem { + border-right: 2px solid $color-black-83-1; + } + } + } + + > .studentspanel { + &.single-student { + overflow-x: auto; + + .studentcolumn { + flex: 1 0 auto; + } + + > .studentcolumn > .standard > .standarditem > .itemheading { + flex-flow: row; + justify-content: flex-end; + } + + > .studentcolumn > .standard > .standarditem > .itembody { + flex-flow: row; + justify-content: flex-end; + } + + > .studentcolumn > .standard > .standarditem > .itemheading svg.donut { + width: 24px; + } + } + } +} + +.columnwrapper { + width: 100%; + overflow: auto; + display: flex; + flex-flow: row nowrap; +} + +.studentnamebar { + position: fixed; + top: 0; + z-index: 999; + width: calc(100% - 32px - 320px); + overflow: auto; + + &.-is-hidden { + visibility: hidden; + pointer-events: none; + } + + > .studentavatars { + display: flex; + flex-flow: row nowrap; + } + + > .studentavatars > .student { + background: $color-white; + flex: 0 0 auto; + display: flex; + flex-flow: column; + align-items: center; + justify-content: center; + height: $cell-height; + width: 40px; + padding: 8px; + border-bottom: 1px solid $color-black-83-1; + + &:hover { + background: $color-black-97-3; + } + } +} + +.dropdown-component > select { + background-color: $color-white; +} + +.icon-legend { + & { + height: 48px; + background: none; + border: 1px solid $color-black-83-1; + border-radius: 4px; + display: inline-flex; + flex-flow: row nowrap; + vertical-align: bottom; + align-items: center; + margin-bottom: 32px; + user-select: none; + overflow: hidden; + position: relative; + } + + > .item { + flex: 1 0 auto; + display: flex; + flex-flow: row nowrap; + align-items: center; + margin: 0 16px; + } + + > .item > .challenge-table-label { + color: $color-darker-gray-blue; + font-size: 16px; + line-height: 22px; + } + + > .item > .svg { + margin-right: 8px; + } +} + +.flex-row { + & { + position: relative; + display: flex; + flex-flow: row; + } + &.-align-middle { + align-items: center; + } + + > .-flex-alignright { + margin-right: 0; + margin-left: auto; + } + + > .-margin-right { + margin-right: 8px; + } +} + +// other flex-row styles +.flex-row > .pullright { + margin-right: 0; + margin-left: auto; +} diff --git a/app/assets/stylesheets/components/_svg-icon.scss b/app/assets/stylesheets/components/_svg-icon.scss new file mode 100644 index 0000000..203a037 --- /dev/null +++ b/app/assets/stylesheets/components/_svg-icon.scss @@ -0,0 +1,33 @@ +svg.icon { + fill: currentColor; + display: inline-block; + height: 24px; + width: 24px; +} + +svg, +.svg { + fill: currentColor; + width: 24px; + height: 24px; +} + +.svg-24px { + width: 24px; + height: 24px; +} + +.svg-21px { + width: 21px; + height: 21px; +} + +.svg-18px { + width: 18px; + height: 18px; +} + +.svg-15px { + width: 15px; + height: 15px; +} diff --git a/app/assets/stylesheets/components/_thresholds.scss b/app/assets/stylesheets/components/_thresholds.scss new file mode 100644 index 0000000..9ada910 --- /dev/null +++ b/app/assets/stylesheets/components/_thresholds.scss @@ -0,0 +1,33 @@ +.thresholds-key-wrapper-div { + display: flex; + padding-top: 10px; + + .settings-cog { + display: flex; + align-items: center; + cursor: pointer; + height: 25px; + width: 25px; + position: relative; + + svg { + fill: $color-cyan; + } + + &:hover { + svg { + fill: #2D909A; + } + } + } +} + +.lesson-layout .body-container .mdown-container .settings-cog { + top: 4px; +} + +.lesson-layout .body-container .mdown-container p.thresholds-key-content { + margin-top: 1px; + margin-right: 30px; + margin-bottom: 0; +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/_universal-list.scss b/app/assets/stylesheets/components/_universal-list.scss new file mode 100644 index 0000000..11d4568 --- /dev/null +++ b/app/assets/stylesheets/components/_universal-list.scss @@ -0,0 +1,46 @@ +.universal-list-wrapper { + + background-color: $color-alabaster; + + .one-column { + @include one-column; + } + + .list__header { + display: flex; + justify-content: flex-start; + margin-bottom: 20px; + align-items: center; + + h1 { + @include font-h1-settings; + } + + } + + .list__header-bar { + display: flex; + justify-content: flex-end; + align-items: center; + margin-bottom: 12px; + + button { + @include button-theme($color-primary-teal, 'material'); + } + } + + .list-container { + @include list-container; + + a:hover { + text-decoration: none; + } + } + + .back-btn { + height: 24px; + margin-right: 8px; + margin-bottom: 40px; + } + +} diff --git a/app/assets/stylesheets/components/_universal-row.scss b/app/assets/stylesheets/components/_universal-row.scss new file mode 100644 index 0000000..4074033 --- /dev/null +++ b/app/assets/stylesheets/components/_universal-row.scss @@ -0,0 +1,68 @@ +.universal-row { + display: flex; + justify-content: space-between; + padding: 30px 40px; + border-bottom: 2px solid $color-black-94-9; + + h2 { + @include font-h2-settings; + } + + a { + @include link-theme; + } + + p { + @include font-paragraph; + font-size: 15px; + line-height: 20px; + margin-bottom: 15px; + } + + img { + margin-right: 6px; + margin-bottom: 1px !important; + fill: #000; + } + + .gray-text { + color: $color-silver-chalice; + font-weight: 500; + } + + .row__footer { + display: flex; + flex-wrap: wrap; + } + + .pill-wrapper { + margin-right: 5px; + margin-top: 5px; + } + + .row__title-col :last-child { + margin-bottom: 0; + } + + .row__title-col { + flex-grow: 1; + + .row__cat-header { + @include font-caption; + margin-left: 3px; + margin-bottom: 15px; + } + } + + &.hidden { + display: none; + } +} + +.universal-row-click-wrapper { + cursor: pointer; + + &:hover { + box-shadow: $hover-box-shadow; + } +} diff --git a/app/assets/stylesheets/components/_universal-table.scss b/app/assets/stylesheets/components/_universal-table.scss new file mode 100644 index 0000000..d9998c7 --- /dev/null +++ b/app/assets/stylesheets/components/_universal-table.scss @@ -0,0 +1,121 @@ +.universal-table__wrapper { + + .universal-table__table { + font-size: 16px; + width: 100%; + table-layout: fixed; + + tbody { + color: $color-tundora; + border: 1px solid $color-mercury; + font-weight: 500; + background: white; + + &.--box-shadow { + box-shadow: 0 1px 10px 0 rgba(0,0,0,0.06); + } + + tr { + border-bottom: 1px solid $color-black-93-3; + height: 56px; + + &.--highlight:hover { + border-bottom: 1px solid #d0d0d0; + box-shadow: 0 0 10px 0 rgba(0,0,0,0.08); + } + + &.--disabled { + background: $color-alabaster; + color: $color-black-60; + + a, span, div { + color: $color-black-60; + } + } + + &.--selected td { + background: rgba(77, 169, 179, 0.1); + } + + &:last-of-type { + td { + border-bottom: none; + } + } + } + } + + th, td { + white-space: nowrap; + padding: 0 20px; + overflow: hidden; + text-overflow: ellipsis; + + &.checkbox-col { + width: 5%; + text-align: center; + } + + &:first-child { + padding-left: 30px; + } + + &:last-child { + padding-right: 30px; + } + } + + th { + height: 36px; + color: $color-dove-gray; + font-weight: 500; + } + + td { + height: inherit; + + a, span { + color: $color-tundora; + text-decoration: none; + height: 100%; + display: flex; + align-items: center; + display: inline-block; + height: fit-content; + vertical-align: middle; + } + + a:hover { + color: $color-link-blue; + } + + .user-avatar { + margin-right: 10px; + } + + div { + max-width: 170px; + overflow: hidden; + text-overflow: ellipsis; + display: inline-block; + vertical-align: middle; + } + } + + .--empty { + text-align: center; + } + + .universal-table__col { + overflow: visible !important; + } + } + + .universal-table__view-all-btn { + @include font-paragraph; + font-size: 15px; + color: $color-primary-teal; + cursor: pointer; + width: fit-content; + } +} diff --git a/app/assets/stylesheets/components/_user-avatar.scss b/app/assets/stylesheets/components/_user-avatar.scss new file mode 100644 index 0000000..c8d3f38 --- /dev/null +++ b/app/assets/stylesheets/components/_user-avatar.scss @@ -0,0 +1,65 @@ +.user-avatar { + & { + background-color: $color-black-60; + background-size: contain; + border-radius: 100%; + height: 24px; + position: relative; + min-width: 24px; + width: 24px; + color: $color-white; + } + + > svg { + height: 100%; + width: 100%; + position: absolute; + top: 0; + left: 0; + } +} + +.student-details { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-start; + + span { + margin-left: 10px; + } +} + +.submit-with-avatars .user-avatar { + border: 2px solid $color-primary-teal; + background: $color-white; + color: $color-primary-teal; + height: 28px; + width: 28px; + svg text { + font-weight: 500; + } +} + +.submit-with-avatars { + display: flex; + align-items: center; + + &.showing-many { + padding-left: 5px; + + .user-avatar:nth-of-type(1) { + margin-left: 5px; + z-index: 3; + } + .user-avatar:nth-of-type(2) { + z-index: 2; + left: -5px; + } + .user-avatar:nth-of-type(3) { + z-index: 1; + left: -10px; + } + } + +} diff --git a/app/assets/stylesheets/components/_video-player.scss b/app/assets/stylesheets/components/_video-player.scss new file mode 100644 index 0000000..5ffe300 --- /dev/null +++ b/app/assets/stylesheets/components/_video-player.scss @@ -0,0 +1,10 @@ +// just additional styles +.fluidvids-item { + box-shadow: 0 0 0 1px $color-black-96-1; +} + +// width overwrite +.p3sdk-container > .p3sdk-interactive-transcript { + min-width: 100%; + max-width: 100%; +} diff --git a/app/assets/stylesheets/components/badge.scss b/app/assets/stylesheets/components/badge.scss new file mode 100644 index 0000000..2a57f35 --- /dev/null +++ b/app/assets/stylesheets/components/badge.scss @@ -0,0 +1,26 @@ +.lp-badge { + height: 20px; + min-width: 80px; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 4px; + color: $color-white; + font-size: 10px; + font-weight: bold; + letter-spacing: 1px; + padding: 2px 8px; + text-transform: uppercase; + + &.-default { + background-color: $color-black-60-8; + } + + &.-info { + background-color: $color-cyan; + } + + &.-error { + background-color: $color-valencia; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/components/challenge-detail-comments.scss b/app/assets/stylesheets/components/challenge-detail-comments.scss new file mode 100644 index 0000000..8cc5af6 --- /dev/null +++ b/app/assets/stylesheets/components/challenge-detail-comments.scss @@ -0,0 +1,94 @@ +.challenge-detail-comments-wrapper { + & { + border-top: 1px solid $color-black-94-9; + padding: 24px; + } + + .label.gray-label { + padding-left: 0px; + } + + .challenge-detail-comments { + & { + padding-top: 15px; + margin-top: 8px; + } + + > div > .avatar-name-date { + display: flex; + flex-direction: row; + align-items: center; + } + + > div > .avatar-name-date > .avatar-wrapper { + height: 32px; + width: 32px; + margin-right: 8px; + } + + .comment-content { + background-color: $color-white; + border-radius: 10px; + margin: 10px 0px 16px 40px; + padding: 5px 10px; + } + + .comment-name { + font-weight: bold; + } + + .date { + font-size: 12px; + margin-left: auto; + color: $color-black-60-8; + } + + .item-wrapper { + position: relative; + } + + .item-wrapper:before, + .item-wrapper:after { + content: ""; + position: absolute; + z-index: -1; + top: 0; + left: 15px; + right: auto; + margin: auto 0; + width: 0; + height: calc(100% + 2rem); + border: 1px solid rgb(191, 191, 191); + } + + .item-wrapper:before { + bottom: 0; + } + + .item-wrapper:last-child:before { + top: -1rem; + bottom: auto; + height: calc(44px / 2 + 1rem); + } + + .item-wrapper:last-child:after { + display: block; + top: calc(44px / 2); + bottom: auto; + height: calc(100% + 1rem); + border-color: $color-black-97-3; + } + + .item-wrapper:first-child:before, + .item-wrapper:only-child:before { + top: auto; + bottom: -1rem; + height: 100%; + } + } + + .null-comments-block { + font-size: 16px; + font-style: italic; + } +} diff --git a/app/assets/stylesheets/components/challenge-detail-view.scss b/app/assets/stylesheets/components/challenge-detail-view.scss new file mode 100644 index 0000000..8ae98b3 --- /dev/null +++ b/app/assets/stylesheets/components/challenge-detail-view.scss @@ -0,0 +1,38 @@ +.user-challenge-show { + .challenge { + padding-left: 28px; + } +} + +.comments-standards { + background-color: $color-black-97-3; + z-index: 0; + + .standard-card { + z-index: 2; + } + + .label { + flex: 1 1 100%; + height: 12px; + font-size: 12px; + font-weight: bold; + letter-spacing: 1px; + line-height: 12px; + text-transform: uppercase; + padding-left: 0; + + &.gray-label { + color: $color-black-60-8; + } + + &.cyan-label { + color: $color-cyan; + } + } +} + +[class*="col-"].no-gutter { + padding-right: 0; + padding-left: 0; +} diff --git a/app/assets/stylesheets/components/challenge-timeline.scss b/app/assets/stylesheets/components/challenge-timeline.scss new file mode 100644 index 0000000..d7c3610 --- /dev/null +++ b/app/assets/stylesheets/components/challenge-timeline.scss @@ -0,0 +1,69 @@ +.challenge-timeline { + background-color: $color-dark-cyan; + color: $color-white; + display: flex; + justify-content: center; + align-items: center; + + > .timestamp { + padding: 19px; + background-color: rgba(0,0,0,0.3); + font-size: 12px; + white-space: nowrap; + } + + > ul { + display: flex; + list-style: none; + flex-grow: 1; + height: 100%; + align-items: center; + margin: 0; + padding: 16px 0; + margin-right: 8px; + overflow-x: auto; + } + > ul > div > li { + height: 4px; + width: 4px; + background-color: white; + border-radius: 50%; + } + > ul > div { + display: flex; + justify-content: center; + align-items: center; + border-radius: 50%; + min-height: 14px; + min-width: 14px; + margin: 0 8px; + cursor: pointer; + cursor: hand; + } + > ul > div.highlight { + background-color: rgba(255,255,255,0.3); + } + + > .buttons { + height: 100%; + display: flex; + align-items: center; + padding: 16px; + border-left: 1px solid $color-white; + } + > .buttons > div { + align-items: center; + display: flex; + cursor: pointer; + cursor: hand; + } + > .buttons > div:first-of-type { + margin-right: 12px; + } + > .buttons > div > .svg.primary-color { + color: $color-white; + } + > .buttons > div.disabled > .svg.primary-color { + color: $color-cyan; + } +} diff --git a/app/assets/stylesheets/components/new-comment-form.scss b/app/assets/stylesheets/components/new-comment-form.scss new file mode 100644 index 0000000..2c10aca --- /dev/null +++ b/app/assets/stylesheets/components/new-comment-form.scss @@ -0,0 +1,94 @@ +.new-comment-form { + & { + border-top: 1px solid $color-black-94-9; + padding: 24px; + } + + textarea { + resize: none; + width: 100%; + border: 2px solid $color-black-92-2; + border-radius: 4px; + background-color: $color-white; + outline: none; + padding: 8px; + } + + textarea:focus { + box-shadow: 0 0 3px 0 $color-cyan; + border-color: $color-cyan; + } + + .actions { + & { + display: flex; + } + + a { + flex: 1 1 auto; + color: $color-black-51; + + &:hover { + color: $color-cyan; + text-decoration: none; + } + + > svg { + margin-top: 6px; + } + + > p { + font-size: 10px; + margin-left: 32px; + margin-top: -24px; + } + } + + span { + height: 16px; + width: 187px; + color: $color-black-60-8; + font-size: 12px; + line-height: 16px; + } + + .comment-preview-link { + display: inline-block; + flex: 0 0 auto; + padding: 12px 16px; + border: none; + font-size: 12px; + font-weight: bold; + letter-spacing: 1px; + text-transform: uppercase; + text-decoration: none; + color: $color-cyan; + cursor: pointer; + } + + .lp-style-button { + min-width: 80px; + } + } + + .comment-preview { + background-color: $color-white; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + padding: 16px; + margin-bottom: 8px; + + p { + margin: 0; + } + } +} + +.comment-and-reject { + background-color: $color-white; + color: $color-black-20; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.3); + position: absolute; + min-width: 448px; + right: 6px; + z-index: 10; +} diff --git a/app/assets/stylesheets/components/partnerup.scss b/app/assets/stylesheets/components/partnerup.scss new file mode 100644 index 0000000..1bc5f7f --- /dev/null +++ b/app/assets/stylesheets/components/partnerup.scss @@ -0,0 +1,249 @@ +.partnerup { + .empty-pairs { + margin-top: 1rem; + } + + max-width: 1200px; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + margin: 0 auto; + + .actions { + width: 60%; + display: flex; + justify-content: space-around; + text-align: center; + border-bottom: 1px solid #ccc; + } + + .actions > .action-item{ + font-size: 18px; + line-height: 16px; + font-weight: 500; + padding: 16px 0; + width: 50%; + border-bottom: 4px solid transparent; + cursor: pointer; + } + + .actions > .action-item.active { + color: $color-cyan; + border-bottom: 4px solid $color-cyan; + } + + .past-pairings { + &.hide { display: none; } + + .pairing { + &:first-child { + margin-top: 30px; + } + + &:not(:first-child) { + margin-top: 70px; + } + + .pairing-header { + display: flex; + justify-content: space-between; + align-items: center; + width: 447px; + margin: 0 auto; + + .pairing-title { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + + .pairing-groups { + display: flex; + flex-flow: column wrap; + + .pairing-edit { + cursor: pointer; + color: $color-cyan; + font-size: 0.9rem; + font-weight: 300; + } + } + + .group { + display: flex; + flex-wrap: wrap; + flex-direction: row; + border-bottom: 1px solid $color-black-83-1; + } + } + + .toggle-history { + display: flex; + justify-content:center; + align-items: center; + margin: 1rem 0; + } + } + + .student { + width: 12rem; + height: 3.5rem; + padding-right: 0.5rem; + display: flex; + flex-grow: 1; + flex-flow: row nowrap; + align-items: center; + + .user-avatar { + margin: 0 0.5rem; + } + + .name { + color: $color-darker-gray-blue; + font-size: 18px; + line-height: 24px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .student-actions { + margin-right: 0; + margin-left: auto; + + .fa { + cursor: pointer; + } + } + } + + .pairingform { + margin-top: 30px; + width: 100%; + + &.hide { display: none; } + + h3 { + text-align: center; + } + + input, select { + border: 2px solid #D4D4D4; + border-radius: 5px; + background-color: #FFFFFF; + padding: 8px; + height: 32px; + } + + .form { + display: flex; + flex-flow: column; + width: 600px; + margin: 0 auto; + + .form-inputs { + display: flex; + justify-content: space-around; + flex-flow: row; + } + + .form-group { + margin: 0 1rem; + } + } + + .form-buttons { + display: flex; + justify-content: space-around; + align-items: center; + margin: 1rem 0; + + button { + width: 5rem; + margin: 0 1rem; + + &:disabled { + cursor: not-allowed; + } + } + } + } + + .copy-to-clipboard-container { + display: flex; + align-items: center; + padding-left: 17px; + + .transition-group { + padding-bottom: 20px; + padding-left: 10px; + font-weight: 600; + } + + .copy-text { + position: absolute; + color: $color-galvanize-orange; + } + + .opacity-fade-enter { + opacity: 0.01; + } + + .opacity-fade-enter.opacity-fade-enter-active { + opacity: 1; + transition: opacity 400ms ease-in; + } + + .opacity-fade-leave { + opacity: 1; + } + + .opacity-fade-leave.opacity-fade-leave-active { + opacity: 0.01; + transition: opacity 400ms ease-in; + } + } + + .drag-area { + padding: 0.5rem; + background: $color-black-88-2; + + .student-list-wrapper { + display: flex; + flex-direction: row; + flex-wrap: wrap; + + .inner-student-list-wrapper { + width: 280px; + margin-right: 1rem; + padding: 0.5rem; + + .student { + box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.2); + margin: 1rem 0; + background-color: #ffffff; + width: 100%; + + .fa-exclamation-triangle { + margin-left: 0.5rem; + color: $color-banana-yellow; + cursor: default; + } + } + } + } + } + + .lp-style-button.-outline.-small { + border: 1px solid transparent; + padding: 2px 10px; + color: $color-cyan; + + &:hover { + color: $color-white; + } + } +} + diff --git a/app/assets/stylesheets/components/progress-table.scss b/app/assets/stylesheets/components/progress-table.scss new file mode 100644 index 0000000..015d8e5 --- /dev/null +++ b/app/assets/stylesheets/components/progress-table.scss @@ -0,0 +1,355 @@ +.progress-table { + & { + position: relative; + margin: 10px 32px; + } + + .column-header { + max-width: 78px; + overflow: hidden; + text-overflow: ellipsis + } + + .bold-font { + font-weight: bold; + } + + .export-container { + display: flex; + flex-flow: row nowrap; + position: relative; + padding-top: 10px; + } + + > .export-container > .lp-style-button { + position: relative; + color: white; + } + + > .export-container > .lp-style-button > img { + width: 20px; + height: 20px; + margin-right: 5px; + } + + > .heading { + border-bottom: 2px solid #d4d4d4; + position: relative; + } + + + > .heading > .progress-row { + display: flex; + flex-flow: row nowrap; + overflow: hidden; + margin-top: 25px; + + .cell.threshold.percentage:not(:first-child) { + margin: 0 5px 0 5px; + } + } + + // Disable scroll bar when horizontally scrolling + .heading .progress-row::-webkit-scrollbar { height: 0 !important } + .heading .progress-row { + -ms-overflow-style: none; + overflow: -moz-scrollbars-none; + } + + .progress-body .progress-row:not(:last-child)::-webkit-scrollbar { height: 0 !important } + .progress-body .progress-row:not(:last-child) { + -ms-overflow-style: none; + overflow: -moz-scrollbars-none; + } + + > .heading > .progress-row > .cell { + flex: 0 0 auto; + display: flex; + flex-flow: row nowrap; + align-items: center; + padding: 0 16px; + } + + > .heading > .progress-row > .cell.avg { + justify-content: center; + } + + > .heading > .progress-row > .cell > .label { + color: #9b9b9b; + font-size: 12px; + letter-spacing: 2px; + height: 24px; + line-height: 24px; + text-transform: uppercase; + display: flex; + flex-flow: row nowrap; + align-items: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + user-select: none; + + &.-is-sortable { + display: flex !important; + justify-content: center !important; + align-items: center !important; + + cursor: pointer; + + &:hover { + color: #000; + } + } + } + + > .heading > .progress-row > .cell.threshold { + display: flex; + flex-flow: row nowrap; + } + + > .heading > .progress-row > .cell.threshold > .label { + flex: 1 1 auto; + width: calc(100% / 3); + display: flex; + flex-flow: column; + align-items: center; + justify-content: flex-end; + height: 60px; + white-space: normal; + line-height: 1rem; + } + + > .heading > .progress-row > .cell.percentage > .label { + flex-flow: row; + justify-content: center; + align-items: flex-end; + } + + > .heading > .progress-row > .cell.threshold > .label > .score-circle { + margin-bottom: 8px; + } + + > .progress-body { + background: #e7e7e7; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1); + } + + > .progress-body > .progress-row { + display: flex; + flex-flow: row nowrap; + overflow: hidden; + height: 60px; + background: white; + border-bottom: 1px solid #e7e7e7; + } + + > .progress-body > .progress-row > .cell { + flex: 0 0 auto; + display: flex; + flex-flow: row nowrap; + align-items: center; + padding: 0 16px; + } + + .progress-body .progress-row .cell:not(:first-child) { + margin: 0 5px 0 5px; + } + + > .progress-body > .progress-row > .cell.avg { + flex: 0 0 auto; + } + + > .progress-body > .progress-row > .cell.mastery-progress, .percentage-progress { + flex: 1 1 auto; + display: flex; + flex-flow: column; + align-items: flex-start; + justify-content: center; + font-weight: 500; + color: #898989; + font-size: 20px; + } + + > .progress-body > .progress-row > .cell.mastery-progress > .label { + height: 16px; + color: #9b9b9b; + font-size: 12px; + line-height: 16px; + margin-top: 8px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + > .progress-body > .progress-row > .cell.threshold { + display: flex; + flex-flow: row nowrap; + } + + > .progress-body > .progress-row > .cell.threshold > .percent-box { + flex: 1 1 100%; + } + + > .progress-body > .progress-row > .cell.student { + display: flex; + flex-flow: row nowrap; + align-items: center; + font-size: 18px; + } + + .heading .progress-row .cell.student:not(:first-child) { + margin: 0 5px 0 5px; + } + + > .progress-body > .progress-row > .cell.student > .user-avatar { + flex: 0 0 auto; + padding-top: 0; + height: 32px; + width: 32px; + } + + a { + color: rgba(0, 165, 181, 1); + margin-left: 16px; + + &:hover { + color: rgba(1, 123, 134, 1); + } + } + + .avg-box { + color: white; + background: $color-black-20; + display: flex; + align-items: center; + justify-content: center; + height: 40px; + width: 100%; + min-width: 40px; + padding: 8px 16px; + font-size: 20px; + font-weight: bolder; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + } + + .percent-box { + $one-color: #dc3549; + $two-color: #4b5259; + $three-color: #9cca68; + color: #898989; + border: 1px solid; + background: white; + display: flex; + align-items: center; + justify-content: center; + height: 45px; + min-width: 56px; + padding: 8px; + font-size: 20px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + border-radius: 5%/15%; + font-weight: 500; + + &:not(:last-of-type) { + margin-right: 8px; + } + + &.-is-one { + color: black; + border-color: rgba($one-color, 0.5); + background-color: white; + } + + &.-is-one.-is-filled { + color: white; + background-color: $one-color; + } + + &.-is-two { + color: black; + border-color: rgba($two-color, 0.5); + background-color: white; + } + + &.-is-two.-is-filled { + color: white; + background-color: $two-color; + } + + &.-is-three { + color: black; + border-color: rgba($three-color, 0.5); + background-color: white; + } + + &.-is-three.-is-filled { + color: white; + background-color: $three-color; + } + } + + .percentage-progressbar { + margin-left: 10px; + .progress-complete { + height: 10px; + background-color: #9A9A9A; + border-radius: 8px; + } + } + + .mastery-progressbar, .percentage-progressbar { + $one-color: #dc3549; + $two-color: #4b5259; + $three-color: #9cca68; + $bg-color: #efefef; + + & { + display: flex; + height: 10px; + width: 100%; + min-width: 100px; + border-radius: 8px; + overflow: hidden; + } + + > .one, + .two, + .three { + height: inherit; + border-right: 1px solid $bg-color; + } + + > .one { + background: $one-color; + } + + > .two { + background: $two-color; + } + + > .three { + background: $three-color; + } + } + + &.Percentage { + a { + color: $color-black-20; + } + + .percent-box { + min-width: 110px; + + &.percentage-mode { + border: 0px; + color: $color-black-30-2; + } + } + } +} diff --git a/app/assets/stylesheets/hopscotch-overrides.scss b/app/assets/stylesheets/hopscotch-overrides.scss new file mode 100644 index 0000000..d9bc5c3 --- /dev/null +++ b/app/assets/stylesheets/hopscotch-overrides.scss @@ -0,0 +1,100 @@ +.hopscotch-bubble-number { + content: ""; + background-image: url(/assets/svg/mobile-logo-d01e8af51b97d9f642d52a826ffe07193b8290e4d1767f6f2b2475bac759e9bc.svg)!important; + background-size: 130%!important; + height: 80px!important; + margin-top: -11px!important; +} + +.hopscotch-bubble { + border: 0px!important; + border-radius: 6px!important; + background-color: #333!important; + box-shadow: 2px 2px 8px rgba(0,0,0,0.4)!important; +} + +.hopscotch-bubble-arrow-border { + visibility: hidden!important; +} + +.hopscotch-bubble-arrow-container.left .hopscotch-bubble-arrow { + border-right: 17px solid #333!important; +} + +.hopscotch-bubble-arrow-container.right .hopscotch-bubble-arrow { + border-left: 17px solid #333!important; +} + +.hopscotch-bubble-arrow-container.up .hopscotch-bubble-arrow { + border-bottom: 17px solid #333!important; +} + +.hopscotch-bubble-arrow-container.down .hopscotch-bubble-arrow { + border-top: 17px solid #333!important; +} + +.hopscotch-bubble-container { + width: 360px!important; + padding: 40px!important; +} + +.hopscotch-title { + font-size: 28px!important; + color: #eee!important; + font-family: "proxima-nova"!important; + line-height: 1!important; + margin-bottom: 20px!important; + margin-left: 15px!important; + letter-spacing: -0.75px!important; +} + +.hopscotch-content { + font-size: 18px!important; + color: #ccc!important; + font-family: "proxima-nova"!important; + font-weight: 300!important; + line-height: 1.3!important; + margin-left: 15px!important; +} + +.hopscotch-actions { + margin-top: 35px!important; + margin-bottom: 10px!important; + text-align: center!important; +} + +.hopscotch-nav-button { + $color-em: 8; + $color-height: ($color-em * 5 + px); + + & { + display: inline-block !important; + height: $color-height !important; + width: auto !important; + min-width: 120px !important; + padding: 0 ($color-em * 3 + px) !important; + border: none !important; + border-radius: 100px !important; + background-color: $color-cyan !important; + background-image: none !important; + cursor: pointer !important; + line-height: $color-height !important; + color: white !important; + font-size: ($color-em * 1.5 + px) !important; + font-weight: bold !important; + letter-spacing: 1px !important; + text-align: center !important; + text-transform: uppercase !important; + vertical-align: middle !important; + } + + &:focus { + outline: 0 !important; + } + + &:hover { + color: white !important; + background-color: #2396a0 !important; + text-decoration: none !important; + } +} diff --git a/app/assets/stylesheets/mixins.scss b/app/assets/stylesheets/mixins.scss new file mode 100644 index 0000000..d06598c --- /dev/null +++ b/app/assets/stylesheets/mixins.scss @@ -0,0 +1,197 @@ +// Fonts +// ===== + +@mixin font-base () { + color: $color-tundora; + font-size: 19px; + font-weight: 300; + line-height: 1.7; + text-align: left; + font-style: normal; +} + +@mixin font-small () { + @include font-base; + font-size: 15px; +} + +@mixin font-h1 () { + font-size: 48px; + font-weight: 300; + letter-spacing: -1px; + line-height: 1.1; +} + +@mixin font-h3 () { + font-size: 32px; + line-height: $font-default-line-height; + text-align: left; + color: $color-emperor; + font-style: normal; + font-weight: 300; + font-family: $font-famiy-system; +} + +@mixin font-h1-settings () { + font-size: 36px; + line-height: $font-default-line-height; + margin-bottom: 40px; + text-align: left; + color: $color-emperor; + font-style: normal; + font-weight: 300; + font-family: $font-famiy-system; +} + +@mixin font-h2-settings () { + font-size: 21px; + font-weight: 500; + color: $color-tundora; + margin-bottom: 15px; + font-family: $font-famiy-system; +} + +@mixin font-paragraph () { + font-size: 16px; + line-height: $font-default-line-height; + text-align: left; + color: $color-tundora; + font-style: normal; + font-weight: 400; + font-family: $font-famiy-system; +} + +@mixin font-caption () { + font-size: 14px; + line-height: $font-default-line-height; + text-align: left; + color: $color-gray; + font-style: normal; + font-weight: 500; + font-family: $font-famiy-system; + letter-spacing: 1.3px; + text-transform: uppercase; +} + +// Components +// =========== + +@mixin input () { + height: $input-height; + border-radius: $round-corners-1; + padding-left: $input-spacing; + padding-right: $input-spacing; + margin: $input-spacing; + background-color: $color-iron; + border: 1px solid $color-athens-gray; + @include font-paragraph; +} + +@mixin input-disabled () { + color: $color-boulder; +} + +@mixin modal-text-input () { + display: block; + width: 100%; + border: 1px solid #E0E0E0; + margin: 3px 0; + padding: 0 16px; + height: 45px; + border-radius: $round-corners-1; + box-shadow: inset 0px 1px 2px $color-inner-shadow; +} + +@mixin button { + display: inline-block; + border-radius: $round-corners-1; + padding: 5px 17px; + font-size: 17px; + font-weight: 400; + margin: 8px; + cursor: pointer; + white-space: nowrap; +}; + +@mixin button-theme ($color, $type: primary) { + @if $type == primary { + background-color: $color; + border: none; + color: $color-white; + } @else if $type == outline { + background-color: unset; + color: $color; + border: 1px solid $color; + } @else if $type == material { + background-color: inherit; + color: $color-primary-teal; + margin: 0; + font-weight: 500; + + &:hover { + background-color: rgba($color-primary-teal, 0.15); + } + + &:active { + background-color: rgba($color-primary-teal, 0.30); + } + } @else { + @error "Unknown button type #{$type}. Must be 'primary' or 'outline' or 'material'." + } +}; + +@mixin link-theme { + color: $color-tundora; + + &:hover { + color: $color-link-blue; + text-decoration: none; + } +} + +@mixin h5-material { + font-size: 20px; + font-weight: 500; + color: $color-tundora; + margin-bottom: 15px; + font-family: $font-famiy-system; +} + +@mixin h6-material { + @include h5-material; + font-size: 14px; + font-weight: 400; + font-family: $font-famiy-system; +} + + + +// Layouts +// ======= + +@mixin one-column () { + max-width: 1084px; + margin: 70px auto 0 auto; + padding: 0 60px; +} + +@mixin container-large () { + background-color: $color-white; + margin: 20px 0; + padding: 5px 30px 40px 30px; + border-radius: $round-corners-1; + box-shadow: $box-shadow-offset-x $box-shadow-offset-y $box-shadow-blur-radius $box-shadow-spread $color-box-shadow; +} + +@mixin list-container { + @include container-large; + padding: 0; + margin: 0; +} + +// styles + +@mixin row-hover () { + border-bottom: 1px solid #d0d0d0; + box-shadow: 0 0 10px 0 rgba(0,0,0,0.08); +} diff --git a/app/assets/stylesheets/mobile.scss b/app/assets/stylesheets/mobile.scss new file mode 100644 index 0000000..5c336bc --- /dev/null +++ b/app/assets/stylesheets/mobile.scss @@ -0,0 +1 @@ +@import "mobile/*"; \ No newline at end of file diff --git a/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss b/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss new file mode 100644 index 0000000..ac9e98f --- /dev/null +++ b/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss @@ -0,0 +1,51 @@ +@media (max-width: 820px) { + .challengeindex-table > .curriculumpanel { + width: 83%; + } + + .challengeindex-table > .studentspanel > .studentcolumn > .standard > .emptyrow { + height: 80px; + background: linear-gradient(top, #d4d4d4 50%,#d4d4d4 50%,#ebebeb 50%) + } + + .challengeindex-table > .curriculumpanel > .standard > .standardtitle { + flex-flow: column nowrap; + height: 80px; + align-items: left; + } + + .challengeindex-table > .curriculumpanel > .standard > .standardtitle > .challenge-table-label, + .challengeindex-table > .curriculumpanel > .standard > .standardtitle > .title { + width: 100%; + height: auto; + padding: 0 2rem; + } + + .challengeindex-table > .studentspanel.single-student > .studentcolumn > .standard > .standarditem > .itemheading, + .challengeindex-table > .studentspanel.single-student > .studentcolumn > .standard > .standarditem > .itembody { + justify-content: center; + } + + .submissions-dashboard-table-wrapper .flex-row { + flex-flow: column !important; // override bootstrap class... + } + + .dropdown-component { + width: 100%; + margin-bottom: 1rem; + } + + .icon-legend { + flex-wrap: wrap; + width: 100%; + margin-bottom: 1rem; + align-items: left; + height: auto; + + .item { + width: 50%; + margin: 0; + padding: .5rem 1rem; + } + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/typography.scss b/app/assets/stylesheets/typography.scss new file mode 100644 index 0000000..5e69baa --- /dev/null +++ b/app/assets/stylesheets/typography.scss @@ -0,0 +1,5 @@ +body { + font-family: "proxima-nova", sans-serif; + font-size: 16px; + font-weight: 300; +} diff --git a/app/assets/stylesheets/variables.scss b/app/assets/stylesheets/variables.scss new file mode 100644 index 0000000..6cfcbbb --- /dev/null +++ b/app/assets/stylesheets/variables.scss @@ -0,0 +1,136 @@ +// Colors (most names from http://chir.ag/projects/name-that-color) +// ================================================================ + +$lighten: 10%; +$darken: 10%; +$more-dark: 20%; +$more-light: 20%; + + +// Brand colors +$color-primary-teal: #4DA9B3; // was #34A8B3 +$color-primary-teal-lighter: lighten( $color-primary-teal, $lighten ); +$color-primary-teal-darker: darken( $color-primary-teal, $darken ); +$color-galvanize-orange-2: #ea8832; +$color-link-blue: #007BFF; + +// Functional colors +$color-white: #ffffff; +$color-black: #000000; +$color-red: #EB574C; +$color-red-lighter: lighten( $color-red, $lighten ); +$color-red-darker: darken( $color-red, $darken ); + +// grays +$color-mine-shaft: #333333; +$color-tundora: #444444; +$color-emperor: #505050; +$color-dove-gray: #666666; +$color-boulder: #777777; +$color-gray: #888888; +$color-silver-chalice: #AAAAAA; +$color-mercury: #E3E3E3; +$color-iron: #E3E4E6; +$color-athens-gray: #F2F3F5; +$color-alabaster: #FAFAFA; +$color-lightened-black: rgba($color-black, 0.6); +$color-light-outline: rgba($color-black, 0.07); +$color-box-shadow: rgba($color-black, 0.04); +$color-inner-shadow: rgba($color-black, 0.1); +$hover-box-shadow: 0 0 10px 0 rgba(0,0,0,0.08); + +// pill colors +$color-pill-light-purple: #E3E3FC; + + +// Fonts +// ===== + +$font-famiy-system: -apple-system, BlinkMacSystemFont, "Segoe UI", + "Roboto", "Oxygen", "Ubuntu", "Helvetica Neue", Arial, sans-serif; +$font-default-line-height: 28px; +$font-family-proxima-nova: "proxima-nova", sans-serif; + +// Layouts +// ======= + +$box-shadow-offset-x: 0; +$box-shadow-offset-y: 4px; +$box-shadow-blur-radius: 20px; +$box-shadow-spread: 0; +$round-corners-1: 4px; +$input-height: 40px; +$input-spacing: 8px; + +// Styles for pages that have not been updated yet +// =============================================== + +$color-dark-gray: #666666; + + +$color-black-10-2: #1A1A1A; +$color-black-14-9: #262626; +$color-black-20: #333333; +$color-black-30-2: #606060; +$color-black-46-7: #777777; +$color-black-50-2: #808080; +$color-black-51: #828282; +$color-black-60-8: #9b9b9b; +$color-black-60-9: #9f9f9f; +$color-black-60: #999999; +$color-black-66-7: #aaaaaa; +$color-black-70-2: #b3b3b3; +$color-black-76-5: #c3c3c3; +$color-black-77: #c6c6c6; +$color-black-80: #cccccc; +$color-black-83-1: #d4d4d4; +$color-black-84-7: #d8d8d8; +$color-black-88-2: #e1e1e1; +$color-black-90-2: #e6e6e6; +$color-black-92-2: #ebebeb; +$color-black-93-3: #eeeeee; // this is gallery "color" from the link above +$color-black-94-2: #efefef; +$color-black-94-3: #e5e5e5; +$color-black-94-9: #f2f2f2; +$color-black-96-1: #f5f5f5; +$color-black-97-3: #f8f8f8; +$color-black-98-2: #fafafa; +$color-black-99-3: #f0f0f0; + +$color-galvanize-orange: #ff901e; +$color-valencia: #dc3549; +$color-med-green: #9cca68; + +$color-sky-blue: #4A90E2; +$color-cyan: #34A8B3; +$color-cyan-opacity-4: #F2FBFB; +$color-dark-cyan: #00737E; +$color-navy-blue: #0C5773; +$color-cyan-very-light: #d2e9ec; + +$color-medium-blue-gray: #3a4352; +$color-dark-blue-gray: #272e39; +$color-darker-gray-blue: #4b5259; +$color-darkest-blue-gray: #3B4248; +$color-darkester-blue-gray: #262A2E; + +// new forge colors +$color-lime-green: #7DCA60; +$color-banana-yellow: #FEC449; +$color-tangerine-orange: #FC7336; + +// old mastery table colors +$color-light-gray-blue: #c6d7f4; +$color-pale-yellow: #f4efd4; +$color-soft-orange: #fce8b2; +$color-soft-red: #f4c7c3; +$color-sage: #b7e1cd; +$color-blue-dark: #33485f; +$color-too-gray: #b6b8ba; +$color-gray-light: #e9eaec; + +// breakpoints +$tablet-breakpoint: 1024px; + +// z-index +$z-primary-navigation: 999999; diff --git a/app/component_props/activity_feed_item_component_props.rb b/app/component_props/activity_feed_item_component_props.rb new file mode 100644 index 0000000..596ab3b --- /dev/null +++ b/app/component_props/activity_feed_item_component_props.rb @@ -0,0 +1,120 @@ +class ActivityFeedItemComponentProps + def self.execute(activity) + { + id: activity.id, + creator: actor_from_activity(activity).full_name, + message: message_from_activity(activity), + created_at: activity.created_at, + label_class: label_class_from_activity(activity), + user_photo: actor_from_activity(activity).profile_image, + user_initials: actor_from_activity(activity).initials + } + end + + private_class_method def self.actor_from_activity(activity) + activity.creator.presence || activity.subject.user + end + + private_class_method def self.label_class_from_activity(activity) + case activity.name + when Activity::NAMES[:checkpoint_submission_evaluated] + performance = Performance.find_by(checkpoint_submission_id: activity.subject_id) + + case performance.score + when 1 + "-score-1" + when 2 + "-score-2" + when 3 + "-score-3" + end + when Activity::NAMES[:checkpoint_submission_created], + Activity::NAMES[:checkpoint_submission_resubmitted] + if activity.subject.submitted_challenge_answers.first.challenge.content_file.autoscore? + "-primary" + else + "-warning" + end + when Activity::NAMES[:comment_created] + "-primary" + when Activity::NAMES[:content_file_viewed] + "-default" + when Activity::NAMES[:submitted_challenge_answer_created], + Activity::NAMES[:submitted_challenge_answer_created_project], + Activity::NAMES[:submitted_challenge_answer_created_code_snippet], + Activity::NAMES[:submitted_challenge_answer_evaluated] + if activity.subject.correct? + "-success" + elsif activity.subject.incorrect? + "-danger" + elsif activity.subject.processing? + "-primary" + else + "-warning" + end + when Activity::NAMES[:video_started] + "-primary" + when Activity::NAMES[:video_ended] + "-success" + end + end + + private_class_method def self.message_from_activity(activity) + case activity.name + when Activity::NAMES[:checkpoint_submission_evaluated] + performance = Performance.find_by(checkpoint_submission_id: activity.subject_id) + content_file = activity.subject.submitted_challenge_answers.first.challenge.content_file + + "scored a #{performance.score} on #{content_file_label(content_file)}." + when Activity::NAMES[:checkpoint_submission_created], + Activity::NAMES[:checkpoint_submission_resubmitted] + + action = activity.name == Activity::NAMES[:checkpoint_submission_resubmitted] ? "resubmitted" : "submitted" + content_file = activity.subject.submitted_challenge_answers.first.challenge.content_file + + suffix = + if content_file.autoscore? + "awaiting automatic evaluation" + else + "awaiting manual scoring" + end + + "#{action} #{content_file_label(content_file)}, #{suffix}." + when Activity::NAMES[:comment_created] + "left a comment." + when Activity::NAMES[:content_file_viewed] + "viewed #{content_file_label(activity.subject)}." + when Activity::NAMES[:submitted_challenge_answer_created], + Activity::NAMES[:submitted_challenge_answer_created_project], + Activity::NAMES[:submitted_challenge_answer_created_code_snippet], + Activity::NAMES[:submitted_challenge_answer_evaluated] + + content_file = activity.subject.challenge.content_file + title = activity.subject.challenge.title + + "answered #{title} - #{content_file_label(content_file)}#{submitted_challenge_answer_suffix(activity.subject)}." + when Activity::NAMES[:video_started] + "started a video." + when Activity::NAMES[:video_ended] + "finished a video." + end + end + + private_class_method def self.content_file_label(content_file) + "#{content_file.standard.release.block.title} - #{content_file.standard.title} - #{content_file.title}" + end + + private_class_method def self.submitted_challenge_answer_suffix(submitted_challenge_answer) + if submitted_challenge_answer.correct? + " correctly" + elsif submitted_challenge_answer.incorrect? + " incorrectly" + elsif submitted_challenge_answer.ungraded? + ", awaiting manual scoring" + elsif submitted_challenge_answer.processing? + ", awaiting automatic evaluation" + else + " but ran into an error: #{submitted_challenge_answer.status}" + end + end +end diff --git a/app/component_props/notifications_component_props.rb b/app/component_props/notifications_component_props.rb new file mode 100644 index 0000000..21dd11c --- /dev/null +++ b/app/component_props/notifications_component_props.rb @@ -0,0 +1,22 @@ +class NotificationsComponentProps + def self.execute(user_id) + notifications = NotificationPolicy::Scope.new(User.find(user_id), Notification).resolve + unread_count = notifications.unread.count + + { + unreadCount: unread_count, + prettyUnreadCount: unread_count > 99 ? "99+" : unread_count, + notifications: notifications.order(read_at: :desc, created_at: :desc).map do |notification| + { + id: notification.id, + tagline: notification.tagline, + title: notification.title, + url: Rails.application.routes.url_helpers.notification_path(notification), + description: notification.description, + read_at: notification.read_at, + created_at: notification.created_at + } + end + } + end +end diff --git a/app/component_props/standard_card_component_props.rb b/app/component_props/standard_card_component_props.rb new file mode 100644 index 0000000..4ab4c68 --- /dev/null +++ b/app/component_props/standard_card_component_props.rb @@ -0,0 +1,9 @@ +class StandardCardComponentProps + attr_reader :standard, + :checkpoint_performance + + def initialize(standard:, latest_performance: nil, checkpoint_performance: nil) + @standard = StandardPresenter::ForCheckpointSubmission.new(standard: standard, latest_performance: latest_performance) + @checkpoint_performance = checkpoint_performance ? PerformancePresenter.new(checkpoint_performance) : nil + end +end diff --git a/app/controllers/api/application_controller.rb b/app/controllers/api/application_controller.rb new file mode 100644 index 0000000..128bcd8 --- /dev/null +++ b/app/controllers/api/application_controller.rb @@ -0,0 +1,114 @@ +class Api::ApplicationController < ActionController::API + include Pundit + include Api::ExceptionHandler + include Api::Response + + before_action :ensure_json_request + before_action :require_authorized_user + + around_action :monitor_api + + rescue_from ActionController::ParameterMissing do |exception| + raise exception if Rails.env.development? + + json_response( + { + "errors": { + "status": "400", + "title": "A parameter is missing to fulfill your request", + "code": "bad_request" + } + }, 400 + ) + end + + def require_authorized_user + unless current_user + json_response( + { + "errors": { + "status": "401", + "title": "You are not authenticated for this request", + "code": "unauthorized" + } + }, :unauthorized + ) + end + end + + def current_user + @current_user ||= if request.headers["Authorization"] + User.find_by(api_token: bearer_token) + else + User.find_by(uid: session[:user_uid]) + end + end + + def current_cohort + @current_cohort ||= if params[:cohort_id].present? + Cohort.find(params[:cohort_id]) + end + end + + def preview_block + @preview_block ||= Block.find_by(title: "preview") + end + + def bearer_token + pattern = /^Bearer / + header = request.headers["Authorization"] + header.gsub(pattern, "") if header&.match(pattern) + end + + def ensure_json_request + return if request.format == :json + + render body: nil, status: 406 + end + + def not_found_or_invalid_version + # does this path exist then 405 + # does this path not exist then 404 + match = request.path.match(/^\/api\/(v(?[1])?)/i) # look for a valid version + error = match && match[:version] ? :not_found : :gone + + json_response( + { + "errors": { + "status": error == :not_found ? 404 : 410, + "title": error == :not_found ? "The requested resource could not be found" : "The requested API version no longer exists", + "code": error == :not_found ? "not_found" : "gone" + } + }, + error + ) + end + + # monitor_api wraps all calls to our Api::ApplicationController and records + # data. Set @api_interaction_metadata anywhere in an api controller action + # to populate additional custom metadata for an api_interaction + def monitor_api + start = Time.now + + yield + + finish = Time.now + duration = duration_in_millseconds(start, finish) + + CreateApiInteractionJob.perform_later( + user_id: current_user&.id, + ip: request.remote_ip, + path: request.path, + method: request.method, + action_name: action_name, + duration: duration, + controller_name: controller_name, + response_code: response.code.to_i, + metadata: @api_interaction_metadata + ) + end + + def duration_in_millseconds(start, finish) + (finish - start) * 1000.0 + end +end diff --git a/app/controllers/api/v1/blocks/releases_controller.rb b/app/controllers/api/v1/blocks/releases_controller.rb new file mode 100644 index 0000000..d3d8171 --- /dev/null +++ b/app/controllers/api/v1/blocks/releases_controller.rb @@ -0,0 +1,28 @@ +class Api::V1::Blocks::ReleasesController < Api::ApplicationController + def create + render_method_not_allowed_response and return unless request.post? + authorize(Release) + + pending_release = Release.create(block_id: current_block.id, + notes: release_notes || "", + github_sha: "pending", + user_id: current_user.id, + state: Release::STATES[:pending]) + + CreateReleaseJob.perform_later(pending_release_id: pending_release.id) if pending_release.id + + json_response({ release_id: pending_release.id }, pending_release.id ? :ok : :bad_request) + end + + private + + def current_block + @current_block ||= Block.find(params[:block_id]) + end + + def release_notes + if params[:release] + params[:release][:notes] + end + end +end diff --git a/app/controllers/api/v1/blocks_controller.rb b/app/controllers/api/v1/blocks_controller.rb new file mode 100644 index 0000000..d35e507 --- /dev/null +++ b/app/controllers/api/v1/blocks_controller.rb @@ -0,0 +1,52 @@ +class Api::V1::BlocksController < Api::ApplicationController + def index + render_method_not_allowed_response and return unless request.get? + authorize(Block) + if params[:repo_name].present? + block = Block.includes(releases: :cohort_releases).where("repo_name ilike ?", params[:repo_name]) + if params[:org].present? && params[:origin].present? + block = block.where("org ilike ? AND origin ilike ?", params[:org], params[:origin]) + end + render json: { blocks: block.empty? ? [] : [block_details(block.first)] } + else + blocks = Block.includes(releases: :cohort_releases).order(title: :asc) + render json: { blocks: blocks.map {|b| block_details(b) } } + end + end + + def create + render_method_not_allowed_response and return unless request.post? + authorize(Block) + + block = Block.new(block_params) + + block.title = block.repo_name if block.title.blank? + + if block.save + json_response({ blocks: [block] }, :ok) + else + json_response( + { errors: { status: 400, title: "errors: #{block.errors.full_messages.join(', ')}" } }, + :bad_request + ) + end + end + + private + + def block_params + params.require(:block).permit(:title, :repo_name, :org, :origin) + end + + def block_details(block) + { + id: block.id, + origin: block.origin, + org: block.org, + repo_name: block.repo_name, + sync_errors: block.sync_errors, + title: block.title, + cohorts_using: block.releases.reduce([]) { |m, r| m + r.cohort_releases.map(&:cohort_id) }.compact + } + end +end diff --git a/app/controllers/api/v1/cohorts/blocks/content_files_controller.rb b/app/controllers/api/v1/cohorts/blocks/content_files_controller.rb new file mode 100644 index 0000000..33d9791 --- /dev/null +++ b/app/controllers/api/v1/cohorts/blocks/content_files_controller.rb @@ -0,0 +1,218 @@ +class Api::V1::Cohorts::Blocks::ContentFilesController < Api::ApplicationController + include Api::ContentVisibilityCrud + + def update + render_method_not_allowed_response and return unless request.patch? + + authorize(Cohort.find(params[:cohort_id]), :toggle_content_visibility?) + + result = handle_param_logic("ContentFile", visibility_type_params) + + json_response({ status: result.to_s }, result) + end + + def take_assessment + render_method_not_allowed_response and return unless request.post? + authorize(current_cohort, :take_assessment?) + + unless current_content_file.checkpoint? + json_response({ error: "only checkpoints can be assessed" }, 403) and return + end + + submission = CheckpointSubmissionFinder.latest_for_checkpoint_content_file_for_user_in_cohort( + cohort_id: current_cohort.id, + content_file: current_content_file, + user_id: current_user.id, + with_started: true + ) + + if submission.nil? || submission.state != CheckpointSubmission::STATES[:started] + # if the state was started, we don't need to check for limit reach + if user_reached_attempt_limit? + json_response(limit_reached_response, :ok) and return + end + + submission = CheckpointSubmission.create!( + cohort_id: current_cohort.id, + content_file_uid: current_content_file.uid, + content_file_block_id: params[:block_id], + user_id: current_user.id, + state: CheckpointSubmission::STATES[:started] + ) + if current_content_file.time_limit.present? + GradeTimedCheckpointJob.set(wait: current_content_file.time_limit.minutes).perform_later(current_content_file.id, submission.id) + end + end + + json_response({ + content_file_html: newer_current_content_file.html, + submission_id: submission&.id, + challenges_with_answers: challenges_with_answers, + created_at: submission&.created_at, + limit_reached: false, + time_limit: newer_current_content_file.time_limit + }, :ok) + end + + def student_scores + render_method_not_allowed_response and return unless request.get? + authorize(current_cohort, :student_scores?) + if current_cohort.students.count < 1000 + students = current_cohort.students + + checkpoint_submissions_for_students = CheckpointSubmissionFinder.lastest_by_state_for_students_in_cohort( + cohort_id: current_cohort.id, + block_id: params[:block_id], + content_file_uid: current_content_file.uid, + student_ids: students.map(&:id) + ).includes(submitted_challenge_answers: :challenge).group_by(&:user_id) + + resp = formatted_student_scores(students, checkpoint_submissions_for_students) + + json_response(resp, :ok) + else + json_response({ student_limit_reached: true }, :ok) + end + end + + private + + def formatted_student_scores(students, submissions) + students.map do |student| + { + id: student.id, + auth_edit_url: student.auth_edit_url, + email: student.email, + initials: student.initials, + first_name: student.first_name, + last_name: student.last_name, + profile_image: student.profile_image, + has_signed_in: student.signed_in?, + submissions: submissions[student.id].nil? ? {} : format_submissions(submissions[student.id]) + } + end + end + + def format_submissions(submissions) + started = submissions.find { |s| s.state == "started"} + needs_review = submissions.find { |s| s.state == "needs_review"} + done = submissions.find { |s| s.state == "done"} + retry_sub = submissions.find { |s| s.state == "retry"} + + ultimate_done = [done, retry_sub].compact.sort {|a, b| b.created_at <=> a.created_at}&.first + submissions = [ultimate_done, needs_review].compact.sort {|a, b| b.created_at <=> a.created_at} + + # Original implementation when 'started' was present in set + # May revisit this when add time feature is built + # [] => nil, nil + # [done] => done score, done state + # [needs_review] => needs review suggest?, needs review state + # [started, needs_review] => needs review suggest?, started state + # [needs_review, done] => done score, scored-pending state + # [done, needs_review] => done score, done state + # [started, done] => done score, started state + # [started, needs_review, done] => done score, started state + # + # Changed to: + # [] => nil, nil + # [done] => done score, done state + # [needs_review] => needs review suggest?, needs review state + # [done, needs_review] => done score, done state + # [needs_review, done] => done score, needs review and done state + + score = nil + if ultimate_done + score = ultimate_done.percent_score + elsif needs_review # no done or retry, see if score is suggested + # if any submitted challenge answer has nil points, we cannot suggest a score + all_points = needs_review.submitted_challenge_answers.map(&:points) + unless all_points.include?(nil) + suggested_points = all_points.reduce(:+) + total_points = needs_review.submitted_challenge_answers.map(&:challenge).map(&:points).reduce(:+) + score = suggested_points && total_points ? ((suggested_points.to_f / total_points.to_f) * 100).to_i : 0 + end + end + + { + needs_review_id: needs_review&.id, + done_id: ultimate_done&.id, + started_id: started&.id, + score: score, + state: submssion_state(started, submissions), + created_at: (submissions[0] || started)&.created_at + } + end + + def submssion_state(started, submissions) + if submissions.length == 2 && submissions[0].state == "needs_review" && submissions[1].state == "done" + "needs_review_and_done" + elsif submissions.empty? && started + "started" + else + submissions[0]&.state + end + end + + def user_reached_attempt_limit? + return false if current_content_file.max_checkpoint_submissions.nil? + + user_attempts = current_user.checkpoint_submissions_count( + current_cohort.id, + current_content_file.standard.release.block_id, + current_content_file.uid + ) + + if user_attempts >= current_content_file.max_checkpoint_submissions + return true + end + + false + end + + def limit_reached_response + { + content_file_html: current_content_file.html, + submission_id: nil, + challenges_with_answers: challenges_with_answers, + limit_reached: true + } + end + + def current_content_file + @current_content_file ||= ContentFile.find(params[:id]) + end + + def newer_current_content_file + @newer_current_content_file ||= ContentFileFinder.from_cohort_block_content_file_path(cohort_id: current_cohort.id, + block_id: params[:block_id], + content_file_path: current_content_file.path) + end + + def challenges_with_answers + challenges = Challenge.where(content_file: current_content_file).order(position: :desc) + all_submitted_challenge_answers = SubmittedChallengeAnswerFinder.latest_for_user_id_content_file_for_cohort( + current_user.id, + current_content_file, + current_cohort.id + ) + challenges.map do |challenge| + submitted_challenge_answers = all_submitted_challenge_answers.select do |sca| + sca.challenge.uid == challenge.uid && sca.answered? + end.flatten.compact + ChallengeWithSubmittedChallengeAnswersPresenter.new( + current_cohort, + challenge, + submitted_challenge_answers, + current_user.instructor_or_admin?(current_cohort.id), + user_context: current_user, + is_checkpoint: true, + can_view_status: false, + can_view_explanation: false + ) + end + end + + def visibility_type_params + params.permit(:visible, :optional) + end +end diff --git a/app/controllers/api/v1/cohorts/blocks/units_controller.rb b/app/controllers/api/v1/cohorts/blocks/units_controller.rb new file mode 100644 index 0000000..68c45a0 --- /dev/null +++ b/app/controllers/api/v1/cohorts/blocks/units_controller.rb @@ -0,0 +1,19 @@ +class Api::V1::Cohorts::Blocks::UnitsController < Api::ApplicationController + include Api::ContentVisibilityCrud + + def update + render_method_not_allowed_response and return unless request.patch? + + authorize(Cohort.find(params[:cohort_id]), :toggle_content_visibility?) + + result = handle_param_logic("Standard", visibility_type_params) + + json_response({ status: result.to_s }, result) + end + + private + + def visibility_type_params + params.permit(:visible, :optional) + end +end diff --git a/app/controllers/api/v1/cohorts/cohorts_controller.rb b/app/controllers/api/v1/cohorts/cohorts_controller.rb new file mode 100644 index 0000000..1d6a393 --- /dev/null +++ b/app/controllers/api/v1/cohorts/cohorts_controller.rb @@ -0,0 +1,118 @@ +class Api::V1::Cohorts::CohortsController < Api::ApplicationController + def create + render_method_not_allowed_response and return unless request.post? + authorize(current_user, :onboarder?) + + if param_errors.any? + json_response({ error: "Validation Error: #{param_errors.join(', ')}" }, 400) and return + end + + client = AuthApi::Client.new("product.write") + + resp = client.create_product(formatted_cohort) + AuthResolverService.resolve(resp) if resp&.uid + + json_response({ status: "ok", uid: resp&.uid }, :ok) and return + rescue AuthApi::RequestFailed => e + json_response({ error: JSON.parse(e.message) }, 400) and return + end + + def curriculum + render_method_not_allowed_response and return unless request.get? + + authorize(current_cohort, :api_documentation?) + + @cohort_releases_by_sections, @visibilities = current_cohort.current_curriculum + + json_response({ + cohort_title: current_cohort.name, + cohort_id: current_cohort.id, + course_yaml_url: ResyncJobResult.find_latest(current_cohort.id)&.data&.dig("course_config_url"), + sections: build_sections + }, :ok) + end + + private + + def formatted_cohort + { + product: { + uid: SecureRandom.hex[0...18], + name: params[:name], + product_type: params[:product_type], + label: params[:label], + campus_name: params[:campus_name], + opt_out_for_marketing: params[:opt_out_for_marketing].nil? ? false : params[:opt_out_for_marketing], + starts_on: params[:starts_on], + ends_on: params[:ends_on] + } + } + end + + def param_errors + validation_response = [] + + if params[:name].blank? + validation_response.push "name can't be blank" + end + if params[:starts_on].blank? + validation_response.push "starts_on can't be blank" + elsif /[0-9]{4}-[0-9]{2}-[0-9]{2}/.match(params[:starts_on]).nil? + validation_response.push "starts_on must be in format yyyy-mm-dd" + end + if params[:product_type].blank? + validation_response.push "product_type can't be blank" + end + if params[:opt_out_for_marketing] && !([true, false].include? params[:opt_out_for_marketing]) + validation_response.push "opt_out_for_marketing must be true or false" + end + validation_response + end + + def build_sections + @cohort_releases_by_sections.map do |section, _v| + { + title: section.title, + units: build_units(section) + } + end + end + + def build_units(section) + @cohort_releases_by_sections[section].map do |r| + StandardFinder.for_cohort_release(cohort_id: current_cohort.id, release_id: r.release_id) + end.flatten.map do |standard| + { + uid: standard.uid, + title: standard.title, + description: standard.description, + visible: @visibilities["Standard"][standard.uid].nil? ? true : !@visibilities["Standard"][standard.uid].hidden?, + block_id: standard.release.block_id, + api_endpoint: api_v1_cohort_block_units_url(current_cohort.id, standard.release.block_id, standard.uid), + content_files: standard.content_files.unscoped.where(standard_id: standard.id).in_default_order.map do |cf| + { + uid: cf.uid, + title: cf.title, + path: content_file_path(current_cohort.id, standard.release.block_id, cf.path), + visible: @visibilities["ContentFile"][cf.uid].nil? ? true : !@visibilities["ContentFile"][cf.uid].hidden?, + git_url: git_url(cf), + type: cf.content_file_type, + api_endpoint: api_v1_cohort_block_content_files_url(current_cohort.id, standard.release.block_id, cf.uid) + } + end + } + end + end + + def git_url(content_file) + release = content_file.standard.release + block = release.block + base_url = block.git_url + branch = release.branch_name + "#{base_url}/blob/#{branch}/#{content_file.path}" + end + + def current_cohort + @current_cohort ||= Cohort.find(params[:cohort_id]) + end +end diff --git a/app/controllers/api/v1/cohorts/users_controller.rb b/app/controllers/api/v1/cohorts/users_controller.rb new file mode 100644 index 0000000..83b9f19 --- /dev/null +++ b/app/controllers/api/v1/cohorts/users_controller.rb @@ -0,0 +1,163 @@ +class Api::V1::Cohorts::UsersController < Api::ApplicationController + def index + render_method_not_allowed_response and return unless request.get? + authorize(current_cohort, :api_documentation?) + + json_response(formatted_cohort_users, :ok) + end + + def create + render_method_not_allowed_response and return unless request.post? + authorize(current_cohort, :enroll?) + + if param_errors.any? + json_response({ error: "Validation Error: #{param_errors.join(', ')}" }, 400) and return + end + + if user_from_email + json_response({ status: "already-exists", uid: user_from_email.uid }, :ok) and return if user_enrolled_in_cohort? + uid = enroll_existing_user(user_from_email) + else + uid = invite_user + end + json_response({ status: "ok", uid: uid }, :ok) and return + rescue AuthApi::RequestFailed => e + json_response({ error: e.message }, 400) and return + end + + def update + render_method_not_allowed_response and return unless request.patch? + authorize(current_cohort, :enroll?) + + if target_user&.student_or_instructor_of?(current_cohort.id) + enroll_existing_user(target_user) + json_response({ status: "ok" }, :ok) + else + json_response({ status: "404" }, 404) + end + rescue AuthApi::RequestFailed => e + json_response({ error: e.message }, 400) and return + end + + def destroy + render_method_not_allowed_response and return unless request.delete? + authorize(current_cohort, :enroll?) + + if target_user&.student_or_instructor_of?(current_cohort.id) + unenroll_existing_user + json_response({ status: "ok" }, :ok) + else + json_response({ status: "404" }, 404) + end + rescue AuthApi::RequestFailed => e + json_response({ error: e.message }, 400) and return + end + + private + + def current_cohort + @current_cohort ||= Cohort.find(params[:cohort_id]) + end + + def target_user + @target_user ||= User.find(params[:id]) + end + + def user_from_email + @user_from_email ||= User.find_by(email: params[:email]) + end + + def user_enrolled_in_cohort? + user_from_email.student_or_instructor_of?(current_cohort.id) + end + + def formatted_cohort_users + User.select("users.id, users.uid, users.first_name, users.last_name, users.email, cohort_users.roles"). + joins(:cohort_users). + where("cohort_users.cohort_id = ?", current_cohort.id). + order("cohort_users.created_at DESC").map do |user| + { + id: user.id, + uid: user.uid, + first_name: user.first_name, + last_name: user.last_name, + email: user.email, + roles: user.roles + } + end + end + + def invite_user + client = AuthApi::Client.new("user.invite user.write user.product_roles") + options = { + user: { + first_name: params[:first_name], + last_name: params[:last_name], + email: params[:email], + registrations: [{ + product_uid: current_cohort.uid, + roles: enroll_roles + }] + } + } + + resp = client.invite_user(options) + AuthResolverService.resolve(resp) if resp&.uid + resp&.uid + end + + def enroll_roles + [ + params[:instructor] == true ? "instructor" : nil, + params[:onboard] == true ? "auth.product_admin" : nil + ].compact + end + + def enroll_existing_user(user) + client = AuthApi::Client.new("user.write user.product_roles") + options = { + id: user.uid, + user: { + registrations: [{ + product_uid: current_cohort.uid, + roles: enroll_roles + }] + } + } + + resp = client.update_user(options) + AuthResolverService.resolve(resp) if resp&.uid + resp&.uid + end + + def unenroll_existing_user + client = AuthApi::Client.new("user.write user.unregister") + options = { + id: target_user.uid, + user: { + registrations: [{ + product_uid: current_cohort.uid, + _destroy: true + }] + } + } + + resp = client.update_user(options) + AuthResolverService.resolve(resp) if resp&.uid + end + + def param_errors + validation_response = [] + + if params[:first_name].blank? + validation_response.push "first_name can't be blank" + end + if params[:last_name].blank? + validation_response.push "last_name can't be blank" + end + if params[:email].blank? + validation_response.push "email can't be blank" + end + validation_response + end +end diff --git a/app/controllers/api/v1/content_files_controller.rb b/app/controllers/api/v1/content_files_controller.rb new file mode 100644 index 0000000..7fe82d5 --- /dev/null +++ b/app/controllers/api/v1/content_files_controller.rb @@ -0,0 +1,34 @@ +class Api::V1::ContentFilesController < Api::ApplicationController + skip_around_action :monitor_api, only: [:show] + + def show + authorize(current_user, :content_file_api?) + + content_file = ContentFile.unscoped.find(params[:id]) + + render json: content_file + end + + def create + render_method_not_allowed_response and return unless request.post? + + authorize(current_user, :content_file_api?) + + outcome = PreviewContentFileService.new(sandbox_id: current_user.sandbox.id, + s3_key: create_params[:s3_key]).execute + + if outcome[:content_file] + render json: { preview_url: content_file_url(current_user.sandbox.id, + preview_block.id, + outcome[:content_file].path) }, status: :ok + elsif outcome[:errors] + render json: { errors: outcome[:errors].join(", ") }, status: :unprocessable_entity + end + end + + private + + def create_params + params.permit(:s3_key) + end +end diff --git a/app/controllers/api/v1/pineapple_controller.rb b/app/controllers/api/v1/pineapple_controller.rb new file mode 100644 index 0000000..4ae3969 --- /dev/null +++ b/app/controllers/api/v1/pineapple_controller.rb @@ -0,0 +1,6 @@ +class Api::V1::PineappleController < Api::ApplicationController + def pineapple + render_method_not_allowed_response and return unless request.get? + json_response(this_is_for_you: "\u{1f34d}") + end +end diff --git a/app/controllers/api/v1/releases_controller.rb b/app/controllers/api/v1/releases_controller.rb new file mode 100644 index 0000000..2e62d2b --- /dev/null +++ b/app/controllers/api/v1/releases_controller.rb @@ -0,0 +1,70 @@ +class Api::V1::ReleasesController < Api::ApplicationController + def create + authorize(current_user, :regenerate_api_token?) + + release = Release.new( + s3_key: release_params[:s3_key], + github_sha: "preview", + branch_name: "preview", + block_id: Block.find_or_create_by(title: "preview").id, + preview_cohort_id: current_user.sandbox.id, + state: Release::STATES[:pending] + ) + + if release.save + render json: { release_id: release.id }, status: :ok + else + render json: { errors: release.errors.full_messages.join(", ") }, status: :internal_server_error + end + end + + def release_polling + authorize(current_user, :release_polling?) + + release = Release.find_by(id: release_params[:release_id]) + + if release.nil? + render json: { errors: "Could not find the release requested", sync_warnings: "" }, status: :unprocessable_entity + elsif release.state == Release::STATES[:success] + preview_url = nil + + if release.preview_cohort_id.present? + cf_count = ContentFileFinder.all_from_cohort_block(cohort_id: current_user.sandbox.id, + block_id: Block.find_by(title: "preview").id) + + preview_url = if cf_count.size == 1 + release.update(sync_warnings: release.sync_warnings.reject { |warning| warning.include?("link") }.compact) + cf_no_link_warnings = cf_count.first.dup + cf_no_link_warnings.warnings = cf_no_link_warnings.warnings.reject { |warning| warning.include?("link") }.compact + cf_no_link_warnings.uid = cf_no_link_warnings.uid.reverse + + if cf_no_link_warnings.save + new_challenges = cf_count.first.challenges.map(&:dup) + new_challenges.each { |c| c.content_file_id = cf_no_link_warnings.id } + cf_count.first.challenges.map(&:delete) + new_challenges.map(&:save) + cf_count.first.delete + end + + content_file_url(current_user.sandbox.id, + preview_block.id, + cf_no_link_warnings.path) + else + Rails.application.routes.url_helpers.cohort_url(current_user.sandbox.id) + end + end + + render json: { status: release.state, preview_url: preview_url, sync_warnings: release.sync_warnings }, status: :ok + elsif release.state == Release::STATES[:pending] || release.state == Release::STATES[:processing] + render json: { status: release.state, sync_warnings: release.sync_warnings }, status: :ok + else + render json: { errors: release.sync_errors.join(", "), sync_warnings: release.sync_warnings }, status: :unprocessable_entity + end + end + + private + + def release_params + params.permit(:release_id, :s3_key) + end +end diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb new file mode 100644 index 0000000..245729e --- /dev/null +++ b/app/controllers/api/v1/users_controller.rb @@ -0,0 +1,55 @@ +class Api::V1::UsersController < Api::ApplicationController + def regenerate_token + authorize(current_user, :regenerate_api_token?) + + current_user.regenerate_api_token + current_user.sandbox + + render json: { token: current_user.api_token } + end + + def learn_cli_credentials + render_method_not_allowed_response and return unless request.get? + + authorize(current_user, :learn_cli_credentials?) + + render json: { + user_id: current_user.id.to_s, + user_email: current_user.email, + s3: { + access_key_id: Rails.application.secrets.glearn_access_key_id, + secret_access_key: Rails.application.secrets.glearn_secret_access_key, + key_prefix: Rails.application.secrets.glearn_key_prefix, + bucket_name: Rails.application.secrets.glearn_bucket_name + }, + slack: { + dev_notify_url: Rails.application.secrets.dev_notify_slack_url + } + } + end + + def learn_cli_metadata + render_method_not_allowed_response and return unless request.post? + + authorize(current_user, :learn_cli_metadata?) + + @api_interaction_metadata = { + cli_benchmark: user_params.as_json + } + + render json: { message: "Success" }, status: :ok + end + + private + + def user_params + params.require(:cli_benchmark).permit( + :time_to_compress, + :time_to_build_on_learn, + :time_to_upload_to_s3, + :master_release_and_build, + :total_cmd_time, + :command_name + ) + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 0000000..a633613 --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,147 @@ +class ApplicationController < ActionController::Base + include Pundit + include JsonHelper + include Pagy::Backend + + protect_from_forgery with: :exception + before_action :require_signed_in_user + before_action :is_mobile? + before_action :miniprofiler + after_action :set_last_viewed_cohort_id + after_action :verify_authorized + before_action :can_access_api? + before_action :warn_browser_compatibility + + rescue_from StandardError do |exception| + raise exception if Rails.env.development? + + if exception.is_a?(Pundit::NotAuthorizedError) + if request.format.json? + render_error_response(status: 403, text: exception.message, backtrace: exception.backtrace) + else + redirect_to root_path, alert: "You do not have permission to access that page." + end + elsif exception.is_a?(ActiveRecord::RecordNotFound) + error_404 + elsif request.format.json? + Honeybadger.notify(exception, context: { referer: request.referer }) + render_error_response(status: 500, text: exception.message, backtrace: exception.backtrace) + else + Honeybadger.notify(exception, context: { referer: request.referer }) + error_500 + end + end + + def require_signed_in_user + if current_user.blank? + session[:requested_path] = request.fullpath unless request.fullpath == "/" + redirect_to auth_api.new_session_path + end + end + + def error_500 + skip_authorization + render template: "error_500", status: :service_unavailable, format: :html + end + + def error_404 + skip_authorization + if request.format.json? + render_error_response(status: 404, text: "not found", backtrace: nil) + else + render template: "error_404", status: :not_found, format: :html + end + end + + def api_token + authorize(current_user, :regenerate_api_token?) + render template: "api_token", format: :html + end + + def api_interactions + authorize(current_user, :view_api_interactions?) + + begin + @pagy, @records = pagy( + ApiInteraction.order(created_at: :desc), + page: params[:page] || 1 + ) + # Main reason for this rescue is for if someone types page=not_a_valid_page_number + # into the URL bar so nothing breaks we send to page 1 + rescue StandardError + @pagy, @records = pagy(ApiInteraction.order(created_at: :desc), page: 1) + + render template: "api_interactions", locals: { + api_interactions: @records, + page: @pagy.vars[:page], + per_page: @pagy.vars[:items], + total_count: @pagy.vars[:count] + }, format: :html and return + end + + render template: "api_interactions", locals: { + api_interactions: @records, + page: @pagy.vars[:page], + per_page: @pagy.vars[:items], + total_count: @pagy.vars[:count] + }, format: :html + end + + private + + def warn_browser_compatibility + return if Rails.env.test? + + @unsupported_browser = false + uagent = request.env["HTTP_USER_AGENT"] + if uagent =~ /Edge/ || uagent =~ /MSIE/ || uagent =~ /Opera/ || uagent =~ /OPR/ || uagent =~ /Trident/ + @unsupported_browser = true + end + end + + def can_access_api? + @authorized_api_access ||= current_user.admin? || CohortUser.instructor.where(user_id: current_user.id).any? + end + + def render_error_response(status:, text:, backtrace:) + json_response = { errors: [message: "#{status} #{text}"] } + json_response[:backtrace] = backtrace unless Rails.env.production? + render json: json_response, status: status + end + + def set_last_viewed_cohort_id + return unless current_user + return unless @current_cohort + + current_user.update(last_viewed_cohort_id: @current_cohort.id) + end + + def is_mobile? + @is_mobile_device = browser.mobile? + end + + def segment_identify + return if Rails.env.test? + return unless current_user + + @current_user_uid = current_user.uid + @identify_properties = { + firstName: current_user.first_name, + lastName: current_user.last_name, + email: current_user.email, + githubUsername: current_user.github_username, + createdAt: current_user.created_at, + learnId: current_user.id, + roleName: current_user.admin? ? "admin" : "student" + } + Analytics.identify( + user_id: @current_user_uid, + traits: @identify_properties + ) + end + + def miniprofiler + emails = ["chad.lillquist@galvanize.com", "brad.lamson@galvanize.com", "grunde@galvanize.com"] + Rack::MiniProfiler.authorize_request if emails.include?(current_user&.email) + end +end diff --git a/app/controllers/blocks/releases_controller.rb b/app/controllers/blocks/releases_controller.rb new file mode 100644 index 0000000..75e3079 --- /dev/null +++ b/app/controllers/blocks/releases_controller.rb @@ -0,0 +1,69 @@ +module Blocks + class ReleasesController < ApplicationController + before_action :set_html_title + + def index + authorize(current_cohort, :create_cohort_release?) + + releases = ReleaseFinder.for_block(params[:block_id]) + release_presenters = BlockPresenter::ForCohortReleases.releases_for_cohort( + releases: releases, + cohort: current_cohort, + block_id: params[:block_id] + ) + + render json: { releases: release_presenters } + end + + def new + authorize(Release) + render(locals: { block: current_block, release: Release.new }) + end + + def create + authorize(Release) + + pending_release = Release.create(block_id: current_block.id, + notes: release_params[:notes], + github_sha: "pending", + user_id: current_user.id, + state: Release::STATES[:pending]) + + CreateReleaseJob.perform_later(pending_release_id: pending_release.id) + + flash[:notice] = "Release creation queued." + redirect_to block_url(current_block) + end + + private + + def set_html_title + @html_title = "Blocks" + end + + def current_cohort + @current_cohort ||= Cohort.find(params[:cohort_id]) + end + + def current_block + @current_block ||= Block.find(params[:block_id]) + end + + def release_params + params.require(:release).permit(:notes) + end + + def set_segment_page_properties + @track_properties = { + productId: nil, + pageType: @html_title, + cohortId: nil, + cohortName: nil, + blockId: current_block&.id, + blockName: current_block&.title, + unitId: nil, + unitName: nil + }.to_json + end + end +end diff --git a/app/controllers/blocks_controller.rb b/app/controllers/blocks_controller.rb new file mode 100644 index 0000000..18c8d39 --- /dev/null +++ b/app/controllers/blocks_controller.rb @@ -0,0 +1,77 @@ +class BlocksController < ApplicationController + before_action :set_html_title + + def index + authorize(Block) + relations = { releases: { cohort_releases: { cohort: :cohort_users } } } + blocks = Block.all.active.without_preview.includes(relations).order(title: :asc) + render( + locals: { + blocks: blocks.map { |b| BlockPresenter::ForBlock.new(block: b) } + } + ) + end + + def show + authorize(current_block) + releases = Release.where(block_id: current_block.id, branch_name: "master").order(id: :desc).map do |r| + BlockPresenter::ForReleases.new(release: r) + end + SetBlockCachesJob.perform_later(block_id: current_block.id) + render(locals: { block: current_block, url: current_block.repo_url, releases: releases }) + end + + def blockpagev2 + authorize(current_block) + releases = Release.where(block_id: current_block.id, branch_name: "master").order(id: :desc).map do |r| + BlockPresenter::ForReleases.new(release: r) + end + render( + locals: { + block: BlockPresenter::ForBlock.new(block: current_block), + github_url: current_block.git_url, + releases: releases + } + ) + end + + def release_polling + authorize(current_block) + releases = Release.where(block_id: current_block.id, branch_name: "master").order(id: :desc).map do |r| + BlockPresenter::ForReleases.new(release: r) + end + render json: { block: current_block, releases: releases } + end + + def new + authorize(Block) + render(locals: { block: Block.new }) + end + + private + + def set_html_title + @html_title = "Blocks" + end + + def current_block + @current_block ||= Block.find(params[:id]) + end + + def block_params + params.require(:block).permit(:title, :repo_name, :org, :origin) + end + + def set_segment_page_properties + @track_properties = { + productId: nil, + pageType: @html_title, + cohortId: nil, + cohortName: nil, + blockId: params[:id].present? ? current_block&.id : nil, + blockName: params[:id].present? ? current_block&.title : nil, + unitId: nil, + unitName: nil + }.to_json + end +end diff --git a/app/controllers/cohorts/blocks/content_files_controller.rb b/app/controllers/cohorts/blocks/content_files_controller.rb new file mode 100644 index 0000000..98a6b2c --- /dev/null +++ b/app/controllers/cohorts/blocks/content_files_controller.rb @@ -0,0 +1,306 @@ +module Cohorts + module Blocks + class ContentFilesController < ApplicationController + def show + skip_authorization and render template: "error_404" and return if current_content_file.nil? + authorize(current_content_file, :show?, visible_for_cohort: current_cohort) + @html_title = current_content_file.title + @page_type = current_content_file.content_file_type.titleize + @current_category = "curriculum" + @force_navigation_hide = current_content_file.content_file_type == ContentFile::TYPES[:resource] + + run_content_file_visit + + render( + locals: current_content_file.checkpoint? ? checkpoint_locals : lesson_locals + ) + end + + def lesson_locals + curriculum_warning_count = current_user.instructor_or_admin?(current_cohort.id) ? current_content_file.warnings.length : 0 + { + repo_details: current_block.repo_details, + visited_lesson_uids: lesson_visits.map(&:content_file_uid) | [current_content_file.uid], # SideBar + standard: standard_presenter, # SideBar + cohort_path: cohort_path(current_cohort), # SideBar, SubmissionRenderer + checkpoint_info: checkpoint_info, # SideBar, SubmissionRenderer + content_files_submissions: single_standard_submissions, # Lesson, SideBar + user_id: current_user.id, # ChallengeBlock, SideBar + content_file: content_file_locals, # ChallengeBlock, SubmissionRenderer + cohort_mode: current_cohort.mode, # ChallengeBlock, SubmissionRenderer + content_file_presenter: content_file_presenter, # ChallengeBlock, SubmissionRenderer + is_instructor_or_admin: instructor_or_admin, # ChallengeBlock, SubmissionRenderer + content_file_submission_path: content_file_submission_path, # ChallengeBlock + is_ipynb_file: current_content_file.path.include?(".ipynb"), # SubmissionRenderer, html + content_file_hidden: content_visisbility.include?(current_content_file.uid), # SubmissionRenderer + standard_hidden: content_visisbility.include?(current_standard.uid), # SubmissionRenderer + visibility_path: instructor_or_admin ? content_cohort_path(current_cohort) : nil, # SubmissionRenderer + cohort_id: current_cohort.id, # SubmissionRenderer + is_sandbox_cohort: current_cohort.sandbox, # SubmissionRenderer + curriculum_warning_count: curriculum_warning_count, # SubmissionRenderer + checkpoint_is_autoscored: current_content_file.autoscore, # SubmissionRenderer + is_enrolled_student: current_user.student_of?(current_cohort.id), # SubmissionRenderer + content_file_footer_presenter: ContentFilePresenter::ForFooter.new( + cohort: current_cohort, + content_file: current_content_file + ) # Lesson, SubmissionRenderer + } + end + + # sidebar attributes are used for both checkpoint and lesson, should be extracted + # pull out links from any locals, refactor to use routes.tsx + # checkpoint landing page locals should be from a separate method, will cleanup lesson locals + def checkpoint_locals + checkpoint_attempts_remaining = nil + checkpoint_submissions_count = current_user.checkpoint_submissions_count( + current_cohort.id, + current_block.id, + current_content_file.uid + ) + if current_content_file.max_checkpoint_submissions.present? + checkpoint_attempts_remaining = current_content_file.max_checkpoint_submissions - checkpoint_submissions_count + checkpoint_attempts_remaining = checkpoint_attempts_remaining < 0 ? 0 : checkpoint_attempts_remaining + end + + { + load_student_scores: current_cohort.students.size < 1000, + repo_details: current_block.repo_details, + is_ipynb_file: current_content_file.path.include?(".ipynb"), + checkpoint_title: current_content_file.title, # SideBar + visited_lesson_uids: lesson_visits.map(&:content_file_uid) | [current_content_file.uid], # SideBar + standard: standard_presenter, # SideBar + cohort_path: cohort_path(current_cohort), # SideBar, SubmissionRenderer + cohort_id: current_cohort.id, + checkpoint_info: checkpoint_info, # SideBar, SubmissionRenderer + content_files_submissions: single_standard_submissions, # Lesson, SideBar + user_id: current_user.id, # ChallengeBlock, SideBar + cohort_mode: current_cohort.mode, # ChallengeBlock, SubmissionRenderer + checkpoint_attempts_remaining: checkpoint_attempts_remaining, + time_limit: current_content_file.time_limit, + challenge_types: current_content_file.challenges.map(&:challenge_type).uniq, + progress_thresholds: current_cohort.settings["progress_thresholds"], + content_file: content_file_locals, # ChallengeBlock, SubmissionRenderer + is_instructor_or_admin: instructor_or_admin, # Checkpoint and children + is_preview_sandbox: current_cohort.sandbox, + student_scores_endpoint: api_v1_cohort_block_student_scores_path(current_cohort.id, + current_block.id, + current_content_file.id), + github_url: content_file_github_url, + take_assessment_path: api_v1_cohort_block_take_assessment_path(current_cohort, + current_block, + current_content_file), + storage_key: "viewed_new_assessment", + is_enrolled_student: current_user.student_of?(current_cohort.id), + cohort_allow_paired_submissions: current_cohort.allow_paired_submissions + } + end + + def content_file_locals + { + id: current_content_file.id, + github_url: content_file_github_url, + html: current_content_file.html.html_safe, + is_checkpoint: current_content_file.checkpoint?, + is_resource: current_content_file.resource?, + title: current_content_file.title, + path: current_content_file.path, + permalink: permalink_url(current_block.repo_name, current_content_file.path), + type: current_content_file.content_file_type, + students_in_cohort: current_cohort.students.map do |s| + { id: s.id, full_name: s.full_name, initials: s.initials, profile_image: s.profile_image } + end + } + end + + def track_activity_event + render nothing: true, status: 404 and return if current_cohort.nil? || current_content_file.nil? + + authorize(current_cohort, :show?) + + Activity.create( + cohort: current_cohort, + creator: current_user, + name: params[:event], + subject: current_content_file, + content: params[:content] + ) + + respond_to do |format| + format.json do + render json: { ack: :ok } + end + end + end + + private + + def current_cohort + @current_cohort ||= Cohort.find(params[:cohort_id]) + end + + def current_content_file + @content_file ||= ContentFileFinder.from_cohort_block_content_file_path( + cohort_id: current_cohort.id, + block_id: params[:block_id], + content_file_path: params[:path] + ) + end + + def content_file_github_url + release = current_content_file.standard.release + block = release.block + base_url = block.git_url + branch = release.branch_name + "#{base_url}/blob/#{branch}/#{current_content_file.path}" + end + + def current_standard + @current_standard ||= current_content_file.standard + end + + def current_block + @current_block ||= current_standard.release.block + end + + def content_visisbility + @content_visisbility ||= ContentVisibility.where( + cohort_id: current_cohort.id, + content_uid: [current_standard.uid, current_content_file.uid] + ).map(&:content_uid) + end + + def all_checkpoints + @all_checkpoints ||= CheckpointSubmission.where( + content_file_uid: current_content_file.uid, + cohort_id: current_cohort.id, + content_file_block_id: current_block.id, + user_id: current_user.id + ).order(created_at: :desc) + end + + def checkpoints_by_state + @checkpoints_by_state ||= { + done: all_checkpoints.find { |chs| chs.state == CheckpointSubmission::STATES[:done] }, + retry: all_checkpoints.find { |chs| chs.state == CheckpointSubmission::STATES[:retry] }, + started: all_checkpoints.find { |chs| chs.state == CheckpointSubmission::STATES[:started] }, + needs_review: all_checkpoints.find { |chs| chs.state == CheckpointSubmission::STATES[:needs_review] } + } + end + + def single_standard_submissions + @single_standard_submissions ||= StandardSubmissionsService.new( + standard: current_standard, + cohort: current_cohort, + student_ids: [current_user.id], + all_checkpoints: all_checkpoints, + instructor_or_admin: current_user.instructor_or_admin?(current_cohort.id) + ).single + end + + def lesson_visits + @lesson_visits ||= LessonVisit.where( + user_id: current_user.id, + standard_uid: current_standard.uid, + cohort_id: current_cohort.id, + block_id: current_block.id + ) + end + + def content_file_presenter + @content_file_presenter ||= ContentFilePresenter::ForShow.new( + current_content_file, + current_cohort, + current_user + ) + end + + def standard_presenter + @standard_presenter ||= StandardPresenter::ForStandardCard.new( + standard: current_standard, + cohort: current_cohort, + current_user_id: current_user.id, + user_performances: + if current_cohort.mode == Cohort::MODES[:mastery] + PerformanceFinder.latest_for_standards_in_cohort( + standard_uids: [current_standard.uid], + user_ids: [current_user.id], + cohort_id: current_cohort.id + ).to_a + else + [] + end, + cohort_standard_progress_data: single_standard_submissions, + instructor_or_admin: instructor_or_admin, + lesson_visits: lesson_visits + ) + end + + def mastery_score + @mastery_score ||= Performance.where( + user_id: current_user.id, standard_uid: current_standard.uid, block_id: current_block.id + ).last&.score || nil + end + + def instructor_or_admin + @instructor_or_admin ||= current_user.instructor_or_admin?(current_cohort.id) + end + + def checkpoint_info + @checkpoint_info ||= { + cohortId: current_cohort.id, + cohortMode: current_cohort.mode, + checkpointsByState: checkpoints_by_state, + challengeTotal: content_file_presenter.challenges_with_answers.length, + contentFilesSubmission: + single_standard_submissions.find do |cfs| + cfs[:content_file_type] == ContentFile::TYPES[:checkpoint] + end. + try(:dig, :checkpoint_submissions, current_user.id) + + }.merge( + if current_cohort.mode == Cohort::MODES[:mastery] + { + masteryScore: mastery_score + } + else + { + challengeCorrect: content_file_presenter.challenges_with_answers.count do |ch| + ch.submitted_challenge_answer_presenters[0]&.status == "correct" + end + } + end + ) + end + + def run_content_file_visit + return if current_content_file.content_file_type == ContentFile::TYPES[:resource] || + current_content_file.content_file_type == ContentFile::TYPES[:instructor] + + ContentFileVisitJob.perform_later( + current_user.id, + current_block.id, + current_cohort.id, + current_content_file.id, + current_standard.uid + ) + end + + def content_file_submission_path + cohort_content_file_submitted_challenge_answers_path(current_cohort, current_content_file) + end + + def set_segment_page_properties + @track_properties = { + productId: current_cohort.uid, + pageType: @page_type, + cohortId: current_cohort.id, + cohortName: current_cohort.name, + blockId: current_block.id, + blockName: current_block.title, + unitId: current_content_file.standard.id, + unitName: current_content_file.standard.title + }.to_json + end + end + end +end diff --git a/app/controllers/cohorts/checkpoint_submissions/activities_controller.rb b/app/controllers/cohorts/checkpoint_submissions/activities_controller.rb new file mode 100644 index 0000000..cc83f67 --- /dev/null +++ b/app/controllers/cohorts/checkpoint_submissions/activities_controller.rb @@ -0,0 +1,80 @@ +module Cohorts + module CheckpointSubmissions + class ActivitiesController < ApplicationController + before_action :current_cohort + + def create + authorize(checkpoint_submission, :comment?) + + activity = Activity.new( + content: activity_params[:content], + subject: checkpoint_submission, + creator: current_user, + name: Activity::NAMES[:comment_created], + cohort: current_cohort + ) + + if activity.save + first_sca = checkpoint_submission.submitted_challenge_answers.first + content_file = first_sca.challenge.content_file + checkpoint_submission_url = cohort_checkpoint_submission_url(current_cohort, checkpoint_submission) + + if activity_params[:notify] == "true" + notification = { + tagline: "Comment Left", + title: + "#{current_user.full_name} commented on #{content_file.standard.release.block.title}" \ + " - #{content_file.standard.title} - Checkpoint", + url: checkpoint_submission_url, + description: "\"#{activity_params[:content]}\"" + } + + if checkpoint_submission.user_id == current_user.id + current_cohort.instructors.each { |instructor| instructor.notify(notification) } + + slack_users(CohortUser.instructor.where(cohort: current_cohort)&.map(&:user), checkpoint_submission_url, content_file.title) + else + checkpoint_submission.user.notify(notification) + + slack_users([first_sca.user], checkpoint_submission_url, content_file.title) + end + end + + render json: ActivityPresenter.new(activity), status: 200 + else + render json: { error: activity.errors.full_messages }, status: 400 + end + end + + private + + def activity_params + params.require(:activity).permit(:content, :notify) + end + + def current_cohort + @cohort ||= CohortPolicy::Scope.new(current_user, Cohort).resolve.find_by!(id: params[:cohort_id]) + end + + def checkpoint_submission + @checkpoint_submission ||= CheckpointSubmission.find(params[:checkpoint_submission_id]) + end + + def slack_users(users, checkpoint_submission_url, content_file_title) + users.each do |user| + user.send_slack_message( + slack_message_content( + commentor: current_user, + url: checkpoint_submission_url, + content_file_title: content_file_title + ) + ) + end + end + + def slack_message_content(commentor:, content_file_title:, url:) + "#{commentor.full_name} left a comment on #{content_file_title}. Read it here: #{url}." + end + end + end +end diff --git a/app/controllers/cohorts/cohort_releases_controller.rb b/app/controllers/cohorts/cohort_releases_controller.rb new file mode 100644 index 0000000..99a2210 --- /dev/null +++ b/app/controllers/cohorts/cohort_releases_controller.rb @@ -0,0 +1,156 @@ +module Cohorts + class CohortReleasesController < ApplicationController + def index + authorize(current_cohort, :create_cohort_release?) + + @current_category = "setup" + render json: { cohort_releases: cohort_release_presenters } + end + + def resync + authorize(current_cohort, :create_cohort_release?) + + ResyncCourseJob.perform_later( + cohort_id: current_cohort.id, + course_url: params[:course_config_url] + ) + + render json: {} + end + + def resync_status + authorize(current_cohort, :create_cohort_release?) + latest = ResyncJobResult.find_latest(current_cohort.id) + latest.timeout_latest! if latest&.older_than_one_minute? + render json: latest + end + + def archive_resync_error + authorize(current_cohort, :create_cohort_release?) + if params[:cancel] + ResyncJobResult.cancel_latest!(current_cohort.id) + else + ResyncJobResult.archive_latest_failure!(current_cohort.id) + end + render json: ResyncJobResult.find_latest(current_cohort.id) + end + + def create + authorize(current_cohort, :create_cohort_release?) + + CohortRelease.create!(cohort: current_cohort, release_id: cohort_release_params[:release_id]) + + redirect_to(setup_cohort_path(current_cohort), flash: { success: "Block attached." }) + end + + def update + authorize(current_cohort_release) + + if cohort_release_params[:use_latest_release] == true + latest_release_id = Release.where(block_id: current_cohort_release.release.block_id, branch_name: "master").order(:id).last.id + cohort_release_params[:release_id] = latest_release_id + elsif cohort_release_params[:release_id].present? && cohort_release_params[:use_latest_release].blank? + cohort_release_params[:use_latest_release] = false + end + + current_cohort_release.update!(cohort_release_params.merge(pending_release_id: nil)) + + respond_to do |format| + format.html do |_f| + redirect_to(setup_cohort_path(current_cohort), flash: { success: "Block release updated." }) + end + format.json do |_f| + render json: { release: CohortReleasePresenter::ForCohortSetup.new(cohort_release: current_cohort_release) } + end + end + end + + def switch_to_branch + authorize(current_cohort, :create_cohort_release?) + block = Block.find(params[:block_id]) + repo_name = block.repo_name + github_sha = branch_sha(params[:block_id], params[:branch_name]) + flash_info = if !github_sha + { type: "danger", + msg: "Error. The provided branch '#{params[:branch_name]}' does not exist on repo: #{block.repo_url}." } + else + resolve_branch_release(github_sha) + end + + render json: { + cohort_release: CohortReleasePresenter::ForCohortSetup.new(cohort_release: current_cohort_release), + flash_info: flash_info.merge(title: repo_name.titleize) + } + end + + def branch_polling + authorize(current_cohort, :create_cohort_release?) + render json: { + cohort_release: CohortReleasePresenter::ForCohortSetup.new(cohort_release: current_cohort_release), + cohort_releases: cohort_release_presenters + } + end + + private + + def resolve_branch_release(github_sha) + release_at_sha = Release.find_by(github_sha: github_sha, block_id: params[:block_id], branch_name: params[:branch_name]) + if release_at_sha.present? && release_at_sha.successfully_synced? + msg = if current_cohort_release.release_id == release_at_sha.id + "Nothing to publish, block is already up-to-date with the latest commit on '#{params[:branch_name]}'." + else + "Attached existing and previously synced branch '#{params[:branch_name]}'" + end + current_cohort_release.update(release_id: release_at_sha.id, pending_release_id: nil, use_latest_release: false) + flash_info = { type: "success", msg: msg } + elsif release_at_sha.present? && !release_at_sha.successfully_synced? + release_at_sha.update_attributes( + state: Release::STATES[:pending], + sync_errors: [] + ) + flash_info = queue_job_with_publish_flash(release_at_sha) + else + new_release = Release.create( + block_id: params[:block_id], + github_sha: branch_sha(params[:block_id], params[:branch_name]), + state: Release::STATES[:pending], + branch_name: params[:branch_name] + ) + flash_info = queue_job_with_publish_flash(new_release) + end + flash_info + end + + def branch_sha(block_id, branch_name) + return @github_sha if @github_sha + + git_type = Block.find(block_id).git_type + git_klass = git_type == "github" ? DownloadGithubRepositoryService : DownloadGitlabRepositoryService + @github_sha = git_klass.new(release: nil, tmp_path: "").branch_sha(block_id, branch_name) + end + + def queue_job_with_publish_flash(release) + current_cohort_release.update(pending_release_id: release.id, use_latest_release: false) + SwitchToBranchJob.perform_later(pending_release_id: release.id, cohort_release_id: current_cohort_release.id) + { type: "warning", msg: "Publishing branch '#{params[:branch_name]}'" } + end + + def current_cohort + @current_cohort ||= Cohort.find(params[:cohort_id]) + end + + def current_cohort_release + @current_cohort_release ||= CohortRelease.includes(:release).find(params[:id]) + end + + def cohort_release_params + @cohort_release_params ||= params.require(:cohort_release).permit(:release_id, :use_latest_release) + end + + def cohort_release_presenters + CohortRelease.attached_to_cohort(current_cohort.id).map do |cohort_release| + CohortReleasePresenter::ForCohortSetup.new(cohort_release: cohort_release) + end + end + end +end diff --git a/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb b/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb new file mode 100644 index 0000000..491b2be --- /dev/null +++ b/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb @@ -0,0 +1,338 @@ +module Cohorts + module ContentFiles + class CheckpointSubmissionsController < ApplicationController + def show + authorize(current_checkpoint_submission, :show?) + + if current_checkpoint_submission.state == CheckpointSubmission::STATES[:started] + redirect_back fallback_location: cohorts_path(current_cohort), + flash: { alert: "Cannot view a Checkpoint that has not yet been submitted." } and return + end + + @html_title = "#{loaded_student.full_name}: #{current_content_file.title}" + activities = + Activity.where( + subject_type: CheckpointSubmission.to_s, + subject_id: CheckpointSubmissionFinder.all_for_user_id_content_file_in_cohort(loaded_student.id, + current_content_file, + current_cohort.id)&.map(&:id), + name: Activity::NAMES[:comment_created] + ).order(created_at: :asc) + is_instructor_or_admin = current_user.instructor_or_admin?(current_cohort.id) + + render locals: { + current_checkpoint_submission: current_checkpoint_submission_presenter, + loaded_student: loaded_student, + loaded_pairs: loaded_pairs, + pair_submissions: pair_submissions, + challenges: challenge_presenters, + standard_card_props: standard_card_props, + cohort_id: current_cohort.id, + cohort_mode: current_cohort.mode, + has_scored: standard_card_props.checkpoint_performance.present?, + current_user_id: current_user.id, + is_instructor_or_admin: is_instructor_or_admin, + resubmit_url: content_file_path(current_cohort, current_content_file.standard.release.block, current_content_file.path), + next_ungraded_submission_url: next_ungraded_submission_url(current_checkpoint_submission), + performance_path: cohort_user_performances_path( + current_cohort, + current_checkpoint_submission.submitted_challenge_answers.first.user + ), + previous_challenge_path: previous_challenge(current_checkpoint_submission), + next_challenge_path: next_challenge(current_checkpoint_submission), + progress_thresholds: current_cohort.settings["progress_thresholds"], + activities: activities.order(created_at: :asc).map { |activity| ActivityPresenter.new(activity) }, + track_event_path: track_activity_event_path( + current_cohort, + current_content_file.standard.release.block_id, + current_content_file.path + ) + } + end + + def update + authorize(current_checkpoint_submission) + + current_answers = current_checkpoint_submission.submitted_challenge_answers.includes(:challenge) + + current_checkpoint_submission.update!(checkpoint_submission_params.merge(grader_id: current_user.id)) + + if checkpoint_submission_params[:state] == CheckpointSubmission::STATES[:done] + total_points = current_answers.map(&:challenge).map(&:points).compact.reduce(:+) + correct_points = current_answers.map(&:points).compact.reduce(:+) + current_checkpoint_submission.update!(total_points: total_points, correct_points: correct_points) + end + + if current_checkpoint_submission.cohort.mode == Cohort::MODES[:percentage] && + checkpoint_submission_params[:state] != CheckpointSubmission::STATES[:retry] + Performance.create( + user_id: loaded_student.id, + updator: current_user, + score: 0, + standard_id: current_standard.id, + cohort_id: current_checkpoint_submission.cohort.id, + checkpoint_submission_id: current_checkpoint_submission.id + ) + end + + student = current_answers.first.user + student.send_slack_message(slack_message_content) + + NotificationService.checkpoint_submission_graded( + user: student, + grader: current_user, + cohort: current_checkpoint_submission.cohort, + content_file: current_content_file, + checkpoint_submission: current_checkpoint_submission.reload + ) + render json: { + current_checkpoint_submission: current_checkpoint_submission_presenter + } + end + + private + + def checkpoint_submission_params + params.require(:checkpoint_submission).permit(:state) + end + + def current_cohort + @current_cohort ||= CohortPolicy::Scope.new(current_user, Cohort).resolve.find(params[:cohort_id]) + end + + def current_content_file + @current_content_file ||= current_checkpoint_submission.submitted_challenge_answers.first.challenge.content_file + end + + def current_block + @current_block ||= current_standard.release.block + end + + def current_standard + @current_standard ||= current_content_file.standard + end + + def current_cohort_release + @current_cohort_release ||= CohortRelease.joins(release: :standards). + where("cohort_releases.cohort_id = ? AND standards.uid = ?", current_cohort.id, current_standard.uid). + first + end + + def current_checkpoint_submission + CheckpointSubmission.find(params[:id]) + end + + def current_checkpoint_submission_presenter + CheckpointSubmissionPresenter.new( + checkpoint_submission: current_checkpoint_submission, + content_file: current_content_file, + url_cohort_id: current_cohort.id + ) + end + + def student + @student ||= current_checkpoint_submission.user + end + + def loaded_student + return @loaded_student if @loaded_student.present? + + @loaded_student ||= UserPresenter::ForAvatar.new(student) + end + + def loaded_pairs + return @loaded_pairs if @loaded_pairs.present? + return [loaded_student] if current_checkpoint_submission.pair_submission_ids.empty? || + current_cohort.mode == Cohort::MODES[:mastery] || + current_user.instructor_or_admin?(current_cohort) == false + + students = User.select(:first_name, :last_name, :id, :profile_image).where(id: pair_submissions.map(&:user_id)).order(id: :desc) + @loaded_pairs ||= students.map { |student| UserPresenter::ForAvatar.new(student) }.unshift(loaded_student) + end + + def pair_submissions + return [] if current_cohort.mode == Cohort::MODES[:mastery] || current_user.student_of?(current_cohort) + return @pair_submissions if @pair_submissions.present? + + @pair_submissions ||= CheckpointSubmission.where(id: current_checkpoint_submission.pair_submission_ids).filter do |cs| + CheckpointSubmission.where( + user_id: cs.user_id, + cohort_id: cs.cohort_id, + content_file_block_id: cs.content_file_block_id, + content_file_uid: cs.content_file_uid + ).where.not(state: CheckpointSubmission::STATES[:started]). + where("created_at > ?", cs.created_at).empty? + end.map do |sub| + CheckpointSubmissionPresenter.new( + checkpoint_submission: sub, + content_file: current_content_file, + url_cohort_id: current_cohort.id + ) + end + end + + def next_ungraded_submission_url(checkpoint_submission) + user_ids = CohortUser.where(cohort_id: checkpoint_submission.cohort_id).student.map(&:user_id) + not_in_ids = [checkpoint_submission.id, checkpoint_submission.pair_submission_ids].compact.flatten + pair_user_ids = CheckpointSubmission.select(:user_id).where(id: not_in_ids).map(&:user_id) + + previous_submission_ids_by_users = CheckpointSubmission.select(:id). + where("created_at < ?", checkpoint_submission.created_at). + where( + user_id: pair_user_ids, + content_file_block_id: checkpoint_submission.content_file_block_id, + content_file_uid: checkpoint_submission.content_file_uid + ).map(&:id) + + oldest_ungraded_submission = CheckpointSubmission. + where(content_file_uid: current_content_file.uid, + content_file_block_id: current_content_file.standard.release.block_id, + user_id: user_ids, + cohort_id: checkpoint_submission.cohort_id). + where.not(id: not_in_ids + previous_submission_ids_by_users, state: CheckpointSubmission::STATES[:started]). + select("DISTINCT ON (checkpoint_submissions.user_id) checkpoint_submissions.id, checkpoint_submissions.state"). + order("checkpoint_submissions.user_id, created_at DESC").to_a.select {|cs| cs.state == "needs_review"}.last + if oldest_ungraded_submission + Rails.application.routes.url_helpers.cohort_checkpoint_submission_path( + current_cohort.id, oldest_ungraded_submission.id + ) + else + Rails.application.routes.url_helpers.unit_progress_cohort_path(current_cohort.id) + end + end + + def challenge_presenters + is_instructor_or_admin = current_user.instructor_or_admin?(current_cohort.id) + challenge_ids = current_checkpoint_submission.submitted_challenge_answers.pluck(:challenge_id).uniq + challenges = Challenge.where(id: challenge_ids).order(position: :asc) + + all_submissions = SubmittedChallengeAnswer.where( + user_id: loaded_pairs.map(&:id), + challenge_uid: challenges.map(&:uid).uniq, + block_id: current_checkpoint_submission.content_file_block_id # select only for submissions in block + ).where( + "status <> 'draft'" + ).order(created_at: :desc) + + challenges.map do |challenge| + ChallengeWithSubmittedChallengeAnswersPresenter.new( + current_cohort, + challenge, + all_submissions.select {|sca| sca.challenge_uid == challenge.uid }, + is_instructor_or_admin, + user_context: loaded_pairs, + is_checkpoint: true, + can_view_status: is_instructor_or_admin || current_checkpoint_submission.done?, + can_view_explanation: current_checkpoint_submission.done? + ) + end + end + + def standard_card_props + standard = current_content_file.standard + @standard_card_props ||= StandardCardComponentProps.new( + standard: standard, + latest_performance: Performance.order(created_at: :desc). + find_by(user_id: loaded_student.id, standard_id: standard.id), + checkpoint_performance: Performance.order(created_at: :desc). + find_by(user_id: loaded_student.id, standard_id: standard.id, checkpoint_submission_id: params[:id]) + ) + end + + def next_checkpoint_submission_url(all_needs_review_checkpoint_submissions, current_checkpoint_submission) + sorted_oldest_first_submissions = all_needs_review_checkpoint_submissions.sort_by(&:updated_at) + + next_index = sorted_oldest_first_submissions.map(&:id).index(current_checkpoint_submission.id) + # next_index = next_index.nil? ? 0 : next_index + 1 + + if next_index.nil? + next_needs_review_checkpoint_submission = sorted_oldest_first_submissions.first + else + next_needs_review_checkpoint_submission = sorted_oldest_first_submissions[next_index + 1] + if next_needs_review_checkpoint_submission.nil? && sorted_oldest_first_submissions.length > 1 + next_needs_review_checkpoint_submission = sorted_oldest_first_submissions.first + end + end + + if next_needs_review_checkpoint_submission + cohort_checkpoint_submission_url( + params[:cohort_id], + next_needs_review_checkpoint_submission.id + ) + else + unit_progress_cohort_path(params[:cohort_id]) + end + end + + def slack_message_content + action = case current_checkpoint_submission.reload.state + when CheckpointSubmission::STATES[:done] + "finished scoring" + when CheckpointSubmission::STATES[:rejected] + "rejected" + end + standard_title = current_content_file.standard.title + checkpoint_name = current_content_file.title + url = cohort_checkpoint_submission_url( + params[:cohort_id], + current_checkpoint_submission.id + ) + + "#{current_user.full_name} #{action} your submission to #{standard_title} Checkpoint: #{checkpoint_name}. Review it here: #{url}." + end + + NO_ORPHANS = "NOT (content_files.content_file_type = 'checkpoint' AND submitted_challenge_answers.checkpoint_submission_id IS NULL)" + + def previous_challenge(checkpoint_submission) + # check for previous within block, not necessarily in standard + previous_sca_in_block = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: :release]]). + where("submitted_challenge_answers.user_id = ?", loaded_student.id). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + where("standards.position <= ?", current_standard.position). + where("releases.block_id = ?", checkpoint_submission.content_file_block_id). + where("challenges.id NOT IN (?)", current_content_file.challenges.map(&:id)). + where(NO_ORPHANS). + order("standards.position DESC, content_files.position DESC, challenges.position DESC").first + return previous_sca_in_block.review_path(cohort_id: current_cohort.id, user_id: loaded_student.id) if previous_sca_in_block.present? + + # check within previous blocks + previous_sca_blocks = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: [release: :cohort_releases]]]). + where("submitted_challenge_answers.user_id = ?", loaded_student.id). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + where("cohort_releases.position < ?", current_cohort_release.position). + where(NO_ORPHANS). + order("cohort_releases.position DESC"). + order("standards.position DESC"). + order("content_files.position DESC"). + order("challenges.position DESC").first + + return previous_sca_blocks.review_path(cohort_id: current_cohort.id, user_id: loaded_student.id) if previous_sca_blocks.present? + end + + def next_challenge(checkpoint_submission) + # check within block with standard position greater than submission's standard position + next_sca = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: :release]]). + where("submitted_challenge_answers.user_id = ?", loaded_student.id). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + where("standards.position > ?", current_standard.position). + where("releases.block_id = ?", checkpoint_submission.content_file_block_id). + where("challenges.id NOT IN (?)", current_content_file.challenges.map(&:id)). + where(NO_ORPHANS). + order("standards.position ASC, content_files.position ASC, challenges.position ASC").first + return next_sca.review_path(cohort_id: current_cohort.id, user_id: loaded_student.id) if next_sca.present? + + # check in blocks later in the cohort + next_sca_blocks = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: [release: :cohort_releases]]]). + where("submitted_challenge_answers.user_id = ?", loaded_student.id). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + where("cohort_releases.position > ?", current_cohort_release.position). + where(NO_ORPHANS). + order("cohort_releases.position DESC"). + order("standards.position DESC"). + order("content_files.position DESC"). + order("challenges.position DESC").first + return next_sca_blocks.review_path(cohort_id: current_cohort.id, user_id: loaded_student.id) if next_sca_blocks.present? + end + end + end +end diff --git a/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb b/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb new file mode 100644 index 0000000..968e4d8 --- /dev/null +++ b/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb @@ -0,0 +1,346 @@ +module Cohorts + module ContentFiles + class SubmittedChallengeAnswersController < ApplicationController + include CreateSubmittedChallengeAnswerService + + before_action :current_cohort # no route should function unless the cohort exists + + def update_local + sca = SubmittedChallengeAnswer.includes(:challenge).find(params[:id]) + + authorize(sca, :update_local_challenge_answer?) + + if params["m"] == true # See ChallengeBlock.tsx + sca.update(status: SubmittedChallengeAnswer::STATUSES[:correct]) + else + sca.update(status: SubmittedChallengeAnswer::STATUSES[:incorrect]) + end + + render json: { + submittedChallengeAnswer: SubmittedChallengeAnswerPresenter.new( + sca, + instructor_or_admin + ) + } + end + + def create + start_time = DateTime.current + authorize(current_cohort, :submit_challenge_answer?) + + challenge = Challenge.find(params[:challenge_id]) + submitted_challenge_answer = if params[:draft] + create_submitted_challenge_answer_draft(challenge, params[:answer]) + else + create_submitted_challenge_answer( + current_cohort, + current_user, + challenge, + params[:answer], + params[:checkpoint_submission_id] + ) + end + + end_time = DateTime.current + if ["checkbox", "local-snippet", "multiple-choice", "number", "poll", "short-answer"].include?(challenge.challenge_type) + # Challenges that dont need callouts to assessment-service should not take longer than 2 secs, alert us + length_of_scoring = end_time.to_i - start_time.to_i + if length_of_scoring > 10 + SlackChallengePerformanceJob.perform_later(submitted_challenge_answer.id, request.referer, length_of_scoring) + end + end + + render json: { + submittedChallengeAnswer: SubmittedChallengeAnswerPresenter.new( + submitted_challenge_answer, + instructor_or_admin + ) + } + end + + def batch_create + authorize(current_cohort, :submit_challenge_answer?) + + unless current_content_file.max_checkpoint_submissions.nil? + user_attempts = current_user.checkpoint_submissions_count( + current_cohort.id, + current_content_file.standard.release.block_id, + current_content_file.uid + ) + + if user_attempts >= current_content_file.max_checkpoint_submissions + render json: { + message: "Sorry, you have already submitted this checkpoint the max amount of times." + } and return + end + end + + checkpoint_submission = params[:draft] == true ? nil : find_or_create_checkpoint_submission(current_user.id) + + challenge_ids = params[:submitted_challenge_answers].map {|s| s[:challenge_id]} + challenges = Challenge.where(id: challenge_ids.compact).to_a + params[:submitted_challenge_answers]&.each do |submission| + challenge = challenges.find { |c| c.id == submission[:challenge_id].to_i } + if checkpoint_submission + create_submitted_challenge_answer( + current_cohort, + current_user, + challenge, + submission[:answer], + checkpoint_submission&.id + ) + else + create_submitted_challenge_answer_draft(challenge, submission[:answer]) + end + end + + if checkpoint_submission + checkpoint_submission.update(submitted_at: DateTime.current) + name = if previous_checkpoint_submission?(checkpoint_submission.id) + Activity::NAMES[:checkpoint_submission_resubmitted] + else + Activity::NAMES[:checkpoint_submission_created] + end + + Activity.create( + cohort: current_cohort, + creator: current_user, + name: name, + subject: checkpoint_submission + ) + end + + checkpoint_submission_presenter = if checkpoint_submission + CheckpointSubmissionPresenter.new( + checkpoint_submission: checkpoint_submission, + content_file: current_content_file + ) + end + + submitted_date = checkpoint_submission&.created_at + + if checkpoint_submission + CheckpointSubmissionService.new( + checkpoint_submission, + current_content_file, + current_cohort, + current_user + ).attempt_autoscore + + if params[:pairs].present? + create_pair_submissions(checkpoint_submission) + CheckpointPairedSubmissionJob.perform_later(checkpoint_submission.id, current_content_file.autoscore) + end + end + + render json: { + submittedDate: submitted_date, + checkpointSubmission: checkpoint_submission_presenter + } + end + + def checkpoint_submission_results + authorize(current_cohort, :submit_challenge_answer?) + + results = {} + + latest_checkpoint_submission = CheckpointSubmissionFinder.latest_for_checkpoint_content_file_for_user_in_cohort( + cohort_id: current_cohort.id, + content_file: current_content_file, + user_id: current_user.id + ) + + if current_content_file.autoscore + scas = SubmittedChallengeAnswer.includes(:challenge).where( + checkpoint_submission_id: latest_checkpoint_submission.id, + user: current_user, + cohort: current_cohort + ) + + failed_scas = scas.where(status: SubmittedChallengeAnswer::FAILURE_STATUSES.values) + + if failed_scas.any? + results["errors"] = failed_scas.map do |sca| + { + challenge_num: sca.challenge.position, + challenge_err: sca.test_results.empty? ? sca.status.gsub("_", " ").titleize : sca.test_results + } + end + + scas.update_all(checkpoint_submission_id: nil) + Performance.where(checkpoint_submission_id: latest_checkpoint_submission.id).delete_all + latest_checkpoint_submission.destroy + + render json: results and return + end + + if scas.where(status: SubmittedChallengeAnswer::UNGRADED_STATUSES[:processing]).empty? + results = assign_checkpoint_results(latest_checkpoint_submission, scas) + end + else + results["url"] = cohort_checkpoint_submission_path(current_cohort, latest_checkpoint_submission) + end + + render json: results + end + + def update + authorize(current_submitted_challenge_answer) + + new_attributes = submitted_challenge_answer_params + new_attributes[:manual_grader_id] = current_user.id if SubmittedChallengeAnswer.new(new_attributes).graded? + + if current_submitted_challenge_answer.update(new_attributes) && current_submitted_challenge_answer.checkpoint_submission + current_submitted_challenge_answer.checkpoint_submission.update_points + if current_submitted_challenge_answer.checkpoint_submission.done? + NotificationService.checkpoint_challenge_graded( + user: current_submitted_challenge_answer.user, + cohort: current_submitted_challenge_answer.cohort, + content_file: current_content_file, + checkpoint_submission: current_submitted_challenge_answer.checkpoint_submission, + challenge: current_submitted_challenge_answer.challenge + ) + end + end + + render json: { + submitted_challenge_answer_presenter: SubmittedChallengeAnswerPresenter.new( + current_submitted_challenge_answer, + true + ) + }, status: 200 + end + + def cancel + authorize(current_submitted_challenge_answer) + + if current_submitted_challenge_answer.processing? + current_submitted_challenge_answer.update_attributes( + status: SubmittedChallengeAnswer::STATUSES[:canceled], + test_results: "Tests canceled by student." + ) + end + + render json: { submittedChallengeAnswer: + SubmittedChallengeAnswerPresenter.new(current_submitted_challenge_answer, instructor_or_admin) } + end + + def show + authorize(current_submitted_challenge_answer) + + render json: SubmittedChallengeAnswerPresenter.new(current_submitted_challenge_answer, instructor_or_admin) + end + + private + + def assign_checkpoint_results(latest_checkpoint_submission, scas) + results = { + "url": cohort_checkpoint_submission_path(current_cohort, latest_checkpoint_submission), + "created_at": latest_checkpoint_submission.created_at, + "state": latest_checkpoint_submission.state, + "cohort_mode": current_cohort.mode, + "correct_challenges": scas.select {|sca| sca.status == SubmittedChallengeAnswer::GRADED_STATUSES[:correct] }.count, + "total_challenges": scas.count, + "total_points": scas.map { |sa| sa.challenge.points }.flatten.reduce(:+), + "correct_points": scas.find_all do |sa| + sa.points if sa.status == SubmittedChallengeAnswer::STATUSES[:correct] || sa.challenge.can_assign_partial_credit + end.map(&:points).reduce(:+) || 0 + } + + perf = latest_checkpoint_submission.performances.order("created_at DESC").last + results["mastery_score"] = perf.score if perf.present? + + results + end + + def previous_checkpoint_submission?(submission_id) + challenge_ids = params[:submitted_challenge_answers]&.map { |sca| sca[:challenge_id] } + return false if challenge_ids.blank? + + SubmittedChallengeAnswer.where(user: current_user, challenge_id: challenge_ids). + where("checkpoint_submission_id != ?", submission_id). + map(&:checkpoint_submission_id).compact.any? + end + + def create_submitted_challenge_answer_draft(challenge, answer) + submitted_challenge_answer = SubmittedChallengeAnswer.new( + cohort: current_cohort, + answer: answer.nil? ? "" : answer, + challenge_id: challenge.id, + checkpoint_submission_id: nil, + user: current_user, + status: SubmittedChallengeAnswer::UNGRADED_STATUSES[:draft], + hints_shown: 0, + test_results: "" + ) + submitted_challenge_answer.save! + submitted_challenge_answer + end + + def instructor_or_admin + current_user.instructor_or_admin?(current_cohort.id) + end + + def current_cohort + @cohort ||= CohortPolicy::Scope.new(current_user, Cohort).resolve.find_by!(id: params[:cohort_id]) + end + + def current_content_file + @current_content_file ||= ContentFile.unscoped.find(params[:content_file_id]) + end + + def current_submitted_challenge_answer + @submitted_challenge_answer ||= SubmittedChallengeAnswer.find(params[:id]) + end + + def find_or_create_checkpoint_submission(user_id) + checkpoint_submission = CheckpointSubmission.find_by( + cohort_id: params[:cohort_id], + user_id: user_id, + content_file_block_id: current_content_file.standard.release.block_id, + content_file_uid: current_content_file.uid, + state: CheckpointSubmission::STATES[:started] + ) + if checkpoint_submission.nil? + checkpoint_submission = CheckpointSubmission.create!( + cohort_id: params[:cohort_id], + user_id: user_id, + content_file_block_id: current_content_file.standard.release.block_id, + content_file_uid: current_content_file.uid, + state: CheckpointSubmission::STATES[:needs_review] + ) + else + # TODO: save on a db transaction by waiting until later? + checkpoint_submission.update(state: CheckpointSubmission::STATES[:needs_review]) + end + checkpoint_submission + end + + def create_pair_submissions(checkpoint_submission) + student_pair_ids = params[:pairs].keys.map(&:to_i).delete_if { |id| id == current_user.id } + submissions = student_pair_ids.map do |user_id| + unless current_content_file.max_checkpoint_submissions.nil? + user = User.find(user_id) + user_attempts = user.checkpoint_submissions_count( + current_cohort.id, + current_content_file.standard.release.block_id, + current_content_file.uid + ) + next if user_attempts >= current_content_file.max_checkpoint_submissions + end + + find_or_create_checkpoint_submission(user_id) + end.compact + checkpoint_submission.update(pair_submission_ids: submissions.map(&:id)) unless submissions.empty? + submissions.each do |sub| + ids = [checkpoint_submission.id, submissions.map(&:id).reject { |id| id == sub.id}].flatten + sub.update(pair_submission_ids: ids) + end + end + + def submitted_challenge_answer_params + params.require(:submitted_challenge_answer).permit(:status, :points) + end + end + end +end diff --git a/app/controllers/cohorts/content_files_controller.rb b/app/controllers/cohorts/content_files_controller.rb new file mode 100644 index 0000000..8302e25 --- /dev/null +++ b/app/controllers/cohorts/content_files_controller.rb @@ -0,0 +1,260 @@ +module Cohorts + class ContentFilesController < ApplicationController + def visibility + authorize(current_cohort, :submissions_dashboard?) + query = ContentVisibility.where( + cohort_id: current_cohort.id, + content_type: "ContentFile", + content_uid: current_content_file.uid, + visibility_type: params[:visibility_type] + ) + if request.method == "PUT" + query.first_or_create! + elsif request.method == "DELETE" + query.delete_all + end + render json: { success: true } + end + + def student_summary_csv + authorize(current_cohort, :submissions_dashboard?) + data = ActiveRecord::Base.connection.exec_query( + student_summary_sql, + "SQL", + [[nil, current_cohort.id], [nil, current_content_file.uid], [nil, current_block_id]] + ) + head :no_content and return if data.empty? + data = add_topics(data) + respond_to do |format| + format.html { redirect_to cohort_path(current_cohort) } + format.csv do + csv_headers("student_summary") + self.response_body = build_csv_enumerator(data[0].map {|k, _| k }, data) + end + end + end + + def challenge_data_csv + authorize(current_cohort, :submissions_dashboard?) + sql_query_params = [[nil, current_cohort.id], [nil, current_content_file.uid], [nil, current_block_id]] + data = ActiveRecord::Base.connection.exec_query(challenge_data_sql, + "SQL", + sql_query_params) + head :no_content and return if data.empty? + respond_to do |format| + format.html { redirect_to cohort_path(current_cohort) } + format.csv do + csv_headers("challenge_data") + self.response_body = build_csv_enumerator(data[0].map {|k, _| k }, data) + end + end + end + + private + + def add_topics(data) + group_checkpoint_submissions = CheckpointSubmission.includes(submitted_challenge_answers: :challenge). + where(id: data.map { |r| r["checkpoint_submission_id"] }). + group_by(&:id) + + data.map do |r| + if r["checkpoint_submission_id"] + submission = group_checkpoint_submissions[r["checkpoint_submission_id"]][0] + ungraded = submission.state == CheckpointSubmission::STATES[:needs_review] && + submission.submitted_challenge_answers.map(&:points).include?(nil) + + topic_hash = parse_checkpoint_topics(submission, ungraded) + if ungraded + topic_hash.merge!("points_earned" => nil, "score_percentage" => nil) + end + r.to_hash.merge(topic_hash) + else + r.to_hash.merge(parse_checkpoint_topics(nil, false)) + end + end + end + + def parse_checkpoint_topics(submission, ungraded) + topic_hash = content_file_topics.each_with_object({}) do |topic, hash| + pts_earned = checkpoint_submission_pts_earned(submission, topic, ungraded) + pts_avail = content_file_pts_avail[topic] + + if submission && pts_avail && pts_earned + percent = (((pts_earned).to_f / (pts_avail).to_f) * 100).to_i + end + hash["topic:#{topic}_pts_earned"] = pts_earned + hash["topic:#{topic}_pts_avail"] = pts_avail + hash["topic:#{topic}_percent"] = percent + end + topic_hash + end + + def content_file_pts_avail + @content_file_pts_avail ||= content_file_topics.each_with_object({}) do |topic, hash| + hash[topic] = current_content_file.challenges.select {|c| c.topics&.include?(topic)}.map(&:points).compact.reduce(:+) + end + end + + def checkpoint_submission_pts_earned(submission, topic, ungraded) + return nil if submission.nil? || ungraded + + matching_scas = submission.submitted_challenge_answers.select { |sca| sca.challenge.topics&.include?(topic)} + matching_scas.map(&:points).compact.reduce(:+) + end + + def content_file_topics + @content_file_topics ||= current_content_file.challenges.map(&:topics).flatten.compact.uniq + @content_file_topics + end + + def current_content_file + @current_content_file ||= ContentFile.unscoped.find(params[:id]) + end + + def current_cohort + @current_cohort ||= CohortPolicy::Scope.new(current_user, Cohort).resolve.find(params[:cohort_id]) + end + + def current_block_id + @current_block ||= current_content_file.standard.release.block_id + end + + def csv_filename(type) + "#{type}-#{current_content_file.title}-cohort#{current_cohort.id}-#{Time.zone.now.to_date.to_s(:default)}.csv" + end + + def csv_headers(filename) + headers["X-Accel-Buffering"] = "no" + headers["Cache-Control"] = "no-cache" + headers["Content-Type"] = "text/csv; charset=utf-8" + headers["Content-Disposition"] = + %(attachment; filename="#{csv_filename(filename)}") + headers["Last-Modified"] = Time.zone.now.ctime.to_s + end + + def build_csv_enumerator(header, data) + Enumerator.new do |y| + CsvBuilder.new(header, data, y).build + end + end + + class CsvBuilder + attr_accessor :output, :header, :data + + def initialize(header, data, output = "") + @output = output + @header = header + @data = data + end + + def build + output << CSV.generate_line(header) + data.each do |row| + output << CSV.generate_line(row.map {|_, v| v}) + end + output + end + end + + def challenge_data_sql + <<~SQL + WITH checkpoint_attempts AS ( + SELECT DISTINCT id, user_id FROM checkpoint_submissions AS cs + WHERE cs.cohort_id = $1 AND cs.content_file_uid = $2 AND cs.content_file_block_id = $3 + ), + checkpoint_user_attempts AS ( + SELECT id, ROW_NUMBER () OVER (PARTITION BY user_id ORDER BY id) from checkpoint_attempts + ) + SELECT u.first_name, + u.last_name, + u.email, + c.challenge_type, + c.position, + c.title, + c.topics, + c.question, + c.answer AS correct_answer, + sca.answer AS student_answer, + c.points AS possible_points, + sca.points AS student_points, + sca.created_at AS submitted_at, + cs.content_file_uid, + cs.state AS checkpoint_state, + cs.id AS checkpoint_id, + c.uid AS challenge_uid, + cua.row_number AS attempt_number + FROM submitted_challenge_answers AS sca + JOIN users AS u + ON sca.user_id = u.id + JOIN cohort_users AS cu + ON cu.user_id = u.id + JOIN checkpoint_submissions AS cs + ON cs.id = sca.checkpoint_submission_id + JOIN challenges AS c + ON c.id = sca.challenge_id + JOIN checkpoint_user_attempts AS cua + ON cua.id = cs.id + WHERE sca.cohort_id = $1 + AND cu.roles = '{}' + AND cu.cohort_id = $1 + AND sca.checkpoint_submission_id IS NOT NULL + AND cs.content_file_uid = $2 + AND cs.content_file_block_id = $3 + AND sca.status <> 'draft' + AND cs.state <> 'started' + ORDER BY u.last_name, + c.position, + cs.created_at + SQL + end + + def student_summary_sql + <<~SQL + SELECT u.first_name, + u.last_name, + u.email, + CASE + WHEN u.last_viewed_cohort_id IS NOT NULL THEN 'y' + ELSE 'n' + END AS has_logged_in, + CASE + WHEN cs.id IS NULL THEN NULL + ELSE Row_number() + OVER( + partition BY u.id + ORDER BY cs.id ) + END AS attempt_number, + cs.id AS checkpoint_submission_id, + cs.state AS submission_state, + cs.created_at AS attempt_started, + cs.submitted_at AS attempt_submitted, + Sum(sca.points) AS points_earned, + Sum(c.points) AS total_points, + 100 * Sum(sca.points) / Sum(c.points) AS score_percentage + FROM users AS u + JOIN cohort_users AS cu + ON cu.user_id = u.id + LEFT JOIN checkpoint_submissions AS cs + ON cs.user_id = u.id + AND cs.cohort_id = cu.cohort_id + AND (cs.content_file_uid = $2 OR cs.content_file_uid IS NULL) + AND cs.content_file_block_id = $3 + LEFT JOIN submitted_challenge_answers AS sca + ON sca.checkpoint_submission_id = cs.id + LEFT JOIN challenges AS c + ON c.id = sca.challenge_id + WHERE cu.cohort_id = $1 + AND cu.roles = '{}' + GROUP BY u.id, + u.first_name, + u.last_name, + u.email, + u.last_viewed_cohort_id, + cs.id + ORDER BY has_logged_in DESC, + u.last_name, + attempt_number + SQL + end + end +end diff --git a/app/controllers/cohorts/pairings_controller.rb b/app/controllers/cohorts/pairings_controller.rb new file mode 100644 index 0000000..c4f872b --- /dev/null +++ b/app/controllers/cohorts/pairings_controller.rb @@ -0,0 +1,49 @@ +module Cohorts + class PairingsController < ApplicationController + def index + authorize(current_cohort, :partnerup?) + + pairings = Pairing.where(cohort_id: current_cohort.id).order(created_at: :desc) + + render json: pairings + end + + def create + authorize(current_cohort, :partnerup?) + + pairing = Pairing.create!(pairing_params.merge(cohort_id: current_cohort.id)) + + render json: pairing + end + + def update + authorize(current_cohort, :partnerup?) + + loaded_pairing.update!(pairing_params) + + render json: loaded_pairing + end + + def destroy + authorize(current_cohort, :partnerup?) + + loaded_pairing.destroy! + + render json: { ack: "ok" } + end + + protected + + def pairing_params + @pairing_params ||= params.require(:pairing).permit(:size, :title, :groups) + end + + def loaded_pairing + @loaded_pairing ||= Pairing.find(params[:id]) + end + + def current_cohort + @current_cohort ||= CohortPolicy::Scope.new(current_user, Cohort).resolve.find(params[:cohort_id]) + end + end +end diff --git a/app/controllers/cohorts/standards_controller.rb b/app/controllers/cohorts/standards_controller.rb new file mode 100644 index 0000000..4a3ce64 --- /dev/null +++ b/app/controllers/cohorts/standards_controller.rb @@ -0,0 +1,43 @@ +module Cohorts + class StandardsController < ApplicationController + def show + authorize(current_cohort, :submissions_dashboard?) + all_checkpoints = CheckpointSubmission.where(content_file_block_id: current_cohort.block_ids, + cohort_id: current_cohort.id). + where.not(state: CheckpointSubmission::STATES[:started]). + order(created_at: :desc) + result = StandardSubmissionsService. + new(standard: current_standard, cohort: current_cohort, + all_checkpoints: all_checkpoints, + instructor_or_admin: false).single(show_hidden: true) + + render json: result + end + + def visibility + authorize(current_cohort, :submissions_dashboard?) + query = ContentVisibility.where( + cohort_id: current_cohort.id, + content_type: "Standard", + content_uid: current_standard.uid, + visibility_type: params["visibility_type"] + ) + if request.method == "PUT" + query.first_or_create! + elsif request.method == "DELETE" + query.delete_all + end + render json: { success: true } + end + + private + + def current_standard + @current_standard ||= Standard.find(params[:id]) + end + + def current_cohort + @current_cohort ||= CohortPolicy::Scope.new(current_user, Cohort).resolve.find(params[:cohort_id]) + end + end +end diff --git a/app/controllers/cohorts/submitted_challenge_answers/activities_controller.rb b/app/controllers/cohorts/submitted_challenge_answers/activities_controller.rb new file mode 100644 index 0000000..0df2ebd --- /dev/null +++ b/app/controllers/cohorts/submitted_challenge_answers/activities_controller.rb @@ -0,0 +1,84 @@ +module Cohorts + module SubmittedChallengeAnswers + class ActivitiesController < ApplicationController + before_action :current_cohort + + def create + activity = Activity.new( + content: activity_content[:content], + subject: current_submitted_challenge_answer, + creator: current_user, + name: Activity::NAMES[:comment_created], + cohort: current_cohort + ) + + authorize(activity, :create_for_submitted_challenge_answer?) + + if activity.save + challenge = current_submitted_challenge_answer.challenge + content_file = challenge.content_file + standard = content_file.standard + block = standard.release.block + + url = cohort_user_challenge_url( + current_cohort, + current_submitted_challenge_answer.user, + current_submitted_challenge_answer.challenge + ) + if current_submitted_challenge_answer.user_id == current_user.id + current_cohort.instructors.each do |instructor| + instructor.send_slack_message( + slack_message_content( + commentor: current_user, + challenge: current_submitted_challenge_answer.challenge, + url: url + ) + ) + + instructor.notify( + tagline: "Comment Left", + title: + "#{current_user.full_name} commented on #{block.title} - #{standard.title} - #{content_file.title} - #{challenge.title}", + url: cohort_user_challenge_url(current_cohort, current_submitted_challenge_answer.user, challenge), + description: "\"#{activity_content[:content]}\"" + ) + end + else + current_submitted_challenge_answer.user.send_slack_message( + slack_message_content(commentor: current_user, challenge: current_submitted_challenge_answer.challenge, url: url) + ) + + current_submitted_challenge_answer.user.notify( + tagline: "Comment Left", + title: + "#{current_user.full_name} commented on #{block.title} - #{standard.title} - #{content_file.title} - #{challenge.title}", + url: cohort_user_challenge_url(current_cohort, current_submitted_challenge_answer.user, challenge), + description: "\"#{activity_content[:content]}\"" + ) + end + render json: ActivityPresenter.new(activity), status: 200 + else + render json: { error: activity.errors.full_messages }, status: 400 + end + end + + private + + def activity_content + params.require(:activity).permit(:content) + end + + def current_cohort + @current_cohort ||= CohortPolicy::Scope.new(current_user, Cohort).resolve.find_by!(id: params[:cohort_id]) + end + + def current_submitted_challenge_answer + @submitted_challenge_answer ||= SubmittedChallengeAnswer.find(params[:submitted_challenge_answer_id]) + end + + def slack_message_content(commentor:, challenge:, url:) + "#{commentor.full_name} left a comment on #{challenge.title}. Read it here: #{url}." + end + end + end +end diff --git a/app/controllers/cohorts/users/challenges_controller.rb b/app/controllers/cohorts/users/challenges_controller.rb new file mode 100644 index 0000000..ca13285 --- /dev/null +++ b/app/controllers/cohorts/users/challenges_controller.rb @@ -0,0 +1,182 @@ +module Cohorts + module Users + class ChallengesController < ApplicationController + def show + current_user.admin? ? skip_authorization : authorize(loaded_cohort_user, :view_user_challenges?) + + @html_title = "#{loaded_user.full_name}: #{current_challenge.title}" + + is_instructor_or_admin = current_user.instructor_or_admin?(current_cohort.id) + submitted_challenge_answers = SubmittedChallengeAnswerFinder.all_for_user_id_challenge_in_cohort(loaded_user.id, + current_challenge, + current_cohort.id). + where.not(status: SubmittedChallengeAnswer::UNGRADED_STATUSES[:draft]) + + activities = Activity.where( + subject_type: SubmittedChallengeAnswer.to_s, + name: Activity::NAMES[:comment_created], + subject_id: submitted_challenge_answers&.map(&:id) + ).order(created_at: :asc) + + render( + locals: { + challenge: ChallengeWithSubmittedChallengeAnswersPresenter.new( + current_cohort, + current_challenge, + submitted_challenge_answers, + is_instructor_or_admin, + user_context: loaded_user, + is_checkpoint: false + ), + block_id: current_release.block_id, + content_file_path: current_challenge.content_file.path, + content_file_position: current_challenge.content_file.position, + content_file_type: current_challenge.content_file.content_file_type, + activities: activities.order(created_at: :asc).map { |activity| ActivityPresenter.new(activity) }, + standard: StandardCardComponentProps.new(standard: current_challenge.standard), + loaded_user: loaded_user_details, + is_instructor_or_admin: is_instructor_or_admin, + cohort_id: current_cohort.id, + cohort_mode: current_cohort.mode, + previous_challenge_path: previous_challenge, + next_challenge_path: next_challenge + } + ) + end + + private + + def loaded_user_details + { id: loaded_user.id, profile_image: loaded_user.profile_image, full_name: loaded_user.full_name, initials: loaded_user.initials } + end + + def loaded_cohort_user + @loaded_cohort_user ||= CohortUser.find_by!(user_id: user_params[:user_id], cohort_id: cohort_params[:cohort_id]) + end + + def loaded_user + @loaded_user ||= UserPolicy::Scope.new(current_user, User).resolve.find(user_params[:user_id]) + end + + def current_cohort + @current_cohort ||= CohortPolicy::Scope.new(current_user, Cohort).resolve.find(cohort_params[:cohort_id]) + end + + def current_challenge + @current_challenge ||= Challenge.find(challenge_params[:id]) + end + + def current_standard + @current_standard ||= current_challenge.content_file.standard + end + + def current_release + @current_release ||= current_standard.release + end + + def current_cohort_release + @current_cohort_release ||= CohortRelease.joins(release: :standards). + where("cohort_releases.cohort_id = ? AND standards.uid = ?", current_cohort.id, current_standard.uid). + first + end + + def user_params + params.permit(:user_id) + end + + def cohort_params + params.permit(:cohort_id) + end + + def challenge_params + params.permit(:id) + end + + def previous_challenge + # first check in content file + previous_in_cf = SubmittedChallengeAnswerFinder.latest_for_user_id_content_file_for_cohort( + loaded_user.id, current_challenge.content_file, current_cohort.id + ).joins(:challenge). + where("challenges.position < ?", current_challenge.position). + order("challenges.position DESC").first + return previous_in_cf.review_path(cohort_id: current_cohort.id, user_id: loaded_user.id) if previous_in_cf.present? + + # check in previous content files in standard + previous_in_standard = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: :release]]). + where("submitted_challenge_answers.user_id = ?", loaded_user.id). + where("content_files.position < ?", current_challenge.content_file.position). + where("standards.uid = ?", current_standard.uid). + where("releases.block_id = ?", current_release.block_id). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + no_orphans. + order("content_files.position DESC, challenges.position DESC").first + return previous_in_standard.review_path(cohort_id: current_cohort.id, user_id: loaded_user.id) if previous_in_standard.present? + + # check in previous standards in block + previous_in_block = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: :release]]). + where("submitted_challenge_answers.user_id = ?", loaded_user.id). + where("standards.position < ?", current_standard.position). + where("releases.block_id = ?", current_release.block_id). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + no_orphans. + order("standards.position DESC, content_files.position DESC, challenges.position DESC").first + return previous_in_block.review_path(cohort_id: current_cohort.id, user_id: loaded_user.id) if previous_in_block.present? + + # check in previous blocks + previous_out_block = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: [release: :cohort_releases]]]). + where("submitted_challenge_answers.user_id = ?", loaded_user.id). + where("cohort_releases.position < ?", current_cohort_release.position). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + no_orphans. + order("cohort_releases.position DESC"). + order("standards.position DESC"). + order("content_files.position DESC"). + order("challenges.position DESC").first + return previous_out_block.review_path(cohort_id: current_cohort.id, user_id: loaded_user.id) if previous_out_block.present? + end + + def next_challenge + # first check in content file + next_in_cf = SubmittedChallengeAnswerFinder.latest_for_user_id_content_file_for_cohort( + loaded_user.id, current_challenge.content_file, current_cohort.id + ).joins(:challenge). + where("challenges.position > ?", current_challenge.position). + order("challenges.position ASC").first + return next_in_cf.review_path(cohort_id: current_cohort.id, user_id: loaded_user.id) if next_in_cf.present? + + # check in next content files in standard + next_in_standard = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: :release]]). + where("submitted_challenge_answers.user_id = ?", loaded_user.id). + where("content_files.position > ?", current_challenge.content_file.position). + where("standards.uid = ?", current_standard.uid). + where("releases.block_id = ?", current_release.block_id). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + no_orphans. + order("content_files.position ASC, challenges.position ASC").first + return next_in_standard.review_path(cohort_id: current_cohort.id, user_id: loaded_user.id) if next_in_standard.present? + + # check in next standards in block + next_in_block = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: :release]]). + where("submitted_challenge_answers.user_id = ?", loaded_user.id). + where("standards.position > ?", current_standard.position). + where("releases.block_id = ?", current_release.block_id). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + no_orphans. + order("standards.position ASC, content_files.position ASC, challenges.position ASC").first + return next_in_block.review_path(cohort_id: current_cohort.id, user_id: loaded_user.id) if next_in_block.present? + + # check in next blocks + next_out_block = SubmittedChallengeAnswer.joins(challenge: [content_file: [standard: [release: :cohort_releases]]]). + where("submitted_challenge_answers.user_id = ?", loaded_user.id). + where("cohort_releases.position > ?", current_cohort_release.position). + where("submitted_challenge_answers.cohort_id = ?", current_cohort.id). + no_orphans. + order("cohort_releases.position ASC"). + order("standards.position ASC"). + order("content_files.position ASC"). + order("challenges.position ASC").first + return next_out_block.review_path(cohort_id: current_cohort.id, user_id: loaded_user.id) if next_out_block.present? + end + end + end +end diff --git a/app/controllers/cohorts/users/performances_controller.rb b/app/controllers/cohorts/users/performances_controller.rb new file mode 100644 index 0000000..e5e34df --- /dev/null +++ b/app/controllers/cohorts/users/performances_controller.rb @@ -0,0 +1,41 @@ +module Cohorts + module Users + class PerformancesController < ApplicationController + def create + performances = [] + + performance_params["standard_ids"].each do |standard_id| + performance = Performance.new( + user: loaded_user, + updator: current_user, + score: performance_params["score"], + standard_id: standard_id, + cohort_id: current_cohort.id, + checkpoint_submission_id: performance_params["checkpoint_submission_id"] + ) + + authorize(performance) + + next unless performance.save + + performances << PerformancePresenter.new(performance) + end + render json: performances + end + + private + + def performance_params + params.require(:performance).permit(:checkpoint_submission_id, :score, standard_ids: []) + end + + def current_cohort + @current_cohort ||= Cohort.find(params[:cohort_id]) + end + + def loaded_user + @loaded_user ||= User.find(params[:user_id]) + end + end + end +end diff --git a/app/controllers/cohorts/users_controller.rb b/app/controllers/cohorts/users_controller.rb new file mode 100644 index 0000000..034a676 --- /dev/null +++ b/app/controllers/cohorts/users_controller.rb @@ -0,0 +1,154 @@ +module Cohorts + class UsersController < ApplicationController + def submissions_dashboard + authorize(loaded_cohort_user, :view_user_challenges?) + + @current_category = "submissions" + students = [{ + id: loaded_user.id, + avatar: loaded_user.profile_image, + name: loaded_user.full_name, + initials: loaded_user.initials, + challenges_path: submissions_dashboard_cohort_user_path(current_cohort, loaded_user.id) + }] + + submission_data = SubmissionsDashboardPresenter.submission_data( + cohort: current_cohort, + cohort_mode: current_cohort.mode, + student_ids: [loaded_user.id] + ) + + render( + locals: { + cohort_id: current_cohort.id, + students: students, + submission_data: submission_data, + progress_thresholds: current_cohort.settings["progress_thresholds"] + } + ) + end + + def curriculum_progress + authorize(current_cohort) + + cohort_releases = ReleaseFinder.for_cohort(current_cohort.id).includes(standards: [{ content_files: :challenges }]).to_a + + hidden_uids = ContentVisibility.where(cohort_id: current_cohort.id, content_type: "Standard").pluck(:content_uid) + all_standards = cohort_releases.flat_map(&:standards).reject {|s| hidden_uids.include?(s.uid) }.sort_by(&:position) + + all_checkpoints = CheckpointSubmission.where( + content_file_block_id: cohort_releases.map(&:block_id), + user_id: current_user.id, + cohort_id: current_cohort.id + ).where.not(state: CheckpointSubmission::STATES[:started]). + order(created_at: :desc).group_by(&:content_file_block_id) + + completion_data = StandardSubmissionsService.new(standard: nil, + cohort: current_cohort, + cohort_releases: cohort_releases, + standard_uids: all_standards.map(&:uid), + student_ids: [current_user.id], + all_checkpoints: all_checkpoints.values.flatten, + instructor_or_admin: current_user.instructor_or_admin?(current_cohort.id)).all_standards + + progress = {} + user_performances = PerformanceFinder.latest_for_standards_in_cohort(standard_uids: all_standards.map(&:uid), + user_ids: [current_user.id], + cohort_id: current_cohort.id). + includes(:checkpoint_submission). + to_a + + if current_cohort.mode == Cohort::MODES[:mastery] + progress = calc_progress(Cohort::MODES[:mastery], cohort_releases, user_performances, nil, current_user.id, nil, all_standards) + progress[:mastery_average] = MasteryAverageService.execute(user_performances&.select { |p| p.score > 0 }&.map(&:score)) + elsif current_cohort.mode == Cohort::MODES[:percentage] + lesson_visits = LessonVisit.visible_for(current_cohort.id). + where(user_id: current_user.id, block_id: cohort_releases.map(&:block_id)). + order(updated_at: :desc) + progress = calc_progress(Cohort::MODES[:percentage], + cohort_releases, nil, + completion_data, + current_user.id, + lesson_visits, + all_standards) + end + + cohort_has_checkpoints, user_has_checkpoints, user_has_scored_submission = checkpoint_presence(completion_data) + render json: { + checkpoint_average: user_has_scored_submission ? progress[:checkpoint_average] : nil, + user_has_checkpoints: user_has_checkpoints, + cohort_uses_checkpoints: cohort_has_checkpoints, + progress: progress.to_json + } + end + + private + + # checkpoint_presence returns three booleans + # the first is the presence of a checkpoint + # the second is the presence of a user's checkpoint + # the third is the presence of a done checkpoint + def checkpoint_presence(completion_data) + cohort_has_checkpoint = false + user_has_checkpoint_submission = false + user_has_scored_submission = false + + completion_data.each_key do |release_id| + completion_data[release_id].each_key do |standard_id| + checkpoint = completion_data[release_id][standard_id].find {|cf| cf[:content_file_type] == ContentFile::TYPES[:checkpoint] } + + if checkpoint + cohort_has_checkpoint = true + user_submission = checkpoint[:checkpoint_submissions][current_user.id] + + user_has_checkpoint_submission = true if user_submission.present? + user_has_scored_submission = true if user_submission && user_submission[:state] == CheckpointSubmission::STATES[:done] + end + end + end + [cohort_has_checkpoint, user_has_checkpoint_submission, user_has_scored_submission] + end + + def calc_progress(cohort_mode, _cohort_releases, performances, completion_data, user_id, lesson_visits, standards) + if cohort_mode == Cohort::MODES[:mastery] + return nil unless current_user.student_of?(current_cohort) && !performances.nil? + + CurriculumProgressService.new(performances: performances, user_id: user_id, standards: standards).execute_mastery + elsif cohort_mode == Cohort::MODES[:percentage] + return nil unless current_user.student_of?(current_cohort) && !completion_data.nil? + + CurriculumProgressService.new( + completion_data: completion_data, + user_id: user_id, + lesson_visits: lesson_visits, + standards: standards + ).execute_completion + end + end + + def set_segment_page_properties + @track_properties = { + productId: current_cohort.uid, + pageType: "Submissions", + cohortId: current_cohort.id, + cohortName: current_cohort.name, + blockId: nil, + blockName: nil, + unitId: nil, + unitName: nil + }.to_json + end + + def loaded_cohort_user + @loaded_cohort_user ||= CohortUser.find_by!(user_id: params[:id], cohort_id: params[:cohort_id]) + end + + def loaded_user + @loaded_user ||= UserPolicy::Scope.new(current_user, User).resolve.find(params[:id]) + end + + def current_cohort + @current_cohort ||= CohortPolicy::Scope.new(current_user, Cohort).resolve.find(params[:cohort_id]) + end + end +end diff --git a/app/controllers/cohorts_controller.rb b/app/controllers/cohorts_controller.rb new file mode 100644 index 0000000..9fce5da --- /dev/null +++ b/app/controllers/cohorts_controller.rb @@ -0,0 +1,463 @@ +class CohortsController < ApplicationController + before_action :handle_last_setup_visit, only: %i[setup users content partnerup] + + def index + authorize(Cohort) + @html_title = "Cohorts" + @page_type = "Cohorts" + + own_cohorts = current_user.cohorts.without_sandboxes.order(name: :asc) + all_other_cohorts = Cohort.all.without_sandboxes.where.not(id: own_cohorts.pluck(:id)).order(name: :asc) + cohort_types = own_cohorts.map(&:product_type).concat(all_other_cohorts.map(&:product_type)).uniq.compact.reject(&:blank?) + cohort_campuses = own_cohorts.map(&:campus_name).concat(all_other_cohorts.map(&:campus_name)).uniq.compact.reject(&:blank?) + cohort_id_counts = Hash[CohortUser.all.pluck(:cohort_id).group_by { |x| x }.map { |k, v| [k, v.count] }] + + render( + locals: { + all_other_cohorts: all_other_cohorts, + own_cohorts: own_cohorts, + cohort_id_counts: cohort_id_counts, + learn_base_url: ENV["LEARN_BASE_URL"], + cohort_types: cohort_types, + cohort_campuses: cohort_campuses, + new_auth_product_url: Rails.application.secrets.auth_url + "/admin/products/new" + } + ) + end + + def update + authorize(current_cohort) + + current_cohort.mode = update_cohort_params[:mode] if update_cohort_params[:mode] + if update_cohort_params.dig("settings", "progress_thresholds") + current_cohort.settings["progress_thresholds"] = update_cohort_params[:settings]["progress_thresholds"] + end + + flash = if current_cohort.save + { type: "success", msg: "successfully updated #{current_cohort.name}." } + else + { type: "error", msg: "could not update #{current_cohort.name}." } + end + + respond_to do |format| + format.json do |_f| + render json: { cohort: current_cohort, flash: flash } + end + end + end + + def users + authorize(current_cohort) + redirect_to content_cohort_path(current_cohort) and return if current_cohort.sandbox + @html_title = "Setup" + @page_type = "Setup" + @current_category = "setup" + release_count = CohortRelease.attached_to_cohort(current_cohort.id).count + + render( + locals: { + cohort: current_cohort, + release_count: release_count, + auth_product_url: Rails.application.secrets.auth_url + "/admin/products/#{current_cohort.uid}" + } + ) + end + + def content + authorize(current_cohort) + @html_title = "Setup" + @page_type = "Setup" + @current_category = "setup" + + cohort_releases_by_sections, visibilities = current_cohort.current_curriculum + + render( + locals: { + cohort: current_cohort, + release_count: cohort_releases_by_sections.values.flatten.count, + sections: cohort_releases_by_sections.map do |k, releases| + CohortSetup::Visibility.new( + section: k, + releases_in_sections: releases, + cohort_id: current_cohort.id, + visibilities: visibilities + ) + end, + auth_product_url: Rails.application.secrets.auth_url + "/admin/products/#{current_cohort.uid}" + } + ) + end + + def show + authorize(current_cohort) + @html_title = current_cohort.name + @page_type = "Curriculum Overview" + @current_category = "curriculum" + @titles_filter = params[:'standard-names']&.split("|") + @body_class = "-with-curriculum-subset-notice" if @titles_filter + + cohort_cohort_releases = CohortRelease.attached_to_cohort(current_cohort.id) + cohort_releases = Release.where(id: cohort_cohort_releases.map(&:release_id)). + includes(standards: [{ content_files: :challenges }]).to_a + + all_standards = cohort_releases.map do |cohort_release| + cohort_release.standards.visible_for(current_cohort.id) + end.flatten.sort_by(&:position) + + visible_content_files = ContentFile.unscoped.where(standard_id: all_standards.map(&:id)) + hidden_content_files = ContentVisibility.where( + cohort: current_cohort, + content_type: "ContentFile", + content_uid: visible_content_files.pluck(:uid) + ) + hidden_content_file_count = hidden_content_files.count + content_file_count = visible_content_files.count + + sections = cohort_cohort_releases. + group_by {|cr| cr.section}. + reject {|section, _crs| section.nil?}. # Legacy data might not have a section + to_a. + sort_by {|section, _crs| section.position}. + map do |section, crs| + release_ids = crs.sort_by(&:position).map(&:release_id) + standards_in_section = all_standards.select { |s| release_ids.include?(s.release_id) } + standards = standards_in_section.reject do |standard| + cfs = visible_content_files.select { |cf| cf.standard_id == standard.id } + cf_count = (cfs.map(&:uid) - hidden_content_files.map(&:content_uid)).count + cf_count == 0 + end + section.as_json.merge( + release_ids: release_ids, + standard_count: standards.count, + standardsForSectionPath: cohort_standards_for_section_path(current_cohort, section.id) + ) + end + + warning_message = no_curriculum_warning( + sections.empty?, + all_standards.empty?, + hidden_content_file_count, + content_file_count + ) + + welcome_to_learn = welcome_to_learn( + sections.empty?, + all_standards.empty?, + hidden_content_file_count, + content_file_count + ) + + last_viewed_presenter = nil + last_viewed_lesson_visit = LessonVisit.visible_for(current_cohort.id). + where(user_id: current_user.id, block_id: cohort_releases.map(&:block_id)). + order(updated_at: :desc).first + unless all_standards.empty? || warning_message != "" || welcome_to_learn + last_viewed_presenter = ContentFilePresenter::ForCurriculumLastViewed.new( + cohort: current_cohort, + standards: all_standards, + last_viewed_lesson_visit: last_viewed_lesson_visit + ) + end + + render( + locals: { + curriculum_progress_path: curriculum_progress_cohort_user_path(current_cohort, current_user), + current_cohort: current_cohort, + content_view: last_viewed_presenter, + sections: sections, + warning_message: warning_message, + warning_count: current_user.instructor_or_admin?(current_cohort.id) ? cohort_releases.flat_map(&:sync_warnings).length : 0, + welcome_to_learn: welcome_to_learn + } + ) + end + + def error + authorize(current_cohort) + + cohort_cohort_releases = CohortRelease.attached_to_cohort(current_cohort.id) + cohort_releases = Release.where(id: cohort_cohort_releases.map(&:release_id)).to_a + + render( + locals: { + warnings: cohort_releases.flat_map(&:sync_warnings).map do |warning| + { warning: warning, anchor: warning.split(":").first } + end + } + ) + end + + def standards_for_section + authorize(current_cohort) + + cohort_cohort_releases = CohortRelease. + attached_to_cohort(current_cohort.id). + where(section_id: params[:section_id]). + includes(release: [standards: [{ content_files: :challenges }]]). + to_a + + cohort_releases = cohort_cohort_releases.map(&:release) + hidden_uids = ContentVisibility.where(cohort_id: current_cohort.id, content_type: "Standard").pluck(:content_uid) + all_standards = cohort_releases.flat_map do |cohort_release| + cohort_release.standards + end.reject {|s| hidden_uids.include?(s.uid) }.sort_by(&:position) + + user_performances = nil + if current_cohort.mode == Cohort::MODES[:mastery] + user_performances = PerformanceFinder.latest_for_standards_in_cohort(standard_uids: all_standards.map(&:uid), + user_ids: [current_user.id], + cohort_id: current_cohort.id).to_a + end + lesson_visits = LessonVisit.visible_for(current_cohort.id). + where(user_id: current_user.id, block_id: cohort_releases.map(&:block_id)). + order(updated_at: :desc) + + all_checkpoints = CheckpointSubmission.includes(submitted_challenge_answers: :challenge).where( + content_file_block_id: cohort_releases.map(&:block_id), + user_id: current_user.id, + cohort_id: current_cohort.id + ).where.not(state: CheckpointSubmission::STATES[:started]). + order(created_at: :desc) + + instructor_or_admin = current_user.instructor_or_admin?(current_cohort.id) + + progress_data = StandardSubmissionsService.new(standard: nil, + cohort: current_cohort, + cohort_releases: cohort_releases, + standard_uids: all_standards.map(&:uid), + student_ids: [current_user.id], + all_checkpoints: all_checkpoints, + instructor_or_admin: instructor_or_admin).all_standards + + cohort_releases = cohort_releases.map do |release| + standards = all_standards.select { |s| s.release_id == release.id } + standards.map do |standard| + cohort_standard_progress_data = progress_data[release.id][standard.id] if progress_data + StandardPresenter::ForStandardCard.new( + standard: standard, + cohort: current_cohort, + user_performances: user_performances, + cohort_standard_progress_data: cohort_standard_progress_data || [], + current_user_id: current_user.id, + instructor_or_admin: instructor_or_admin, + lesson_visits: lesson_visits, + checkpoint_submissions: all_checkpoints + ) + end + end + render json: cohort_releases.to_json + end + + def setup + authorize(current_cohort) + redirect_to content_cohort_path(current_cohort) and return if current_cohort.sandbox + @html_title = "Setup" + @page_type = "Setup" + @current_category = "setup" + + available_blocks = BlockFinder.available_blocks_for(current_cohort.id). + includes(releases: { cohort_releases: :cohort }). + uniq + + render( + locals: { + resync_job: ResyncJobResult.find_latest(current_cohort.id), + successful_resync_job: ResyncJobResult.find_latest_successful(current_cohort.id), + all_available_blocks: available_blocks.map { |block| BlockPresenter::ForBlock.new(block: block) }, + cohort: current_cohort, + cohort_releases: + CohortRelease.attached_to_cohort(current_cohort.id).map do |cohort_release| + CohortReleasePresenter::ForCohortSetup.new(cohort_release: cohort_release) + end, + unattached_block_release_options: + ReleaseFinder.latest_for_all_blocks_not_attached_to_cohort(current_cohort.id), + auth_product_url: Rails.application.secrets.auth_url + "/admin/products/#{current_cohort.uid}" + } + ) + end + + def partnerup + authorize(current_cohort) + redirect_to content_cohort_path(current_cohort) and return if current_cohort.sandbox + if current_cohort.students.count > 500 + flash[:error] = "Partnerup disabled for cohorts over 500." + redirect_to content_cohort_path(current_cohort) and return + end + + @html_title = "Setup" + @page_type = "Setup" + @current_category = "setup" + release_count = CohortRelease.attached_to_cohort(current_cohort.id).count + + render( + locals: { + cohort: current_cohort, + students: current_cohort.cohort_users.student.map { |s| { uid: s.user.uid, name: s.user.full_name, profile_image: s.user.profile_image }}, + release_count: release_count, + pairings: Pairing.where(cohort_id: current_cohort.id).order(created_at: :desc), + auth_product_url: "#{Rails.application.secrets.auth_url}/admin/products/#{current_cohort.uid}" + } + ) + end + + def unit_progress + authorize(current_cohort, :submissions_dashboard?) + redirect_to cohort_path(current_cohort) and return if current_cohort.sandbox + @html_title = "Unit Progress" + @page_type = "Unit Progress" + @current_category = "submissions" + + students = nil + submission_data = nil + if current_cohort.students.length < 500 + student_ids = [] + students = current_cohort.students.order(:first_name).map do |student| + student_ids.push(student.id) + { + id: student.id, + avatar: student.profile_image, + name: student.full_name, + initials: student.initials, + cohort_reg_date: student.cohort_users.where(cohort_id: current_cohort.id).first&.created_at, + challenges_path: submissions_dashboard_cohort_user_path(current_cohort, student.id) + } + end + + submission_data = SubmissionsDashboardPresenter.submission_data( + cohort: current_cohort, + cohort_mode: current_cohort.mode, + student_ids: student_ids + ) + end + + render( + locals: { + cohort_mode: current_cohort.mode, + cohort_id: current_cohort.id, + students: students, + submission_data: submission_data, + progress_thresholds: current_cohort.settings["progress_thresholds"] + } + ) + end + + def activity_dashboard + authorize(current_cohort) + redirect_to cohort_path(current_cohort) and return if current_cohort.sandbox + @html_title = "Activity Dashboard" + @page_type = "Activity" + @current_category = "activity" + + respond_to do |format| + format.html do + render locals: { + url: activity_dashboard_cohort_path(current_cohort, format: :json), + student_count: current_cohort.students.count + } + end + format.json do + render json: ActivityAggregatorService.new(current_cohort, params[:offset], current_user.timezone).execute + end + end + end + + def course_stats + authorize(current_cohort, :progress?) + redirect_to cohort_path(current_cohort) and return if current_cohort.sandbox + @html_title = "Course Stats" + @page_type = "Course Stats" + @current_category = "progress" + + students = nil + if current_cohort.id == 1974 || current_cohort.students.length < 100 + students = CohortStudentProgressService.new(current_cohort).students + end + + render( + locals: { + students: students, + cohort_mode: current_cohort.mode, + progress_thresholds: current_cohort.settings["progress_thresholds"], + cohort_id: current_cohort.id + } + ) + end + + def course_stats_csv + authorize(current_cohort, :progress?) + if current_cohort.id == 1974 + ProgressCsvJob.perform_later(current_cohort.id, current_user.id, false) + else + ProgressCsvJob.perform_later(current_cohort.id, current_user.id, current_cohort.students.length > 100) + end + render json: { ack: "ok" } + end + + def import_users_work + authorize(current_cohort) + ImportStudentWorkJob.perform_later(current_cohort.id, current_user.email) + render json: { ack: "ok" } + end + + def import_users_work_status + authorize(current_cohort) + render json: { student_import_state: current_cohort.student_import_state, + student_import_completed_at: current_cohort.student_import_completed_at } + end + + def feed + authorize(current_cohort) + + render( + locals: { + activities: Activity. + where(cohort: current_cohort). + order(created_at: :desc). + first(50). + map { |activity| ActivityFeedItemComponentProps.execute(activity) }, + current_cohort: current_cohort + } + ) + end + + private + + def current_cohort + @current_cohort ||= params[:cohort_id].present? ? Cohort.find(params[:cohort_id]) : Cohort.find(params[:id]) + end + + def update_cohort_params + params.require(:cohort).permit(:mode, settings: [progress_thresholds: []]) + end + + def handle_last_setup_visit + cohort_user = CohortUser.where(cohort_id: current_cohort.id, user_id: current_user.id).first + return unless cohort_user + + unless cohort_user.last_setup_visit == request.original_fullpath + cohort_user.update(last_setup_visit: request.original_fullpath) + end + end + + def no_curriculum_warning(no_sections, no_standards, hidden_content_file_count, content_file_count) + if current_user.instructor_or_admin?(current_cohort.id) + if no_sections + "It looks like you don't have any curriculum yet. To fix this you can add your Curriculum #{view_context.link_to 'here', setup_cohort_path(current_cohort)}." + elsif no_standards || hidden_content_file_count == content_file_count + "It looks the curriculum is not visible yet. To fix this you can #{view_context.link_to 'change visibility here', content_cohort_path(current_cohort)}." + else + "" + end + else + "" + end + end + + def welcome_to_learn(no_sections, no_standards, hidden_content_file_count, content_file_count) + no_content = no_sections || no_standards || hidden_content_file_count == content_file_count + + return true if !current_user.instructor_or_admin?(current_cohort.id) && no_content + + false + end +end diff --git a/app/controllers/concerns/api/content_visibility_crud.rb b/app/controllers/concerns/api/content_visibility_crud.rb new file mode 100644 index 0000000..ec247f8 --- /dev/null +++ b/app/controllers/concerns/api/content_visibility_crud.rb @@ -0,0 +1,58 @@ +module Api::ContentVisibilityCrud + extend ActiveSupport::Concern + + included do + def handle_param_logic(content_type, the_params) + result = :error + + return :not_found if content_type.constantize.where(uid: params[:id]).empty? + return :not_found unless exists_in_requested_cohort?(content_type) + + if the_params.key?(:visible) + result = create_content_visibility(content_type, "hidden") if the_params[:visible].to_s == "false" + result = destroy_content_visibility(content_type, "hidden") if the_params[:visible].to_s == "true" + end + + if the_params.key?(:optional) + result = create_content_visibility(content_type, "optional") if the_params[:optional].to_s == "false" + result = destroy_content_visibility(content_type, "optional") if the_params[:optional].to_s == "true" + end + + result + end + + def exists_in_requested_cohort?(content_type) + if content_type == "Standard" + StandardFinder.all_for_cohort(cohort_id: params[:cohort_id]).map(&:uid) + elsif content_type == "ContentFile" + ContentFileFinder.all_from_cohort_block(cohort_id: params[:cohort_id], + block_id: params[:block_id]).map(&:uid) + end.include?(params[:id]) + end + + def create_content_visibility(content_type, visibility_type) + cv = ContentVisibility.new(cohort_id: params[:cohort_id], + content_uid: params[:id], + content_type: content_type, + visibility_type: visibility_type) + if cv.save + :ok + else + :error + end + rescue ActiveRecord::RecordNotUnique + :ok + end + + def destroy_content_visibility(content_type, visibility_type) + cv = ContentVisibility.find_by(cohort_id: params[:cohort_id], + content_uid: params[:id], + content_type: content_type, + visibility_type: visibility_type) + + cv&.destroy + + :ok + end + end +end diff --git a/app/controllers/concerns/api/exception_handler.rb b/app/controllers/concerns/api/exception_handler.rb new file mode 100644 index 0000000..0cfa71b --- /dev/null +++ b/app/controllers/concerns/api/exception_handler.rb @@ -0,0 +1,75 @@ +module Api::ExceptionHandler + extend ActiveSupport::Concern + + CODES = { + # routing layer + not_allowed: { status: 405, title: "Method not allowed" }, + invalid_version: { status: 410, title: "The requested API version no longer exists" }, + + # controller layer + unauthorized: { status: 401, title: "You do not have access to the requested resource" }, + unauthenticated: { status: 403, title: "You are not authenticated for this request" }, + not_found: { status: 404, title: "The requested resource could not be found" }, + malformed_params: { status: 422, title: "The params provided are malformed" }, + missing_param: { status: 422, title: "A required param was not provided" }, + invalid_resource: { status: 422, title: "The current resource was invalid", messages: [], attrs: {} }, + server_error: { status: 500, title: "An unknown error has occurred" } + } + + included do + rescue_from ActiveRecord::RecordNotFound, with: :not_found_response + rescue_from Aws::S3::Errors::NoSuchKey, with: :not_found_response + + rescue_from BlockParser::ConvertMdToJson::MdParseError, with: :unprocessable_entity_response + rescue_from BlockParser::BuildHtmlFromMdJson::InvalidMdError, with: :unprocessable_entity_response + rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity_response + + rescue_from Pundit::NotAuthorizedError do |_e| + json_response( + { + "errors": { + "status": "401", + "title": "You are not authenticated for this request", + "code": "unauthorized" + } + }, :unauthorized + ) + end + + def not_found_response + json_response({ message: "The requested resource could not be found" }, :not_found) + end + + def unprocessable_entity_response + json_response({ message: "The current resource was invalid" }, :unprocessable_entity) + end + + def render_method_not_allowed_response + json_response( + { + "errors": { + "status": "405", + "title": "Method not allowed", + "code": "not_allowed" + } + }, :method_not_allowed + ) + end + end + + class_methods do + def rack_response(code, options = {}) + status, json = error_options(code, options) + [status, { "Content-Type" => "application/json" }, [json.to_json]] + end + + def error_options(code, options = {}) + hash = (CODES[code] || CODES[:server_error]).merge(options) + hash[:code] ||= code + hash[:status] ||= 500 + status = hash[:status] + hash[:status] = hash[:status].to_s + [status, { errors: hash }] + end + end +end diff --git a/app/controllers/concerns/api/response.rb b/app/controllers/concerns/api/response.rb new file mode 100644 index 0000000..acac2d2 --- /dev/null +++ b/app/controllers/concerns/api/response.rb @@ -0,0 +1,5 @@ +module Api::Response + def json_response(object, status = :ok) + render json: object, status: status + end +end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb new file mode 100644 index 0000000..8bcebc6 --- /dev/null +++ b/app/controllers/home_controller.rb @@ -0,0 +1,28 @@ +class HomeController < ApplicationController + skip_before_action :require_signed_in_user, only: :hello + + def hello + skip_authorization + render plain: "Hello!" + end + + def index + authorize(current_user, :show?) + last_viewed_cohort_id = current_user.last_viewed_cohort_id + + path = if session[:requested_path].present? + requested_path = session[:requested_path] + session[:requested_path] = nil + requested_path + elsif last_viewed_cohort_id && current_user.student_instructor_or_admin_of?(last_viewed_cohort_id) + current_user.update(last_viewed_cohort_id: nil) + cohort_path(last_viewed_cohort_id) + elsif current_user.cohorts.any? + cohort_path(CohortUser.where(user: current_user).order(:created_at).last.cohort_id) + elsif current_user.admin? + cohorts_path + end + + redirect_to path if path.present? + end +end diff --git a/app/controllers/notifications_controller.rb b/app/controllers/notifications_controller.rb new file mode 100644 index 0000000..37896e7 --- /dev/null +++ b/app/controllers/notifications_controller.rb @@ -0,0 +1,32 @@ +class NotificationsController < ApplicationController + def index + authorize(Notification) + + render_notifications_json + end + + def show + authorize(current_notification) + + current_notification.update(read_at: DateTime.current) unless current_notification.read_at? + + redirect_to(current_notification.url) + end + + def mark_all_read + authorize(Notification) + policy_scope(Notification).unread.update_all(read_at: DateTime.current) + + render_notifications_json + end + + private + + def render_notifications_json + render json: NotificationsComponentProps.execute(current_user.id) + end + + def current_notification + @current_notification ||= policy_scope(Notification).find(params[:id]) + end +end diff --git a/app/controllers/permalinks_controller.rb b/app/controllers/permalinks_controller.rb new file mode 100644 index 0000000..6001e7e --- /dev/null +++ b/app/controllers/permalinks_controller.rb @@ -0,0 +1,85 @@ +class PermalinksController < ApplicationController + def permalink + authorize(current_user, :show?) + + block = Block.find_by(repo_name: params[:repo_name]) + error_404 and return if block.nil? + + if referer_cohort && referer_cohort_contains_content? + redirect_to content_file_path(referer_cohort, block.id, params[:path]) and return + end + + if all_content_file_paths.count == 1 && !referer_cohort + redirect_to all_content_file_paths.first[:path] and return + end + return error_404 if all_content_file_paths.count == 0 + + cohort_count = all_content_file_paths.length + render(locals: { + fullpath: "#{request.protocol}#{request.host_with_port}#{request.fullpath}", + header: cohort_count == 1 ? "Switch cohorts" : "Choose a Cohort", + paragraph: if cohort_count == 1 + " a different cohort. Click the link below to go where it is located." + + else + " multiple cohorts. Select the cohort where you'd like to open it." + end, + all_content_file_paths: all_content_file_paths + }) + end + + private + + def referer_cohort_contains_content? + @referer_cohort_contains_content ||= !content_file_paths_for_cohorts([referer_cohort.id]).empty? + end + + def referer_cohort + @referer_cohort ||= if request.referer && referer_is_from_learn + path = request.referer[root_url.length..-1] + path = path[1..path.length - 1] if path[0] == "/" + path.start_with?("cohorts/") ? Cohort.find(path.split("/")[1].to_i) : nil + end + end + + def referer_is_from_learn + return false if request.referer.nil? + + request_url = request.referer[0..root_url.length - 1] + request_url == root_url + end + + def block + @block ||= Block.find_by(repo_name: params[:repo_name]) + end + + def cohorts_using_block + @cohorts_using_block ||= Cohort.joins(cohort_releases: :release).where("releases.block_id = ?", block.id) + end + + def users_cohorts_using_block + @users_cohorts_that_have_block ||= cohorts_using_block.where(id: current_user.cohort_users.map(&:cohort_id)) + end + + def cohorts + @cohorts ||= current_user.admin? ? cohorts_using_block : users_cohorts_using_block + end + + def all_content_file_paths + return @all_content_files if @all_content_files + + all_content_files = content_file_paths_for_cohorts(cohorts.map(&:id)) + + @all_content_files = all_content_files.map do |cf| + { name: cohorts.find { |c| c.id == cf.cohort_id }.name, path: content_file_path(cf.cohort_id, block.id, cf.path) } + end.uniq.sort { |a, b| a[:name] <=> b[:name] } + end + + def content_file_paths_for_cohorts(cohort_ids) + ContentFile.unscoped.select("cohort_releases.cohort_id AS cohort_id, path"). + joins(standard: { release: :cohort_releases }). + where("releases.block_id = ?", block.id). + where("cohort_id IN (?)", cohort_ids). + where("path = ?", params[:path]) + end +end diff --git a/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb b/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb new file mode 100644 index 0000000..8eedbb0 --- /dev/null +++ b/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb @@ -0,0 +1,120 @@ +module Webhooks + module AssessmentsService + class SubmittedChallengeAnswersController < ActionController::Base + class InvalidChallengeTypeEvaluation < StandardError + end + + def update + if params[:token] == Rails.application.secrets.assessments_callback_token + submitted_challenge_answer = SubmittedChallengeAnswer.find(params[:id]) + + if params[:status_only] + submitted_challenge_answer.update(status: params[:status]) + head :ok, content_type: "application/json" and return + end + + if submitted_challenge_answer.challenge.code_snippet? || + submitted_challenge_answer.challenge.testable_project? || + submitted_challenge_answer.challenge.custom_snippet? + unless submitted_challenge_answer.status == "canceled" + results = params[:results] + status = params[:status] + correct = status == SubmittedChallengeAnswer::STATUSES[:correct] + points = correct ? submitted_challenge_answer.challenge.points : 0 + submitted_challenge_answer.update_attributes( + status: status, + test_results: results, + points: points, + assessment_ran_at: params[:assessment_ran_at] + ) + cohort = Cohort.find_by(id: params[:cohort_id]) + + checkpoint_submission = submitted_challenge_answer.checkpoint_submission + if checkpoint_submission.present? + CheckpointSubmissionService.new(checkpoint_submission, + submitted_challenge_answer.challenge.content_file, + cohort, + submitted_challenge_answer.user).attempt_autoscore + else + # Alert when challenge takes a while + slack_performance_delays(submitted_challenge_answer) + + Activity.create( + cohort_id: params[:cohort_id], + subject: submitted_challenge_answer, + name: Activity::NAMES[:submitted_challenge_answer_evaluated] + ) + end + end + else + Honeybadger.notify( + InvalidChallengeTypeEvaluation.new("An invalid challenge type was submitted to the assessments service for evaluation.") + ) + end + + head :ok, content_type: "application/json" + else + head :unauthorized, content_type: "application/json" + end + rescue ActiveRecord::RecordNotFound + head :not_found, content_type: "application/json" + end + + private + + def slack_performance_delays(submitted_challenge_answer) + length_of_scoring = submitted_challenge_answer.updated_at.to_i - submitted_challenge_answer.created_at.to_i + content_file_url = content_file_url(submitted_challenge_answer.cohort_id, + submitted_challenge_answer.block_id, + submitted_challenge_answer.challenge.content_file.path) + + if submitted_challenge_answer.challenge.code_snippet? + if length_of_scoring > 10 && submitted_challenge_answer.challenge.language != "java" + SlackChallengePerformanceJob.perform_later(submitted_challenge_answer.id, content_file_url, length_of_scoring) + elsif length_of_scoring > 20 && submitted_challenge_answer.challenge.language == "java" + SlackChallengePerformanceJob.perform_later(submitted_challenge_answer.id, content_file_url, length_of_scoring) + end + + elsif submitted_challenge_answer.challenge.testable_project? + if length_of_scoring > 80 + SlackChallengePerformanceJob.perform_later(submitted_challenge_answer.id, content_file_url, length_of_scoring) + end + + elsif submitted_challenge_answer.challenge.custom_snippet? + if length_of_scoring > 60 + SlackChallengePerformanceJob.perform_later(submitted_challenge_answer.id, content_file_url, length_of_scoring) + end + end + end + + def segment_track_challenge_submitted(sca, cohort) + return if Rails.env.test? + + standard = sca.challenge.content_file.standard + block = standard.release.block + Analytics.track( + user_id: sca.user.uid, + event: "Challenge Submitted", + properties: { + pageType: sca.challenge.content_file.content_file_type.titleize, + path: Rails.application.routes.url_helpers.content_file_path(cohort, block, sca.challenge.content_file.path), + title: sca.challenge.content_file.title, + productId: cohort.uid, + cohortId: cohort.id, + cohortName: cohort.name, + blockId: block.id, + blockName: block.title, + unitId: standard.id, + unitUId: standard.uid, + unitName: standard.title, + challengeID: sca.challenge.id, + challengeUID: sca.challenge.uid, + challengeName: sca.challenge.title, + challengeType: sca.challenge.challenge_type, + challengeStatus: sca.status + } + ) + end + end + end +end diff --git a/app/controllers/webhooks/auth/cohorts_controller.rb b/app/controllers/webhooks/auth/cohorts_controller.rb new file mode 100644 index 0000000..c51cc92 --- /dev/null +++ b/app/controllers/webhooks/auth/cohorts_controller.rb @@ -0,0 +1,34 @@ +module Webhooks + module Auth + class CohortsController < ActionController::Base + def update + cohort = AuthResolverService.resolve(request.env[:resolved_resource]) + + if cohort.nil? + error_message = "Provided cohort is not compatible with Learn V2" + results = { cohort: cohort_params, errors: [error_message] } + + Honeybadger.notify( + error_message, + context: { auth_resource: request.env[:resolved_resource] } + ) + + render json: { errors: results }, status: 400 + elsif cohort.errors.empty? + render json: {}, status: 200 + else + results = { cohort: cohort_params, errors: cohort.errors.full_messages } + + Honeybadger.notify("Invalid cohort payload", context: results) + render json: { errors: results }, status: 400 + end + end + + private + + def cohort_params + params.require(:data).permit! + end + end + end +end diff --git a/app/controllers/webhooks/auth/users_controller.rb b/app/controllers/webhooks/auth/users_controller.rb new file mode 100644 index 0000000..866f006 --- /dev/null +++ b/app/controllers/webhooks/auth/users_controller.rb @@ -0,0 +1,24 @@ +module Webhooks + module Auth + class UsersController < ActionController::Base + def update + user = AuthResolverService.resolve(request.env[:resolved_resource]) + + if user.errors.empty? + render json: {}, status: 200 + else + results = { user: user_params, errors: user.errors.full_messages } + + Honeybadger.notify("Invalid user payload", context: results) + render json: { errors: results }, status: 400 + end + end + + private + + def user_params + params.require(:data).permit! + end + end + end +end diff --git a/app/exporters/performance_exporter.rb b/app/exporters/performance_exporter.rb new file mode 100644 index 0000000..548dc80 --- /dev/null +++ b/app/exporters/performance_exporter.rb @@ -0,0 +1,55 @@ +require "csv" + +class PerformanceExporter + def initialize(cohort) + @cohort = cohort + end + + def to_csv + headers = [ + "Cohort Title", + "Cohort Label", + "Block Title", + "Standard" + ] + + users = User.where(id: [@cohort.student_ids]).sort_by(&:full_name) + headers += users.map(&:full_name) + + standards = [] + performances = {} + + ReleaseFinder.for_cohort(@cohort.id).includes(%i[block standards]).each do |release| + standards.concat(release.standards) + + release_performances = PerformanceFinder.latest_for_cohort_release(cohort_id: @cohort.id, release: release) + + release.standards.each do |standard| + release_performances.where("standards.uid = ?", standard.uid).each do |performance| + performances[standard.id] ||= {} + performances[standard.id][performance.user_id] = performance.score + end + end + end + + CSV.generate do |csv| + csv << headers + standards.each do |standard| + block = standard.release.block + + row = [ + @cohort.name, + @cohort.label, + block.title, + standard.description + ] + + row += users.map do |user| + performances.fetch(standard.id) { {} }.fetch(user.id) { 0 } + end + + csv << row + end + end + end +end diff --git a/app/exporters/progress_exporter.rb b/app/exporters/progress_exporter.rb new file mode 100644 index 0000000..54c92e4 --- /dev/null +++ b/app/exporters/progress_exporter.rb @@ -0,0 +1,107 @@ +require "csv" + +# ProgressExporter is coupled to the data format form StudentProgressPresenter +class ProgressExporter + def initialize(students_data, cohort_mode, cohort_id) + @students_data = students_data + @cohort_mode = cohort_mode + @cohort_id = cohort_id + end + + def topics + @topics ||= @students_data.map(&:progress_percentages).map { |pp| pp[:topic_averages] }.map(&:keys).flatten.uniq + end + + def topic_headers + topics.map { |t| ["topic:#{t}_pts_earned", "topic:#{t}_pts_avail", "topic:#{t}_percent"]}.flatten + end + + def student_topics(student) + student_topics = topics.map do |t| + topic_avg = student.progress_percentages[:topic_averages][t] + if topic_avg + [topic_avg[:earned], topic_avg[:possible], topic_avg[:average]] + else + [nil, nil, nil] + end + end.flatten + student_totals = student.progress_percentages[:totals] + [student_totals[:total_earned], student_totals[:total_possible], student_totals[:percent]] + student_topics + end + + def headers + if @cohort_mode == Cohort::MODES[:percentage] + [ + "student", + "email", + "link to student submission page in Learn", + "link to student contact in sfdc", + "preferred campus", + "timestamp of last activity", + "sign up date", + "checkpoint avg", + "% complete", + "course_points_earned", + "course_points_avail", + "course_points_percent" + ] + topic_headers + elsif @cohort_mode == Cohort::MODES[:mastery] + [ + "student", + "email", + "link to student submission page in Learn", + "link to student contact in sfdc", + "preferred campus", + "timestamp of last activity", + "sign up date", + "% 3s", + "% 2s", + "% 1s", + "% complete", + "avg" + ] + end + end + + def to_csv + sfdc_url = if Rails.env != "production" + "https://test.salesforce.com/" + else + "https://login.salesforce.com/" + end + CSV.generate do |csv| + csv << headers + @students_data.each do |s| + row = if @cohort_mode == Cohort::MODES[:percentage] + [ + s.full_name, + s.email, + "https://learn-2.galvanize.com#{s.performances_url}", + "#{sfdc_url}#{s.uid}", + s.preferred_campus.to_s, + s.latest_activity, + s.registration_date, + "#{s.progress_percentages[:checkpoint_average]} %", + "#{s.progress_percentages[:percent_completed]} %" + ] + student_topics(s) + elsif @cohort_mode == Cohort::MODES[:mastery] + [ + s.full_name, + s.email, + "https://learn-2.galvanize.com#{s.performances_url}", + "#{sfdc_url}#{s.uid}", + s.preferred_campus.to_s, + s.latest_activity, + s.registration_date, + "#{s.score_percentages[:three]} %", + "#{s.score_percentages[:two]} %", + "#{s.score_percentages[:one]} %", + "#{s.progress_percentages[:three]} %", + s.average + ] + end + csv << row + end + end + end +end diff --git a/app/finders/block_finder.rb b/app/finders/block_finder.rb new file mode 100644 index 0000000..31ead2a --- /dev/null +++ b/app/finders/block_finder.rb @@ -0,0 +1,7 @@ +class BlockFinder + def self.available_blocks_for(cohort_id) + attached_release_ids = CohortRelease.where(cohort_id: cohort_id).pluck(:release_id) + block_ids = Release.where.not(id: attached_release_ids).pluck(:block_id).uniq + Block.where(id: block_ids).order(:title) + end +end diff --git a/app/finders/checkpoint_submission_finder.rb b/app/finders/checkpoint_submission_finder.rb new file mode 100644 index 0000000..e4ab94b --- /dev/null +++ b/app/finders/checkpoint_submission_finder.rb @@ -0,0 +1,74 @@ +class CheckpointSubmissionFinder + # rubocop:disable Metrics/LineLength + def self.latest_two_by_state_by_standards(cohort_id:, student_ids:, standard_uids:, block_ids:) + firsts = CheckpointSubmission.includes(submitted_challenge_answers: :challenge). + select("DISTINCT ON (checkpoint_submissions.user_id, checkpoint_submissions.state, standards.uid) checkpoint_submissions.*, standards.uid"). + joins(submitted_challenge_answers: { challenge: { content_file: :standard } }). + where("checkpoint_submissions.cohort_id = ? AND content_file_block_id IN (?) AND standards.uid IN (?) AND checkpoint_submissions.user_id IN (?)", + cohort_id, + block_ids, + standard_uids, + student_ids). + where.not(state: "started"). + order("checkpoint_submissions.user_id, checkpoint_submissions.state, standards.uid, created_at DESC") + seconds = CheckpointSubmission.includes(submitted_challenge_answers: :challenge). + select("DISTINCT ON (checkpoint_submissions.user_id, checkpoint_submissions.state, standards.uid) checkpoint_submissions.*, standards.uid"). + joins(submitted_challenge_answers: { challenge: { content_file: :standard } }). + where("checkpoint_submissions.cohort_id = ? AND content_file_block_id IN (?) AND standards.uid IN (?) AND checkpoint_submissions.user_id IN (?) AND checkpoint_submissions.id NOT IN (?)", + cohort_id, + block_ids, + standard_uids, + student_ids, + firsts.map(&:id)). + where.not(state: "started"). + order("checkpoint_submissions.user_id, checkpoint_submissions.state, standards.uid, created_at DESC") + firsts + seconds + end + # rubocop:enable Metrics/LineLength + + def self.lastest_by_state_for_students_in_cohort(cohort_id:, block_id:, content_file_uid:, student_ids:) + CheckpointSubmission. + select("DISTINCT ON (user_id, state) checkpoint_submissions.*"). + where("cohort_id = ? AND content_file_block_id = ? AND content_file_uid = ? AND user_id IN (?)", + cohort_id, + block_id, + content_file_uid, + student_ids). + order("user_id, state, created_at DESC") + end + + def self.latest_for_checkpoint_content_file_for_user_in_cohort(cohort_id:, content_file:, user_id:, with_started: false) + submissions = CheckpointSubmission.where( + content_file_uid: content_file.uid, + content_file_block_id: content_file.standard.release.block_id, + user_id: user_id, + cohort_id: cohort_id + ) + + unless with_started + submissions = submissions.where.not(state: CheckpointSubmission::STATES[:started]) + end + + submissions.order("created_at DESC").first + end + + def self.all_for_user_id_content_file_in_cohort(user_id, content_file, cohort_id) + CheckpointSubmission. + where(user_id: user_id, + cohort_id: cohort_id, + content_file_uid: content_file.uid, + content_file_block_id: content_file.standard.release.block_id). + where("state != 'retry'"). + order(created_at: :desc) + end + + # all_for_user_standards yields the checkpoints a user has submitted for a set of standards + # it includes the uid of the standard for that checkpoint submission + def self.all_for_user_standards(user_id, standard_uids) + CheckpointSubmission.joins(submitted_challenge_answers: [challenge: [content_file: :standard]]). + select("DISTINCT ON (checkpoint_submissions.id) checkpoint_submissions.*, standards.uid as standard_uid"). + where("checkpoint_submissions.user_id = ? AND standards.uid IN (?)", user_id, standard_uids). + group("checkpoint_submissions.id, standards.uid"). + order("checkpoint_submissions.id") + end +end diff --git a/app/finders/cohort_user_finder.rb b/app/finders/cohort_user_finder.rb new file mode 100644 index 0000000..2c5f806 --- /dev/null +++ b/app/finders/cohort_user_finder.rb @@ -0,0 +1,9 @@ +class CohortUserFinder + def self.instructors_for_cohorts_using_different_version_of_release_block(release) + CohortUser. + instructor. + joins(cohort: [cohort_releases: :release]). + where("releases.block_id = ? AND cohort_releases.release_id != ?", release.block_id, release.id). + order(:id) + end +end diff --git a/app/finders/content_file_finder.rb b/app/finders/content_file_finder.rb new file mode 100644 index 0000000..96a2eae --- /dev/null +++ b/app/finders/content_file_finder.rb @@ -0,0 +1,20 @@ +class ContentFileFinder + def self.from_cohort_block_content_file_path(cohort_id:, block_id:, content_file_path:) + ContentFile.unscoped. + joins(standard: :release). + joins("INNER JOIN cohort_releases ON releases.id = cohort_releases.release_id"). + where("releases.block_id = ? AND cohort_releases.cohort_id = ? AND path = ?", block_id, cohort_id, content_file_path). + first + end + + def self.all_from_cohort_block(cohort_id:, block_id:) + ContentFile.unscoped. + joins(standard: :release). + joins("INNER JOIN cohort_releases ON releases.id = cohort_releases.release_id"). + where("releases.block_id = ? AND cohort_releases.cohort_id = ?", block_id, cohort_id) + end + + def self.first_for_standard(standard_id) + ContentFile.where(standard_id: standard_id).order(position: :asc).first + end +end diff --git a/app/finders/performance_finder.rb b/app/finders/performance_finder.rb new file mode 100644 index 0000000..b18c98e --- /dev/null +++ b/app/finders/performance_finder.rb @@ -0,0 +1,71 @@ +class PerformanceFinder + def self.latest_for_cohort_release(cohort_id:, release:) + Performance. + joins(standard: :release). + where("releases.block_id = ? AND performances.cohort_id = ?", release.block_id, cohort_id). + joins("INNER JOIN cohort_users ON cohort_users.user_id = performances.user_id"). + where("cohort_users.cohort_id = ?", cohort_id). + merge(CohortUser.student). + where("standards.uid in (SELECT uid FROM standards WHERE standards.release_id=?)", release.id). + select("DISTINCT ON (performances.user_id, standards.uid) performances.*"). + order("performances.user_id, standards.uid, performances.created_at DESC") + end + + def self.latest_for_standards_in_cohort(standard_uids:, user_ids:, cohort_id:) + Performance. + joins(:standard). + where("cohort_id = ?", cohort_id). + where("standards.uid in (?)", standard_uids). + where("performances.user_id in (?)", user_ids). + where("performances.cohort_id = ?", cohort_id). + select("DISTINCT ON (performances.user_id, standards.uid, performances.block_id) performances.*"). + order("performances.user_id, standards.uid, performances.block_id, performances.created_at DESC") + end + + def self.latest_for_cohort_student_release(cohort_id:, release:, user_id:) + latest_for_cohort_release(cohort_id: cohort_id, release: release).where("cohort_users.user_id = ?", user_id) + end + + def self.latest_for_cohort_user(cohort_id:, user_id:) + Performance. + where("performances.cohort_id = ? AND performances.user_id = ?", cohort_id, user_id). + select("DISTINCT ON (performances.user_id, performances.standard_uid) performances.*"). + order("performances.user_id, performances.standard_uid, performances.created_at DESC") + end + + def self.latest_standard_scores_for_cohort(user_ids: [], content_file_uids: nil, cohort_id:) + return {} if user_ids.empty? + + performances = Performance.joins( + "LEFT OUTER JOIN checkpoint_submissions ON checkpoint_submissions.id = performances.checkpoint_submission_id" + ).select( + "performances.user_id, performances.score, performances.standard_uid, performances.block_id, " \ + "performances.checkpoint_submission_id, checkpoint_submissions.state, checkpoint_submissions.created_at AS checkpoint_created_at, " \ + "checkpoint_submissions.total_points AS checkpoint_total_points, checkpoint_submissions.correct_points AS checkpoint_correct_points" + ).where( + "performances.cohort_id = ? AND performances.user_id IN (?)", cohort_id, user_ids + ).order("performances.created_at DESC") + + if content_file_uids + performances = performances.where("checkpoint_submissions.content_file_uid in (?)", content_file_uids) + end + + grouped_performances = performances.to_a.group_by(&:user_id) + result = Hash[user_ids.map { |key, _value| [key, {}] }] + grouped_performances.each do |key, value| + result[key] = value.each_with_object({}) do |perf, hash| + hash[perf.block_id] ||= {} + hash[perf.block_id][perf.standard_uid] ||= { + score: perf.score, + checkpoint_submission_id: perf.checkpoint_submission_id, + checkpoint_created_at: perf.checkpoint_created_at, + standard_uid: perf.standard_uid, + checkpoint_state: perf.state, + total_points: perf.checkpoint_total_points, + correct_points: perf.checkpoint_correct_points + } + end || {} + end + result + end +end diff --git a/app/finders/release_finder.rb b/app/finders/release_finder.rb new file mode 100644 index 0000000..4dad71f --- /dev/null +++ b/app/finders/release_finder.rb @@ -0,0 +1,24 @@ +class ReleaseFinder + def self.latest_for_all_blocks_not_attached_to_cohort(cohort_id) + Release. + where( + "releases.block_id NOT IN (SELECT releases.block_id FROM cohort_releases INNER " \ + " JOIN releases ON cohort_releases.release_id = releases.id WHERE cohort_releases.cohort_id = ?)", + cohort_id + ). + select("DISTINCT ON (releases.block_id) releases.*"). + order("releases.block_id, releases.created_at DESC") + end + + def self.for_cohort(cohort_id) + Release.joins(:cohort_releases).where("cohort_releases.cohort_id = ?", cohort_id).order("cohort_releases.position ASC") + end + + def self.for_block(block_id) + Release.where("block_id = ? AND branch_name = 'master'", block_id).order("releases.created_at DESC") + end + + def self.available_update_count_for(release) + Release.where("id > ? AND block_id = ? AND branch_name = 'master'", release.id, release.block_id).count + end +end diff --git a/app/finders/standard_finder.rb b/app/finders/standard_finder.rb new file mode 100644 index 0000000..ddd5d44 --- /dev/null +++ b/app/finders/standard_finder.rb @@ -0,0 +1,10 @@ +class StandardFinder + def self.all_for_cohort(cohort_id:) + Standard.joins(release: :cohort_releases).where("cohort_releases.cohort_id = ?", cohort_id) + end + + def self.for_cohort_release(cohort_id:, release_id:) + Standard.joins(release: :cohort_releases).where(release_id: release_id). + where("cohort_releases.cohort_id = ?", cohort_id).in_default_order + end +end diff --git a/app/finders/submitted_challenge_answer_finder.rb b/app/finders/submitted_challenge_answer_finder.rb new file mode 100644 index 0000000..05f7403 --- /dev/null +++ b/app/finders/submitted_challenge_answer_finder.rb @@ -0,0 +1,28 @@ +class SubmittedChallengeAnswerFinder + def self.latest_for_user_id_content_file_for_cohort(user_id, content_file, cohort_id) + challenge_uids = Challenge.where(content_file_id: content_file.id).pluck(:uid) + + SubmittedChallengeAnswer. + where(user_id: user_id, block_id: content_file.standard.release.block_id, + challenge_uid: challenge_uids, cohort_id: cohort_id). + latest_for_user_id_challenge_uid + end + + def self.all_for_user_id_challenge_in_cohort(user_id, challenge, cohort_id) + content_file = ContentFile.unscoped.find(challenge.content_file_id) + SubmittedChallengeAnswer. + where(user_id: user_id, block_id: content_file.standard.release.block_id, + challenge_uid: challenge.uid, cohort_id: cohort_id). + order(created_at: :desc) + end + + # only used in ChallengeWithSubmittedChallengeAnswersPresenter + def self.all_for_challenge_in_cohort(cohort, challenge, users_id = nil) + content_file = ContentFile.unscoped.find(challenge.content_file_id) + SubmittedChallengeAnswer.includes(:user). + where(user_id: users_id || cohort.students.map(&:id), block_id: content_file.standard.release.block_id, challenge_uid: challenge.uid). + select("DISTINCT ON (submitted_challenge_answers.user_id) submitted_challenge_answers.*"). + order("submitted_challenge_answers.user_id, created_at DESC"). + sort { |a, b| a.user.first_name <=> b.user.first_name } + end +end diff --git a/app/finders/user_finder.rb b/app/finders/user_finder.rb new file mode 100644 index 0000000..ac5d5b1 --- /dev/null +++ b/app/finders/user_finder.rb @@ -0,0 +1,8 @@ +class UserFinder + def self.students_joined_cohort_yesterday(cohort_id) + User.joins(:cohort_users). + where("cohort_users.cohort_id = ?", cohort_id). + where("cohort_users.created_at > ?", Time.current - 1.day). + merge(CohortUser.student) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 0000000..d9a1585 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,34 @@ +module ApplicationHelper + def sprite_navigation(name) + content_tag( + :svg, + content_tag( + :use, + "", + "xlink:href" => "/assets/images/svg/svg-sprite-navigation-symbol.svg#{name}" + ), + class: "icon" + ) + end + + def flash_class(level) + case level.to_sym + when :notice then "alert alert-info" + when :success then "alert alert-success" + when :error then "alert alert-danger" + when :alert then "alert alert-danger" + end + end + + def errors_for?(model, attribute) + model.errors[attribute].any? + end + + def errors_for(model, attribute) + return unless errors_for?(model, attribute) + + content_tag :small, class: "text-danger" do + model.errors[attribute].map(&:humanize).join(", ") + end + end +end diff --git a/app/helpers/json_helper.rb b/app/helpers/json_helper.rb new file mode 100644 index 0000000..45516ad --- /dev/null +++ b/app/helpers/json_helper.rb @@ -0,0 +1,9 @@ +module JsonHelper + def true?(obj) + obj.to_s == "true" + end + + def false?(obj) + obj.to_s == "false" + end +end diff --git a/app/helpers/secondary_navigation_helper.rb b/app/helpers/secondary_navigation_helper.rb new file mode 100644 index 0000000..fe79992 --- /dev/null +++ b/app/helpers/secondary_navigation_helper.rb @@ -0,0 +1,10 @@ +module SecondaryNavigationHelper + def setup_href(cohort) + return content_cohort_path(cohort.id) if cohort.sandbox + + cohort_user = current_user.cohort_users.where(cohort_id: cohort.id).first + return content_cohort_path(cohort.id) if cohort_user.nil? || cohort_user.last_setup_visit.nil? + + cohort_user.last_setup_visit + end +end diff --git a/app/helpers/standard_navigation_helper.rb b/app/helpers/standard_navigation_helper.rb new file mode 100644 index 0000000..6019a28 --- /dev/null +++ b/app/helpers/standard_navigation_helper.rb @@ -0,0 +1,165 @@ +module StandardNavigationHelper + delegate :url_helpers, to: "Rails.application.routes" + + def get_previous_standard_link(current_standard:, cohort:, content_file_id:) + @cohort = cohort + @current_content_file_id = content_file_id + @current_standard = current_standard + + if current_content_file_position.zero? # current file is first standard file + if previous_standard&.content_files&.any? + standard_link(@previous_standard, @previous_standard.content_files.order(position: :asc).last) + end + elsif current_content_file_position > 0 # current file is not first standard file + previous_content_file = current_content_files.where("position < ?", current_content_file_position).last + previous_content_file ? standard_link(@current_standard, previous_content_file) : nil + end + end + + def get_next_standard_link(current_standard:, cohort:, content_file_id:) + @cohort = cohort + @current_content_file_id = content_file_id + @current_standard = current_standard + + if current_content_file_position == current_content_files.length - 1 # current file is last standard file + return nil unless next_standard + + standard_link(next_standard, ContentFileFinder.first_for_standard(next_standard.id)) + else + next_content_file = current_content_files.where("position > ?", current_content_file_position).first + standard_link(@current_standard, next_content_file) + end + end + + def get_previous_content_file_link(current_standard:, cohort:, content_file_id:) + @cohort = cohort + @current_content_file_id = content_file_id + @current_standard = current_standard + + previous_content_file_id = current_content_files. + where("position < ?", current_content_file_position). + last&.id + + if previous_content_file_id.nil? + if previous_standard.present? && !previous_standard_visible_files.empty? + content_file_link( + previous_standard, + previous_standard_visible_files.order(position: :asc).last.id + ) + else + return nil unless current_standard_cohort_release_position + + previous_release = current_cohorts_releases. + where("position = ?", current_standard_cohort_release_position - 1). + last&.release + + return nil unless previous_release + + previous_release_standard = previous_release&.standards&.last + previous_content_file_id = previous_release_standard&.content_files&.last&.id + + content_file_link(previous_release_standard, previous_content_file_id) + end + else + content_file_link(@current_standard, previous_content_file_id) + end + end + + def get_next_content_file_link(current_standard:, cohort:, content_file_id:) + @cohort = cohort + @current_content_file_id = content_file_id + @current_standard = current_standard + + next_content_file_id = current_content_files. + where("position > ?", current_content_file_position). + first&. + id + + if next_content_file_id.nil? + next_standards_content_files = + next_standard.present? ? next_standard.content_files.order(position: :asc).visible_for(@cohort.id) : [] + + if !next_standards_content_files.empty? + content_file_link( + next_standard, + next_standards_content_files.first.id + ) + else + return nil unless current_standard_cohort_release_position + + next_release = current_cohorts_releases. + where("position = ?", current_standard_cohort_release_position + 1). + first&. + release + + return nil unless next_release + + next_release_standard = next_release&.standards&.visible_for(@cohort.id)&.first + next_content_file_id = next_release_standard&.content_files&.visible_for(@cohort.id)&.first&.id + + content_file_link(next_release_standard, next_content_file_id) + end + else + content_file_link(@current_standard, next_content_file_id) + end + end + + private + + def current_standard_position + @current_standard_position ||= @current_standard.position + end + + def current_content_file_position + @current_content_file_position ||= current_content_files.unscoped.find(@current_content_file_id).position + end + + def current_content_files + @current_content_files ||= ContentFile.where(standard: @current_standard). + visible_for(@cohort.id). + order(position: :asc) + end + + def current_standard_cohort_release_position + @current_standard_cohort_release_position ||= current_cohorts_releases. + select { |cohort_release| cohort_release.release == @current_standard.release }. + first&.position + end + + def current_cohorts_releases + @current_cohorts_releases ||= CohortRelease.where(cohort: @cohort).in_default_order + end + + def standard_link(standard, content_file) + return nil if content_file.blank? || standard.blank? + + { title: content_file.title, url: url_helpers.content_file_path(@cohort, standard.release.block, content_file.path) } + end + + def content_file_link(standard, content_file_id) + return nil if content_file_id.blank? + + content_file = standard.content_files.find_by(id: content_file_id) + block = standard.release.block + + { title: content_file.title, url: url_helpers.content_file_path(@cohort, block, content_file.path) } + end + + def cohort_standards + @cohort_standards ||= StandardFinder. + for_cohort_release(cohort_id: @cohort.id, release_id: @current_standard.release_id). + visible_for(@cohort.id) + end + + def previous_standard + @previous_standard ||= cohort_standards.where("standards.position < ?", current_standard_position).last + end + + def previous_standard_visible_files + @previous_standard_visible_files ||= previous_standard.content_files.visible_for(@cohort.id) + end + + def next_standard + @next_standard ||= cohort_standards.where("standards.position > ?", current_standard_position).first + end +end diff --git a/app/javascript/api.d.ts b/app/javascript/api.d.ts new file mode 100644 index 0000000..51881ec --- /dev/null +++ b/app/javascript/api.d.ts @@ -0,0 +1,550 @@ +declare namespace Api { + type rails_path = string + type user_id = number + + type Score = null | 1 | 2 | 3 + + type Activity = { + id: number + // formatted_created_at: number + name: string + content: string + created_at: string + user_photo: string + user_initials: string + } + + type Standard = { + id: number + success_criteria: string + current_score: Api.Score + description: string + } + + type Student = { + id: number, + full_name: string, + initials: string, + profile_image: string + } + + type ContentFile = { + id: number + uid: string + title: string + hidden: boolean + content_file_path: string + content_file_type: 'lesson' | 'checkpoint' | 'instructor' | 'resource' + challenges: ChallengeWithSubmissions[] + checkpoint_submissions?: Record + type_relative_position: number, + max_checkpoint_submissions: number + } + + type CheckpointStudentScore = { + email: string + first_name: string + has_signed_in: boolean + id: number + auth_edit_url: string + initials: string + last_name: string + profile_image: string + submissions: CheckpointStudentScoreSubmission + } + + type CheckpointStudentScoreSubmission = { + created_at: string + done_id: number + needs_review_id: number + score: number + started_id: number + state: string + } + + type CheckpointSubmission = { + id: number + cohort_id: number + state: CheckpointSubmissionState + created_at: string + updated_at: string + last_performance_percent: number + total_possible_points: number + points_earned: number + challenge_total: number + path: string + } + + type CheckpointSubmissionState = 'needs_review' | 'rejected' | 'done' | 'retry' | 'started' + + type CheckpointDataBase = { + cohortId: number + challengeTotal: number + contentFilesSubmission: Api.CheckpointSubmission | null + checkpointsByState: { + done: Api.CheckpointSubmission | null + retry: Api.CheckpointSubmission | null + needs_review: Api.CheckpointSubmission | null + started: Api.CheckpointSubmission | null + } + } + + type CheckpointInfo + = CheckpointDataBase & { + cohortMode: Api.CohortModeMastery | Api.CohortModePercentage + masteryScore: number + challengeCorrect: number + } + + type ContentFileType = 'checkpoint' | 'lesson' | 'resource' | 'instructor' | undefined + + type ChallengeWithSubmissions = { + id: number + title: string + student_submissions: Record> + } + + type Notification = { + id: number + created_at: Date + description: null | string + read_at: null | Date + tagline: string + title: string + updated_at: Date + url: string + user_id: number + } + + type Challenge = { + id: number + uid: string + answer: null | string + content_file_id: number + created_at: Date + docker_directory_path: null | string + explanation: null | string + hints: string[] + language: null | string + position: number + max_attemps: number + raw_json: any + setup: string + tests: null | string + title: string + updated_at: Date + upstream_repo_path: null | string + validate_fork: boolean + } + + type ChallengeWithSubmittedChallengeAnswersPresenterBase = { + id: number + uid: string + title: string + autoscorable: boolean + hints: string[] + index: number // Where does this come from?? + next_student: rails_path + number_of_hints: number + previous_student: rails_path + points: number + topics: string[] + + show_tests: boolean + tests?: string // Present if show_tests is true + + can_view_status: boolean + can_check_answer: boolean + can_view_explanation: boolean + submitted_challenge_answer_presenters: SubmittedChallengeAnswerPresenter[] + submitted_challenge_answer_presenter: SubmittedChallengeAnswerPresenter + } + + type ChallengeType + = 'checkbox' | 'code-snippet' | 'custom-snippet' + | 'multiple-choice' | 'number' | 'poll' + | 'short-answer' | 'testable-project' + | 'paragraph' | 'project' | 'local-snippet' + + type ChallengeWithSubmittedChallengeAnswersPresenter + = ChallengeWithSubmittedChallengeAnswersPresenterBase & + { + challenge_type: Exclude + + // explanation: any + placeholder: null | string + decimal: null | number + question: null | string + language: null | string + options: null | Array<{ + rendered_option: string, + option: any + }> + // grader_label: any + } + | LocalJsChallenge + + type LocalJsChallenge + = ChallengeWithSubmittedChallengeAnswersPresenterBase & + { + challenge_type: 'local-snippet' + tests: string + helpers: string[] + language: 'javascript' + question: string + placeholder: string + options: null | Array<{ + rendered_option: string, + option: any + }> + } + + type SubmittedChallengeAnswerPresenter = { + status: SubmitedChallengeAnswerStatus + overall_status: SubmittedChallengeAnswerOverallStatus + answer: null | string + last_graded_at: null | Date + challenge_id: number + student_comment_url: null | rails_path + test_results: string + cancel_url: null | string + submitted_challenge_answer_id: number + submitted_challenge_answer_url: null | rails_path + // increment_challenge_answer_url: any + attempted_on: null | string + challenge_explanation: null | string + rubric: null | string + incorrect_attempts: number + hints: string[] + grader_label: null | string + permissions: { update: boolean } + created_at: null | Date + increment_hints_shown_submitted_challenge_answer_url: null | rails_path + points: null | number + max_points: null | number + checkpoint_submission_id: null | number + } + + type SubmitedChallengeAnswerStatus + = 'correct' | 'incorrect' + | 'ungraded' | 'processing' | 'failed' | 'invalid_repo' + | 'canceled' | 'invalid_fork' | 'timeout' | 'missing_file' | 'unknown' | 'retry' | 'building' + + type SubmittedChallengeAnswerOverallStatus + = 'none' | 'graded' | 'ungraded' + + type CohortModeMastery = 'Mastery' + type CohortModePercentage = 'Percentage' + type CohortMode = CohortModePercentage | CohortModeMastery + + type UserForAvatar = { + id: number + initials: string + full_name: string + profile_image: null | string + } + + type StandardPresenter_ForCardBase = { + id: number + block_id: number + cohort_id: number + cohort_standard_progress: { + percent_done: number + percent_pending: number + remaining: number + total_completed: number + total_pending: number + } + cohort_standard_progress_data: any[] + title: string + description: string + has_progress: boolean + progress_complete: number + progress_percentage: number + } + + type StandardPresenter_ForCard + = StandardPresenter_ForCardBase & { + cohort_mode: 'Percentage' + progress_pending: number + remaining_to_completion: number + is_completed: boolean + has_progress: boolean + } + | StandardPresenter_ForCardBase & { + cohort_mode: 'Mastery' + mastery_score: number + // These exist but we shouldn't use them + // last_visited_by_user: '-is-current' | '-is-visited' | '-is-unvisited' + // mastery_class: '-score-1' | '-score-2' | '-score-3' | '-unscored' + } + + type LessonVisit = { + id: number + user_id: number + block_id: number + cohort_id: number + standard_uid: string + content_file_path: string + content_file_uid: null | string + + visit_count: number + } + + namespace CurriculumPage { + type release_id = number + type Section = { + id: number + title: string + release_ids: release_id[] + standard_count: number + standardsForSectionPath: string | null + } + } + + namespace CheckpointSubmissionShow { + type t = { + id: number + user_id: number + url: string + title: string + autoscorable: boolean + block_id: number + max_checkpoint_submissions: number + submission_count: number + content_file_url: string + grader: any + submitted_on: string + state: CheckpointSubmissionState + } + + type StandardCardProps = { + checkpoint_performance: null | CheckpointPerformance + standard: Standard + } + type CheckpointPerformance = { + score: number + // checkpoint_submission_id: number + created_at: string + standard_id: number + // updator_id: number + // user_id: number + updator_full_name: string + // updator_full_name: performance.updator.nil ? "Autoscored" : performance.updator.full_name + } + } + + namespace Submissions { + type block_id = number + type standard_uid = string + + type SubmissionData + = { + cohort_mode: 'Mastery' | 'Percentage' + blocks: Block[] + sections: Block[] + completion_progress: Record + > + > + } + + type Student = { + id: user_id + initials: string + name: string + avatar: null | string + challenges_path: rails_path + cohort_reg_date: string + } + + type Block = { + id: number + title: string + standards: Standard[] + } + + type Standard = { + id: number + uid: string + title: string + description: string + success_criteria?: string // Only present in mastery mode + student_performances: Record + content_files: ContentFile[] + submissions_path: rails_path + } + + type StudentPerformance = { + score?: number + student_id: number + standard_uid?: string + checkpoint_created_at?: null | string + checkpoint_state?: null | CheckpointSubmissionState + checkpoint_submission_id?: null | number + ungraded_submission_id?: number + ungraded_submission_state?: CheckpointSubmissionState + total_points: number + correct_points: number + } + } + + namespace Setup { + type Flash = { + title: string + msg: string | any + type: string + } + type ResyncJobBase = { + id: number + cohort_id: number + course_config_url: string + } + type SuccessfulResyncJob = ResyncJobBase & { + status: 'success' + synced_at: string + } + type CanceledResyncJob = ResyncJobBase & { + status: 'canceled' + } + type FailureResyncJob = ResyncJobBase & { + status: 'failure' + archived: boolean + error_type: string + error_data: any + } + type TimeoutResyncJob = ResyncJobBase & { + status: 'timeout' + archived: boolean + error_type: string + error_data: any + } + type PendingResyncJob = ResyncJobBase & { + status: 'pending' + } + type ResyncJob = SuccessfulResyncJob | FailureResyncJob | PendingResyncJob | CanceledResyncJob | TimeoutResyncJob + + type VisibilitySection = { + id: number + title: string + standards_with_lessons: VisibilityStandard[] + } + type VisibilityStandard = { + id: number + title: number + hidden: boolean + content_file_count: number + content_files: Array + } + type VisibilityContentFile = { + id: number, + uid: string, + title: string, + hidden: boolean, + path: string, + github_url: string, + visible: boolean, + } + } + + type BlockPresenter_ForReleases = { + cohorts_using_release: Cohort[] + last_update: string + name: string + release_id: number + version: string + repo_name: string + notes: string + repo_url: string + commit_url: string + github_sha: string + diff_url: string + block_id: number + user_avatar: UserForAvatar + } + + type BlockPresenter_ForBlocks = { + submission_count: number, + cohorts_using_this_block: object[], + cohort_count: number, + id: number, + last_update: string, + latest_release_id: number, + name: string, + readme_link: string, + readme_text: string, + release_count: number, + repo_description?: string, + repo_name: string, + repo_tags?: string[], + repo_url: string, + student_count: number, + sync_errors?: string[], + user_avatar: UserForAvatar + } + + type Cohort = { + id: number + uid: string + name: string + product_type: string + label: string + gcode: string + campus_name: string + slack_channel: string + starts_on: string + ends_on: string + created_at: string + updated_at: string + deleted_at: string + mode: string + } + + namespace Pairs { + type Student = { + uid: string + name: string + profile_image: string + } + + type Pairing = { + size: number + groups: Api.Pairs.Student[][] | string + id: number | null + title: string + } + } + + type ApiInteraction = { + id: number + user_id: number + ip: string + path: string + method: string + action_name: string + duration: number + controller_name: string + response_code: string + } + + type RepoDetails = { + repo_name: string + org: string + origin: string + } +} + +declare module 'objectify-array/*'{ + var _a: any; + export = _a; +} + +interface Array { + flat(): Array; + flatMap(func: (x: T) => T): Array; +} diff --git a/app/javascript/components/AceEditor.tsx b/app/javascript/components/AceEditor.tsx new file mode 100644 index 0000000..525be56 --- /dev/null +++ b/app/javascript/components/AceEditor.tsx @@ -0,0 +1,133 @@ +import * as React from 'react' +import ace from './lib/ace' + +type Props = { + mode: string + theme: string + name: string + width: string + height: string + fontSize: number + showGutter: boolean + + value: string + defaultValue: string + + maxLines?: number + readOnly: boolean + wrapEnabled: boolean + selectFirstLine: boolean + showPrintMargin: boolean + behavioursEnabled: boolean + highlightActiveLine: boolean + wrapBehavioursEnabled: boolean + + onLoad: (e: React.SyntheticEvent) => void + onChange: (input: string) => void + onFocus: (e: React.SyntheticEvent) => void + handleCodeSubmit: (e: React.SyntheticEvent) => void + + setShowPrintMargin: any // Not used? + editorProps: any // Not used? +} + +export default class AceEditor extends React.Component { + editor: any + + public static defaultProps: Partial = { + name: 'ace-editor', + mode: '', + theme: '', + height: '100%', + width: '100%', + defaultValue: '', + value: '', + fontSize: 14, + showGutter: true, + readOnly: false, + highlightActiveLine: true, + showPrintMargin: true, + selectFirstLine: false, + wrapEnabled: true, + behavioursEnabled: true, + } + + componentDidMount() { + this.editor = ace.edit(this.props.name); + this.editor.getSession().$useWorker = false; + this.editor.$blockScrolling = Infinity; + this.editor.getSession().setMode('ace/mode/' + this.props.mode); + this.editor.setTheme('ace/theme/' + this.props.theme); + this.editor.setFontSize(this.props.fontSize); + this.editor.on('change', this.onChange); + this.editor.on('focus', this.props.onFocus); + this.editor.setValue(this.props.defaultValue || this.props.value, (this.props.selectFirstLine === true ? -1 : null)); + this.editor.setOption('maxLines', this.props.maxLines); + this.editor.setOption('readOnly', this.props.readOnly); + this.editor.setOption('highlightActiveLine', this.props.highlightActiveLine); + this.editor.setOption('behavioursEnabled', this.props.behavioursEnabled); + this.editor.setOption('wrapBehavioursEnabled', this.props.wrapBehavioursEnabled); + this.editor.setShowPrintMargin(this.props.setShowPrintMargin); + this.editor.renderer.setShowGutter(this.props.showGutter); + this.editor.commands.addCommand({ + name: 'submit-code', + exec: this.props.handleCodeSubmit, + bindKey: { mac: 'cmd-enter', win: 'ctrl-enter' } + }); + + if (this.props.onLoad) this.props.onLoad(this.editor); + } + + componentWillReceiveProps(nextProps: Props) { + let currentRange = this.editor.selection.getRange(); + + // only update props if they are changed + if (nextProps.mode !== this.props.mode) { + this.editor.getSession().setMode('ace/mode/' + nextProps.mode); + } + if (nextProps.theme !== this.props.theme) { + this.editor.setTheme('ace/theme/' + nextProps.theme); + } + if (nextProps.fontSize !== this.props.fontSize) { + this.editor.setFontSize(nextProps.fontSize); + } + if (nextProps.maxLines !== this.props.maxLines) { + this.editor.setOption('maxLines', nextProps.maxLines); + } + if (nextProps.readOnly !== this.props.readOnly) { + this.editor.setOption('readOnly', nextProps.readOnly); + } + if (nextProps.highlightActiveLine !== this.props.highlightActiveLine) { + this.editor.setOption('highlightActiveLine', nextProps.highlightActiveLine); + } + if (nextProps.setShowPrintMargin !== this.props.setShowPrintMargin) { + this.editor.setShowPrintMargin(nextProps.setShowPrintMargin); + } + if (nextProps.wrapEnabled !== this.props.wrapEnabled) { + this.editor.getSession().setUseWrapMode(nextProps.wrapEnabled); + } + if (nextProps.value && this.editor.getValue() !== nextProps.value) { + this.editor.setValue(nextProps.value, (this.props.selectFirstLine === true ? -1 : null)); + if (currentRange && typeof currentRange === 'object') { + this.editor.getSession().getSelection().setSelectionRange(currentRange); + } + } + if (nextProps.showGutter !== this.props.showGutter) { + this.editor.renderer.setShowGutter(nextProps.showGutter); + } + } + + onChange = () => { + if (this.props.onChange) { + const value = this.editor.getValue(); + + this.props.onChange(value); + } + } + + render() { + const divStyle = { width: this.props.width, height: this.props.height }; + + return

+ } +} diff --git a/app/javascript/components/Badge.tsx b/app/javascript/components/Badge.tsx new file mode 100644 index 0000000..3512874 --- /dev/null +++ b/app/javascript/components/Badge.tsx @@ -0,0 +1,18 @@ +import * as React from 'react' + +type Props = { + id?: string + text: string + className?: string +} + +export default (props: Props) => { + let classes = `lp-badge ${(props.className ? props.className : '')}`; + let badgeText = props.text.replace('_', ' '); + + return ( + + {badgeText} + + ); +}; diff --git a/app/javascript/components/Button.tsx b/app/javascript/components/Button.tsx new file mode 100644 index 0000000..2323ff9 --- /dev/null +++ b/app/javascript/components/Button.tsx @@ -0,0 +1,26 @@ +import * as React from 'react' + +type Props = { + className?: string + id?: string + type?: any + onClick?: any + children?: React.ReactNode + disabled?: boolean +} + +export default (props: Props) => { + let classes = `lp-style-button ${(props.className ? props.className : '')}`; + + return ( + + ); +}; diff --git a/app/javascript/components/ButtonTo.tsx b/app/javascript/components/ButtonTo.tsx new file mode 100644 index 0000000..f8efe0e --- /dev/null +++ b/app/javascript/components/ButtonTo.tsx @@ -0,0 +1,75 @@ +import * as React from 'react' + +type Props = { + buttonText: string + method: string + paramName: string + url: string + disabled?: boolean + parentId?: string + paramVal: string + buttonClass: string +} + +export default (props: Props) => { + let formMethod = props.method; + let methodInput; + let csrfToken = $_('meta[name=csrf-token]', el => el.getAttribute('content')) || undefined; + let inputClass = props.buttonClass; + let hasMadeComment = false; + + if (props.disabled) { + inputClass = inputClass.concat(' -disabled'); + } + + if (props.method === 'patch') { + formMethod = 'post'; + methodInput = ( + + ); + } + + const submit = (e: any) => { + if (props.buttonText === 'Reject' && !hasMadeComment) { + e.preventDefault(); + $_('#comment-and-reject', el => el.style.display = 'block'); + } + }; + + const newCommentHandler = (e: any) => { + if (props.buttonText === 'Reject') { + e.preventDefault(); + console.error('Route does not exist: api_v1_checkpoint_submission_checkpoint_submission_comments_path') + // $.post(Routes.api_v1_checkpoint_submission_checkpoint_submission_comments_path(props.parentId), { + // comment: { + // content: $('.comment-and-reject textarea').text() + // } + // }).then((data) => { + // $(`#${props.buttonText}Form`).first().trigger('submit', { hasMadeComment: true }); + // }); + } + }; + + const injectCommentFormForReject = () => { + if (props.buttonText === 'Reject' && props.parentId !== null) { + console.error('NewCommentForm does not exist') + // return
; + } + }; + + return ( + + {injectCommentFormForReject()} +
+ {methodInput} + + + +
+
+ ); +}; diff --git a/app/javascript/components/Dropdown.tsx b/app/javascript/components/Dropdown.tsx new file mode 100644 index 0000000..6f659f1 --- /dev/null +++ b/app/javascript/components/Dropdown.tsx @@ -0,0 +1,40 @@ +import * as React from 'react' +import {isEmpty} from 'lodash-es' +import Icon from './Icon' + +type Props = { + placeholder?: string + selectedStandard?: string + options: { + id: string + savedValue?: null | string + items: Array<{ id: number | string, title: string }> + } + onChange: any +} + +export default (props: Props) => { + let placeholder; + let uniqueId = props.options.id; + let selected = props.options.savedValue || undefined; + let options = props.options.items.map((option) => { + return ( + + ); + }); + + if (!isEmpty(props.placeholder)) { + placeholder = ; + } + + return ( +
+ + +
+ ); +}; + diff --git a/app/javascript/components/Icon.tsx b/app/javascript/components/Icon.tsx new file mode 100644 index 0000000..b8b61fb --- /dev/null +++ b/app/javascript/components/Icon.tsx @@ -0,0 +1,20 @@ +import * as React from 'react' + +type Props = { + id: string + sprite: string + color?: string + viewBox?: string +} + +export default (props: Props) => { + let url = `/assets/images/svg/svg-sprite-${props.sprite}-symbol.svg${props.id}`; + let classes = `svg svg-24px ${props.color || ''}`; + let viewBox = props.viewBox || '0 0 24 24'; + + return ( + + + + ); +}; diff --git a/app/javascript/components/Loading.tsx b/app/javascript/components/Loading.tsx new file mode 100644 index 0000000..d3acbe5 --- /dev/null +++ b/app/javascript/components/Loading.tsx @@ -0,0 +1,59 @@ +import * as React from 'react' + +export default class LoadingLogo extends React.Component { + + componentDidMount() { + var {TimelineMax, Sine} = window + let bubbleAnimation1 = new TimelineMax({ paused: false }); + let bubbleAnimation2 = new TimelineMax({ paused: false }); + let bubbleAnimation3 = new TimelineMax({ repeat: -1, paused: false }); + + bubbleAnimation1 + .to('#bubble-1', 1.25, { scaleX: 0.74, scaleY: 0.74, ease: Sine.easeOut }) + .to('#bubble-1', 1.25, { x: -30, y: -20, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25') + .to('#bubble-1', 1.25, { rotation: -100, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25') + .to('#bubble-1', 1.25, { scaleX: 0.45, scaleY: 0.45, ease: Sine.easeOut }) + .to('#bubble-1', 1.25, { x: -10, y: -53, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25') + .to('#bubble-1', 1.25, { rotation: 0, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25') + .to('#bubble-1', 1.25, { scaleX: 0.0, scaleY: 0.0, transformOrigin: '50% 50%', ease: Sine.easeOut }); + + bubbleAnimation2 + .to('#bubble-2', 1.25, { scaleX: 0.69, scaleY: 0.69, ease: Sine.easeOut }) + .to('#bubble-2', 1.25, { x: 20, y: -31, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25') + .to('#bubble-2', 1.25, { rotation: 100, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25') + .to('#bubble-2', 1.25, { scaleX: 0.0, scaleY: 0.0, transformOrigin: '50% 50%', ease: Sine.easeOut }); + + bubbleAnimation3 + .to('#bubble-3', 1.25, { scaleX: 0.0, scaleY: 0.0, transformOrigin: '50% 50%', ease: Sine.easeOut }) + .to('#bubble-3', 0, { x: 10, y: 52, transformOrigin: '50% 50%' }) + .to('#bubble-3', 1.25, { scaleX: 2.25, scaleY: 2.25, transformOrigin: '50% 50%', ease: Sine.easeOut }) + .to('#bubble-3', 1.25, { scaleX: 1.58, scaleY: 1.58, ease: Sine.easeOut }) + .to('#bubble-3', 1.25, { x: -19, y: 31, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25') + .to('#bubble-3', 1.25, { rotation: -92, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25') + .to('#bubble-3', 1.25, { scaleX: 1.1, scaleY: 1.1, ease: Sine.easeOut }) + .to('#bubble-3', 1.25, { x: 0, y: 1, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25') + .to('#bubble-3', 1.25, { rotation: 5, transformOrigin: '50% 50%', ease: Sine.easeOut }, '-=1.25'); + } + + render() { + return ( +
+ +
+ ); + } +} diff --git a/app/javascript/components/Marked.tsx b/app/javascript/components/Marked.tsx new file mode 100644 index 0000000..c5e9a99 --- /dev/null +++ b/app/javascript/components/Marked.tsx @@ -0,0 +1,25 @@ +import * as React from 'react' +const marked = require('marked') + +type Props = { + source: null | string + className?: string +} + +export default class Marked extends React.Component { + + rawMarkup() { + if (this.props.source !== null) { + let rawMarkup = marked(this.props.source.toString()); + + rawMarkup = rawMarkup + .replace(//g, '
'); + + return { __html: rawMarkup }; + } + } + + render() { + return
; + } +} diff --git a/app/javascript/components/Menu.tsx b/app/javascript/components/Menu.tsx new file mode 100644 index 0000000..43833cd --- /dev/null +++ b/app/javascript/components/Menu.tsx @@ -0,0 +1,45 @@ +import * as React from 'react' + +type Props = { + items: Array<{ url: string, text: string }> + unmountMe: () => void +} + +export default class Menu extends React.Component { + + private nodeRef: any + + componentWillMount() { + document.addEventListener('click', this.handleClickOff, false); + } + + componentWillUnmount() { + document.removeEventListener('click', this.handleClickOff, false); + } + + handleClickOff = (e: MouseEvent | TouchEvent) => { + if (! this.nodeRef.contains(e.target)) { + this.props.unmountMe(); + } + } + + listItems() { + const listItems = this.props.items.map((item, i) => +
  • + {item.text} +
  • + ); + + return ( +
      { listItems }
    + ); + } + + render() { + return ( +
    this.nodeRef = node}> + { this.listItems() } +
    + ); + } +} diff --git a/app/javascript/components/Notifications.tsx b/app/javascript/components/Notifications.tsx new file mode 100644 index 0000000..55f3967 --- /dev/null +++ b/app/javascript/components/Notifications.tsx @@ -0,0 +1,116 @@ +import * as React from 'react' +import * as Routes from '../generated/routes' +import NotificationsIcon from './notifications/NotificationsIcon' +import http from '../lib/http' + + +type Props = {} +type State = { + loading: boolean + open: boolean + unreadCount: number + notifications: Api.Notification[] +} + +export default class Notifications extends React.Component { + private node: any + + constructor(props: Props) { + super(props); + + this.handleOutsideClick = this.handleOutsideClick.bind(this); + + this.state = { + loading: true, + open: false, + unreadCount: 0, + notifications: [] + }; + } + + componentDidMount() { + this.bootstrap(); + } + + async bootstrap() { + var data = await http('GET', Routes.notificationsPath()); + this.updateFromPayload(data); + } + + updateFromPayload(data: any) { + this.setState({ + notifications: data.notifications, + unreadCount: data.unreadCount, + loading: false + }); + } + + togglePanel() { + if (!this.state.open) { + document.addEventListener('touchmove', this.handleOutsideClick, false); + document.addEventListener('click', this.handleOutsideClick, false); + } else { + document.removeEventListener('touchmove', this.handleOutsideClick, false); + document.removeEventListener('click', this.handleOutsideClick, false); + } + + this.setState({ open: !this.state.open }); + } + + handleOutsideClick(e: any) { + if (this.node.contains(e.target)) { + return; + } + + this.togglePanel(); + } + + async markAllRead() { + var data = await http('POST', Routes.markAllReadNotificationsPath()); + this.updateFromPayload(data); + } + + render() { + let panel; + + if (!this.state.loading && this.state.open) { + let footerRow; + + if (this.state.unreadCount > 0) { + footerRow = (
    Mark all read
    ); + } + + let notifications = this.state.notifications.map((notification) => { + let klass = notification.read_at ? 'read' : 'unread'; + + return ( + +
    +
    {notification.tagline}
    +
    {window.moment(notification.created_at).tz(window.moment.tz.guess()).fromNow()}
    +
    +
    {notification.title}
    +
    {notification.description}
    +
    + ); + }); + + panel = ( +
    +
    Notifications
    +
    {notifications}
    + { footerRow } +
    + ); + } + + let openClass = this.state.open ? 'open' : ''; + + return ( +
    { this.node = node; }}> + + { panel } +
    + ); + } +} diff --git a/app/javascript/components/ProcessingIcon.tsx b/app/javascript/components/ProcessingIcon.tsx new file mode 100644 index 0000000..f2d1db4 --- /dev/null +++ b/app/javascript/components/ProcessingIcon.tsx @@ -0,0 +1,31 @@ +import * as React from 'react' + +export default () => ( + + + + + +); diff --git a/app/javascript/components/RowKebab.tsx b/app/javascript/components/RowKebab.tsx new file mode 100644 index 0000000..313fc02 --- /dev/null +++ b/app/javascript/components/RowKebab.tsx @@ -0,0 +1,36 @@ +import * as React from 'react' +import SvgRenderer from './SvgRenderer' + +type Props = { + active: boolean + toggleAction:() => void +} + +type State = { + kebabClass: string +} + +export default class RowKebab extends React.Component { + constructor(props: Props) { + super(props) + this.state = { + kebabClass: this.props.active ? 'action-kebab active' : 'action-kebab' + } + } + + static getDerivedStateFromProps(nextProps: Props, prevState: State): State { + let kb:string = 'action-kebab' + if (nextProps.active) { + kb = kb + ' active' + } + return { kebabClass: kb } + } + + render() { + return ( + + + + ) + } +} diff --git a/app/javascript/components/ScoreCircle.tsx b/app/javascript/components/ScoreCircle.tsx new file mode 100644 index 0000000..dc2baed --- /dev/null +++ b/app/javascript/components/ScoreCircle.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' + +export default (n: number) => { + let circleClass = function(n: number) { + switch (n) { + case 1: + return ' -is-one'; + case 2: + return ' -is-two'; + case 3: + return ' -is-three'; + default: + return ''; + } + }; + + return ( +
    + {n} +
    + ); +}; + diff --git a/app/javascript/components/SidebarProgress.tsx b/app/javascript/components/SidebarProgress.tsx new file mode 100644 index 0000000..bed2c83 --- /dev/null +++ b/app/javascript/components/SidebarProgress.tsx @@ -0,0 +1,28 @@ +import * as React from 'react' + +type Props = { + masteryCohort: boolean +} +type State = {} + +export default class SidebarProgress extends React.Component { + constructor(props: Props) { + super(props); + } + + render() { + if (this.props.masteryCohort) { + return ( +
    +
    + ); + } else { + return ( +
    +
    +
    +
    + ); + } + } + } diff --git a/app/javascript/components/SortDropdown.tsx b/app/javascript/components/SortDropdown.tsx new file mode 100644 index 0000000..6b9a463 --- /dev/null +++ b/app/javascript/components/SortDropdown.tsx @@ -0,0 +1,36 @@ +import * as React from 'react' +import Icon from './Icon' + +type Props = { + className: string, + selectedSort?: string, + options: { + id: any, + savedValue: string, + sorts: Array<{ + id: any, + title: string + }> + } + onChange: (e: any) => void +} + +export default (props: Props) => { + let uniqueId = props.options.id; + let selected = props.options.savedValue || props.selectedSort || ''; + let options = props.options.sorts.map((option) => { + return ( + + ); + }); + + return ( +
    +
    Sort by:
    + + +
    + ); +}; diff --git a/app/javascript/components/StandardBean.tsx b/app/javascript/components/StandardBean.tsx new file mode 100644 index 0000000..1f918cc --- /dev/null +++ b/app/javascript/components/StandardBean.tsx @@ -0,0 +1,36 @@ +import * as React from 'react' + +type Props = { + score: number, + isCompleted: boolean +} + +export default class StandardBean extends React.Component { + render() { + let className; + + if (this.props.score) { + switch (this.props.score) { + case 1: + className = '-score-1'; + break; + case 2: + className = '-score-2'; + break; + case 3: + className = '-score-3'; + break; + default: + className = '-unscored'; + } + } else if(this.props.isCompleted) { + className = '-is-completed'; + } else { + className = '-unscored'; + } + + return ( +
    + ); + } +} diff --git a/app/javascript/components/SvgRenderer.tsx b/app/javascript/components/SvgRenderer.tsx new file mode 100644 index 0000000..f0e65b2 --- /dev/null +++ b/app/javascript/components/SvgRenderer.tsx @@ -0,0 +1,26 @@ +import * as React from 'react' + +type Props = { + viewBox?: string + url?: string + style?: object + onClick?: (e: any) => void + className?: string + dataAttrs?: any +} + +export default class SvgRenderer extends React.Component { + render() { + return ( + + + + ); + } +} diff --git a/app/javascript/components/Timestamp.tsx b/app/javascript/components/Timestamp.tsx new file mode 100644 index 0000000..c321777 --- /dev/null +++ b/app/javascript/components/Timestamp.tsx @@ -0,0 +1,16 @@ +import * as React from 'react' + +type Props = { + timestamp: any +} + +export default (props: Props) => ( +
    + {stampFromString(props.timestamp)} +
    +); + +export function stampFromString (date: string) { + const timezone = window.moment.tz.guess() + return `${window.moment(date).tz(timezone).calendar()} ${window.moment.tz(timezone).zoneAbbr()}` +} diff --git a/app/javascript/components/UserAvatar.tsx b/app/javascript/components/UserAvatar.tsx new file mode 100644 index 0000000..3071827 --- /dev/null +++ b/app/javascript/components/UserAvatar.tsx @@ -0,0 +1,29 @@ +import * as React from 'react' + +type Props = { + src: null | string + initials: string +} + +export default (props: Props) => { + if (props.src) { + return ( +
    + ); + } else { + return ( +
    + + + + {props.initials} + + + +
    + ); + } +}; diff --git a/app/javascript/components/activities/NewActivityForm.tsx b/app/javascript/components/activities/NewActivityForm.tsx new file mode 100644 index 0000000..a7d567e --- /dev/null +++ b/app/javascript/components/activities/NewActivityForm.tsx @@ -0,0 +1,138 @@ +import * as React from 'react' +import Textarea from 'react-textarea-autosize' +import Marked from '../Marked' +import http from '../../lib/http' + +type Props = { + afterActivityHandler: (activity: Api.Activity) => void + buttonType: string + maxRows: number + minRows: number + createActivityUrl: string + notify: boolean +} +type State = { + newActivity: string + showPreview: boolean +} + +export default class NewActivityForm extends React.Component { + constructor(props: Props) { + super(props); + } + + state = { + newActivity: '', + showPreview: false + } + + static get defaultProps() { + return { + afterActivityHandler: null, + buttonType: 'comment', + maxRows: 5, + minRows: 1 + }; + } + + createActivity = () => { + http('POST', this.props.createActivityUrl, { + body: { + activity: { + content: this.state.newActivity, + notify: this.props.notify + } + } + }) + .then((data: any) => { + if (this.props.afterActivityHandler) this.props.afterActivityHandler(data); + this.setState({ + newActivity: '', + showPreview: false + }); + }); + } + + get ActivityButton() { + let buttonClass = '', buttonText = ''; + let disabled = this.state.newActivity === ''; + + if (this.props.buttonType === 'comment') { + buttonClass = ''; + buttonText = 'Send'; + } else if (this.props.buttonType === 'reject') { + buttonClass = '-reject'; + buttonText = 'Comment & Reject'; + } + + if (disabled) { + buttonClass = buttonClass.concat(' -disabled'); + } + + return ( + + ); + } + + get PreviewButton() { + if (this.state.newActivity == '') { return null; } + + let buttonText = this.state.showPreview ? 'Edit' : 'Preview'; + + return ( + + {buttonText} + + ); + } + + toggleActivityPreview = () => { + this.setState({ showPreview: !this.state.showPreview }); + } + + setActivityState = (e: any) => { + this.setState({ newActivity: e.target.value }); + } + + getTextarea() { + let textArea = ( + + ); + + if (this.state.showPreview) { + textArea = ; + } + + return textArea; + } + + render() { + let commentFormClass = this.props.buttonType === 'reject' ? ' comment-and-reject' : ''; + + return ( +
    +
    + {this.getTextarea()} +
    +
    + + + + {this.PreviewButton} + {this.ActivityButton} +
    +
    + ); + } +} diff --git a/app/javascript/components/api/ApiInteractions.tsx b/app/javascript/components/api/ApiInteractions.tsx new file mode 100644 index 0000000..903cf17 --- /dev/null +++ b/app/javascript/components/api/ApiInteractions.tsx @@ -0,0 +1,142 @@ +import React, { Component, Fragment } from 'react'; +import JSONPretty from 'react-json-pretty'; +import StatusCircle from '../shared/Icons/StatusCircle'; +import Done from '../shared/Icons/Done'; +import Clear from '../shared/Icons/Clear'; +import Pagination from '../shared/Pagination/Pagination'; + +require('react-json-pretty/themes/1337.css'); + +type Props = { + apiInteractions: Api.ApiInteraction[] + page: string + perPage: number + totalCount: number +} + +type State = { + openDrawers: any +} + +export default class ApiInteractions extends Component { + state: State = { + openDrawers: {}, + }; + + toggleMetadataDrawer = (drawerIndex: number) => { + this.setState(prevState => ({ + openDrawers: { + ...prevState.openDrawers, + [drawerIndex]: !prevState.openDrawers[drawerIndex], + } + })); + } + + goToPage = (pageObj: any) => { + window.location.href = + `${window.location.href.split('?')[0]}?page=${pageObj.selected + 1}` + } + + render() { + const { apiInteractions, perPage, totalCount } = this.props; + const page = Number(this.props.page); + const totalPages = Math.ceil(totalCount / perPage); + + return ( +
    +
    +

    API Interactions

    +
    +
    + + + + + + + + + + + + + + {apiInteractions.map((interaction: any, i: number) => { + const anyMetadata = interaction.metadata ? Object.keys(interaction.metadata).length > 0 : false; + + return ( + + this.toggleMetadataDrawer(i)} + className="tr" + > + + + + + + + + + + {this.state.openDrawers[i] && ( + + + + )} + + )} + )} + +
    RESPONSE CODEMETHODPATHDURATIONUSER IDIP ADDRRECORD IDMETADATA
    +
    + +
    +
    + {interaction.response_code} +
    +
    {interaction.method}{interaction.path}{`${interaction.duration}ms`}{interaction.user_id}{interaction.ip}{interaction.id} + {anyMetadata ? ( +
    + +
    + ) : ( +
    + +
    + )} +
    +
    + {anyMetadata ? ( + + ) : ( +

    No metadata on this call

    + )} +
    +
    + + + {totalPages > 0 && ( + + )} + + ); + } +} + +const statusCodeColor = (responseCode: number) => { + const code = responseCode.toString(); + return code.startsWith('1') || code.startsWith('2') || code.startsWith('3') ? 'green' : 'red'; +} \ No newline at end of file diff --git a/app/javascript/components/api/ApiToken.tsx b/app/javascript/components/api/ApiToken.tsx new file mode 100644 index 0000000..0810733 --- /dev/null +++ b/app/javascript/components/api/ApiToken.tsx @@ -0,0 +1,86 @@ +import React, { Component } from 'react' +import CopyToClipboard from 'react-copy-to-clipboard'; +import http from '../../lib/http' +import {apiV1RegenerateTokenPath} from '../../generated/routes' +import Button from '../shared/Button/Button' + +type Props = { + apiToken: string + regeneratePath: string +} + +type State = { + apiToken: string + copyButtonText: string +} + +export default class ApiToken extends Component { + state: State = { + apiToken: this.props.apiToken, + copyButtonText: 'Copy Token', + } + + regenerateApiToken = () => { + http('GET', apiV1RegenerateTokenPath()).then((resp: any) => { + this.setState({ + apiToken: resp["token"], + copyButtonText: 'Copy Token', + }) + }) + } + + onCopyClick = () => { + this.setState({ + copyButtonText: 'Copied!' + }); +} + + render() { + const { apiToken } = this.state + + return ( +
    +
    +

    Learn API

    +
    +

    Docs

    +

    + + API Documentation is auto-generated and contains samples that can be used for mocking tests. +

    +

    Token

    +

    + This token allows your app to authenticate requests to the Learn API. + Please don't share it with anyone, include it in any code repositories, or store it in an insecure way. + It will not expire unless you generate a new token. +

    +
    + + {apiToken !== "" && + +
    +
    +
    +
    + ) + } +} diff --git a/app/javascript/components/blocks/BlockPage-v2.tsx b/app/javascript/components/blocks/BlockPage-v2.tsx new file mode 100644 index 0000000..a8a749e --- /dev/null +++ b/app/javascript/components/blocks/BlockPage-v2.tsx @@ -0,0 +1,331 @@ +import * as React from 'react'; +import { debounce } from 'lodash'; +import * as Routes from '../../generated/routes' +import Avatar from '../UserAvatar'; +import UniversalList from '../shared/UniversalList/UniversalList'; +import UniversalRow from '../shared/UniversalRow/UniversalRow'; +import Button from '../shared/Button/Button'; +import UniversalTable from '../shared/UniversalTable/UniversalTable'; +import BlocksStats from './BlocksStats'; +import Icon from '../Icon'; +import Pill from '../shared/Pill/Pill'; +import ProcessingIcon from '../ProcessingIcon'; +import FlashAlert from '../shared/FlashAlert/FlashAlert'; +import http from '../../lib/http'; +import ErrorMessagesTable from '../shared/ErrorMessagesTable/ErrorMessagesTable'; +import BlocksNewRelease from './BlocksNewRelease'; + +const timezone = window.moment.tz.guess(); + +const ALERT_STATES = { + success: 'success', + queued: 'queued', + error: 'error' +} + +const pollingThrottleDelay = 2000; + +type Props = { + block: Api.BlockPresenter_ForBlocks, + releases: Api.BlockPresenter_ForReleases[], + github_url: string +} + +type State = { + alertState: string, + newReleaseModalVisible: boolean, + releases: Api.BlockPresenter_ForReleases[], + syncErrors: string[] +} + +class BlockPage extends React.Component { + releasePollingThrottled: any; + + constructor(props: Props) { + super(props) + + this.state = { + alertState: '', + newReleaseModalVisible: false, + releases: this.props.releases || [], + syncErrors: this.props.block.sync_errors || [] + } + + this.releasePollingThrottled = debounce(this.releasePolling.bind(this), pollingThrottleDelay); + } + + componentDidMount() { + if (this.state.releases[0] && this.state.releases[0].github_sha === 'pending') { + this.releasePollingThrottled(this.props.block.id); + } + } + + // TODO: call (debounced) after creating a new modal + releasePolling = () => { + http('GET', Routes.releasePollingBlockPath(this.props.block.id)) + .then((data: any) => { + if (data.block.sync_errors.length > 0) { + this.setState({ + releases: data.releases, + syncErrors: data.block.sync_errors, + alertState: ALERT_STATES.error + }); + } else if (data.releases[0] && data.releases[0].github_sha === 'pending') { + this.setState({ alertState: ALERT_STATES.queued }) + this.releasePollingThrottled(); + } else if (data.releases.length > 0) { + this.setState({ + releases: data.releases, + syncErrors: [], + alertState: ALERT_STATES.success + }); + } + }); + } + + generateAlert = (block: string) => ({ + success: { type: 'success', message: 'Release created.' }, + queued: { type: 'notice', message: 'Release creation queued.' }, + error: { type: 'error', message: <>{block}:{' Error. Unable to build release.'} }, + }) + + generateBody = () => { + const infoRow = this.generateInfoRow(); + const statsRow = this.generateStatsRow(); + const releasesRow = this.generateReleasesRow(); + const cohortsRow = this.generateCohortsRow(); + + return [infoRow, statsRow, releasesRow, cohortsRow] + } + + generateButtons = () => { + // TODO: implement other addt'l buttons + // TODO: implement modal work + const newReleaseBtn = ( + {this.renderResults()} -- GitLab From fd8d8edd97d3d4df4fe553b990ad27ceb0f37554 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Mon, 14 Sep 2020 17:29:26 -1000 Subject: [PATCH 032/287] Add alert styling to add users to cohorts form results --- app/javascript/components/cohorts/UsersNew.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/javascript/components/cohorts/UsersNew.tsx b/app/javascript/components/cohorts/UsersNew.tsx index c7af1b5..e80f1d8 100644 --- a/app/javascript/components/cohorts/UsersNew.tsx +++ b/app/javascript/components/cohorts/UsersNew.tsx @@ -32,12 +32,12 @@ export default class UsersNew extends Component { const body = { email: value }; const data = await http('POST', Routes.apiV1CohortUsersPath(this.props.cohortId), { body }); if (data.error) { - results.push(`${value} - ERROR: ${data.error}`); + results.push({ success: false, message: `${value} - ERROR: ${data.error}`}); } else { - results.push(`${value} - Added to cohort`); + results.push({ success: true, message: `${value} - Added to cohort`}); } } - this.setState({ results : results }); + this.setState({value: '', results : results }); } renderResults = () => { @@ -50,7 +50,7 @@ export default class UsersNew extends Component {
    Results
    { this.state.results.map(result => { return ( -

    {result}

    +
    {result.message}
    ) })} -- GitLab From d1d29471c998185125db05842f304911b6756d4f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 14 Sep 2020 17:38:24 -1000 Subject: [PATCH 033/287] removing error from message --- app/javascript/components/cohorts/UsersNew.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/components/cohorts/UsersNew.tsx b/app/javascript/components/cohorts/UsersNew.tsx index e80f1d8..ca91b95 100644 --- a/app/javascript/components/cohorts/UsersNew.tsx +++ b/app/javascript/components/cohorts/UsersNew.tsx @@ -32,12 +32,12 @@ export default class UsersNew extends Component { const body = { email: value }; const data = await http('POST', Routes.apiV1CohortUsersPath(this.props.cohortId), { body }); if (data.error) { - results.push({ success: false, message: `${value} - ERROR: ${data.error}`}); + results.push({ success: false, message: `${value} - ${data.error}`}); } else { - results.push({ success: true, message: `${value} - Added to cohort`}); + results.push({ success: true, message: `${value} - Added to cohort.`}); } } - this.setState({value: '', results : results }); + this.setState({ value: '', results: results }); } renderResults = () => { -- GitLab From b5f4959f07bc2cd0782d86075953523fd3ae853f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 14 Sep 2020 17:41:48 -1000 Subject: [PATCH 034/287] fixing type error --- app/javascript/components/cohorts/UsersNew.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/javascript/components/cohorts/UsersNew.tsx b/app/javascript/components/cohorts/UsersNew.tsx index ca91b95..1447fda 100644 --- a/app/javascript/components/cohorts/UsersNew.tsx +++ b/app/javascript/components/cohorts/UsersNew.tsx @@ -2,12 +2,17 @@ import React, { Component } from 'react' import * as Routes from '../../generated/routes' import http from '../../lib/http' +interface ResultObj { + success: boolean, + message: string +} + type Props = { cohortId: number } type State = { value: string, - results: string[] + results: ResultObj[] } export default class UsersNew extends Component { @@ -26,7 +31,7 @@ export default class UsersNew extends Component { let values:string[] = this.state.value.split('\n').map(value => value.trim()); values = values.filter(value => value !== ''); - let results = []; + let results:ResultObj[] = []; for (let i = 0; i < values.length; i++) { let value = values[i]; const body = { email: value }; -- GitLab From 63060bc5574e36e60a59d002ecf5a69907dbf457 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 14 Sep 2020 17:50:50 -1000 Subject: [PATCH 035/287] fixing type issues. fixing all warnings from IDE. --- app/javascript/components/cohorts/UsersNew.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/javascript/components/cohorts/UsersNew.tsx b/app/javascript/components/cohorts/UsersNew.tsx index 1447fda..ef41550 100644 --- a/app/javascript/components/cohorts/UsersNew.tsx +++ b/app/javascript/components/cohorts/UsersNew.tsx @@ -12,20 +12,21 @@ type Props = { } type State = { value: string, - results: ResultObj[] + results: ResultObj[], + textAreaDisplayRows: number } export default class UsersNew extends Component { - constructor(props) { + constructor(props: Props) { super(props); - this.state = { value: '', results: [] }; + this.state = { value: '', results: [], textAreaDisplayRows: 10 }; } - handleChange = event => { - this.setState({value: event.target.value}); + handleChange = (event:any) => { + this.setState({ value: event.target.value }); } - handleSubmit = async event => { + handleSubmit = async (event:any) => { event.preventDefault(); let values:string[] = this.state.value.split('\n').map(value => value.trim()); @@ -76,7 +77,7 @@ export default class UsersNew extends Component {

    - +
    -- GitLab From 3c0a79d96bcaba250e3567f1f3da8c3d4ff2c2f4 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 15 Sep 2020 10:20:49 -1000 Subject: [PATCH 036/287] styling the new and edit cohort form. --- app/assets/stylesheets/cohorts.scss | 5 ++++ app/controllers/cohorts_controller.rb | 4 ++- app/views/cohorts/_cohort_edit_form.html.haml | 27 ++++++++++++------- app/views/cohorts/edit.html.haml | 4 ++- app/views/cohorts/new.html.haml | 4 ++- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/cohorts.scss b/app/assets/stylesheets/cohorts.scss index aaf876c..18fbca1 100644 --- a/app/assets/stylesheets/cohorts.scss +++ b/app/assets/stylesheets/cohorts.scss @@ -7,3 +7,8 @@ color: $color-lightened-black; } } + +.form-group.required .control-label:after { + content:"*"; + color:red; +} \ No newline at end of file diff --git a/app/controllers/cohorts_controller.rb b/app/controllers/cohorts_controller.rb index 4668e3e..ded9086 100644 --- a/app/controllers/cohorts_controller.rb +++ b/app/controllers/cohorts_controller.rb @@ -6,6 +6,7 @@ class CohortsController < ApplicationController @cohort = Cohort.new() @controller_action = 'create' + @button_text = 'Create Cohort' end def create @@ -28,6 +29,7 @@ class CohortsController < ApplicationController @cohort = current_cohort @controller_action = 'update_cohort' + @button_text = 'Update Cohort' end def update_cohort @@ -470,7 +472,7 @@ class CohortsController < ApplicationController # Cohort Create and Update Admin Views. def cohort_params - params.require(:cohort).permit(:name, :product_type, :starts_on) + params.require(:cohort).permit(:name, :product_type, :starts_on, :ends_on) end def update_cohort_params diff --git a/app/views/cohorts/_cohort_edit_form.html.haml b/app/views/cohorts/_cohort_edit_form.html.haml index aa09598..b96ee78 100644 --- a/app/views/cohorts/_cohort_edit_form.html.haml +++ b/app/views/cohorts/_cohort_edit_form.html.haml @@ -1,11 +1,18 @@ = form_for @cohort, :url => { :controller => 'cohorts', :action => @controller_action } do |f| - = f.label :name, "Name", :class => '' - = f.text_field :name - - = f.label :product_type, "Product Type", :class => '' - = f.text_field :product_type - - = f.label :starts_on, "Start Date", :class => '' - = f.date_field :starts_on - - = f.submit \ No newline at end of file + .form-group.required + .control-label + = f.label :name, "Name", required: true + = f.text_field :name + .form-group.required + .control-label + = f.label :product_type, "Product Type" + = f.text_field :product_type + .form-group.required + .control-label + = f.label :starts_on, "Start Date" + = f.date_field :starts_on + .form-group + = f.label :ends_on, "End Date" + %br + = f.date_field :ends_on + = f.submit @button_text, class: "btn btn-sm btn-primary" \ No newline at end of file diff --git a/app/views/cohorts/edit.html.haml b/app/views/cohorts/edit.html.haml index 1dfdac6..669f3ab 100644 --- a/app/views/cohorts/edit.html.haml +++ b/app/views/cohorts/edit.html.haml @@ -1 +1,3 @@ -= render 'cohort_edit_form' \ No newline at end of file +.with-margins + %h1 Update Cohort + = render 'cohort_edit_form' \ No newline at end of file diff --git a/app/views/cohorts/new.html.haml b/app/views/cohorts/new.html.haml index 1dfdac6..30b08aa 100644 --- a/app/views/cohorts/new.html.haml +++ b/app/views/cohorts/new.html.haml @@ -1 +1,3 @@ -= render 'cohort_edit_form' \ No newline at end of file +.with-margins + %h1 New Cohort + = render 'cohort_edit_form' \ No newline at end of file -- GitLab From df9dfffd248c3e60e16c1560857bdb64768bbe34 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 15 Sep 2020 11:29:40 -1000 Subject: [PATCH 037/287] removing unused property --- app/views/cohorts/_cohort_edit_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/cohorts/_cohort_edit_form.html.haml b/app/views/cohorts/_cohort_edit_form.html.haml index b96ee78..abbd806 100644 --- a/app/views/cohorts/_cohort_edit_form.html.haml +++ b/app/views/cohorts/_cohort_edit_form.html.haml @@ -1,7 +1,7 @@ = form_for @cohort, :url => { :controller => 'cohorts', :action => @controller_action } do |f| .form-group.required .control-label - = f.label :name, "Name", required: true + = f.label :name, "Name" = f.text_field :name .form-group.required .control-label -- GitLab From 56929587158247a60f2599e8f6082fc688bb64a1 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Tue, 15 Sep 2020 12:48:56 -1000 Subject: [PATCH 038/287] Add ability to edit cohort users settings --- app/controllers/cohorts_controller.rb | 2 +- app/controllers/users_controller.rb | 27 ++++ .../components/cohorts/UsersNew.tsx | 138 +++++++++--------- .../cohorts/settings/UserCohortKebab.tsx | 73 +++++++++ app/policies/cohort_policy.rb | 4 + app/views/cohorts/_cohort_edit_form.html.haml | 6 +- app/views/cohorts/users.html.haml | 22 ++- app/views/users/edit.html.haml | 9 ++ config/routes.rb | 2 +- 9 files changed, 200 insertions(+), 83 deletions(-) create mode 100644 app/javascript/components/cohorts/settings/UserCohortKebab.tsx create mode 100644 app/views/users/edit.html.haml diff --git a/app/controllers/cohorts_controller.rb b/app/controllers/cohorts_controller.rb index ded9086..374df2b 100644 --- a/app/controllers/cohorts_controller.rb +++ b/app/controllers/cohorts_controller.rb @@ -472,7 +472,7 @@ class CohortsController < ApplicationController # Cohort Create and Update Admin Views. def cohort_params - params.require(:cohort).permit(:name, :product_type, :starts_on, :ends_on) + params.require(:cohort).permit(:name, :product_type, :campus_name, :starts_on, :ends_on) end def update_cohort_params diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 9ef84ba..53d6131 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,4 +3,31 @@ class UsersController < ApplicationController authorize(Cohort) @cohort_id = params[:cohort_id] end + + def edit + authorize(current_cohort) + current_cohort_user + end + + def update + authorize(current_cohort) + current_cohort_user.update(roles: [params[:instructor]].compact) + redirect_to users_cohort_path(current_cohort) + end + + def destroy + authorize(current_cohort) + current_cohort_user.destroy + redirect_to users_cohort_path(current_cohort) + end + + private + + def current_cohort + @current_cohort ||= Cohort.find(params[:cohort_id]) + end + + def current_cohort_user + @cohort_user ||= CohortUser.find_by(cohort_id: params[:cohort_id], user_id: params[:id]) + end end diff --git a/app/javascript/components/cohorts/UsersNew.tsx b/app/javascript/components/cohorts/UsersNew.tsx index ef41550..6cf86b2 100644 --- a/app/javascript/components/cohorts/UsersNew.tsx +++ b/app/javascript/components/cohorts/UsersNew.tsx @@ -1,89 +1,91 @@ -import React, { Component } from 'react' +import React, {Component} from 'react' import * as Routes from '../../generated/routes' import http from '../../lib/http' interface ResultObj { - success: boolean, - message: string + success: boolean, + message: string } type Props = { - cohortId: number + cohortId: number } type State = { - value: string, - results: ResultObj[], - textAreaDisplayRows: number + value: string, + results: ResultObj[], + textAreaDisplayRows: number } export default class UsersNew extends Component { - constructor(props: Props) { - super(props); - this.state = { value: '', results: [], textAreaDisplayRows: 10 }; - } + constructor(props: Props) { + super(props); + this.state = {value: '', results: [], textAreaDisplayRows: 10}; + } - handleChange = (event:any) => { - this.setState({ value: event.target.value }); - } + handleChange = (event: any) => { + this.setState({value: event.target.value}); + } - handleSubmit = async (event:any) => { - event.preventDefault(); + handleSubmit = async (event: any) => { + event.preventDefault(); - let values:string[] = this.state.value.split('\n').map(value => value.trim()); - values = values.filter(value => value !== ''); + let values: string[] = this.state.value.split('\n').map(value => value.trim()); + values = values.filter(value => value !== ''); - let results:ResultObj[] = []; - for (let i = 0; i < values.length; i++) { - let value = values[i]; - const body = { email: value }; - const data = await http('POST', Routes.apiV1CohortUsersPath(this.props.cohortId), { body }); - if (data.error) { - results.push({ success: false, message: `${value} - ${data.error}`}); - } else { - results.push({ success: true, message: `${value} - Added to cohort.`}); - } - } - this.setState({ value: '', results: results }); + let results: ResultObj[] = []; + for (let i = 0; i < values.length; i++) { + let value = values[i]; + const body = {email: value}; + const data = await http('POST', Routes.apiV1CohortUsersPath(this.props.cohortId), {body}); + if (data.error) { + results.push({success: false, message: `${value} - ${data.error}`}); + } else { + results.push({success: true, message: `${value} - Added to cohort.`}); + } } + this.setState({value: '', results: results}); + } - renderResults = () => { - if (!this.state.results.length) return null; - return ( -
    -
    -
    -
    -
    Results
    - { this.state.results.map(result => { - return ( -
    {result.message}
    - ) - })} -
    -
    -
    + renderResults = () => { + if (!this.state.results.length) return null; + return ( +
    +
    +
    +
    +
    Results
    + {this.state.results.map(result => { + return ( +
    {result.message}
    + ) + })}
    - ); - } +
    +
    +
    + ); + } - render() { - return ( -
    -

    Add Users to Cohort

    -
    -

    Enter email addresses of the users you wish to add to this cohort. - These users need to exist in KeyCloak in the proper realm. - You can enter multiple email addresses by separating them with new lines. -

    -
    - - -
    -
    - -
    - {this.renderResults()} -
    - ); - } + render() { + return ( +
    +

    Add Users to Cohort

    +
    +

    Enter email addresses of the users you wish to add to this cohort. + These users need to exist in KeyCloak in the proper realm. + You can enter multiple email addresses by separating them with new lines. +

    +
    + + +
    +
    + +
    + {this.renderResults()} +
    + ); + } } diff --git a/app/javascript/components/cohorts/settings/UserCohortKebab.tsx b/app/javascript/components/cohorts/settings/UserCohortKebab.tsx new file mode 100644 index 0000000..beb3fdc --- /dev/null +++ b/app/javascript/components/cohorts/settings/UserCohortKebab.tsx @@ -0,0 +1,73 @@ +import * as React from 'react' +import * as Routes from '../../../generated/routes' +import RowKebab from '../../RowKebab' + +type Props = { + studentId: number + studentUid: string + authUrl: string + cohortId: number +} + +type State = { + showActionMenu: boolean +} + +export default class UserKebab extends React.Component { + constructor(props: Props) { + super(props) + this.state = { + showActionMenu: false + } + } + + toggleActionMenu = () => { + this.setState({showActionMenu: !this.state.showActionMenu}); + } + + handleMenuOutsideClick = (e: any) => { + if ($_('#action-menu', el => el.contains(e.target))) { + return; + } + + this.toggleActionMenu(); + } + + addClickListeners(handler: any) { + document.addEventListener('touchmove', handler, false); + document.addEventListener('click', handler, false); + } + + removeClickListeners(handler: any) { + document.removeEventListener('touchmove', handler, false); + document.removeEventListener('click', handler, false); + } + + get actionMenu() { + if (this.state.showActionMenu) { + this.addClickListeners(this.handleMenuOutsideClick); + + return ( + + ); + } else { + this.removeClickListeners(this.handleMenuOutsideClick); + } + } + + render() { + return ( + <> + + {this.actionMenu} + + ) + } +} diff --git a/app/policies/cohort_policy.rb b/app/policies/cohort_policy.rb index 02fe526..bd9d955 100644 --- a/app/policies/cohort_policy.rb +++ b/app/policies/cohort_policy.rb @@ -16,6 +16,10 @@ class CohortPolicy < ApplicationPolicy user.instructor_or_admin?(record.id) end + def destroy? + user.instructor_or_admin?(record.id) + end + def update? user.instructor_or_admin?(record.id) end diff --git a/app/views/cohorts/_cohort_edit_form.html.haml b/app/views/cohorts/_cohort_edit_form.html.haml index b96ee78..9027241 100644 --- a/app/views/cohorts/_cohort_edit_form.html.haml +++ b/app/views/cohorts/_cohort_edit_form.html.haml @@ -7,6 +7,10 @@ .control-label = f.label :product_type, "Product Type" = f.text_field :product_type + .form-group + = f.label :campus_name, "Campus Name" + %br + = f.text_field :campus_name .form-group.required .control-label = f.label :starts_on, "Start Date" @@ -15,4 +19,4 @@ = f.label :ends_on, "End Date" %br = f.date_field :ends_on - = f.submit @button_text, class: "btn btn-sm btn-primary" \ No newline at end of file + = f.submit @button_text, class: "btn btn-sm btn-primary" diff --git a/app/views/cohorts/users.html.haml b/app/views/cohorts/users.html.haml index 4a1dc15..c3e505a 100644 --- a/app/views/cohorts/users.html.haml +++ b/app/views/cohorts/users.html.haml @@ -17,18 +17,13 @@ %div %p= link_to("Partnerup", partnerup_cohort_path(cohort), class: "") - if cohort.mode == "Percentage" - = react_component "cohorts/settings/UserImport", - cohortName: cohort.name, - importState: cohort.student_import_state, - importCompletedAt: cohort.student_import_completed_at, - userEmail: current_user.email, + = react_component "cohorts/settings/UserImport", + cohortName: cohort.name, + importState: cohort.student_import_state, + importCompletedAt: cohort.student_import_completed_at, + userEmail: current_user.email, importPath: import_users_work_cohort_path(cohort), importStatusPath: import_users_work_status_cohort_path(cohort) - .managebutton - - if current_user.role?(User::ROLES[:product_admin]) || current_user.product_admin_of?(cohort.id) - = link_to auth_product_url, class: "lp-style-button", target: "_blank" do - Manage Users in Auth - = react_component "SvgRenderer", viewBox: "-8 -8 10 24", style: {fill: "white"}, url: path_to_image("svg/svg-sprite-action-symbol.svg#ic_launch_24px") %table.cohortsetuptable %thead.cohortsetupheader %tr @@ -42,7 +37,10 @@ %td.cohortusername= instructor.full_name %td= instructor.github_username %td= instructor.email - %td yes + %td.action-cell + yes + %div.user-action + = react_component "cohorts/settings/UserCohortKebab", studentId: instructor.id, studentUid: instructor.uid, cohortId: cohort.id, authUrl: Rails.application.secrets.auth_url - cohort.students.order(first_name: :asc).each do |student| %tr.cohortsetuprow %td.cohortusername= student.full_name @@ -51,4 +49,4 @@ %td.action-cell no %div.user-action - = react_component "cohorts/settings/UserKebab", studentId: student.id, studentUid: student.uid, cohortId: cohort.id, authUrl: Rails.application.secrets.auth_url + = react_component "cohorts/settings/UserCohortKebab", studentId: student.id, studentUid: student.uid, cohortId: cohort.id, authUrl: Rails.application.secrets.auth_url diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml new file mode 100644 index 0000000..1093e93 --- /dev/null +++ b/app/views/users/edit.html.haml @@ -0,0 +1,9 @@ +.container + = @cohort_user.user.full_name + = @cohort_user.cohort.name + = form_for @cohort_user, :url => { :controller => 'users', :action => :update } do |f| + .form-group + = label :instructor, "Instructor" + = check_box_tag :instructor, "forge.instructor", @cohort_user.roles.include?("forge.instructor") + = f.submit "Update Cohort User Settings", class: "btn btn-sm btn-primary" + = link_to "Delete", cohort_user_path(@cohort_user.cohort_id, @cohort_user.user_id), method: :delete, data: { confirm: "Really?" } diff --git a/config/routes.rb b/config/routes.rb index e575ff9..60296a1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -57,7 +57,7 @@ Rails.application.routes.draw do get "/blocks-v2/:id" => "blocks#blockpagev2", as: "blockpage", format: false resources :cohorts, only: %i[new create index edit show update] do - resources :users, only: %i[new] + resources :users, only: %i[new edit update destroy] get "standards_for_section/:section_id" => :standards_for_section, as: "standards_for_section" member do -- GitLab From 159d46c30d9c09d11fe79580b056e764cd1cc50b Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 15 Sep 2020 13:31:33 -1000 Subject: [PATCH 039/287] styling the cohort edit forms --- app/views/cohorts/_cohort_edit_form.html.haml | 12 ++++++------ app/views/cohorts/edit.html.haml | 2 +- app/views/cohorts/new.html.haml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/cohorts/_cohort_edit_form.html.haml b/app/views/cohorts/_cohort_edit_form.html.haml index 9c48666..87a34c2 100644 --- a/app/views/cohorts/_cohort_edit_form.html.haml +++ b/app/views/cohorts/_cohort_edit_form.html.haml @@ -2,21 +2,21 @@ .form-group.required .control-label = f.label :name, "Name" - = f.text_field :name + = f.text_field :name, class: "form-control" .form-group.required .control-label = f.label :product_type, "Product Type" - = f.text_field :product_type + = f.text_field :product_type, class: "form-control" .form-group = f.label :campus_name, "Campus Name" %br - = f.text_field :campus_name + = f.text_field :campus_name, class: "form-control" .form-group.required .control-label = f.label :starts_on, "Start Date" - = f.date_field :starts_on + = f.date_field :starts_on, class: "form-control" .form-group = f.label :ends_on, "End Date" %br - = f.date_field :ends_on - = f.submit @button_text, class: "btn btn-sm btn-primary" + = f.date_field :ends_on, class: "form-control" + = f.submit @button_text, class: "btn btn-primary" diff --git a/app/views/cohorts/edit.html.haml b/app/views/cohorts/edit.html.haml index 669f3ab..f2c0744 100644 --- a/app/views/cohorts/edit.html.haml +++ b/app/views/cohorts/edit.html.haml @@ -1,3 +1,3 @@ -.with-margins +.container %h1 Update Cohort = render 'cohort_edit_form' \ No newline at end of file diff --git a/app/views/cohorts/new.html.haml b/app/views/cohorts/new.html.haml index 30b08aa..1a3ad0f 100644 --- a/app/views/cohorts/new.html.haml +++ b/app/views/cohorts/new.html.haml @@ -1,3 +1,3 @@ -.with-margins +.container %h1 New Cohort = render 'cohort_edit_form' \ No newline at end of file -- GitLab From ba1e0b22d45d351338fd2957ad96ce019f15459e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 15 Sep 2020 17:29:21 -1000 Subject: [PATCH 040/287] styling the cohort user edit screen --- app/views/users/edit.html.haml | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index 1093e93..30c53cf 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -1,9 +1,20 @@ .container - = @cohort_user.user.full_name - = @cohort_user.cohort.name + %h1 Update Cohort User +%br +.container = form_for @cohort_user, :url => { :controller => 'users', :action => :update } do |f| + %h5 Basic Info: + .form-group + = label :cohort_name, :title, "Cohort Name" + = text_field_tag :cohort_name, @cohort_user.cohort.name, class: "form-control", readonly: true + .form-group + = label :user_name, :title, "Full Name" + = text_field_tag :user_name, @cohort_user.user.full_name, class: "form-control", readonly: true + %h5 Roles: .form-group - = label :instructor, "Instructor" - = check_box_tag :instructor, "forge.instructor", @cohort_user.roles.include?("forge.instructor") - = f.submit "Update Cohort User Settings", class: "btn btn-sm btn-primary" - = link_to "Delete", cohort_user_path(@cohort_user.cohort_id, @cohort_user.user_id), method: :delete, data: { confirm: "Really?" } + .form-check + = check_box_tag :instructor, "forge.instructor", @cohort_user.roles.include?("forge.instructor"), class: "form-check-input" + = label :instructor, "Instructor" + = f.submit "Update Cohort User", class: "btn btn-primary" + %br + = link_to "Remove User From Cohort", cohort_user_path(@cohort_user.cohort_id, @cohort_user.user_id), method: :delete, data: { confirm: "Really?" }, class: "btn btn-danger" -- GitLab From ffb29c23053b8b6aa90ce5dc99ed83049c12312e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 15 Sep 2020 17:48:10 -1000 Subject: [PATCH 041/287] fixing links --- app/controllers/cohorts_controller.rb | 2 +- app/javascript/components/cohorts/settings/CohortInfo.tsx | 3 +-- app/views/cohorts/setup.html.haml | 2 +- app/views/cohorts/users.html.haml | 5 +++++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/controllers/cohorts_controller.rb b/app/controllers/cohorts_controller.rb index 374df2b..159fdd6 100644 --- a/app/controllers/cohorts_controller.rb +++ b/app/controllers/cohorts_controller.rb @@ -38,7 +38,7 @@ class CohortsController < ApplicationController @cohort = current_cohort if @cohort.update(cohort_params) - redirect_to cohort_path + redirect_to setup_cohort_path(@cohort) else flash[:error] = @cohort.errors.full_messages.join(", ") render :edit diff --git a/app/javascript/components/cohorts/settings/CohortInfo.tsx b/app/javascript/components/cohorts/settings/CohortInfo.tsx index 1bedfc5..03dcabf 100644 --- a/app/javascript/components/cohorts/settings/CohortInfo.tsx +++ b/app/javascript/components/cohorts/settings/CohortInfo.tsx @@ -28,9 +28,8 @@ export default class CohortInfo extends React.Component {

    {cohortStartDateFormatted}

    { editUrl && !sandboxCohort && - + Edit - } diff --git a/app/views/cohorts/setup.html.haml b/app/views/cohorts/setup.html.haml index d528196..0ae17ab 100644 --- a/app/views/cohorts/setup.html.haml +++ b/app/views/cohorts/setup.html.haml @@ -1,7 +1,7 @@ = react_component "cohorts/settings/CohortInfo", cohort: cohort, cohortStartDateFormatted: cohort.starts_on.strftime("%b %e, %Y"), - editUrl: current_user.role?(User::ROLES[:product_admin]) || current_user.product_admin_of?(cohort.id) ? edit_cohort_path : nil, + editUrl: current_user.role?(User::ROLES[:product_admin]) || current_user.product_admin_of?(cohort.id) ? edit_cohort_path(cohort.id) : nil, targetBlankIconSrc: path_to_image("svg/svg-sprite-action-symbol.svg#ic_launch_24px") = react_component "cohorts/settings/CohortSettingsTabs", diff --git a/app/views/cohorts/users.html.haml b/app/views/cohorts/users.html.haml index c3e505a..9d63cf7 100644 --- a/app/views/cohorts/users.html.haml +++ b/app/views/cohorts/users.html.haml @@ -24,6 +24,11 @@ userEmail: current_user.email, importPath: import_users_work_cohort_path(cohort), importStatusPath: import_users_work_status_cohort_path(cohort) + .managebutton + - if current_user.role?(User::ROLES[:product_admin]) || current_user.product_admin_of?(cohort.id) + = link_to new_cohort_user_path(cohort.id), class: "lp-style-button", target: "_blank" do + Add Users + = react_component "SvgRenderer", viewBox: "-8 -8 10 24", style: {fill: "white"}, url: path_to_image("svg/svg-sprite-action-symbol.svg#ic_launch_24px") %table.cohortsetuptable %thead.cohortsetupheader %tr -- GitLab From b60848ae506a9c6413b1e04f2868d3d67cef1c41 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 15 Sep 2020 17:50:19 -1000 Subject: [PATCH 042/287] removing manager users in auth button from partnerup tab --- app/views/cohorts/partnerup.html.haml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/views/cohorts/partnerup.html.haml b/app/views/cohorts/partnerup.html.haml index 1986a41..f692e28 100644 --- a/app/views/cohorts/partnerup.html.haml +++ b/app/views/cohorts/partnerup.html.haml @@ -15,11 +15,6 @@ %p= link_to("Users (#{cohort.cohort_users.length})", users_cohort_path(cohort), class: "") .selected %p= link_to("Partnerup", partnerup_cohort_path(cohort), class: "") - .managebutton - - if current_user.role?(User::ROLES[:product_admin]) || current_user.product_admin_of?(cohort.id) - = link_to auth_product_url, class: "lp-style-button", target: "_blank" do - Manage Users in Auth - = react_component "SvgRenderer", viewBox: "-8 -8 10 24", style: {fill: "white"}, url: path_to_image("svg/svg-sprite-action-symbol.svg#ic_launch_24px") = react_component "cohorts/pairing/GroupPairs", cohort_id: cohort.id, -- GitLab From 118ec99b52ae30fe3e4027213fc29ce5f22bee6d Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 16 Sep 2020 20:32:09 -1000 Subject: [PATCH 044/287] fixing remove confirmation message. changing the link to edit a cohort user to not open in a new tab. --- app/javascript/components/cohorts/settings/UserCohortKebab.tsx | 2 +- app/views/users/edit.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/components/cohorts/settings/UserCohortKebab.tsx b/app/javascript/components/cohorts/settings/UserCohortKebab.tsx index beb3fdc..7db7964 100644 --- a/app/javascript/components/cohorts/settings/UserCohortKebab.tsx +++ b/app/javascript/components/cohorts/settings/UserCohortKebab.tsx @@ -52,7 +52,7 @@ export default class UserKebab extends React.Component { diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index 30c53cf..3513485 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -17,4 +17,4 @@ = label :instructor, "Instructor" = f.submit "Update Cohort User", class: "btn btn-primary" %br - = link_to "Remove User From Cohort", cohort_user_path(@cohort_user.cohort_id, @cohort_user.user_id), method: :delete, data: { confirm: "Really?" }, class: "btn btn-danger" + = link_to "Remove User From Cohort", cohort_user_path(@cohort_user.cohort_id, @cohort_user.user_id), method: :delete, data: { confirm: "Are you sure you want to remove #{@cohort_user.user.full_name} from this cohort?" }, class: "btn btn-danger" \ No newline at end of file -- GitLab From 2da175745a63009323f5414d47f91dae93c018dc Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 17 Sep 2020 10:21:23 -1000 Subject: [PATCH 045/287] changing remove cohort user confirmation to a modal. --- app/views/users/edit.html.haml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index 3513485..a702522 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -16,5 +16,14 @@ = check_box_tag :instructor, "forge.instructor", @cohort_user.roles.include?("forge.instructor"), class: "form-check-input" = label :instructor, "Instructor" = f.submit "Update Cohort User", class: "btn btn-primary" - %br - = link_to "Remove User From Cohort", cohort_user_path(@cohort_user.cohort_id, @cohort_user.user_id), method: :delete, data: { confirm: "Are you sure you want to remove #{@cohort_user.user.full_name} from this cohort?" }, class: "btn btn-danger" \ No newline at end of file + = link_to "Remove User From Cohort", "#my-modal", class: "btn btn-danger", "data-toggle": "modal", style: "float:right;" + .modal{id: "my-modal"} + .modal-dialog.modal-dialog-centered + .modal-content + .modal-header + %h3 Remove Cohort User + .modal-body + = p "Are you sure you want to remove #{@cohort_user.user.full_name} from this cohort?" + .modal-footer + %button{"data-dismiss": "modal", class: "btn btn-secondary"} Close + = link_to "Remove", cohort_user_path(@cohort_user.cohort_id, @cohort_user.user_id), method: :delete, class: "btn btn-danger" -- GitLab From a284c91257904bf442d7918eb05d51a073fb11da Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Mon, 21 Sep 2020 11:49:53 -1000 Subject: [PATCH 046/287] Testing changes, bug fix --- app.json | 2 +- app/services/course_validator.rb | 2 +- config/secrets.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app.json b/app.json index ac9eda1..8384ccf 100644 --- a/app.json +++ b/app.json @@ -136,7 +136,7 @@ "AWS_SQLSNIPPETS_HOST": { "required": true }, - "GITLAB_GALVANIZE_COM_TOKEN": { + "GITLAB_DSOP_IL2_TOKEN": { "required": true } }, diff --git a/app/services/course_validator.rb b/app/services/course_validator.rb index b64db7d..0be07f2 100644 --- a/app/services/course_validator.rb +++ b/app/services/course_validator.rb @@ -143,7 +143,7 @@ class CourseValidator def gitlab_api_request(git_url, token) id = gitlab_project_search_by_repo_name(git_url, token) - open("https://#{git_url.host}/api/v4/projects/#{id}/repository/files/#{URI.encode(git_url.path, /\./)}?ref=#{git_url.branch}", + open("https://#{git_url.host}/api/v4/projects/#{id}/repository/files/#{CGI.escape(git_url.path)}?ref=#{git_url.branch}", "PRIVATE-TOKEN" => token.to_s, "User-Agent" => "Galvanize Forge").read end diff --git a/config/secrets.yml b/config/secrets.yml index cdf2219..fd7c36f 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -39,9 +39,9 @@ shared: github_com: type: github token: <%= ENV['GITHUB_COM_TOKEN'] %> - gitlab_galvanize_com: + code_il2_dsop_io: type: gitlab - token: <%= ENV['GITLAB_GALVANIZE_COM_TOKEN'] %> + token: <%= ENV['GITLAB_DSOP_IL2_TOKEN'] %> gitlab_com: type: gitlab token: <%= ENV['GITLAB_COM_TOKEN'] %> -- GitLab From caf6ee61185b616722a8008e02bbe060467878e1 Mon Sep 17 00:00:00 2001 From: Benton Gallun Date: Tue, 22 Sep 2020 00:25:28 +0000 Subject: [PATCH 047/287] first time temp ci file for forge development --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..dcfae11 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,4 @@ +include: + - project: 'platform-one/devops/pipeline-products' + ref: 'learn-ci' + file: 'products/tron/galvanize/learn/forge-ci.yml' -- GitLab From e3bbe2e05f1b523ec913a84ede713602d7a4b120 Mon Sep 17 00:00:00 2001 From: Benton Gallun Date: Tue, 22 Sep 2020 01:02:15 +0000 Subject: [PATCH 048/287] change repo/docker image --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 194e722..3589313 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ -FROM docker2.k8s.revacomm.net:5000/galvanize/rails-2.6.0-node:latest - +#FROM docker2.k8s.revacomm.net:5000/galvanize/rails-2.6.0-node:latest +FROM registry.il2.dsop.io/tron/products/learn-lms/microservices/forge/cht-rails # Copy in all the application dependencies ADD . . ADD Gemfile /usr/src/app/Gemfile -- GitLab From 6bf7c364eae93977b1856539f61ec36abb208461 Mon Sep 17 00:00:00 2001 From: Benton Gallun Date: Tue, 22 Sep 2020 01:09:47 +0000 Subject: [PATCH 049/287] port contents from Dockerfile.base --- Dockerfile | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3589313..cbbb371 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,49 @@ -#FROM docker2.k8s.revacomm.net:5000/galvanize/rails-2.6.0-node:latest -FROM registry.il2.dsop.io/tron/products/learn-lms/microservices/forge/cht-rails +FROM bitnami/ruby:2.6.0 + +ENV count 6 + +# Install what we can through OS package management +RUN apt-get -y update +RUN apt-get -y install --no-install-recommends \ + curl \ + software-properties-common \ + postgresql-client\ + libpq-dev \ + libxml2-dev \ + libxslt1-dev \ + qt5-default \ + libqt5webkit5-dev \ + xvfb + +# Install the right version of nodejs +RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - +RUN apt-get -y install --no-install-recommends nodejs +RUN apt-get -q clean +RUN npm cache clean -f +RUN npm install -g n +RUN n stable +RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10 + # Copy in all the application dependencies +ADD package.json /usr/src/app/package.json +RUN npm install + +# Set the user +RUN groupadd -r ruby +RUN useradd --no-log-init -r -g ruby ruby +# USER ruby +RUN whoami + +# Setup our environment +WORKDIR /usr/src/app +ENV RAILS_ENV production + +# Stuff that changes ADD . . ADD Gemfile /usr/src/app/Gemfile ADD Gemfile.lock /usr/src/app/Gemfile.lock -RUN gem install bundler --version 1.17.3 -RUN bundle -v +# RUN gem install bundler --version 1.17.3 +RUN gem install rake -v '12.3.1' --source 'https://rubygems.org/' RUN bundle install ENV PATH /usr/src/app/node_modules/.bin:$PATH -ADD package.json /usr/src/app/package.json -RUN npm install -ENV COUNT 1 CMD ["./entrypoint-server.sh"] -- GitLab From bb7613fbfd8f375edf93abac57e7e0b25c7ed7dd Mon Sep 17 00:00:00 2001 From: Benton Gallun Date: Mon, 21 Sep 2020 18:31:25 -0700 Subject: [PATCH 050/287] delete bad block parser, replace with good --- gems/block-parser | 1 - gems/bp/Dockerfile | 12 + gems/bp/Gemfile | 6 + gems/bp/Gemfile.lock | 110 ++ gems/bp/README.md | 25 + gems/bp/Rakefile | 6 + gems/bp/bin/console | 14 + gems/bp/bin/parse_block | 21 + gems/bp/bin/setup | 8 + gems/bp/block_parser-0.1.0.gem | Bin 0 -> 5120 bytes gems/bp/block_parser.gemspec | 45 + gems/bp/build | 3 + gems/bp/lib/block_parser.rb | 26 + gems/bp/lib/block_parser/build_challenge.rb | 143 ++ gems/bp/lib/block_parser/build_html.rb | 85 + .../block_parser/build_html_from_md_json.rb | 158 ++ gems/bp/lib/block_parser/build_image_link.rb | 32 + gems/bp/lib/block_parser/build_link.rb | 76 + .../build_title_from_filename_and_html.rb | 28 + .../challenge_validators/challenge.rb | 148 ++ .../challenge_validator.rb | 41 + .../checkbox_challenge_validator.rb | 43 + .../code_snippet_challenge_validator.rb | 32 + .../custom_snippet_challenge_validator.rb | 34 + .../local_snippet_challenge_validator.rb | 16 + .../multiple_choice_challenge_validator.rb | 34 + .../number_challenge_validator.rb | 16 + .../paragraph_challenge_validator.rb | 6 + .../poll_challenge_validator.rb | 6 + .../project_challenge_validator.rb | 14 + .../short_answer_challenge_validator.rb | 14 + .../testable_project_challenge_validator.rb | 167 ++ .../bp/lib/block_parser/convert_md_to_json.rb | 361 ++++ gems/bp/lib/block_parser/parse_directory.rb | 182 ++ .../lib/block_parser/parse_markdown_file.rb | 40 + gems/bp/lib/block_parser/parse_standards.rb | 145 ++ gems/bp/lib/block_parser/version.rb | 3 + gems/bp/pkg/block_parser-0.1.0.gem | Bin 0 -> 5120 bytes gems/bp/spec/build_challenge_spec.rb | 1317 +++++++++++++ gems/bp/spec/build_html_from_md_json_spec.rb | 316 +++ gems/bp/spec/build_html_spec.rb | 196 ++ gems/bp/spec/build_image_link_spec.rb | 308 +++ gems/bp/spec/build_link_spec.rb | 173 ++ ...build_title_from_filename_and_html_spec.rb | 53 + .../challenge_validators/challenge_spec.rb | 288 +++ .../challenge_validator_spec.rb | 116 ++ .../checkbox_challenge_validator_spec.rb | 118 ++ .../code_snippet_challenge_validator_spec.rb | 131 ++ ...custom_snippet_challenge_validator_spec.rb | 69 + ...ultiple_choice_challenge_validator_spec.rb | 107 ++ .../number_challenge_validator_spec.rb | 72 + .../project_challenge_validator_spec.rb | 56 + .../short_answer_challenge_validator_spec.rb | 53 + ...stable_project_challenge_validator_spec.rb | 296 +++ gems/bp/spec/convert_md_to_json_spec.rb | 1702 +++++++++++++++++ .../bad-challenges.md | 128 ++ .../config.yaml | 14 + .../config.yaml | 14 + .../no-challenges.md | 1 + .../config.yaml | 3 + .../no-lessons-nor-checkpoints/config.yaml | 14 + .../no-lessons-nor-checkpoints/resources.md | 1 + .../config.yaml | 2 + gems/bp/spec/fixtures/sample-iframe.md | 5 + .../fixtures/test-block-repo-yml/config.yml | 13 + .../fixtures/test-block-repo-yml/dummy.pdf | Bin 0 -> 13264 bytes .../test-block-repo-yml/folder/sibling.md | 3 + .../test-block-repo-yml/folder/target.md | 3 + .../images/galvanize-logo.png | Bin 0 -> 4134 bytes .../images/register_klass.gif | Bin 0 -> 20804 bytes .../fixtures/test-block-repo-yml/lessoN.md | 1 + .../test-block-repo-yml/markdown-smoketest.md | 107 ++ .../fixtures/test-block-repo-yml/target.md | 1 + .../spec/fixtures/test-block-repo/README.md | 22 + .../spec/fixtures/test-block-repo/config.yaml | 18 + .../test-block-repo/folder/sibling.md | 3 + .../fixtures/test-block-repo/folder/target.md | 3 + .../test-block-repo/images/galvanize-logo.png | Bin 0 -> 4134 bytes .../test-block-repo/images/register_klass.gif | Bin 0 -> 20804 bytes .../fixtures/test-block-repo/ipynb-test.ipynb | 524 +++++ .../fixtures/test-block-repo/ipynb-test.md | 220 +++ .../spec/fixtures/test-block-repo/lessoN.md | 1 + .../test-block-repo/markdown-smoketest.md | 107 ++ .../spec/fixtures/test-block-repo/target.md | 1 + .../test-block-two-configs/config.yaml | 2 + .../test-block-two-configs/config.yml | 2 + .../Dockerfile | 0 .../test.sh | 0 .../config.yaml | 2 + gems/bp/spec/parse_directory_spec.rb | 320 ++++ gems/bp/spec/parse_markdown_file_spec.rb | 29 + gems/bp/spec/parse_standards_spec.rb | 368 ++++ gems/bp/spec/spec_helper.rb | 23 + gems/bp/spec/support/freeloader.rb | 8 + gems/bp/spec/support/mocktokit.rb | 31 + gems/bp/spec/tmp/.keep | 1 + gems/bp/validate-block | 11 + 97 files changed, 9477 insertions(+), 1 deletion(-) delete mode 160000 gems/block-parser create mode 100644 gems/bp/Dockerfile create mode 100644 gems/bp/Gemfile create mode 100644 gems/bp/Gemfile.lock create mode 100644 gems/bp/README.md create mode 100644 gems/bp/Rakefile create mode 100755 gems/bp/bin/console create mode 100755 gems/bp/bin/parse_block create mode 100755 gems/bp/bin/setup create mode 100644 gems/bp/block_parser-0.1.0.gem create mode 100644 gems/bp/block_parser.gemspec create mode 100755 gems/bp/build create mode 100644 gems/bp/lib/block_parser.rb create mode 100644 gems/bp/lib/block_parser/build_challenge.rb create mode 100644 gems/bp/lib/block_parser/build_html.rb create mode 100644 gems/bp/lib/block_parser/build_html_from_md_json.rb create mode 100644 gems/bp/lib/block_parser/build_image_link.rb create mode 100644 gems/bp/lib/block_parser/build_link.rb create mode 100644 gems/bp/lib/block_parser/build_title_from_filename_and_html.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/challenge.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/number_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/poll_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/project_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb create mode 100644 gems/bp/lib/block_parser/convert_md_to_json.rb create mode 100644 gems/bp/lib/block_parser/parse_directory.rb create mode 100644 gems/bp/lib/block_parser/parse_markdown_file.rb create mode 100644 gems/bp/lib/block_parser/parse_standards.rb create mode 100644 gems/bp/lib/block_parser/version.rb create mode 100644 gems/bp/pkg/block_parser-0.1.0.gem create mode 100644 gems/bp/spec/build_challenge_spec.rb create mode 100644 gems/bp/spec/build_html_from_md_json_spec.rb create mode 100644 gems/bp/spec/build_html_spec.rb create mode 100644 gems/bp/spec/build_image_link_spec.rb create mode 100644 gems/bp/spec/build_link_spec.rb create mode 100644 gems/bp/spec/build_title_from_filename_and_html_spec.rb create mode 100644 gems/bp/spec/challenge_validators/challenge_spec.rb create mode 100644 gems/bp/spec/challenge_validators/challenge_validator_spec.rb create mode 100644 gems/bp/spec/challenge_validators/checkbox_challenge_validator_spec.rb create mode 100644 gems/bp/spec/challenge_validators/code_snippet_challenge_validator_spec.rb create mode 100644 gems/bp/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb create mode 100644 gems/bp/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb create mode 100644 gems/bp/spec/challenge_validators/number_challenge_validator_spec.rb create mode 100644 gems/bp/spec/challenge_validators/project_challenge_validator_spec.rb create mode 100644 gems/bp/spec/challenge_validators/short_answer_challenge_validator_spec.rb create mode 100644 gems/bp/spec/challenge_validators/testable_project_challenge_validator_spec.rb create mode 100644 gems/bp/spec/convert_md_to_json_spec.rb create mode 100644 gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md create mode 100644 gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml create mode 100644 gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml create mode 100644 gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md create mode 100644 gems/bp/spec/fixtures/invalid-config-test-block-repo/config.yaml create mode 100644 gems/bp/spec/fixtures/no-lessons-nor-checkpoints/config.yaml create mode 100644 gems/bp/spec/fixtures/no-lessons-nor-checkpoints/resources.md create mode 100644 gems/bp/spec/fixtures/no-standards-config-test-block-repo/config.yaml create mode 100644 gems/bp/spec/fixtures/sample-iframe.md create mode 100644 gems/bp/spec/fixtures/test-block-repo-yml/config.yml create mode 100644 gems/bp/spec/fixtures/test-block-repo-yml/dummy.pdf create mode 100644 gems/bp/spec/fixtures/test-block-repo-yml/folder/sibling.md create mode 100644 gems/bp/spec/fixtures/test-block-repo-yml/folder/target.md create mode 100644 gems/bp/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png create mode 100644 gems/bp/spec/fixtures/test-block-repo-yml/images/register_klass.gif create mode 100644 gems/bp/spec/fixtures/test-block-repo-yml/lessoN.md create mode 100644 gems/bp/spec/fixtures/test-block-repo-yml/markdown-smoketest.md create mode 100644 gems/bp/spec/fixtures/test-block-repo-yml/target.md create mode 100644 gems/bp/spec/fixtures/test-block-repo/README.md create mode 100644 gems/bp/spec/fixtures/test-block-repo/config.yaml create mode 100644 gems/bp/spec/fixtures/test-block-repo/folder/sibling.md create mode 100644 gems/bp/spec/fixtures/test-block-repo/folder/target.md create mode 100644 gems/bp/spec/fixtures/test-block-repo/images/galvanize-logo.png create mode 100644 gems/bp/spec/fixtures/test-block-repo/images/register_klass.gif create mode 100644 gems/bp/spec/fixtures/test-block-repo/ipynb-test.ipynb create mode 100644 gems/bp/spec/fixtures/test-block-repo/ipynb-test.md create mode 100644 gems/bp/spec/fixtures/test-block-repo/lessoN.md create mode 100644 gems/bp/spec/fixtures/test-block-repo/markdown-smoketest.md create mode 100644 gems/bp/spec/fixtures/test-block-repo/target.md create mode 100644 gems/bp/spec/fixtures/test-block-two-configs/config.yaml create mode 100644 gems/bp/spec/fixtures/test-block-two-configs/config.yml create mode 100644 gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile create mode 100644 gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh create mode 100644 gems/bp/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml create mode 100644 gems/bp/spec/parse_directory_spec.rb create mode 100644 gems/bp/spec/parse_markdown_file_spec.rb create mode 100644 gems/bp/spec/parse_standards_spec.rb create mode 100644 gems/bp/spec/spec_helper.rb create mode 100644 gems/bp/spec/support/freeloader.rb create mode 100644 gems/bp/spec/support/mocktokit.rb create mode 100644 gems/bp/spec/tmp/.keep create mode 100755 gems/bp/validate-block diff --git a/gems/block-parser b/gems/block-parser deleted file mode 160000 index bdf3acd..0000000 --- a/gems/block-parser +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bdf3acdb4145805d306fc022f4abc2803a6bacb7 diff --git a/gems/bp/Dockerfile b/gems/bp/Dockerfile new file mode 100644 index 0000000..34a3ba8 --- /dev/null +++ b/gems/bp/Dockerfile @@ -0,0 +1,12 @@ +FROM ruby:latest +RUN mkdir /app +WORKDIR /app +ENV LANG=C.UTF-8 +ADD Gemfile . +ADD Gemfile.lock . +ADD block_parser.gemspec . +ADD lib/block_parser/version.rb lib/block_parser/version.rb +RUN gem install bundler +RUN bundle +ADD . . +ENTRYPOINT /app/bin/parse_block /block diff --git a/gems/bp/Gemfile b/gems/bp/Gemfile new file mode 100644 index 0000000..cc9928f --- /dev/null +++ b/gems/bp/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } + +# Specify your gem's dependencies in block_parser.gemspec +gemspec diff --git a/gems/bp/Gemfile.lock b/gems/bp/Gemfile.lock new file mode 100644 index 0000000..f077409 --- /dev/null +++ b/gems/bp/Gemfile.lock @@ -0,0 +1,110 @@ +PATH + remote: . + specs: + block_parser (0.1.0) + activemodel (> 4.2) + github-markdown (= 0.6.9) + github-markup (= 1.6.1) + github_url (= 0.2.1) + gitlab (= 4.14.1) + nokogiri (= 1.8.0) + octokit (= 4.3.0) + psych (= 2.2.4) + redcarpet (= 3.3.4) + rspec_junit_formatter (> 0.2.3) + +GEM + remote: https://rubygems.org/ + specs: + activemodel (6.0.3.1) + activesupport (= 6.0.3.1) + activesupport (6.0.3.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.4.0) + ast (2.4.0) + byebug (9.1.0) + concurrent-ruby (1.1.6) + diff-lcs (1.3) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + github-markdown (0.6.9) + github-markup (1.6.1) + github_url (0.2.1) + gitlab (4.14.1) + httparty (~> 0.14, >= 0.14.0) + terminal-table (~> 1.5, >= 1.5.1) + httparty (0.18.0) + mime-types (~> 3.0) + multi_xml (>= 0.5.2) + i18n (1.8.2) + concurrent-ruby (~> 1.0) + mime-types (3.3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2020.0512) + mini_portile2 (2.2.0) + minitest (5.14.1) + multi_xml (0.6.0) + multipart-post (2.1.1) + nokogiri (1.8.0) + mini_portile2 (~> 2.2.0) + octokit (4.3.0) + sawyer (~> 0.7.0, >= 0.5.3) + parallel (1.19.1) + parser (2.7.1.2) + ast (~> 2.4.0) + psych (2.2.4) + rainbow (3.0.0) + rake (10.5.0) + redcarpet (3.3.4) + rexml (3.2.4) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.2) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.3) + rspec_junit_formatter (0.4.1) + rspec-core (>= 2, < 4, != 2.12.0) + rubocop (0.83.0) + parallel (~> 1.10) + parser (>= 2.7.0.1) + rainbow (>= 2.2.2, < 4.0) + rexml + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 2.0) + ruby-progressbar (1.10.1) + sawyer (0.7.0) + addressable (>= 2.3.5, < 2.5) + faraday (~> 0.8, < 0.10) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thread_safe (0.3.6) + tzinfo (1.2.7) + thread_safe (~> 0.1) + unicode-display_width (1.7.0) + zeitwerk (2.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + block_parser! + bundler (~> 1.15) + byebug (= 9.1.0) + rake (~> 10.0) + rspec (~> 3.0) + rubocop (> 0.72) + +BUNDLED WITH + 1.17.2 diff --git a/gems/bp/README.md b/gems/bp/README.md new file mode 100644 index 0000000..2646cc2 --- /dev/null +++ b/gems/bp/README.md @@ -0,0 +1,25 @@ +# block_parser + +`block_parser` contains shared curriculum repository processing logic used in our Learn and Forge applications. + +## Validating Local Blocks via Docker + +Ensure that you have docker installed and running. + +Then fork and clone this repository and run: + +``` +./build +``` + +You can validate the current directory by doing this: + +``` +/path/to/this/repo/validate-block . +``` + +You can validate the another directory by doing this: + +``` +/path/to/this/repo/validate-block ../some/other/dir +``` diff --git a/gems/bp/Rakefile b/gems/bp/Rakefile new file mode 100644 index 0000000..c92b11e --- /dev/null +++ b/gems/bp/Rakefile @@ -0,0 +1,6 @@ +require "bundler/gem_tasks" +require "rspec/core/rake_task" + +RSpec::Core::RakeTask.new(:spec) + +task default: :spec diff --git a/gems/bp/bin/console b/gems/bp/bin/console new file mode 100755 index 0000000..b6e7308 --- /dev/null +++ b/gems/bp/bin/console @@ -0,0 +1,14 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "block_parser" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/gems/bp/bin/parse_block b/gems/bp/bin/parse_block new file mode 100755 index 0000000..7704898 --- /dev/null +++ b/gems/bp/bin/parse_block @@ -0,0 +1,21 @@ +#!/usr/bin/env ruby + +require "bundler/setup" +require "block_parser" + +unless ARGV.length == 1 + puts "Please pass exactly one argument." + return +end + +result = BlockParser::ParseDirectory.new(path: ARGV[0], asset_uploader: nil).execute + +if result[:errors].any? + result[:errors].each do |error| + puts error + end + + exit(1) +else + puts "Directory is valid." +end diff --git a/gems/bp/bin/setup b/gems/bp/bin/setup new file mode 100755 index 0000000..dce67d8 --- /dev/null +++ b/gems/bp/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +bundle install + +# Do any other automated setup that you need to do here diff --git a/gems/bp/block_parser-0.1.0.gem b/gems/bp/block_parser-0.1.0.gem new file mode 100644 index 0000000000000000000000000000000000000000..d98013fdc53ad0f531beb556989cfd115dc929d3 GIT binary patch literal 5120 zcmeH}dpHwpAIC?@DO5yl&eT&5Gi+p9Npegz6owZe6ml41P9=oNkhCl&goTlig+xP6 zlVeUx4mlr&P|P0B`~Fc^f4uK=UC-6^_D8>ee)swM-QWAZKljhw32pCakG2Op2LgVU zLADJFg#!Na+xDYZhN-FplvSbX$`CbW2owf@Y*)6OB;;oW^!>WNK4^RI?U4LkoSfXg z@A#qozxw~Pw{M&KVfwFHm5Aj69J)Ma&Ay}ifw502WKZP6*Zm|V0+yloFcOarY=kzT zAD$DKLm!apAs&#uxX*9sbSNf8pusQc?Xaw>(Cm)HOE1ad!fcx2@lznv`hn<}g|L^4 zz!*qncMa9V^V97_ooDRYz=;^LbSswQbdQ|^e?)u>vw!zq!-Y8Z&}mMci5A7=quQue zseCkEMNQTruyqNRG@{;aRf+~(Ap$S%3b|iv=dzC%&lL{5Eh~zRy-~jtzO@xD+mb<5 zcu8dwh>`2lEWnhw)zSnL z0+hfzJSRD0U%g9T%cLM-=_5U&Yb%v`ClSjGCkm#nAEjix5Db%&MOn6B|hnbLh?$edayr{mxUmWQs&VA|$tLT7Aeqf<@b$Ql-* zH*TcUK851kWh!(+OlVq{VUU~IF;)cJo;q=&)p@H@Vob?oaIePbx%`X^++&FF zRpG4`kkF*AmtMO`fteh3l36f1RHHV%-O$wU5zwPLa*!Tc*PjKVa$9)Z_VX3Fc`Le7 zZnm#1Y+Y)oTPV1Yhy7Jp%3W%J=@!WNMdGD#2npniR3-5ejH5Wrr}wsbOFku*fnk?B ziyCEP^o)=JBzUSSVLxxha(@~BU>Ty+_Bid#agF?(uZIug<+MtKT%FsdJXQMK)V3IM z-mj78K`#Ie`#xDvlg2Chwf0SQTtPljc#^O-{%QJfR{3zQY1c9;hpHCAE+gIAlsyrk zlNl_V=9tO{O>rkBP zLQHup9k{!v1C!s;1@~`X`vs1p`&3DOr{129R%MC4;wsS3#<@PfrHJaQTiT$bPq|qR z_ewqtB6ZZO!2%xISp<>Q*f9`EMa2tJQw?%u=S7FdhbCrhZ7y%bFhqXUNB>=1|7QO- z0so);SA)QQpkUjK?%^3SrSLKt3ZXD(y7685aVw=+mDQ!m&%E`kiJD$pjNw!t$5uR@pQTsl zvnj#f!MM-zk7S*aNAssu2YfEfxo|$ZIw}fx2;jTOPictJ$6oL;sXe4riMsjjjh!Z6 z!fF*b54B|Pdkdul03Q*ET2z3;JvK!}p|pve#Kp)vxn9LqKDOe}yC<{KS8s|Eoii>) z^7gZ#NLAwCVlMHd+w+m79|bsF3X11V<7c{)>etAfjj@Sfo@1_u&*_h1jvZ3;`Up0W2|A~1 zEfTRfI&9II?5RDm1?^+zFheVNV=X=IyE&}gBe%F(_I9niyJ5jQnxdbo=kzfTYIu=V1oHWwWHhwd; z7o`2f`|Wx1#^*ppb?AoSvyEx6%b{YDk;Jiwv*@~f*KzQc?({N zGYUm;Or4}%!IeF2w{GYuO|bNIjJuMiKMKIB5B}kzsn|X)76NfRhf};&DASLqqw@#v zYPX7t?Gh}a-76}*FE%%rp9XX 1.15" + spec.add_development_dependency "byebug", "9.1.0" + spec.add_development_dependency "rake", "~> 10.0" + spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "rubocop", "> 0.72" + + spec.add_dependency "activemodel", "> 4.2" + spec.add_dependency "github-markdown", "0.6.9" + spec.add_dependency "github-markup", "1.6.1" + spec.add_dependency "github_url", "0.2.1" + spec.add_dependency "gitlab", "4.14.1" + spec.add_dependency "nokogiri", "1.8.0" + spec.add_dependency "octokit", "4.3.0" + spec.add_dependency "psych", "2.2.4" + spec.add_dependency "redcarpet", "3.3.4" + spec.add_dependency "rspec_junit_formatter", "> 0.2.3" +end diff --git a/gems/bp/build b/gems/bp/build new file mode 100755 index 0000000..c1512e5 --- /dev/null +++ b/gems/bp/build @@ -0,0 +1,3 @@ +#!/bin/bash + +docker build -t block-parser . diff --git a/gems/bp/lib/block_parser.rb b/gems/bp/lib/block_parser.rb new file mode 100644 index 0000000..4888a71 --- /dev/null +++ b/gems/bp/lib/block_parser.rb @@ -0,0 +1,26 @@ +require "active_model" +require "block_parser/build_challenge" +require "block_parser/build_html_from_md_json" +require "block_parser/build_html" +require "block_parser/build_image_link" +require "block_parser/build_link" +require "block_parser/build_title_from_filename_and_html" +require "block_parser/convert_md_to_json" +require "block_parser/parse_directory" +require "block_parser/parse_markdown_file" +require "block_parser/parse_standards" +require "block_parser/version" + +require "block_parser/challenge_validators/challenge_validator" +require "block_parser/challenge_validators/challenge" +require "block_parser/challenge_validators/checkbox_challenge_validator" +require "block_parser/challenge_validators/code_snippet_challenge_validator" +require "block_parser/challenge_validators/custom_snippet_challenge_validator" +require "block_parser/challenge_validators/local_snippet_challenge_validator" +require "block_parser/challenge_validators/multiple_choice_challenge_validator" +require "block_parser/challenge_validators/number_challenge_validator" +require "block_parser/challenge_validators/paragraph_challenge_validator" +require "block_parser/challenge_validators/poll_challenge_validator" +require "block_parser/challenge_validators/project_challenge_validator" +require "block_parser/challenge_validators/short_answer_challenge_validator" +require "block_parser/challenge_validators/testable_project_challenge_validator" diff --git a/gems/bp/lib/block_parser/build_challenge.rb b/gems/bp/lib/block_parser/build_challenge.rb new file mode 100644 index 0000000..a81ef4e --- /dev/null +++ b/gems/bp/lib/block_parser/build_challenge.rb @@ -0,0 +1,143 @@ +module BlockParser + class BuildChallenge + class ChallengeDataInvalidError < StandardError + end + + def self.execute( + content_object:, + json_hash:, + position:, + asset_uploader:, + root_directory_path:, + current_content_file_path:, + content_file_paths:, + autoscore: false + ) + + challenge_hash = json_hash[:challenges][content_object[:id].to_sym] + challenge_validator = validator(challenge_hash, autoscore, root_directory_path) + errors = [] + warnings = [] + + unless challenge_validator.valid? + if challenge_hash[:type] == "custom-snippet" + warnings.concat(challenge_validator.errors.full_messages) + else + raise ChallengeDataInvalidError, challenge_validator.errors.full_messages.join(", ") + end + end + + html_explanation_text = + if challenge_hash[:explanation].present? + explanation = BuildHtmlFromMdJson.process_challenge_json( + json_hash: challenge_hash[:explanation], + asset_uploader: asset_uploader, + root_directory_path: root_directory_path, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths + ) + + errors.concat(explanation[:errors]) + warnings.concat(explanation[:warnings]) + explanation[:html] + end + + html_hints = challenge_hash[:hints]&.map do |hint| + processed_md = BuildHtml.execute( + html: GitHub::Markup.render("file.markdown", hint), + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + asset_uploader: asset_uploader, + root_directory_path: root_directory_path + ) + + processed_md[:html] + end + + html_rubric_text = + if challenge_hash[:rubric].present? + rubric = BuildHtmlFromMdJson.process_challenge_json( + json_hash: challenge_hash[:rubric], + asset_uploader: asset_uploader, + root_directory_path: root_directory_path, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths + ) + + errors.concat(rubric[:errors]) + warnings.concat(rubric[:warnings]) + rubric[:html] + end + + question_result = BuildHtmlFromMdJson.process_challenge_json( + json_hash: challenge_hash[:question], + asset_uploader: asset_uploader, + root_directory_path: root_directory_path, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths + ) + + errors.concat(question_result[:errors]) + warnings.concat(question_result[:warnings]) + question = question_result[:html] + + { + attributes: { + uid: challenge_hash[:id], + challenge_type: challenge_hash[:type], + title: challenge_hash[:title], + hints: html_hints || [], + rubric: html_rubric_text, + explanation: html_explanation_text, + decimal: challenge_hash[:decimal], + answer: challenge_hash[:answer], + question: question, + position: position, + points: challenge_hash[:points] || 1, + partial_credit: challenge_hash[:partial_credit] || false, + language: challenge_hash[:language], + data_path: challenge_hash[:data_path], + upstream_repo_path: challenge_hash[:upstream_repo_path], + setup: challenge_hash[:setup] || "", + tests: challenge_hash[:tests], + topics: challenge_hash[:topics], + show_tests: challenge_hash[:show_tests] || false, + raw_json: challenge_hash, + validate_fork: challenge_hash[:validate_fork] || false, + docker_directory_path: challenge_hash[:docker_directory_path] || nil, + external_id: challenge_hash[:external_id] || nil, + external_source: challenge_hash[:external_source] || nil + }, + errors: errors, + warnings: warnings, + html: "
    " + } + rescue ChallengeDataInvalidError => e + { + errors: [e.message], + warnings: [] + } + end + + def self.validator(challenge_hash, autoscore, root_directory_path) + unless ChallengeValidators::Challenge::TYPES.values.include?(challenge_hash[:type]) + raise ChallengeDataInvalidError, "#{challenge_hash[:type]} is not a valid challenge type" + end + + if autoscore && ChallengeValidators::Challenge::MANUAL_SCORE_TYPES.values.include?(challenge_hash[:type]) + autoscore_error = "#{challenge_hash[:type]} is not an autoscoreable challenge type but was found in an autograde checkpoint." + raise ChallengeDataInvalidError, autoscore_error + end + + if challenge_hash[:type] == ChallengeValidators::Challenge::TYPES[:custom_snippet] + challenge_hash[:root_directory_path] = root_directory_path + end + + ( + "BlockParser::ChallengeValidators::" + + challenge_hash[:type].underscore.classify + + "ChallengeValidator" + ).constantize.new(challenge_hash) + end + end +end diff --git a/gems/bp/lib/block_parser/build_html.rb b/gems/bp/lib/block_parser/build_html.rb new file mode 100644 index 0000000..e67ce60 --- /dev/null +++ b/gems/bp/lib/block_parser/build_html.rb @@ -0,0 +1,85 @@ +module BlockParser + class BuildHtml + def self.execute(html:, asset_uploader:, root_directory_path:, current_content_file_path:, content_file_paths:, resource_paths: []) + doc = Nokogiri::HTML::DocumentFragment.parse(html) + doc.css("meta, script, style, link, title").each do |bad_element| + html = html.sub("#{bad_element.to_html}\n", "") + end + + warnings = [] + + if html.include?("[ ]") || + html.include?("[x]") || + html.include?("[X]") + + html = html.gsub("[ ]", ""). + gsub("[X]", ""). + gsub("[x]", "") + end + + headers = {} + doc.css("a, img, pre", "h1, h2, h3, h4, h5, h6").each do |element| + original_text = nil + new_text = nil + if element.name == "a" + begin + original_text, new_text = + BuildLink.execute( + link: element, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + root_directory_path: root_directory_path, + resource_paths: resource_paths + ) + rescue BuildLink::BadInternalLink => e + warnings << "#{current_content_file_path.gsub(root_directory_path, '')}: #{e.message}" + end + elsif element.name == "img" + image_link = + BuildImageLink.execute( + asset_uploader: asset_uploader, + element: element, + current_content_file_path: current_content_file_path, + root_directory_path: root_directory_path + ) + + if image_link[:errors].any? + warnings.concat(image_link[:errors]) + else + original_text, new_text = image_link[:result] + end + elsif ["h1", "h2", "h3", "h4", "h5", "h6"].include? element.name + original_text, new_text, headers = process_header(element, headers) + end + + html = html.sub(original_text, new_text) if original_text + end + + { + html: html, + errors: [], + warnings: warnings + } + end + + def self.process_header(header, existing_headers) + punctuation_regexp = /[^\p{Word}\- ]/u + text = header.text + id = text.downcase + id.gsub!(punctuation_regexp, "") # remove punctuation + id.tr!(" ", "-") # replace spaces with dash + + if existing_headers[id.to_sym].present? + existing_headers[id.to_sym] = existing_headers[id.to_sym] + 1 + id = "#{id}-#{existing_headers[id.to_sym]}" + else + existing_headers[id.to_sym] = 1 + end + + new_header = header.clone + new_header.set_attribute("id", id) + + [header.to_html, new_header.to_html, existing_headers] + end + end +end diff --git a/gems/bp/lib/block_parser/build_html_from_md_json.rb b/gems/bp/lib/block_parser/build_html_from_md_json.rb new file mode 100644 index 0000000..7ff3277 --- /dev/null +++ b/gems/bp/lib/block_parser/build_html_from_md_json.rb @@ -0,0 +1,158 @@ +require "github/markup" + +module BlockParser + class BuildHtmlFromMdJson + class InvalidMdError < StandardError; end + + def self.process_json(json_hash:, asset_uploader:, root_directory_path:, current_content_file_path:, content_file_paths:, autoscore: false, resource_paths: []) + challenge_position = 0 + vimeo_position = -1 + challenges = [] + errors = [] + warnings = [] + + html = json_hash[:content].collect do |content_object| + if content_object[:type] == "markdown" + processed_md = process_md( + content_object_value: content_object[:value], + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + asset_uploader: asset_uploader, + root_directory_path: root_directory_path, + resource_paths: resource_paths + ) + + errors.concat(processed_md[:errors]) + warnings.concat(processed_md[:warnings]) + + processed_md[:html] + elsif content_object[:type] == "callout" + processed_md = process_md( + content_object_value: content_object[:value], + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + asset_uploader: asset_uploader, + root_directory_path: root_directory_path, + resource_paths: resource_paths + ) + + errors.concat(processed_md[:errors]) + warnings.concat(processed_md[:warnings]) + + processed_md[:html] = "
    #{callout_html(processed_md, content_object[:class])}
    " + elsif content_object[:type] == "vimeo" + vimeo_position += 1 + process_vimeo_json(content_object, vimeo_position) + elsif content_object[:type] == "challenge" + challenge_position += 1 + + processed_challenge = BuildChallenge.execute( + content_object: content_object, + json_hash: json_hash, + position: challenge_position, + asset_uploader: asset_uploader, + root_directory_path: root_directory_path, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + autoscore: autoscore + ) + + challenges << processed_challenge[:attributes] if processed_challenge[:errors].empty? + errors.concat(processed_challenge[:errors].map { |error| "Challenge ##{challenge_position}: #{content_object[:id]} #{error}" }) + warnings.concat(processed_challenge[:warnings].map { |warning| "Challenge ##{challenge_position}: #{content_object[:id]} #{warning}" }) + + processed_challenge[:html] + end + end.join + + errors << "content cannot be blank" if html.blank? + + html.gsub!("#{content_object_class.gsub('callout-', '').capitalize}" + + icon = if ["callout", "callout-info", "callout-warning", "callout-secondary", "callout-attention", "callout-note", "callout-tip", "callout-hint", "callout-primary", "callout-caution", "callout-light", "callout-dark", "callout-admonition"].include?(content_object_class) + "" + elsif ["callout-danger", "callout-error"].include?(content_object_class) + "" + elsif ["callout-success", "callout-important"].include?(content_object_class) + "" + else + "" + end + + if starts_with_header + icon + processed_md[:html] + else + icon + heading + processed_md[:html] + end + end + + def self.process_vimeo_json(content_object, vimeo_position) + player_id = "vpt_learn#{vimeo_position}" + out = "" + out << "" + + if content_object.key?(:transcript_project) + out << "" + end + + out + end + + def self.process_challenge_json(json_hash:, asset_uploader:, root_directory_path:, current_content_file_path:, content_file_paths:) + errors = [] + warnings = [] + + html = json_hash[:content].collect do |content_object| + next unless content_object[:type] == "markdown" + + processed_md = process_md( + content_object_value: content_object[:value], + asset_uploader: asset_uploader, + root_directory_path: root_directory_path, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths + ) + + warnings.concat(processed_md[:warnings]) + if processed_md[:errors].any? + errors.concat(processed_md[:errors]) + else + processed_md[:html] + end + end.join + html.gsub(" _e + { errors: ["Bad image link #{element['src']}"] } + end + end + + private_class_method def self.empty_result + { errors: [], result: [] } + end + end +end diff --git a/gems/bp/lib/block_parser/build_link.rb b/gems/bp/lib/block_parser/build_link.rb new file mode 100644 index 0000000..6a678b5 --- /dev/null +++ b/gems/bp/lib/block_parser/build_link.rb @@ -0,0 +1,76 @@ +require "nokogiri" + +module BlockParser + class BuildLink + class BadInternalLink < StandardError + end + + def self.execute(link:, current_content_file_path:, content_file_paths:, root_directory_path:, resource_paths: []) + href_link = link["href"] + + return [] if ignored_link?(href_link) + + if href_link.start_with?("www", "http", "https") + if link_is_content_link?(href_link) + return transform_to_content_link(href_link, href_link) + else + return transform_to_external_link(href_link, href_link) + end + end + + external_link = true if resource_paths.map { |path| path.include?(href_link) }.include?(true) + + if href_link[0] == "/" + target_file_path = File.join(root_directory_path, href_link) + + raise BadInternalLink, "Bad internal link #{href_link}" unless content_file_paths.include?(link_without_anchor(target_file_path)) + + slashes_count = current_content_file_path.gsub(root_directory_path, "").count("/") + new_link = "" + slashes_count.times { new_link += "../" } + new_link += href_link[1..-1] + + return transform_to_external_link(href_link, new_link) if external_link + + ["href=\"#{href_link}\"", "href=\"#{new_link}\""] + else + target_file_path = Pathname.new(File.dirname(current_content_file_path)).join(CGI.unescape(href_link)).to_s + content_file_path = content_file_paths.find { |path| path.eql?(link_without_anchor(target_file_path)) } + + raise BadInternalLink, "Bad internal link #{href_link}" if content_file_path.blank? + + if external_link && link_is_content_link?(href_link) + transform_to_content_link(href_link, href_link) + elsif external_link + return transform_to_external_link(href_link, href_link) + end + + [] + end + end + + def self.link_is_content_link?(link) + link.include?("content_link/gSchool") + end + + def self.transform_to_external_link(link1, link2) + ["href=\"#{link1}\"", "href=\"#{link2}\" class=\"external-link\" target=\"_blank\""] + end + + def self.transform_to_content_link(link1, link2) + ["href=\"#{link1}\"", "href=\"#{link2}\""] + end + + def self.link_without_anchor(link) + link.include?("#") ? link.split("#").first : link + end + + def self.ignored_link?(href_link) + href_link.nil? || + href_link.strip.empty? || + href_link.start_with?("#") || + href_link.include?("?raw") || + href_link.start_with?("mailto:") + end + end +end diff --git a/gems/bp/lib/block_parser/build_title_from_filename_and_html.rb b/gems/bp/lib/block_parser/build_title_from_filename_and_html.rb new file mode 100644 index 0000000..0812752 --- /dev/null +++ b/gems/bp/lib/block_parser/build_title_from_filename_and_html.rb @@ -0,0 +1,28 @@ +class BuildTitleFromFilenameAndHtml + def self.execute(filename:, html:) + first_h1(html) || humanize_filename(filename) + end + + private_class_method def self.humanize_filename(filename) + title = if CGI.unescape(filename.downcase).start_with?("checkpoint", "instructor", "resource", "hidden") + filename.downcase + else + filename.downcase.gsub(/(\.instructor)/, ""). + gsub(/(\.checkpoint)/, ""). + gsub(/(\.hidden)/, ""). + gsub(/(\.resource)/, "") + end + CGI.unescape(title). + gsub(/\A\d+\s*-\s*/, ""). + gsub(/[-_]/, " "). + gsub(/(\.md)\z/, ""). + gsub(/(md)\z/, ""). + titleize + end + + private_class_method def self.first_h1(html) + return unless html.split("\n").first&.match(/\A

    ]*>(.*)<\/h1>/)[1]) + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/challenge.rb b/gems/bp/lib/block_parser/challenge_validators/challenge.rb new file mode 100644 index 0000000..cddb2ec --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/challenge.rb @@ -0,0 +1,148 @@ +module BlockParser + module ChallengeValidators + class Challenge + attr_accessor :id, :type, :title, :placeholder, :question, :explanation, :decimal, :answer, :options, :language, + :tests, :standard_uuids, :upstream_repo_path, :hints, :setup, :validate_fork, :docker_directory_path, + :show_tests, :data_path, :rubric, :points, :topics, :partial_credit, :external_source, :external_id + + AUTOSCORE_TYPES = { + checkbox: "checkbox", + code_snippet: "code-snippet", + local_snippet: "local-snippet", + custom_snippet: "custom-snippet", + multiple_choice: "multiple-choice", + number: "number", + poll: "poll", + short_answer: "short-answer", + testable_project: "testable-project" + }.freeze + + MANUAL_SCORE_TYPES = { + paragraph: "paragraph", + project: "project" + }.freeze + + TYPES = AUTOSCORE_TYPES.merge(MANUAL_SCORE_TYPES).freeze + + def initialize(challenge_types) + @hints = [] + @challenge_types = challenge_types + end + + def to_hash + attributes_hash = { + id: id, + type: type, + title: title, + placeholder: nil, + hints: hints + } + + if question.present? + attributes_hash[:question] = { + content: [ + { + type: "markdown", + value: question + } + ] + } + end + + if explanation.present? + attributes_hash[:explanation] = { + content: [ + { + type: "markdown", + value: explanation + } + ] + } + end + + if rubric.present? + attributes_hash[:rubric] = { + content: [ + { + type: "markdown", + value: rubric + } + ] + } + end + + attributes_hash[:decimal] = decimal if type == @challenge_types[:number] + + if type == @challenge_types[:code_snippet] || type == @challenge_types[:local_snippet] + attributes_hash[:show_tests] = convert_bool_string(show_tests) + end + + attributes_hash[:language] = language if type == @challenge_types[:code_snippet] || type == @challenge_types[:custom_snippet] + + attributes_hash[:placeholder] = placeholder unless placeholder.nil? + + attributes_hash[:options] = build_array_from_bullets(options) unless options.nil? + + attributes_hash[:data_path] = data_path unless data_path.nil? + + attributes_hash[:setup] = setup + + attributes_hash[:tests] = tests unless tests.nil? + + attributes_hash[:topics] = topics unless topics.nil? + + attributes_hash[:external_id] = external_id unless external_id.nil? + + attributes_hash[:external_source] = external_source unless external_source.nil? + + if locally_gradeable? || type == @challenge_types[:checkbox] + attributes_hash[:answer] = if locally_gradeable? && type == @challenge_types[:checkbox] + build_array_from_bullets(answer) + elsif locally_gradeable? + answer&.start_with?("* ") ? answer.gsub("* ", "") : answer + end + end + + attributes_hash[:upstream_repo_path] = upstream_repo_path if type == @challenge_types[:testable_project] + + attributes_hash[:standard_uuids] = standard_uuids unless standard_uuids.nil? + + attributes_hash[:points] = points unless points.nil? + + attributes_hash[:partial_credit] = partial_credit.nil? ? false : partial_credit + + attributes_hash[:validate_fork] = convert_bool_string(validate_fork) unless validate_fork.nil? + + attributes_hash[:docker_directory_path] = docker_directory_path if type == @challenge_types[:custom_snippet] + + attributes_hash + end + + private + + def convert_bool_string(bool_string) + bool_string == "true" + end + + def build_array_from_bullets(content) + content.split(/\n\s*[*-]+/).reject(&:empty?).map { |option| unindent(option).gsub(/^[*-]\s/, "") }.map(&:strip) + end + + def unindent(str) + return str unless str.include?("\n") + + lines = str.split("\n") + + minimum_indent = lines.map { |line| !line.empty? ? line[/\A */].size : nil }.compact.min + lines.map { |line| line.gsub(/^ {#{minimum_indent}}/, "") }.join("\n") + end + + def locally_gradeable? + type == @challenge_types[:number] || + type == @challenge_types[:short_answer] || + type == @challenge_types[:multiple_choice] || + type == @challenge_types[:checkbox] + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/challenge_validator.rb new file mode 100644 index 0000000..72604d4 --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/challenge_validator.rb @@ -0,0 +1,41 @@ +module BlockParser + module ChallengeValidators + class ChallengeValidator + include ::ActiveModel::Validations + + attr_reader :id, :type, :title + + validates :id, presence: true + validates :type, presence: true + validates :title, presence: true + validate :validate_question + validate :validate_points + validate :validate_topics + + def initialize(attributes) + @id = attributes[:id] + @type = attributes[:type] + @title = attributes[:title] + @question = attributes[:question] + @points = attributes[:points] + @topics = attributes[:topics] + end + + private + + def validate_question + return unless @question.nil? || @question[:content].all? { |content| content[:value].blank? } + + errors.add(:question, "can't be blank") + end + + def validate_points + errors.add(:points, "can't be worth more than 10") if !@points.nil? && @points > 10 + end + + def validate_topics + errors.add(:topics, "can't have a length of more than 20 characters") if @topics&.any? { |t| t.length > 20 } + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb new file mode 100644 index 0000000..0b0e25a --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb @@ -0,0 +1,43 @@ +module BlockParser + module ChallengeValidators + class CheckboxChallengeValidator < ChallengeValidator + attr_reader :options, :answer + + validates :answer, presence: true + validates :options, presence: true + validate :options_has_at_least_two_choices + validate :answer_has_at_least_one_choice + validate :answers_exist_in_options + + def initialize(attributes) + @options = attributes[:options] + @answer = attributes[:answer] + super(attributes) + end + + private + + def options_has_at_least_two_choices + return unless options.present? && options.length < 2 + + errors.add(:options, "must have at least two choices") + end + + def answer_has_at_least_one_choice + return if answer.present? && !answer.empty? + + errors.add(:answer, "must have at least one choice") + end + + def answers_exist_in_options + o = (options || []).map { |opt| opt.delete("\r") } + a = (answer || []).map { |ans| ans.delete("\r") } + return if (o & a).length == a.length + # This is a allow "any" answer hack + return if a.length == 1 && a.first == "*" + + errors.add(:answer, "set must exist in the options") + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb new file mode 100644 index 0000000..28ed635 --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb @@ -0,0 +1,32 @@ +module BlockParser + module ChallengeValidators + class CodeSnippetChallengeValidator < ChallengeValidator + attr_reader :language, :tests + + validates( + :language, + presence: true, + inclusion: { + in: ["java", "javascript", "python2.7", "python3.6", "sql"], + message: "language must be java, javascript, python2.7, or python3.6, or sql" + } + ) + validates :tests, presence: true + + validate :validate_data_path + + def initialize(attributes) + @language = attributes[:language] + @tests = attributes[:tests] + @data_path = attributes[:data_path] + super(attributes) + end + + def validate_data_path + return true unless @language == "sql" + + errors.add(:data_path, "can't be blank") if @data_path.blank? + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb new file mode 100644 index 0000000..86a4e22 --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb @@ -0,0 +1,34 @@ +module BlockParser + module ChallengeValidators + class CustomSnippetChallengeValidator < ChallengeValidator + attr_reader :docker_directory_path, :language + + validates( + :docker_directory_path, + :language, + presence: true + ) + + validate :valid_docker_directory, if: :docker_directory_path + + def initialize(attributes) + return unless attributes[:root_directory_path] && attributes[:docker_directory_path] + + @docker_directory_path = File.join(attributes[:root_directory_path], attributes[:docker_directory_path]) + @language = attributes[:language] + super(attributes) + end + + private + + def valid_docker_directory + return errors.add(:docker_directory_path, "points to a directory that doesn't exist") unless File.directory?(@docker_directory_path) + + file_entries = Dir.entries(@docker_directory_path) + ["Dockerfile", "test.sh"].each do |filename| + errors.add(:docker_directory_path, "is missing required file '#{filename}'") unless file_entries.include?(filename) + end + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb new file mode 100644 index 0000000..bdfcbdb --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb @@ -0,0 +1,16 @@ +module BlockParser + module ChallengeValidators + class LocalSnippetChallengeValidator < ChallengeValidator + attr_reader :placeholder, :tests + + validates :placeholder, presence: true + validates :tests, presence: true + + def initialize(attributes) + @tests = attributes[:tests] + @placeholder = attributes[:placeholder] + super(attributes) + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb new file mode 100644 index 0000000..bc0aa8c --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb @@ -0,0 +1,34 @@ +module BlockParser + module ChallengeValidators + class MultipleChoiceChallengeValidator < ChallengeValidator + attr_reader :options, :answer + + validates :answer, presence: true + validates :options, presence: true + validate :options_has_at_least_two_choices + validate :answer_exists_in_options + + def initialize(attributes) + @options = attributes[:options] + @answer = attributes[:answer] + super(attributes) + end + + private + + def options_has_at_least_two_choices + return unless options.present? && options.length < 2 + + errors.add(:options, "must have at least two choices") + end + + def answer_exists_in_options + return if options.present? && options.map { |option| option.delete("\r") }.include?(answer) + # This is a allow "any" answer hack + return if answer == "*" + + errors.add(:answer, "must exist in the options") + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/number_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/number_challenge_validator.rb new file mode 100644 index 0000000..76a0db0 --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/number_challenge_validator.rb @@ -0,0 +1,16 @@ +module BlockParser + module ChallengeValidators + class NumberChallengeValidator < ChallengeValidator + attr_reader :answer, :decimal + + validates :answer, presence: true + validates :decimal, numericality: { only_integer: true, allow_blank: true, allow_nil: true } + + def initialize(attributes) + super(attributes) + @answer = attributes[:answer] + @decimal = attributes[:decimal] + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb new file mode 100644 index 0000000..7c04eac --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb @@ -0,0 +1,6 @@ +module BlockParser + module ChallengeValidators + class ParagraphChallengeValidator < ChallengeValidator + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/poll_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/poll_challenge_validator.rb new file mode 100644 index 0000000..b17d911 --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/poll_challenge_validator.rb @@ -0,0 +1,6 @@ +module BlockParser + module ChallengeValidators + class PollChallengeValidator < ChallengeValidator + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/project_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/project_challenge_validator.rb new file mode 100644 index 0000000..d3fcf41 --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/project_challenge_validator.rb @@ -0,0 +1,14 @@ +module BlockParser + module ChallengeValidators + class ProjectChallengeValidator < ChallengeValidator + attr_reader :upstream_repo_path + + validates :upstream_repo_path, absence: true + + def initialize(attributes) + super(attributes) + @upstream_repo_path = attributes[:upstream_repo_path] + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb new file mode 100644 index 0000000..5e2357e --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb @@ -0,0 +1,14 @@ +module BlockParser + module ChallengeValidators + class ShortAnswerChallengeValidator < ChallengeValidator + attr_reader :answer + + validates :answer, presence: true + + def initialize(attributes) + super(attributes) + @answer = attributes[:answer] + end + end + end +end diff --git a/gems/bp/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb b/gems/bp/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb new file mode 100644 index 0000000..1288003 --- /dev/null +++ b/gems/bp/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb @@ -0,0 +1,167 @@ +require "github_url" +require "octokit" +require "gitlab" + +module BlockParser + module ChallengeValidators + class TestableProjectChallengeValidator < ChallengeValidator + attr_reader :upstream_repo_path + + validates :upstream_repo_path, presence: true + validate :valid_upstream_test_repo + + class InvalidGithubUrlError < StandardError + end + + def initialize(attributes) + super(attributes) + @upstream_repo_path = attributes[:upstream_repo_path] + set_remote_token + @required_files = ["Dockerfile", "test.sh"] + end + + private + + def set_remote_token + return nil if @upstream_repo_path.blank? + + prevent_whitespace_input + if @upstream_repo_path.include?("github") + return if ENV["GITHUB_TOKEN"].nil? + + @remote = "github" + @token = ENV["GITHUB_TOKEN"] + + elsif @upstream_repo_path.include?("gitlab.galvanize") + return if ENV["GITLAB_GALVANIZE_TOKEN"].nil? + + @remote = "gitlab.galvanize" + @token = ENV["GITLAB_GALVANIZE_TOKEN"] + + elsif @upstream_repo_path.include?("gitlab") + return if ENV["GITLAB_TOKEN"].nil? + + @remote = "gitlab" + @token = ENV["GITLAB_TOKEN"] + end + end + + def client + return @client if @client + + if @remote == "github" + @client ||= Octokit::Client.new(api_endpoint: "https://api.github.com", access_token: @token) + elsif @remote == "gitlab.galvanize" + @client ||= Gitlab.client( + endpoint: "https://gitlab.galvanize.com/api/v4", + private_token: @token + ) + elsif @remote == "gitlab" + @client ||= Gitlab.client( + endpoint: "https://gitlab.com/api/v4", + private_token: @token + ) + end + end + + def valid_upstream_test_repo + if @remote == "github" && @token + valid_github + elsif @remote == "gitlab.galvanize" && @token + valid_gitlab + elsif @remote == "gitlab" && @token + valid_gitlab + else + errors.add(:upstream_repo_path, "must be from Github.com or Gitlab.com") + end + end + + def valid_gitlab + files = client.tree(gitlab_repo_props[:repo], ref: gitlab_repo_props[:branch], path: gitlab_repo_props[:path_to_folder]).map(&:name) + if (files & @required_files).length != @required_files.length + (@required_files - (files & @required_files)).each do |file| + errors.add(:upstream_repo_path, ": upstream test repo is missing required file - '#{file}'") + end + end + + return false if errors.any? + rescue Gitlab::Error::NotFound + errors.add(:upstream_repo_path, ": upstream test repo couldn't be found. Verify the repo/branch exists.") + false + end + + def valid_github + unless repo_exists?(client) + errors.add(:upstream_repo_path, ": upstream test repo couldn't be found. Verify the repo/branch exists.") + return false + end + + if repo_props[:path_to_folder] + begin + !!client.contents(repo_props[:repo], path: repo_props[:path_to_folder], ref: repo_props[:branch]) + rescue Octokit::NotFound + errors.add(:upstream_repo_path, ": upstream test repo is missing subfolder(s) '#{repo_props[:path_to_folder]}'") + return false + end + @required_files.map! { |file| file.prepend("#{repo_props[:path_to_folder]}/") } + end + + @required_files.each do |file| + !!client.contents(repo_props[:repo], path: file, ref: repo_props[:branch]) + rescue Octokit::NotFound + errors.add(:upstream_repo_path, ": upstream test repo is missing required file - '#{file}'") + end + + return false if errors.any? + rescue GithubUrl::Invalid => e + errors.add(:upstream_repo_path, e.message) + false + end + + def repo_props + @repo_props ||= parse_repo_url + end + + def gitlab_repo_props + @gitlab_repo_props ||= parse_gitlab_repo_url + end + + def parse_repo_url + github_url = GithubUrl.new(url: upstream_repo_path) + path = github_url.path != "" ? github_url.path : nil + + { repo: github_url.organization + "/" + github_url.repository, branch: github_url.branch, path_to_folder: path } + end + + def parse_gitlab_repo_url + gitlab_url = @upstream_repo_path.split(".com/").last + + @upstream_repo_path.gsub!("/-", "") if gitlab_url.include?("/-/tree/") + + git_url = if gitlab_url.include?("galvanize") + GithubUrl.new(url: @upstream_repo_path, host: "gitlab.galvanize.com") + else + GithubUrl.new(url: @upstream_repo_path, host: "gitlab.com") + end + path = git_url.path != "" ? git_url.path : nil + + { repo: git_url.organization + "/" + git_url.repository, branch: git_url.branch, path_to_folder: path } + end + + def prevent_whitespace_input + upstream_repo_path.gsub!(/\s+/, "") + end + + def repo_exists?(client) + if repo_props[:branch] == "master" + client.repository?(repo_props[:repo]) + else + !!client.branch(repo_props[:repo], repo_props[:branch]) + end + rescue Octokit::NotFound + errors.add(:upstream_repo_path, ": the provided branch of the upstream repo was not found.") + false + end + end + end +end diff --git a/gems/bp/lib/block_parser/convert_md_to_json.rb b/gems/bp/lib/block_parser/convert_md_to_json.rb new file mode 100644 index 0000000..550a70f --- /dev/null +++ b/gems/bp/lib/block_parser/convert_md_to_json.rb @@ -0,0 +1,361 @@ +require "json" + +module BlockParser + class ConvertMdToJson + class MdParseError < StandardError; end + + META_DATA_KEY_REGEX = /\A\s*\*\s+\S+:\s+/ + TYPE_REGEX = /\A\s*\*\s+type:\s+/ + ID_REGEX = /\A\s*\*\s+id:\s+/ + TITLE_REGEX = /\A\s*\*\s+title:\s+/ + STANDARD_UUIDS_REGEX = /\A\s*\*\s+standard_uuids:\s+/ + DECIMAL_REGEX = /\A\s*\*\s+decimal:\s+/ + LANGUAGE_REGEX = /\A\s*\*\s+language:\s+/ + DATAPATH_REGEX = /\A\s*\*\s+data_path:\s+/ + UPSTREAM_REPO_REGEX = /\A\s*\*\s+upstream:\s+/ + VALIDATE_FORK_REGEX = /\A\s*\*\s+validate_fork:\s+/ + DOCKER_DIRECTORY_PATH_REGEX = /\A\s*\*\s+docker_directory_path:\s+/ + SHOW_TESTS_REGEX = /\A\s*\*\s+show_tests:\s+/ + POINTS_REGEX = /\A\s*\*\s+points:\s+/ + PARTIAL_CREDIT_REGEX = /\A\s*\*\s+partial_credit:\s+/ + TOPICS_REGEX = /\A\s*\*\s+topics:\s+/ + EXTERNAL_ID_REGEX = /\A\s*\*\s+external_id:\s+/ + EXTERNAL_SOURCE_REGEX = /\A\s*\*\s+external_source:\s+/ + + TRANSCRIPT_PROJECT_REGEX = /\A\s*\*\s+transcript_project:\s+/ + TRANSCRIPT_FILE_REGEX = /\A\s*\*\s+transcript_file:\s+/ + TRANSCRIPT_PLUGIN_REGEX = /\A\s*\*\s+transcript_plugin:\s+/ + TRANSCRIPT_TAGS = %i[transcript_project transcript_file transcript_plugin] + + TAG_REGEX = /\A\s*\#{1,5}\s+\!.*\z/ + TAG_THAT_IS_NOT_START_CHALLENGE_CHILD_TAG_REGEX = + /\A\s*\#{1,5}\s+\!(?!(question|placeholder|explanation|options|given-code|rubric|callout)\s*\z)/ + + def self.convert(md_plain_text) + content_array = [] + challenges_hash = {} + + md_lines = [] + challenge_lines = [] + vimeo_lines = [] + callout_lines = [] + + parsing_challenge = false + parsing_instructor_notes = false + parsing_vimeo = false + parsing_callout = false + callout_type = nil + + vimeo_videos_count = 0 + + begin + lines = md_plain_text.split("\n") + rescue ArgumentError + raise MdParseError, "file does not appear to be a valid markdown file" + end + + lines.each do |line| + if parsing_challenge + if end_tag?("challenge", line) + parsing_challenge = false + + if challenge_lines.present? + challenge = build_challenge_object(challenge_lines) + + return raise MdParseError, "Found !challenge without an id." if challenge.id.nil? + return raise MdParseError, "Found duplicate challenge id: #{challenge.id}." if challenges_hash[challenge.id.to_sym].present? + + content_array << { + "type": "challenge", + "id": challenge.id + } + challenges_hash[challenge.id.to_sym] = challenge.to_hash + + challenge_lines = [] + end + elsif start_tag?("challenge", line) + raise MdParseError, "Found second !challenge tag before finding !end-challenge." + else + challenge_lines << line + end + elsif parsing_instructor_notes + parsing_instructor_notes = false if end_tag?("instructor", line) + elsif parsing_vimeo + if end_tag?("vimeo", line) + parsing_vimeo = false + + if vimeo_lines.any? + content_array << build_vimeo_object(vimeo_lines) + + vimeo_videos_count += 1 + vimeo_lines = [] + end + else + vimeo_lines << line + end + elsif parsing_callout + if end_tag?("callout", line) + parsing_callout = false + if callout_lines.any? + content_array << { + "type": "callout", + "class": callout_type, + "value": callout_lines.map(&:strip).join("\n") + } + callout_lines = [] + callout_type = nil + end + else + callout_lines << line + end + elsif start_tag?("challenge", line) + parsing_challenge = true + + if md_lines.present? + md_value = md_lines.join("\n") + if md_value.strip.present? + content_array << { + "type": "markdown", + "value": md_value + } + end + md_lines = [] + end + elsif start_tag?("instructor", line) + parsing_instructor_notes = true + elsif start_callout_tag?("callout", line) + parsing_callout = true + callout_type = line.split("!").last + if md_lines.present? + md_value = md_lines.join("\n") + if md_value.strip.present? + content_array << { + "type": "markdown", + "value": md_value + } + end + md_lines = [] + end + elsif start_tag?("vimeo", line) + parsing_vimeo = true + + if md_lines.present? + md_value = md_lines.join("\n") + if md_value.strip.present? + content_array << { + "type": "markdown", + "value": md_value + } + end + md_lines = [] + end + elsif end_tag?("instructor", line) + raise MdParseError, "Found invalid tag 'end-instructor' without a starting !instructor tag." + elsif end_tag?("challenge", line) + raise MdParseError, "Found invalid tag 'end-challenge' without a starting !challenge tag." + elsif end_tag?("vimeo", line) + raise MdParseError, "Found invalid tag 'end-vimeo' without a starting !vimeo tag." + elsif line.match(TAG_REGEX) + raise MdParseError, "Found invalid tag '#{line.strip}' outside of a !challenge tag." + else + md_lines << line + end + end + + raise MdParseError, "Didn't find !end-challenge tag." if parsing_challenge + raise MdParseError, "Didn't find !end-vimeo tag." if parsing_vimeo + raise MdParseError, "Missing !end-instructor tag for existing !instructor tag" if parsing_instructor_notes + raise MdParseError, "There can only be up to 10 vimeo videos in single page" if vimeo_videos_count > 10 + + if md_lines.present? + md_value = md_lines.join("\n") + if md_value.strip.present? + content_array << { + "type": "markdown", + "value": md_value + } + end + md_lines = [] + end + + result = { + "content": content_array + } + + result[:challenges] = challenges_hash if challenges_hash.present? + + result + end + + def self.build_vimeo_object(vimeo_lines) + line_index = 0 + + vimeo_object = { + type: "vimeo" + } + + while line_index < vimeo_lines.length + if vimeo_lines[line_index].downcase.match(ID_REGEX) + vimeo_object[:id] = vimeo_lines[line_index].gsub(ID_REGEX, "").strip + elsif vimeo_lines[line_index].downcase.match(TRANSCRIPT_PROJECT_REGEX) + vimeo_object[:transcript_project] = vimeo_lines[line_index].gsub(TRANSCRIPT_PROJECT_REGEX, "").strip + elsif vimeo_lines[line_index].downcase.match(TRANSCRIPT_FILE_REGEX) + vimeo_object[:transcript_file] = vimeo_lines[line_index].gsub(TRANSCRIPT_FILE_REGEX, "").strip + elsif vimeo_lines[line_index].downcase.match(TRANSCRIPT_PLUGIN_REGEX) + vimeo_object[:transcript_plugin] = vimeo_lines[line_index].gsub(TRANSCRIPT_PLUGIN_REGEX, "").strip + end + + line_index += 1 + end + + raise(MdParseError, "Vimeo tag missing ID") unless vimeo_object.key?(:id) + + # If transcript tags are present, but not all are provided + if !(TRANSCRIPT_TAGS & vimeo_object.keys).empty? && (TRANSCRIPT_TAGS & vimeo_object.keys).length != TRANSCRIPT_TAGS.length + raise(MdParseError, "Vimeo tags with transcripts must include the project, file, and plugin") + end + + vimeo_object + end + + def self.build_challenge_object(challenge_lines) + challenge = ChallengeValidators::Challenge.new(ChallengeValidators::Challenge::TYPES) + + line_index = 0 + + parsing_meta_information = true + has_setup = false + + while line_index < challenge_lines.length + parsing_meta_information = false if parsing_meta_information && challenge_lines[line_index].match(/\A\s*\#{1,5}\s+\!/) + + if parsing_meta_information + if challenge_lines[line_index].downcase.match(TYPE_REGEX) + challenge.type = challenge_lines[line_index].gsub(TYPE_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(ID_REGEX) + challenge.id = challenge_lines[line_index].gsub(ID_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(TITLE_REGEX) + challenge.title = challenge_lines[line_index].gsub(TITLE_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(STANDARD_UUIDS_REGEX) + if challenge.standard_uuids.present? + raise( + MdParseError, + "Found multiple standard assignments on '#{challenge_lines[line_index].strip}' within a !challenge tag." + ) + end + challenge.standard_uuids = challenge_lines[line_index].gsub(STANDARD_UUIDS_REGEX, "").split(",").map(&:strip) + elsif challenge_lines[line_index].downcase.match(DECIMAL_REGEX) + challenge.decimal = challenge_lines[line_index].gsub(DECIMAL_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(LANGUAGE_REGEX) + challenge.language = challenge_lines[line_index].gsub(LANGUAGE_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(DATAPATH_REGEX) + challenge.data_path = challenge_lines[line_index].gsub(DATAPATH_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(UPSTREAM_REPO_REGEX) + challenge.upstream_repo_path = challenge_lines[line_index].gsub(UPSTREAM_REPO_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(VALIDATE_FORK_REGEX) + challenge.validate_fork = challenge_lines[line_index].gsub(VALIDATE_FORK_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(DOCKER_DIRECTORY_PATH_REGEX) + challenge.docker_directory_path = challenge_lines[line_index].gsub(DOCKER_DIRECTORY_PATH_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(SHOW_TESTS_REGEX) + challenge.show_tests = challenge_lines[line_index].gsub(SHOW_TESTS_REGEX, "").strip.downcase + elsif challenge_lines[line_index].downcase.match(POINTS_REGEX) + challenge.points = challenge_lines[line_index].gsub(POINTS_REGEX, "").strip.downcase.to_i + elsif challenge_lines[line_index].downcase.match(PARTIAL_CREDIT_REGEX) + challenge.partial_credit = challenge_lines[line_index].gsub(PARTIAL_CREDIT_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(TOPICS_REGEX) + challenge.topics = challenge_lines[line_index].gsub(TOPICS_REGEX, "").gsub(/\[|\]/, "").split(",").map do |topic| + topic.delete('"').delete("'").strip.downcase + end.uniq + elsif challenge_lines[line_index].downcase.match(EXTERNAL_ID_REGEX) + challenge.external_id = challenge_lines[line_index].gsub(EXTERNAL_ID_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(EXTERNAL_SOURCE_REGEX) + challenge.external_source = challenge_lines[line_index].gsub(EXTERNAL_SOURCE_REGEX, "").strip + elsif challenge_lines[line_index].downcase.match(META_DATA_KEY_REGEX) + raise MdParseError, "Found invalid meta data key '#{challenge_lines[line_index].strip}' within a !challenge tag." + end + elsif start_tag?("placeholder", challenge_lines[line_index]) + line_index, placeholder_text = parse_challenge_child_tag(challenge_lines, line_index, "placeholder") + challenge.placeholder = placeholder_text.sub(/\A\s*```.*\s*/, "").sub(/```\s*\z/, "").strip + elsif start_tag?("question", challenge_lines[line_index]) + line_index, question_text = parse_challenge_child_tag(challenge_lines, line_index, "question") + challenge.question = question_text + elsif start_tag?("explanation", challenge_lines[line_index]) + line_index, explanation_text = parse_challenge_child_tag(challenge_lines, line_index, "explanation") + challenge.explanation = explanation_text + elsif start_tag?("rubric", challenge_lines[line_index]) + line_index, rubric_text = parse_challenge_child_tag(challenge_lines, line_index, "rubric") + challenge.rubric = rubric_text + elsif start_tag?("hint", challenge_lines[line_index]) + line_index, hint_text = parse_challenge_child_tag(challenge_lines, line_index, "hint") + if hint_text.include?("```") + lang = hint_text.match(/```(js|java|python|json)/m) + lang = lang[1] unless lang.nil? + plain_text = hint_text.sub(/\A\s*```.*\s*/, "").sub(/```\s*\z/, "") + proccessed = "
    #{plain_text.tr('"', "'")}
    " + challenge.hints << proccessed + elsif hint_text.include?("`") # TODO: if ambitious, these could be made into markdown content types... + backticks = hint_text.scan(/`.*?`/) + backticks.each { |backtick| hint_text.gsub!(backtick, "#{backtick[1..backtick.length - 2]}") } + challenge.hints << hint_text.strip + else + challenge.hints << hint_text.strip + end + elsif start_tag?("answer", challenge_lines[line_index]) + line_index, answer_text = parse_challenge_child_tag(challenge_lines, line_index, "answer") + challenge.answer = answer_text.strip + elsif start_tag?("options", challenge_lines[line_index]) + line_index, options_text = parse_challenge_child_tag(challenge_lines, line_index, "options") + challenge.options = options_text + elsif start_tag?("setup", challenge_lines[line_index]) + line_with_error = line_index + 2 + raise MdParseError, "Duplicate !setup tag detected on line #{line_with_error}" if has_setup + + line_index, setup_text = parse_challenge_child_tag(challenge_lines, line_index, "setup") + challenge.setup = setup_text.sub(/\A\s*```.*\s*/, "").sub(/```\s*\z/, "") + has_setup = true + elsif start_tag?("tests", challenge_lines[line_index]) + line_index, tests_text = parse_challenge_child_tag(challenge_lines, line_index, "tests") + challenge.tests = tests_text.sub(/\A\s*```.*\s*/, "").sub(/```\s*\z/, "") + elsif challenge_lines[line_index].downcase.match(TAG_THAT_IS_NOT_START_CHALLENGE_CHILD_TAG_REGEX) + raise MdParseError, "Found invalid tag '#{challenge_lines[line_index]}' within a !challenge tag." + end + line_index += 1 + end + challenge + end + + def self.parse_challenge_child_tag(challenge_lines, line_index, tag_name) + line_index += 1 + child_tag_lines = [] + while challenge_lines.length > line_index && !end_tag?(tag_name, challenge_lines[line_index]) + if challenge_lines[line_index].match(TAG_REGEX) + raise MdParseError, "Found invalid tag '#{challenge_lines[line_index].strip}' within a !#{tag_name} tag." + end + + child_tag_lines << challenge_lines[line_index] + line_index += 1 + end + + raise MdParseError, "Didn't find !end-#{tag_name} tag." if challenge_lines[line_index].nil? + + [line_index, child_tag_lines.join("\n")] + end + + def self.end_tag?(type, line) + end_tag_regex = /\A\s*\#{1,5}\s+\!end-#{type}\s*\z/ + line.downcase.match(end_tag_regex) + end + + def self.start_tag?(type, line) + end_tag_regex = /\A\s*\#{1,5}\s+\!#{type}\s*\z/ + line.downcase.match(end_tag_regex) + end + + def self.start_callout_tag?(_type, line) + end_tag_regex = /\A\s*\#{1,5}\s+\!callout.*\z/ + line.downcase.match(end_tag_regex) + end + + private_class_method :build_challenge_object, :build_vimeo_object, :parse_challenge_child_tag, :end_tag?, :start_tag? + end +end diff --git a/gems/bp/lib/block_parser/parse_directory.rb b/gems/bp/lib/block_parser/parse_directory.rb new file mode 100644 index 0000000..b5fc270 --- /dev/null +++ b/gems/bp/lib/block_parser/parse_directory.rb @@ -0,0 +1,182 @@ +require "psych" +require "yaml" + +module BlockParser + class ParseDirectory + README_CAPS = "README.md" + README_LOWER = "readme.md" + CONFIG_FILENAME_YAML = "config.yaml" + CONFIG_FILENAME_YML = "config.yml" + AUTO_CONFIG_FILENAME_YAML = "autoconfig.yaml" + class InvalidConfigError < StandardError + end + + def initialize(path:, asset_uploader:) + @block_hash = {} + @errors = [] + @warnings = [] + @content_file_attribute_hashes = [] + @path = path + @asset_uploader = asset_uploader + end + + def execute + raise InvalidConfigError, "Directory does not exist" unless File.directory?(@path) + + if Dir.entries(@path).include?(CONFIG_FILENAME_YAML) && Dir.entries(@path).include?(CONFIG_FILENAME_YML) + raise(InvalidConfigError, "Found both a config.yaml and a config.yml. only one config file is allowed.") + end + + unless Dir.entries(@path).include?(CONFIG_FILENAME_YAML) || + Dir.entries(@path).include?(CONFIG_FILENAME_YML) || + Dir.entries(@path).include?(AUTO_CONFIG_FILENAME_YAML) + raise(InvalidConfigError, "Root directory does not contain a #{CONFIG_FILENAME_YAML} or #{CONFIG_FILENAME_YML} file") + end + + config_path = if Dir.entries(@path).include?(CONFIG_FILENAME_YAML) + File.join(@path, CONFIG_FILENAME_YAML) + elsif Dir.entries(@path).include?(CONFIG_FILENAME_YML) + File.join(@path, CONFIG_FILENAME_YML) + elsif Dir.entries(@path).include?(AUTO_CONFIG_FILENAME_YAML) + File.join(@path, AUTO_CONFIG_FILENAME_YAML) + end + + readme_path = if Dir.entries(@path).include?(README_CAPS) + File.join(@path, README_CAPS) + elsif Dir.entries(@path).include?(README_LOWER) + File.join(@path, README_LOWER) + end + + @readme_text = unless readme_path.blank? + text = File.read(readme_path).split("\n").reject(&:empty?) + text[0..2].join("\n") + end + + begin + config = YAML.load_file(config_path) + rescue Psych::SyntaxError => e + raise InvalidConfigError, "Could not parse config.yaml: #{e.message.gsub("(#{config_path}): ", '')}" + end + + raise InvalidConfigError, "config.yaml must be a hash" unless config.is_a?(Hash) + raise InvalidConfigError, "config.yaml must have a key of 'Standards'" unless config.key?("Standards") + + parsed_standards = ParseStandards.new(config["Standards"], @path).execute + if parsed_standards[:errors].any? + @errors.concat(parsed_standards[:errors]) + else + content_file_local_paths = + Dir[File.join(@path, "**/*")]. + reject { |fn| File.directory?(fn) }. + select { |file_path| file_path[-3..-1] == ".md" || file_path[-6..-1] == ".ipynb" || file_path[-4..-1] == ".pdf" } + + resource_paths = [] + parsed_standards[:standards_attributes].each do |standard| + standard[:content_files_attributes].each do |cfa| + resource_paths << cfa[:path] if cfa[:content_file_type] == BlockParser::ParseStandards::CONTENT_FILE_TYPES[:resource] + end + end + + @block_hash = parsed_standards[:standards_attributes].map do |standard| + content_file_attribute_hashes = standard[:content_files_attributes].map do |cfa| + content_file_search_path = [@path.gsub(/\/$/, ""), cfa[:path].gsub(/^\//, "")].join("/") + content_file_local_path = content_file_local_paths.find { |path| path == content_file_search_path } + + raise InvalidConfigError, "content file in config.yaml must exist" if content_file_local_path.nil? + + parsed_file = parse_file( + content_file_local_path: content_file_local_path, + content_file_local_paths: content_file_local_paths, + resource_paths: resource_paths, + cfa: cfa + ) + @warnings.concat(parsed_file[:warnings]) + + path = content_file_local_path.gsub(@path.to_s, "") + + parsed_errors = parsed_file[:errors] + + if cfa[:content_file_type] == BlockParser::ParseStandards::CONTENT_FILE_TYPES[:checkpoint] && + (parsed_file[:challenges].nil? || parsed_file[:challenges].empty?) + + parsed_errors << "checkpoint must have at least one challenge and all challenges must be valid" + end + + @errors.concat(parsed_errors.map { |error| "#{path}: #{error}" }) + + { + title: parsed_file[:title], + html: parsed_file[:html], + challenges: parsed_file[:challenges], + path: content_file_local_path[-4..-1] == ".pdf" ? path.gsub(".pdf", "_pdf") : path, + content_file_type: cfa[:content_file_type], + uid: cfa[:uid], + autoscore: cfa[:autoscore], + default_visibility: cfa[:default_visibility], + max_checkpoint_submissions: cfa[:max_checkpoint_submissions], + time_limit: cfa[:time_limit], + warnings: parsed_file[:warnings] + } + end + + { standard: standard, content_file_attribute_hashes: content_file_attribute_hashes } + end + end + + results + rescue InvalidConfigError => e + @errors << e.message + results + end + + private + + def parse_file(content_file_local_path:, content_file_local_paths:, resource_paths:, cfa:) + if content_file_local_path[-3..-1] == ".md" + BlockParser::ParseMarkdownFile.execute( + file_body: File.read(content_file_local_path), + asset_uploader: @asset_uploader, + root_directory_path: @path, + current_content_file_path: content_file_local_path, + content_file_paths: content_file_local_paths, + autoscore: cfa[:autoscore], + resource_paths: resource_paths + ) + elsif content_file_local_path[-6..-1] == ".ipynb" + _out, failure, status = Open3.capture3("jupyter nbconvert --to markdown #{content_file_local_path}") + + if status.success? + BlockParser::ParseMarkdownFile.execute( + file_body: File.read(content_file_local_path.gsub(".ipynb", ".md")), + asset_uploader: @asset_uploader, + root_directory_path: @path, + current_content_file_path: content_file_local_path, + content_file_paths: content_file_local_paths, + autoscore: cfa[:autoscore], + resource_paths: resource_paths + ) + else + { title: "", html: "", challenges: [], errors: [failure], warnings: [] } + end + elsif content_file_local_path[-4..-1] == ".pdf" + content_url = @asset_uploader.find_or_create_content(full_path: content_file_local_path) + { + title: cfa[:path].split("/").last.titleize, + html: content_url, + errors: [], + warnings: [], + challenges: [] + } + end + end + + def results + { + readme_text: @readme_text, + errors: @errors, + warnings: @warnings, + block_hash: @block_hash + } + end + end +end diff --git a/gems/bp/lib/block_parser/parse_markdown_file.rb b/gems/bp/lib/block_parser/parse_markdown_file.rb new file mode 100644 index 0000000..34b55f3 --- /dev/null +++ b/gems/bp/lib/block_parser/parse_markdown_file.rb @@ -0,0 +1,40 @@ +module BlockParser + class ParseMarkdownFile + def self.execute(file_body:, asset_uploader:, root_directory_path:, + current_content_file_path:, content_file_paths:, autoscore: false, resource_paths: []) + errors = [] + warnings = [] + html = "" + + json_hash = ConvertMdToJson.convert(file_body) + processed_md_results = BuildHtmlFromMdJson.process_json( + json_hash: json_hash, + asset_uploader: asset_uploader, + root_directory_path: root_directory_path, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + autoscore: autoscore, + resource_paths: resource_paths + ) + + html = processed_md_results[:html] + title = BuildTitleFromFilenameAndHtml.execute( + filename: File.basename(current_content_file_path), + html: processed_md_results[:html] + ) + challenges = processed_md_results[:challenges] + errors.concat(processed_md_results[:errors]) + warnings.concat(processed_md_results[:warnings]) + + results(html, title, challenges, errors, warnings) + rescue ConvertMdToJson::MdParseError => e + errors << "Could not parse markdown: #{e}" + + results(html, title, challenges, errors, warnings) + end + + def self.results(html, title, challenges, errors, warnings) + { challenges: challenges, errors: errors, html: html, title: title, warnings: warnings } + end + end +end diff --git a/gems/bp/lib/block_parser/parse_standards.rb b/gems/bp/lib/block_parser/parse_standards.rb new file mode 100644 index 0000000..83f842b --- /dev/null +++ b/gems/bp/lib/block_parser/parse_standards.rb @@ -0,0 +1,145 @@ +module BlockParser + class ParseStandards + CONTENT_FILE_TYPES = { + checkpoint: "checkpoint", + lesson: "lesson", + resource: "resource", + instructor: "instructor" + }.freeze + + def initialize(standards, absolute_directory_path) + @absolute_directory_path = absolute_directory_path + @standards = standards + @standards_attributes = [] + @errors = [] + end + + def execute + unless @standards&.any? + @errors << "Configuration must have at least one standard" + return results + end + + all_content_file_uids = {} # use this hash to ensure unique content file uids + @standards.each_with_index do |standard, index| + unless standard.is_a?(Hash) + @errors << "Standard ##{index + 1} must be a hash" + next + end + + standard_errors = [] + + standard_errors << "Standard ##{index + 1} must have Title" if standard["Title"].blank? + standard_errors << "Standard ##{index + 1} must have UID" if standard["UID"].blank? + standard_errors << "Standard ##{index + 1} must have Description" if standard["Description"].blank? + + unless standard.key?("SuccessCriteria") && standard["SuccessCriteria"].is_a?(Array) && standard["SuccessCriteria"].any? + standard_errors << "Standard ##{index + 1} must have at least one SuccessCriteria" + end + + content_files_attributes = [] + + if standard.key?("ContentFiles") && standard["ContentFiles"].is_a?(Array) && standard["ContentFiles"].any? + + checkpoint_count = standard["ContentFiles"].count { |content_file| content_file["Type"] == "Checkpoint" } + standard_errors << "Standard ##{index + 1} may not have more than one Checkpoint." unless checkpoint_count <= 1 + + standard["ContentFiles"].each_with_index do |content_file, content_file_index| + if content_file["Path"].present? && content_file["Path"].start_with?("/") + file_ext = content_file["Path"].split(".").last + if ["md", "ipynb", "pdf"].include?(file_ext) + if File.exist?(File.join(@absolute_directory_path, content_file["Path"])) + visibility = content_file["DefaultVisibility"].present? ? content_file["DefaultVisibility"] : "visible" + content_file_attributes = { + content_file_type: content_file["Type"].downcase, + path: content_file["Path"], + autoscore: false, + uid: content_file["UID"], + default_visibility: visibility.downcase + } + if content_file["UID"].blank? + standard_errors << "Standard ##{index + 1}: Content File referenced at #{content_file['Path']} does not have a UID" + elsif all_content_file_uids[content_file["UID"]] + standard_errors << "Duplicate Content File UID: #{content_file['UID']}" + end + all_content_file_uids[content_file["UID"]] = true + if content_file["Type"].casecmp(CONTENT_FILE_TYPES[:checkpoint]).zero? + content_file_attributes[:autoscore] = content_file["Autoscore"] == true + end + if content_file["Type"].casecmp(CONTENT_FILE_TYPES[:checkpoint]).zero? && + content_file["MaxCheckpointSubmissions"] && + content_file["MaxCheckpointSubmissions"].to_i != 0 + content_file_attributes[:max_checkpoint_submissions] = content_file["MaxCheckpointSubmissions"].to_i + end + if content_file["Type"].casecmp(CONTENT_FILE_TYPES[:checkpoint]).zero? && + content_file["TimeLimit"] && + content_file["TimeLimit"].to_i != 0 + if content_file["TimeLimit"].class == Integer + content_file_attributes[:time_limit] = content_file["TimeLimit"] + else + standard_errors << "Checkpoint #{content_file['UID']} TimeLimit must be a number only (number specifies minutes)" + end + end + content_files_attributes << content_file_attributes + else + standard_errors << "Standard ##{index + 1}: Content File referenced at #{content_file['Path']} does not exist" + end + else + valid_ext = ".md, .ipynb, or .pdf" + standard_errors << "Standard ##{index + 1}: Content File referenced at #{content_file['Path']} must be a #{valid_ext} file" + end + else + standard_errors << + "Standard ##{index + 1}: ContentFile ##{content_file_index + 1} must have a path starting with /" + end + end + + if content_files_attributes.none? { |cfa| %w[lesson checkpoint].include?(cfa[:content_file_type]) } + standard_errors << "Standard ##{index + 1}: Must have at least one lesson or checkpoint" + end + else + standard_errors << "Standard ##{index + 1} must have at least one ContentFile" + end + + @errors.concat(standard_errors) + + @standards_attributes << { + title: standard["Title"], + uid: standard["UID"], + description: standard["Description"], + success_criteria: standard["SuccessCriteria"], + content_files_attributes: content_files_attributes + } + end + + @standards_attributes.group_by { |attributes| attributes[:uid] }.values.select { |value| value.length > 1 }.each do |duplicate_set| + titles = duplicate_set.map { |standard| standard[:title] } + @errors << "Duplicate Standard UID used for standards with titles: #{titles.join(', ')}" + end + + content_file_paths = @standards_attributes.flat_map do |attrs| + attrs[:content_files_attributes].map { |content_file| content_file[:path] } + end + content_file_paths.select { |p1| content_file_paths.count { |p2| p2 == p1 } > 1 }.uniq.each do |duplicate_path| + @errors << "ContentFile #{duplicate_path} used more than once" + end + + checkpoint_content_file_uids = @standards_attributes.flat_map do |attrs| + attrs[:content_files_attributes].map do |content_file| + content_file[:uid] if content_file[:content_file_type] == CONTENT_FILE_TYPES[:checkpoint] + end + end.compact + checkpoint_content_file_uids.select { |p1| checkpoint_content_file_uids.count { |p2| p2 == p1 } > 1 }.uniq.each do |duplicate_uid| + @errors << "Checkpoint UID #{duplicate_uid} used more than once" + end + + results + end + + private + + def results + { errors: @errors, standards_attributes: @standards_attributes } + end + end +end diff --git a/gems/bp/lib/block_parser/version.rb b/gems/bp/lib/block_parser/version.rb new file mode 100644 index 0000000..121bc61 --- /dev/null +++ b/gems/bp/lib/block_parser/version.rb @@ -0,0 +1,3 @@ +module BlockParser + VERSION = "0.1.0" +end diff --git a/gems/bp/pkg/block_parser-0.1.0.gem b/gems/bp/pkg/block_parser-0.1.0.gem new file mode 100644 index 0000000000000000000000000000000000000000..0cee2b78d29536c4bedc1e70fc6639200e28076a GIT binary patch literal 5120 zcmeH}XH*l&7RMu~QKU#Of*?pU3Iqcf!37k7NLxTeK)?h-4Fm`^=1Gwb5{lGNLst+5 zMWkuyy@V2sG=UXGkt!f1S$ywjDj=D+^U{p*Cpd=?O}B z3nIc94_%H~NlTHae4LTwF^Qgq!YGM)#s{@eII=z7*&e54)ow2m^b1!*iU)Rd^Q{AG zHU#dUs~>vVZ0`0pI9{Wi36Yi*iRS2zK5(h>=4sYueAmthEBlp&=(ljLo(P##3y^X1 zVDE_%?+|%K31e(`$C-pFV4uZPZ)Lle_*0A%PH;I0ETk(~o#i42Ym0d8O$H%<+C}-d zeF!kVr@#=Bq2>7-p5T8@RdAKFUF}Ynn#Vr9rm*rdR-Ri|$4je{%p#)lpBNhjdg9M~ z*>UH)YtRKwTS%QkFRyrzA-EN3*Lim>+3<#U$bcQ!GDDMe63>8b|&5)ca)w7i!9Ji9)KYIp(+)mrX_+^%y9%3!mpj2uzA;EWp*py6_z;y?-GKlJZ(9$*NRX%n{Y_j^% zqj%RLVuy2*^BA0uvFDi;Isu;TtBDGax9l(BGj)I0#o@mbFF9vdD7|#+B7`v98Fl!5 zGD|B9$ko)~IrOUv(wCsILNw z;)Pl6R5V6+06ue|+7zSBww1M``g((8xTraogy(4a?#Sf>Wg%V;ThSLneI(RmAmPM- z)Eiqj`vd!$`>w~DW2NtHYxz1tW(w4#;zoTL6R(R*R%?PlgI04;s0GZrp=JLOObH+) z!2vjNQOaKA-2Qzb>D_H{Ym528Z0bEkajEAo)pb#Hl=4v)ob=cUF3knM6nB5hI?zE*z z0%oQ=b)1cc*P<+{@)tUW(Zho152WDgAWv2rZfAL3Ehm#R#lYYM65zGSx;Yyua`*Qm zMh#Ef>Zk$z!S>2*OB{TwupuLj2mEuQ1=B&xZcM$1krj!#cGwC|-yMTKlq6uf^>pzM z80W^nmY0r70kFrNkQ(Yd+4<-BCIwa|oB{x*lbX^AXhzLhO-G633HtX1Kj(=MEMK+o|6VniVHy_zJxqx?(N;TSdW?2}u>2i#A15ViQxu zOpRozpQ(^{s_=;6)_b*%-A=E$c&%U6Se9~l8>S?_UFyxTOzhC(ZQ%JNq0rIWo)id&W?ujN`@Dp)Kdg5X zkxpj>1S&000Qmk+R{p#(HV#M|XN;E%M#c~Bg8DJ9z}K(wfBFB*LBI;%@gJfHmfPe1 z&$tEO_>YWLc4d@fzOW0_>o8S> zqZ!e#{lS22CyzGg@^n$?v!=NIB;gR88?OQO3$U6!#CLN_T_clogSo=qTlNB|uY(i5 zKfI(7y6V=eQg0)Woao)duE|+}WQz6k`68prd$^=qt&w<8-IYT}pfgx#WOW%>T*8AQ V?)w>X@2# { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:multiple_choice], + "title": "People Skills", + "answer": "Peter Grunde", + "topics": ["cool", "beans", "dude"], + "options": [ + "Peter Grunde", + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWho is the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause,duh.\n" + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nBecause,duh again.\n" + } + ] + } + } + } + } + end + + let(:checkbox_json) do + { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:checkbox], + "title": "Humans", + "answer": ["Peter Grunde", "Bradford Hamilton"], + "options": [ + "Peter Grunde", + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws", + "Bradford Hamilton" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWho is the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThey are humans.\n" + } + ] + } + } + } + } + end + + let(:project_challenge_json) do + { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released.\n" + } + ] + } + } + } + } + end + + let(:short_answer_challenge_json) do + { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:short_answer], + "title": "add stuff", + "placeholder": "Write your answer here", + "question": { + content: [ + { + "type": "markdown", + "value": "\nWhat is 1 + 1?\n" + } + ] + }, + "answer": "2", + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nIt is math\n" + } + ] + } + } + } + } + end + + let(:local_snippet_json) do + { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:local_snippet], + "title": "Random Code", + "question": { + content: [ + { + "type": "markdown", + "value": "\nFix the bug.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\ngremlins\n" + } + ] + }, + placeholder: '\nfunction computeFactorialOfN(n) {\n // your code here\n}', + tests: 'describe("computeFactorialOfN", function() {\n it("should return a number", function() {\n expect(typeof computeFactorialOfN(7)).toBe("number");\n });\n it("should return the factorial of \'n\'", function() {\n expect(computeFactorialOfN(4)).toBe(24);\n });\n it("should return the factorial of 1", function() {\n expect(computeFactorialOfN(1)).toBe(1);\n });\n});\n', + show_tests: true + } + } + } + end + + context "short-answer type" do + it "returns the attributes for a short-answer record" do + result = described_class.execute( + content_object: short_answer_challenge_json[:content].first, + json_hash: short_answer_challenge_json, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to eq([]) + + challenge = result[:attributes] + expect(challenge[:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:short_answer]) + expect(challenge[:title]).to eq("add stuff") + expect(challenge[:explanation]).to eq("

    It is math

    \n") + expect(challenge[:answer]).to eq("2") + end + end + + context "multiple choice challenges" do + it "returns the attributes for a multiple-choice record" do + result = described_class.execute( + content_object: multiple_choice_json[:content].first, + json_hash: multiple_choice_json, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to eq([]) + + challenge = result[:attributes] + expect(challenge[:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:multiple_choice]) + expect(challenge[:title]).to eq("People Skills") + expect(challenge[:rubric]).to eq("

    Because,duh again.

    \n") + expect(challenge[:explanation]).to eq("

    Because,duh.

    \n") + expect(challenge[:topics]).to eq(["cool", "beans", "dude"]) + expect(challenge[:position]).to eq(1) + end + end + + context "checkbox challenges" do + it "returns the attributes for a checkbox record" do + result = described_class.execute( + content_object: checkbox_json[:content].first, + json_hash: checkbox_json, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to eq([]) + + challenge = result[:attributes] + expect(challenge[:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:checkbox]) + expect(challenge[:title]).to eq("Humans") + expect(challenge[:explanation]).to eq("

    They are humans.

    \n") + expect(challenge[:position]).to eq(1) + end + end + + context "local snippet challenges" do + it "returns the attributes for a multiple-choice record" do + result = described_class.execute( + content_object: local_snippet_json[:content].first, + json_hash: local_snippet_json, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to eq([]) + + challenge = result[:attributes] + expect(challenge[:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:local_snippet]) + expect(challenge[:title]).to eq("Random Code") + expect(challenge[:explanation]).to eq("

    gremlins

    \n") + expect(challenge[:position]).to eq(1) + expect(challenge[:show_tests]).to eq(true) + end + end + + context "project challenges" do + it "returns the attributes for a project challenge" do + json_hash = { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released.\n" + } + ] + } + } + } + } + + result = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to eq([]) + + challenge = result[:attributes] + expect(challenge[:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:project]) + expect(challenge[:title]).to eq("partial derivative") + expect(challenge[:explanation]).to include('

    Thanks for submitting! You will be able to download the solutions once they are released.

    ') + expect(challenge[:position]).to eq(1) + expect(challenge[:validate_fork]).to eq(false) + end + + it "handles blank explanations" do + json_hash = { + content: [ + { + type: "challenge", + id: "asdf1234" + } + ], + challenges: { + "asdf1234": { + "id": "asdf1234", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "new title", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + } + } + } + } + + result = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to eq([]) + + expect(result[:attributes][:explanation]).to eq(nil) + end + + context "in an autograde checkpoint" do + it "raises an error" do + json_hash = { + content: [ + { + type: "challenge", + id: "asdf1234" + } + ], + challenges: { + "asdf1234": { + "id": "asdf1234", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "error", + "question": { + content: [ + { + "type": "markdown", + "value": "\nnope\n" + } + ] + } + } + } + } + + result = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [], + autoscore: true + ) + expect(result[:errors]).to eq(["project is not an autoscoreable challenge type but was found in an autograde checkpoint."]) + end + end + end + + context "custom snippet challenges" do + it "returns the attributes for a custom snippet challenge" do + json_hash = { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:custom_snippet], + "title": "some custom stuff", + "language": "java", + "placeholder": "Type some codes here", + "docker_directory_path": "/spec/fixtures/valid-custom-snippet-challenge-directory", + "question": { + content: [ + { + "type": "markdown", + "value": "\nHow's about you type some code in here.\n" + } + ] + } + } + } + } + + result = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: File.dirname(__dir__), + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to eq([]) + + challenge = result[:attributes] + expect(challenge[:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:custom_snippet]) + expect(challenge[:title]).to eq("some custom stuff") + expect(challenge[:language]).to eq("java") + expect(challenge[:docker_directory_path]).to eq("/spec/fixtures/valid-custom-snippet-challenge-directory") + expect(challenge[:position]).to eq(1) + expect(challenge[:validate_fork]).to eq(false) + end + end + + context "testable project challenges" do + before { allow(ENV).to receive(:[]) { "foo" } } + it "returns the attributes for a project challenge" do + json_hash = { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "upstream_repo_path": "www.github.com/a/valid/repo/with/specs", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released.\n" + } + ] + } + } + } + } + + result = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to eq([]) + + challenge = result[:attributes] + expect(challenge[:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project]) + expect(challenge[:title]).to eq("partial derivative") + expect(challenge[:upstream_repo_path]).to eq("www.github.com/a/valid/repo/with/specs") + expect(challenge[:explanation]).to include('

    Thanks for submitting! You will be able to download the solutions once they are released.

    ') + expect(challenge[:position]).to eq(1) + expect(challenge[:validate_fork]).to eq(false) + end + + it "requires an upstream repo path" do + json_hash = { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released.\n" + } + ] + } + } + } + } + + result = + described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to eq(["Upstream repo path can't be blank, Upstream repo path must be from Github.com or Gitlab.com"]) + end + + it "handles blank explanations" do + json_hash = { + content: [ + { + type: "challenge", + id: "asdf1234" + } + ], + challenges: { + "asdf1234": { + "id": "asdf1234", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "new title", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + } + } + } + } + + result = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + expect(result[:attributes][:explanation]).to eq(nil) + end + + it "allows toggling of fork validation" do + json_hash = { + content: [ + { + type: "challenge", + id: "asdf1234" + } + ], + challenges: { + "asdf1234": { + "id": "asdf1234", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "new title", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "validate_fork": true + } + } + } + + result = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:attributes][:validate_fork]).to eq(true) + end + end + + context "paragraph challenges" do + let(:json_hash) do + { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:paragraph], + "title": "partial derivative", + "placeholder": "Your answer here.", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSomething something question about something?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting!\n" + } + ] + } + } + } + } + end + + it "returns the attributes for a paragraph challenge record" do + challenge = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + )[:attributes] + + expect(challenge[:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:paragraph]) + expect(challenge[:title]).to eq("partial derivative") + expect(challenge[:explanation]).to eq("

    Thanks for submitting!

    \n") + end + + it "handles blank explanations" do + json_hash = { + content: [ + { + type: "challenge", + id: "asdf1234" + } + ], + challenges: { + "asdf1234": { + "id": "asdf1234", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:paragraph], + "title": "new title", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit your text.\n" + } + ] + } + } + } + } + + result = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + expect(result[:attributes][:explanation]).to eq(nil) + end + + context "in an autograde checkpoint" do + it "raises an error" do + json_hash = { + content: [ + { + type: "challenge", + id: "asdf1234" + } + ], + challenges: { + "asdf1234": { + "id": "asdf1234", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:paragraph], + "title": "error", + "question": { + content: [ + { + "type": "markdown", + "value": "\nnope\n" + } + ] + } + } + } + } + + result = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [], + autoscore: true + ) + expect(result[:errors]).to eq(["paragraph is not an autoscoreable challenge type but was found in an autograde checkpoint."]) + end + end + end + + context "number challenges" do + let(:json_hash) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:number], + "title": "sum", + "placeholder": "enter your answer here", + "decimal": "2", + "answer": "3", + "question": { + content: [ + { + "type": "markdown", + "value": "\n1+2=?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nGood job!\n" + } + ] + } + } + } + } + end + + it "returns the attributes for a number challenge" do + challenge = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + )[:attributes] + + expect(challenge[:decimal]).to eq("2") + expect(challenge[:answer]).to eq("3") + end + + it "validates attributes on number challenges" do + json_hash[:challenges][:"1"].delete(:answer) + + result = + described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to include("Answer can't be blank") + end + end + + context "code snippet challenges" do + let(:json_hash) do + { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + "title": "repeats", + "placeholder": "// Write your function in Javascript below", + "hints": ["Eat more cake."], + "question": { + content: [ + { + "type": "markdown", + "value": "Write a function named repeats that returns true if the first half of the string equals the last half, and false if not." + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "You might have solved this with a while loop, but using recursion is another way to do this." + } + ] + }, + "show_tests": true, + "language": "javascript", + "setup": "(function () { + this.cStub = sinon.stub(console, 'log'); + })()", + "tests": %{describe('repeats', function() { + + it("returns true when given an empty string (which seems strange, but go with it :)", function() { + expect(repeats(""), "Default value is incorrect").to.deep.eq(true) + }) + + it("returns true when the second half of the string equals the first", function() { + expect(repeats("bahbah")).to.deep.eq(true) + expect(repeats("nananananananana")).to.deep.eq(true) + }) + + it("returns false when the second half of the string does not equal the first", function() { + expect(repeats("bahba")).to.deep.eq(false) + expect(repeats("nananananann")).to.deep.eq(false) + }) + + it("does not use .repeat", function() { + expect(repeats.toString()).to.not.match(/\.repeat/) + }) + + }) + } + } + } + } + end + + it "returns the attributes for a javascript code snippet challengeb" do + expected_tests = %{describe('repeats', function() { + + it("returns true when given an empty string (which seems strange, but go with it :)", function() { + expect(repeats(""), "Default value is incorrect").to.deep.eq(true) + }) + + it("returns true when the second half of the string equals the first", function() { + expect(repeats("bahbah")).to.deep.eq(true) + expect(repeats("nananananananana")).to.deep.eq(true) + }) + + it("returns false when the second half of the string does not equal the first", function() { + expect(repeats("bahba")).to.deep.eq(false) + expect(repeats("nananananann")).to.deep.eq(false) + }) + + it("does not use .repeat", function() { + expect(repeats.toString()).to.not.match(/\.repeat/) + }) + + }) + } + + challenge = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + )[:attributes] + + expect(challenge[:language]).to eq("javascript") + expect(challenge[:tests]).to eq(expected_tests) + expect(challenge[:hints]).to match_array ["

    Eat more cake.

    \n"] + expect(challenge[:show_tests]).to eq true + end + + context "Python Challenges" do + it "returns the attributes for a python 2.7 code snippet challenge" do + json_hash = + { content: [{ type: "challenge", id: "python-challenge-01" }], + challenges: { "python-challenge-01": { id: "python-challenge-01", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + title: "filter by class", + placeholder: "def filter_by_class(X, y, label): + ''' + INPUT: 2 dimensional numpy array, numpy array, object + OUTPUT: 2 dimensional numpy array + + Return the rows from X whose corresponding label from y is the given label. + ''' + pass", + question: { content: [{ type: "markdown", + value: %{Implement the function `filter_by_class`: It takes a feature matrix, `X`, an array of classes, `y`, and a class label, `label`. It should return all of the rows from X whose label is the given label. + + >>> X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + >>> y = np.array(["a", "c", "a", "b"]) + >>> filter_by_class(X, y, "a") + array([[1, 2, 3], + [7, 8, 9]]) + >>> filter_by_class(X, y, "b") + array([[10, 11, 12]]) + } }] }, + explanation: { content: [{ type: "markdown", + value: %{ + def filter_by_class(X, y, label): + return X[y == label] + } }] }, + language: "python2.7", + tests: %{ + def test_filter_by_class1(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "a") + answer = np.array([[1, 2, 3], [7, 8, 9]]) + assert np.array_equal(result, answer) + + def test_filter_by_class2(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "b") + answer = np.array([[10, 11, 12]]) + assert np.array_equal(result, answer) + } } } } + + expected_tests = %{ + def test_filter_by_class1(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "a") + answer = np.array([[1, 2, 3], [7, 8, 9]]) + assert np.array_equal(result, answer) + + def test_filter_by_class2(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "b") + answer = np.array([[10, 11, 12]]) + assert np.array_equal(result, answer) + } + + challenge = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + )[:attributes] + + expect(challenge[:language]).to eq("python2.7") + expect(challenge[:tests]).to eq(expected_tests) + end + + it "returns the attributes for a python 3.6 code snippet challenge" do + json_hash = + { content: [{ type: "challenge", id: "python-challenge-01" }], + challenges: { "python-challenge-01": { id: "python-challenge-01", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + title: "filter by class", + placeholder: "def filter_by_class(X, y, label): + ''' + INPUT: 2 dimensional numpy array, numpy array, object + OUTPUT: 2 dimensional numpy array + + Return the rows from X whose corresponding label from y is the given label. + ''' + pass", + question: { content: [{ type: "markdown", + value: %{Implement the function `filter_by_class`: It takes a feature matrix, `X`, an array of classes, `y`, and a class label, `label`. It should return all of the rows from X whose label is the given label. + + >>> X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + >>> y = np.array(["a", "c", "a", "b"]) + >>> filter_by_class(X, y, "a") + array([[1, 2, 3], + [7, 8, 9]]) + >>> filter_by_class(X, y, "b") + array([[10, 11, 12]]) + } }] }, + explanation: { content: [{ type: "markdown", + value: %{ + def filter_by_class(X, y, label): + return X[y == label] + } }] }, + language: "python3.6", + tests: %{ + def test_filter_by_class1(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "a") + answer = np.array([[1, 2, 3], [7, 8, 9]]) + assert np.array_equal(result, answer) + + def test_filter_by_class2(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "b") + answer = np.array([[10, 11, 12]]) + assert np.array_equal(result, answer) + } } } } + + expected_tests = %{ + def test_filter_by_class1(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "a") + answer = np.array([[1, 2, 3], [7, 8, 9]]) + assert np.array_equal(result, answer) + + def test_filter_by_class2(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "b") + answer = np.array([[10, 11, 12]]) + assert np.array_equal(result, answer) + } + + challenge = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + )[:attributes] + + expect(challenge[:language]).to eq("python3.6") + expect(challenge[:tests]).to eq(expected_tests) + end + end + + context "java code snippet challenges" do + it "returns the attributes for a java code snippet challenge" do + expected_tests = %{@Test + public void passingTest(){ + assertThat(new Foo().bar(), equalTo("foo")); + } + + @Test + public void failingTest(){ + Assert.assertEquals("foo", "bar"); + } + + @Test + public void testJson() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + System.out.println(mapper.writeValueAsString(new Date())); + } + } + + json_hash = + { content: [{ type: "challenge", id: "java-challenge-01" }], + challenges: { "java-challenge-01": { id: "java-challenge-01", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + title: "Java Challenge #1", + placeholder: "Input java code here", + question: { content: [{ + type: "markdown", + value: "Some java question" + }] }, + explanation: { content: [{ + type: "markdown", + value: "Cuz this is java" + }] }, + language: "java", + tests: expected_tests } } } + + challenge = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + )[:attributes] + + expect(challenge[:language]).to eq("java") + expect(challenge[:tests]).to eq(expected_tests) + end + end + + it "returns expected html" do + expected_html = %(
    ) + result_html = described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + )[:html] + expect(result_html).to eq(expected_html) + end + end + + context "invalid challenge type" do + it "raises an exception with invalid challenge type message" do + json_hash = { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": "type_of_q", + "title": "Some Challenge Title", + "placeholder": "Enter the github URL to your work.", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + } + } + } + } + result = + described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + expect(result[:errors]).to include("type_of_q is not a valid challenge type") + end + end + + context "data validations" do + it "raises an exception with the data validation error messages" do + json_hash = { + content: [ + { + type: "challenge", + id: content_file_identifier + } + ], + challenges: { + content_file_identifier.to_sym => { + "id": content_file_identifier, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:short_answer], + "title": "", + "placeholder": "Enter the github URL to your work.", + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released.\n" + } + ] + } + } + } + } + + result = + described_class.execute( + content_object: json_hash[:content].first, + json_hash: json_hash, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to include("Title can't be blank, Question can't be blank, Answer can't be blank") + end + end + + context "challenge is not gradeable" do + it "builds a project challenge in html" do + result = + described_class.execute( + content_object: project_challenge_json[:content].first, + json_hash: project_challenge_json, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + )[:html] + + expected_html = %(
    ) + + expect(result).to eq(expected_html) + end + end + + context "challenge is gradeable" do + it "builds a multiple choice (gradeable) challenge in html" do + result = + described_class.execute( + content_object: multiple_choice_json[:content].first, + json_hash: multiple_choice_json, + position: 1, + root_directory_path: "foo", + asset_uploader: Freeloader.new, + current_content_file_path: "", + content_file_paths: [] + )[:html] + + expected_html = %(
    ) + + expect(result).to eq(expected_html) + end + end + end +end diff --git a/gems/bp/spec/build_html_from_md_json_spec.rb b/gems/bp/spec/build_html_from_md_json_spec.rb new file mode 100644 index 0000000..3f4216c --- /dev/null +++ b/gems/bp/spec/build_html_from_md_json_spec.rb @@ -0,0 +1,316 @@ +require "spec_helper" + +describe BlockParser::BuildHtmlFromMdJson do + describe ".process_json" do + context "when a json_hash containing a vimeo embed without a transcript is passed" do + let(:json_hash) do + { + content: [ + { + type: "vimeo", + id: "123" + } + ] + } + end + + it "returns the vimeo embed tags" do + expected_html = "" + + expect(described_class.process_json( + json_hash: json_hash, + asset_uploader: Freeloader.new, + root_directory_path: "", + current_content_file_path: "", + content_file_paths: [] + )[:html]).to eq(expected_html) + end + end + + context "when a json_hash containing two vimeo embeds with transcripts is passed" do + let(:json_hash) do + { + content: [ + { + type: "vimeo", + id: "123", + transcript_project: "24099", + transcript_file: "1795859", + transcript_plugin: "11378" + }, + { + type: "vimeo", + id: "456", + transcript_project: "24099", + transcript_file: "1795859", + transcript_plugin: "11378" + } + ] + } + end + + it "returns the vimeo embed tags and the transcript tag" do + expected_html = + ""\ + ""\ + ""\ + ""\ + ""\ + "" + + expect(described_class.process_json( + json_hash: json_hash, + asset_uploader: Freeloader.new, + root_directory_path: "", + current_content_file_path: "", + content_file_paths: [] + )[:html]).to eq(expected_html) + end + end + + context "when a json_hash containing callout is passed" do + let(:json_hash) do + { + content: [ + { + type: "callout", + class: "callout-danger", + value: "#test\ntest" + } + ] + } + end + + it "returns the html wrapped in a callout container div" do + expected_html = "

    test

    \n\n

    test

    \n
    " + + expect(described_class.process_json( + json_hash: json_hash, + asset_uploader: Freeloader.new, + root_directory_path: "", + current_content_file_path: "", + content_file_paths: [] + )[:html]).to eq(expected_html) + end + end + + context "when a hash containing two challenges is passed" do + let(:two_challenge_hash) do + { + content: [ + { + type: "challenge", + id: "1" + }, + { + type: "challenge", + id: "abc" + } + ], + challenges: { + "1": { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released.\n" + } + ] + } + }, + "abc": { + "id": "abc", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:paragraph], + "title": "Explain Yourself", + "placeholder": "Enter your work here.", + "question": { + content: [ + { + "type": "markdown", + "value": "\nExplain why you think you are right.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting!\n" + } + ] + } + } + } + } + end + + it "converts markdown json to html" do + json_hash = { + content: [ + { + type: "markdown", + value: "## Header 1" + }, + { + type: "markdown", + value: "## Header 2" + } + ] + } + + expected_html = "

    Header 1

    \n

    Header 2

    \n" + + expect(described_class.process_json( + json_hash: json_hash, + asset_uploader: Freeloader.new, + root_directory_path: "", + current_content_file_path: "", + content_file_paths: [] + )[:html]).to eq(expected_html) + end + + it "adds additional classes to tables" do + json_hash = { + content: [ + { + type: "markdown", + value: "
    " + } + ] + } + + expected_html = "
    \n" + + expect(described_class.process_json( + json_hash: json_hash, + asset_uploader: Freeloader.new, + root_directory_path: "", + current_content_file_path: "", + content_file_paths: [] + )[:html]).to eq(expected_html) + end + + it "converts markdown challenges to html" do + json_hash = { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released.\n" + } + ] + } + } + } + } + + result = described_class.process_json( + json_hash: json_hash, + asset_uploader: Freeloader.new, + root_directory_path: "", + current_content_file_path: "", + content_file_paths: [] + ) + + expected_html = %(
    ) + + expect(result[:html]).to eq(expected_html) + end + + it "returns the position of the challenge" do + result = + described_class.process_json( + json_hash: two_challenge_hash, + asset_uploader: Freeloader.new, + root_directory_path: "", + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:challenges].length).to eq(2) + expect(result[:challenges][0][:position]).to eq(1) + expect(result[:challenges][0][:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:project]) + expect(result[:challenges][1][:position]).to eq(2) + expect(result[:challenges][1][:challenge_type]).to eq(BlockParser::ChallengeValidators::Challenge::TYPES[:paragraph]) + end + + it "ignores unknown content types" do + json_hash = { + content: [ + { + type: "markdown", + value: "## Header 1" + }, + { + type: "bad_type", + value: "## Header 2" + } + ] + } + + expected_html = "

    Header 1

    \n" + + expect( + described_class.process_json( + json_hash: json_hash, + asset_uploader: Freeloader.new, + root_directory_path: "", + current_content_file_path: "", + content_file_paths: [] + )[:html] + ).to eq(expected_html) + end + end + + context "when a hash containing challenges but with no content" do + it "returns an error for empty html parsing" do + json_hash = { + content: [], + challenges: {} + } + + result = described_class.process_json( + json_hash: json_hash, + asset_uploader: Freeloader.new, + root_directory_path: "", + current_content_file_path: "", + content_file_paths: [] + ) + + expect(result[:errors]).to include("content cannot be blank") + end + end + end +end diff --git a/gems/bp/spec/build_html_spec.rb b/gems/bp/spec/build_html_spec.rb new file mode 100644 index 0000000..e6301fa --- /dev/null +++ b/gems/bp/spec/build_html_spec.rb @@ -0,0 +1,196 @@ +require "spec_helper" + +describe BlockParser::BuildHtml do + describe ".execute" do + let(:test_client) { instance_double("Mocktokit::Client") } + + before { allow(ENV).to receive(:[]) { nil } } + before { allow(ENV).to receive(:[]).with("HOST").and_return("http://www.foo.com") } + + it "converts the markdown containing links into html that is ready to be rendered" do + content_file_paths = ["/foo/foo.md", "/foo/bar.md"] + + html = "foo" + expected_html = "foo" + + results = + described_class.execute( + html: html, + current_content_file_path: "/foo/bar.md", + content_file_paths: content_file_paths, + root_directory_path: "foo", + asset_uploader: Freeloader.new + ) + + expect(results[:html]).to eq(expected_html) + expect(results[:errors]).to eq([]) + expect(results[:warnings]).to eq([]) + end + + it "converts the markdown containing images into html that is ready to be rendered" do + expect_any_instance_of(Freeloader).to receive(:find_or_create_content) { "http://some-s3-url/" } + + html = '' + + expected_html = "" + + expect(described_class.execute( + html: html, + current_content_file_path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo", "bar.md"), + content_file_paths: [], + root_directory_path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo"), + asset_uploader: Freeloader.new + )[:html]).to eq(expected_html) + end + + it "converts the markdown containing headers to include ids for anchor tags" do + html = "

    Sum Title

    Sum Title

    " + + expected_html = "

    Sum Title

    Sum Title

    " + + expect(described_class.execute( + html: html, + current_content_file_path: "bar.md", + content_file_paths: [], + root_directory_path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo"), + asset_uploader: Freeloader.new + )[:html]).to eq(expected_html) + end + + it "still converts when there are no conversions to be made" do + html = "foo" + + expected_html = "foo" + + expect(described_class.execute( + html: html, + current_content_file_path: "bar.md", + content_file_paths: [], + root_directory_path: "baz", + asset_uploader: Freeloader.new + )[:html]).to eq(expected_html) + end + + it "converts lists of 'checkboxes' to inputs " do + html = "
      \n
    • [ ] Replace stringifyJSON with your own function in src/stringifyJSON.js, and make the specs pass.
    • \n
    • [ ] Implement getElementsByClassName with your own function in src/getElementsByClassName.js, and make the specs pass.\n\n
        \n
      • [ ] You should use document.body, element.childNodes, and element.classList\n
      • \n
      \n
    • \n
    " + + expected_html = "
      \n
    • Replace stringifyJSON with your own function in src/stringifyJSON.js, and make the specs pass.
    • \n
    • Implement getElementsByClassName with your own function in src/getElementsByClassName.js, and make the specs pass.\n\n
        \n
      • You should use document.body, element.childNodes, and element.classList\n
      • \n
      \n
    • \n
    " + + expect(described_class.execute( + html: html, + current_content_file_path: "bar.md", + content_file_paths: [], + root_directory_path: "baz", + asset_uploader: Freeloader.new + )[:html]).to eq(expected_html) + end + + it "removes meta, script, style, title, and link tags from html" do + html = %( + + +morning-slides slides + +

    Keep Me!

    +) + expected_html = "

    Keep Me!

    \n" + expect(described_class.execute( + html: html, + current_content_file_path: "bar.md", + content_file_paths: [], + root_directory_path: "baz", + asset_uploader: Freeloader.new + )[:html]).to eq(expected_html) + end + + context "when provided an iframe" do + let(:input_markdown) do + File.read(File.join(File.dirname(__FILE__), "fixtures", "sample-iframe.md")) + end + let(:processed_text) do + described_class.execute( + html: input_markdown, + current_content_file_path: "bar.md", + content_file_paths: [], + root_directory_path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo"), + asset_uploader: Freeloader.new + )[:html] + end + let(:expected_html) { input_markdown } + + it "should output the html exactly as provided" do + expect(processed_text).to eq(expected_html) + end + end + + context "when image tags fail" do + let(:text) { "" } + + it "continues to process the content file" do + result = described_class.execute( + html: text, + current_content_file_path: "bar.md", + content_file_paths: [], + root_directory_path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo"), + asset_uploader: Freeloader.new + ) + + expect(result[:html]).to eq(text) + expect(result[:warnings].first).to include("Bad image link /Curriculum/someFolder/bad_image_1.jpg") + expect(result[:errors]).to eq([]) + end + end + + context "when link tags fail" do + let(:text) { "" } + + it "continues to process the content file and adds warnings" do + result = described_class.execute( + html: text, + current_content_file_path: "bar.md", + content_file_paths: [], + root_directory_path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo"), + asset_uploader: Freeloader.new + ) + + expect(result[:html]).to eq(text) + expect(result[:errors]).to eq([]) + expect(result[:warnings]).to eq(["bar.md: Bad internal link ../bad_link_bears.md"]) + end + end + end + + describe ".process_header" do + it "adds an id for use as an anchor tag on all H tags" do + html = "

    Sum Header

    " + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expected_html = "

    Sum Header

    " + + results = described_class.process_header(doc.children.first, {}) + expect(results[0]).to eq(html) + expect(results[1]).to eq(expected_html) + expect(results[2]).to eq("sum-header": 1) + end + + it "appends a unique number if there are identical headers" do + html = "

    Header

    " + doc = Nokogiri::HTML::DocumentFragment.parse(html) + existing_headers = { 'header': 1 } + + expected_html = "

    Header

    " + + results = described_class.process_header(doc.children.first, existing_headers) + expect(results[0]).to eq(html) + expect(results[1]).to eq(expected_html) + expect(results[2]).to eq("header": 2) + end + end +end diff --git a/gems/bp/spec/build_image_link_spec.rb b/gems/bp/spec/build_image_link_spec.rb new file mode 100644 index 0000000..85b25e8 --- /dev/null +++ b/gems/bp/spec/build_image_link_spec.rb @@ -0,0 +1,308 @@ +require "spec_helper" + +describe BlockParser::BuildImageLink do + describe ".execute" do + let(:mock_uploader) { double("uploader") } + + context "when an update to the url is required" do + let(:image_text) { "image" } + + it "returns a tuple of strings to be gsub'ed for images with a relative src path to a different directory than the content file and uploads the file to s3" do + expect(File).to receive(:read) { image_text } + expect(mock_uploader).to receive(:find_or_create_content).with(full_path: "/images/test.jpg") { "http://some-s3-url/" } + + html = '' + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute( + element: doc.children.first, + current_content_file_path: "/stuff/foo.md", + asset_uploader: mock_uploader, + root_directory_path: File.join(File.dirname(__FILE__), "tmp", "block-repos", "sample-directory") + )[:result]).to eq(["src=\"../images\/test.jpg\"", "src=\"http://some-s3-url/\""]) + end + + it "returns a tuple of strings to be gsub'ed for images with an absolute src path from root of repo and uploads the file to s3" do + expect(File).to receive(:read) { image_text } + expect(mock_uploader).to receive(:find_or_create_content).with(any_args) { "http://some-s3-url/" } + + html = '' + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute( + element: doc.children.first, + current_content_file_path: "/stuff/foo.md", + asset_uploader: mock_uploader, + root_directory_path: File.join(File.dirname(__FILE__), "tmp", "block-repos", "sample-directory") + )[:result]).to eq(["src=\"/stuff\/images\/img.jpeg\"", "src=\"http://some-s3-url/\""]) + end + + it "returns a tuple of strings to be gsub'ed for images with an absolute src path from folder of repo and uploads the file to s3" do + expect(File).to receive(:read) { image_text } + expect(mock_uploader).to receive(:find_or_create_content).with(any_args) { "http://some-s3-url/" } + + html = '' + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute( + element: doc.children.first, + current_content_file_path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo", "folder", "target.md"), + asset_uploader: mock_uploader, + root_directory_path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo") + )[:result]).to eq(["src=\"\/images\/galvanize-logo.png\"", "src=\"http://some-s3-url/\""]) + end + + it "returns a tuple of strings to be gsub'ed for images with a relative src path within the content file folder and uploads the file to s3" do + expect(File).to receive(:read) { image_text } + expect(mock_uploader).to receive(:find_or_create_content).with(full_path: "/images/img.jpeg") { "http://some-s3-url/" } + + html = '' + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute( + element: doc.children.first, + current_content_file_path: "/foo.md", + asset_uploader: mock_uploader, + root_directory_path: File.join(File.dirname(__FILE__), "tmp", "block-repos", "sample-directory") + )[:result]).to eq(["src=\"images/img.jpeg\"", "src=\"http://some-s3-url/\""]) + end + + it "returns a tuple of strings to be gsub'ed for images with a relative src path within the content file (using dot slash) folder and uploads the file to s3" do + expect(File).to receive(:read) { image_text } + expect(mock_uploader).to receive(:find_or_create_content).with(full_path: "/images/img.jpeg") { "http://some-s3-url/" } + + html = '' + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute( + element: doc.children.first, + current_content_file_path: "/foo.md", + asset_uploader: mock_uploader, + root_directory_path: File.join(File.dirname(__FILE__), "tmp", "block-repos", "sample-directory") + )[:result]).to eq(["src=\"./images/img.jpeg\"", "src=\"http://some-s3-url/\""]) + end + end + + context "when an update to the url is NOT required" do + it "does not change image urls that start with www, http, or https" do + html = '' + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute( + element: doc.children.first, + current_content_file_path: "/foo.md", + asset_uploader: mock_uploader, + root_directory_path: File.join(File.dirname(__FILE__), "tmp", "block-repos", "sample-directory") + )[:result]).to eq([]) + end + + it "returns an empty array if there is no src for the image tag" do + html = '' + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute( + element: doc.children.first, + current_content_file_path: "/foo.md", + asset_uploader: mock_uploader, + root_directory_path: File.join(File.dirname(__FILE__), "tmp", "block-repos", "sample-directory") + )[:result]).to eq([]) + end + + it "raises a BadLink exception when unable to retrieve image" do + html = '' + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + result = + described_class.execute( + element: doc.children.first, + current_content_file_path: "/foo.md", + asset_uploader: mock_uploader, + root_directory_path: File.join(File.dirname(__FILE__), "tmp", "block-repos", "sample-directory") + ) + expect(result[:errors].first).to include("Bad image link /images/img.jpeg") + end + + it "does not change base64 src images" do + html = %() + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute( + element: doc.children.first, + current_content_file_path: "/foo.md", + asset_uploader: mock_uploader, + root_directory_path: File.join(File.dirname(__FILE__), "tmp", "block-repos", "sample-directory") + )[:result]).to eq([]) + end + end + end +end diff --git a/gems/bp/spec/build_link_spec.rb b/gems/bp/spec/build_link_spec.rb new file mode 100644 index 0000000..8954b29 --- /dev/null +++ b/gems/bp/spec/build_link_spec.rb @@ -0,0 +1,173 @@ +require "spec_helper" + +describe BlockParser::BuildLink do + describe ".execute" do + before { allow(ENV).to receive(:[]) { nil } } + + it "returns an empty array if there is no href" do + html = "
    foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect( + described_class.execute(link: doc.children.first, current_content_file_path: "", content_file_paths: [], root_directory_path: "") + ).to eq([]) + end + + it "returns a content link if the link is an external http with content_link/gSchool" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect( + described_class.execute(link: doc.children.first, current_content_file_path: "", content_file_paths: [], root_directory_path: "") + ).to eq(["href=\"http://localhost:3003/content_link/gSchool/blocks-test/simple-challenge.md\"", "href=\"http://localhost:3003/content_link/gSchool/blocks-test/simple-challenge.md\""]) + end + + it "returns an external link if the link is an external http link" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect( + described_class.execute(link: doc.children.first, current_content_file_path: "", content_file_paths: [], root_directory_path: "") + ).to eq(["href=\"http://google.com\"", "href=\"http://google.com\" class=\"external-link\" target=\"_blank\""]) + end + + it "returns an external link if the link is an external https link" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect( + described_class.execute(link: doc.children.first, current_content_file_path: "", content_file_paths: [], root_directory_path: "") + ).to eq(["href=\"https://google.com\"", "href=\"https://google.com\" class=\"external-link\" target=\"_blank\""]) + end + + context "resource link" do + let(:current_content_file_path) { "/tmp/repo/foo/bar/baz/resource.md" } + let(:content_file_paths) { ["/tmp/repo/foo/bar/baz/resource.md", "/tmp/repo/bar.md"] } + + context "handles no leading slash" do + it "returns an external link if the link is a resource link" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + expect( + described_class.execute(link: doc.children.first, current_content_file_path: current_content_file_path, content_file_paths: content_file_paths, root_directory_path: "", resource_paths: ["resource.md"]) + ).to eq(["href=\"resource.md\"", "href=\"resource.md\" class=\"external-link\" target=\"_blank\""]) + end + end + + context "handles a leading slash" do + it "returns an external link if the link is a resource link" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + expect( + described_class.execute(link: doc.children.first, current_content_file_path: current_content_file_path, content_file_paths: content_file_paths, root_directory_path: "/tmp/repo/foo/bar/baz/", resource_paths: ["/resource.md"]) + ).to eq(["href=\"/resource.md\"", "href=\"resource.md\" class=\"external-link\" target=\"_blank\""]) + end + end + end + + it "returns an empty array if the link is to an email" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect( + described_class.execute(link: doc.children.first, current_content_file_path: "", content_file_paths: [], root_directory_path: "") + ).to eq([]) + end + + it "handles absolute links within a subdirectory" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + current_content_file_path = "/tmp/repo/foo/bar/baz/foo.md" + content_file_paths = ["/tmp/repo/foo/bar/baz/foo.md", "/tmp/repo/bar.md"] + + expect( + described_class.execute( + link: doc.children.first, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + root_directory_path: "/tmp/repo/" + ) + ).to eq(["href=\"/bar.md\"", "href=\"../../../bar.md\""]) + end + + it "handles invalid absolute links within a subdirectory" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + current_content_file_path = "/tmp/repo/foo/bar/baz/foo.md" + content_file_paths = ["/tmp/repo/foo/bar/baz/foo.md", "/tmp/repo/bar.md"] + + expect do + described_class.execute( + link: doc.children.first, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + root_directory_path: "/tmp/repo/" + ) + end.to raise_error(described_class::BadInternalLink) + end + + it "handles relative links within a subdirectory" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + current_content_file_path = "/foo/bar.md" + content_file_paths = ["/foo/foo.md", "/foo/bar.md"] + + expect( + described_class.execute( + link: doc.children.first, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + root_directory_path: "" + ) + ).to eq([]) + end + + it "handles relative link within a subdirectory with an anchor tag" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + current_content_file_path = "/foo/bar.md" + content_file_paths = ["/foo/foo.md", "/foo/bar.md"] + + expect( + described_class.execute( + link: doc.children.first, + current_content_file_path: current_content_file_path, + content_file_paths: content_file_paths, + root_directory_path: "" + ) + ).to eq([]) + end + + it "raises an error when the linked file does not exist" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + current_content_file_path = "/foo/bar.md" + content_file_paths = ["/foo/bar.md"] + + expect do + described_class.execute(link: doc.children.first, current_content_file_path: current_content_file_path, content_file_paths: content_file_paths, root_directory_path: "") + end.to raise_error(described_class::BadInternalLink) + end + + it "returns empty array if href is empty" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute(link: doc.children.first, current_content_file_path: "", content_file_paths: [], root_directory_path: "")).to eq([]) + end + + it "returns empty array href starts with a #" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute(link: doc.children.first, current_content_file_path: "", content_file_paths: [], root_directory_path: "")).to eq([]) + end + + it "returns empty array if href is a #" do + html = "foo" + doc = Nokogiri::HTML::DocumentFragment.parse(html) + + expect(described_class.execute(link: doc.children.first, current_content_file_path: "", content_file_paths: [], root_directory_path: "")).to eq([]) + end + end +end diff --git a/gems/bp/spec/build_title_from_filename_and_html_spec.rb b/gems/bp/spec/build_title_from_filename_and_html_spec.rb new file mode 100644 index 0000000..55030ad --- /dev/null +++ b/gems/bp/spec/build_title_from_filename_and_html_spec.rb @@ -0,0 +1,53 @@ +require "spec_helper" + +describe BuildTitleFromFilenameAndHtml do + context "when html does not start with an h1" do + it "removes the number and dash at the beginning of the filename" do + expect(described_class.execute(filename: "123 - Some Title.md", html: "")).to eq("Some Title") + end + + it "replaces dashes and underscores with spaces" do + expect(described_class.execute(filename: "Some_Title-Here.md", html: "")).to eq("Some Title Here") + end + + it "removes a trailing .md from the title" do + expect(described_class.execute(filename: "File.md", html: "")).to eq("File") + end + + it "titleizes the title" do + expect(described_class.execute(filename: "some title.md", html: "")).to eq("Some Title") + end + + it "url decodes the title" do + expect(described_class.execute(filename: "some%20title.md", html: "")).to eq("Some Title") + end + end + + context "when it includes a keyword (instructor, checkpoint, resource)" do + context "when its the first word" do + it "keeps the keyword in place" do + expect(described_class.execute(filename: "checkpoint.md", html: "")).to eq("Checkpoint") + expect(described_class.execute(filename: "instructor.md", html: "")).to eq("Instructor") + expect(described_class.execute(filename: "resource.md", html: "")).to eq("Resource") + expect(described_class.execute(filename: "hidden.md", html: "")).to eq("Hidden") + end + end + + context "when its not the first word" do + it "scrubs the keyword out of the title" do + expect(described_class.execute(filename: "some-checkpoint.md", html: "")).to eq("Some Checkpoint") + expect(described_class.execute(filename: "some-instructor.checkpoint.md", html: "")).to eq("Some Instructor") + expect(described_class.execute(filename: "some.instructor.checkpoint.md", html: "")).to eq("Some") + expect(described_class.execute(filename: "some.hidden.checkpoint.md", html: "")).to eq("Some") + expect(described_class.execute(filename: "node-checkpoint.md", html: "")).to eq("Node Checkpoint") + expect(described_class.execute(filename: "node-checkpoint.instructor.md", html: "")).to eq("Node Checkpoint") + end + end + end + + context "when the first line of the rendered text contains an h1 tag" do + it "returns the unescaped version of the h1 on the first line" do + expect(described_class.execute(filename: "some%20title.md", html: "

    "some text"

    ")).to eq('"some text"') + end + end +end diff --git a/gems/bp/spec/challenge_validators/challenge_spec.rb b/gems/bp/spec/challenge_validators/challenge_spec.rb new file mode 100644 index 0000000..7bcf0df --- /dev/null +++ b/gems/bp/spec/challenge_validators/challenge_spec.rb @@ -0,0 +1,288 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::Challenge do + describe "#to_hash" do + it "returns a hash representing the attributes of the challenge" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.id = "123" + challenge.type = BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project] + challenge.title = "exercise1" + challenge.placeholder = "Enter GH Url" + challenge.answer = "www.github.com/some/url" + challenge.question = "this is the question?" + challenge.explanation = "this is the solution" + challenge.standard_uuids = ["foo123", "bar123"] + challenge.rubric = "this is the rubric" + challenge.upstream_repo_path = "www.github.com/some/url" + challenge.hints = ["I am a hint1", "I am a hint2"] + challenge.setup = "I am the setup, most likely some stubs to run before tests" + challenge.validate_fork = "true" + challenge.topics = ["cool", "beans", "dude"] + + expect(challenge.to_hash).to eq( + id: "123", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project], + title: "exercise1", + placeholder: "Enter GH Url", + standard_uuids: ["foo123", "bar123"], + hints: ["I am a hint1", "I am a hint2"], + setup: "I am the setup, most likely some stubs to run before tests", + topics: ["cool", "beans", "dude"], + partial_credit: false, + question: { + content: [ + { + type: "markdown", + value: "this is the question?" + } + ] + }, + upstream_repo_path: "www.github.com/some/url", + explanation: { + content: [ + { + type: "markdown", + value: "this is the solution" + } + ] + }, + rubric: { + content: [ + { + type: "markdown", + value: "this is the rubric" + } + ] + }, + validate_fork: true + ) + end + + it "returns a hash containing decimal and answer for number challenges" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.id = "123" + challenge.type = BlockParser::ChallengeValidators::Challenge::TYPES[:number] + challenge.title = "exercise1" + challenge.question = "this is the question?" + challenge.decimal = "2" + challenge.answer = "3" + + expect(challenge.to_hash).to eq( + id: "123", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:number], + title: "exercise1", + placeholder: nil, + answer: "3", + decimal: "2", + setup: nil, + partial_credit: false, + hints: [], + question: { + content: [ + { + type: "markdown", + value: "this is the question?" + } + ] + } + ) + end + + it "returns a hash containing decimal and answer for number challenges" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.id = "123" + challenge.type = BlockParser::ChallengeValidators::Challenge::TYPES[:short_answer] + challenge.title = "Logan, so amaze!" + challenge.question = "this is the question?" + challenge.answer = "3" + + expect(challenge.to_hash).to eq( + id: "123", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:short_answer], + title: "Logan, so amaze!", + hints: [], + placeholder: nil, + partial_credit: false, + answer: "3", + setup: nil, + question: { + content: [ + { + type: "markdown", + value: "this is the question?" + } + ] + } + ) + end + + it "returns a hash containing docker file path for a custom snippet" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.id = "123" + challenge.type = BlockParser::ChallengeValidators::Challenge::TYPES[:custom_snippet] + challenge.title = "Logan, so amaze!" + challenge.question = "a custom snippet question" + challenge.docker_directory_path = "/root/some/path/" + challenge.language = "Java" + + expect(challenge.to_hash).to eq( + id: "123", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:custom_snippet], + title: "Logan, so amaze!", + placeholder: nil, + hints: [], + setup: nil, + partial_credit: false, + language: "Java", + question: { + content: [ + { + type: "markdown", + value: "a custom snippet question" + } + ] + }, + docker_directory_path: "/root/some/path/" + ) + end + + it "does not set a question hash if question is nil" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.id = "123" + challenge.type = BlockParser::ChallengeValidators::Challenge::TYPES[:project] + challenge.title = "exercise1" + challenge.placeholder = "Enter GH Url" + challenge.question = nil + challenge.explanation = "this is the solution" + expect(challenge.to_hash).to eq( + id: "123", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:project], + title: "exercise1", + placeholder: "Enter GH Url", + setup: nil, + partial_credit: false, + hints: [], + explanation: { + content: [ + { + type: "markdown", + value: "this is the solution" + } + ] + } + ) + end + + it "does not set an explanation hash if the explanation is nil" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.id = "123" + challenge.type = BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project] + challenge.title = "exercise1" + challenge.placeholder = "Enter GH Url" + challenge.question = "this is the question?" + challenge.answer = "www.githerb.com/some/url" + challenge.explanation = nil + challenge.upstream_repo_path = "www.githerb.com/some/url" + + expect(challenge.to_hash).to eq( + id: "123", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project], + title: "exercise1", + hints: [], + placeholder: "Enter GH Url", + setup: nil, + partial_credit: false, + question: { + content: [ + { + type: "markdown", + value: "this is the question?" + } + ] + }, + upstream_repo_path: "www.githerb.com/some/url" + ) + end + + it "does not set validate_fork if the validate_fork is nil" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.id = "123" + challenge.type = BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project] + challenge.title = "exercise1" + challenge.placeholder = "Enter GH Url" + challenge.question = "this is the question?" + challenge.answer = "www.githerb.com/some/url" + challenge.explanation = nil + challenge.upstream_repo_path = "www.githerb.com/some/url" + challenge.validate_fork = nil + + expect(challenge.to_hash).to eq( + id: "123", + type: BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project], + title: "exercise1", + hints: [], + placeholder: "Enter GH Url", + setup: nil, + partial_credit: false, + question: { + content: [ + { + type: "markdown", + value: "this is the question?" + } + ] + }, + upstream_repo_path: "www.githerb.com/some/url" + ) + end + + it "coverts options into an array of strings" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.options = "\n * option 1\n- option 2\n" + + expect(challenge.to_hash[:options]).to eq(["option 1", "option 2"]) + end + + it "coverts options without a beginning newline into an array of strings" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.options = "- Nothing - it would work perfectly 1\n- ``\n" + + expect(challenge.to_hash[:options]).to eq(["Nothing - it would work perfectly 1", "``"]) + end + + it "coverts multiline options correctly" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.options = "\n* La Chaiffre\n*\n a bunch of text\n\n split across\n\n multiple lines\n" + + expect(challenge.to_hash[:options]).to eq(["La Chaiffre", "a bunch of text\n\nsplit across\n\nmultiple lines"]) + end + + it "coverts multiline options with backticks correctly" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.options = "\n* La Chaiffre\n*\n ```\na bunch of text\n\n split across\n\n multiple lines\n ```\n" + + expect(challenge.to_hash[:options]).to eq(["La Chaiffre", "```\na bunch of text\n\n split across\n\n multiple lines\n ```"]) + end + + it "coverts multiline options with math symbols that could confuse our line detection correctly" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.options = "\n* La Chaiffre\n*\n ```\na bunch of * text\n\n split - across\n\n multiple lines\n ```\n" + + expect(challenge.to_hash[:options]).to eq(["La Chaiffre", "```\na bunch of * text\n\n split - across\n\n multiple lines\n ```"]) + end + + it "coverts multiline options with backticks and a language correctly" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.options = "\n* La Chaiffre\n*\n ```java\na bunch of text\n\n split across\n\n multiple lines\n ```\n" + + expect(challenge.to_hash[:options]).to eq(["La Chaiffre", "```java\na bunch of text\n\n split across\n\n multiple lines\n ```"]) + end + + it "removes excessive indentation from multiline options" do + challenge = described_class.new(BlockParser::ChallengeValidators::Challenge::TYPES) + challenge.options = "\n* La Chaiffre\n*\n ```java\n a bunch of text\n\n split across\n\n multiple lines\n ```\n" + + expect(challenge.to_hash[:options]).to eq(["La Chaiffre", "```java\na bunch of text\n\nsplit across\n\n multiple lines\n```"]) + end + end +end diff --git a/gems/bp/spec/challenge_validators/challenge_validator_spec.rb b/gems/bp/spec/challenge_validators/challenge_validator_spec.rb new file mode 100644 index 0000000..dd35420 --- /dev/null +++ b/gems/bp/spec/challenge_validators/challenge_validator_spec.rb @@ -0,0 +1,116 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::ChallengeValidator do + describe "validations" do + let(:valid_attributes) do + { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:paragraph], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "topics": ["first", "second"], + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released.\n" + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + } + } + end + + it "can be valid" do + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(true) + end + + it "requires an id" do + valid_attributes.delete(:id) + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:id]).to eq(["can't be blank"]) + end + + it "requires a type" do + valid_attributes.delete(:type) + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:type]).to eq(["can't be blank"]) + end + + it "requires a title" do + valid_attributes.delete(:title) + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:title]).to eq(["can't be blank"]) + end + + it "requires a question" do + valid_attributes.delete(:question) + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:question]).to eq(["can't be blank"]) + + valid_attributes[:question] = { + content: [ + { + "type": "markdown", + "value": " " + } + ] + } + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:question]).to eq(["can't be blank"]) + end + + it "does not allow points over 10" do + valid_attributes[:points] = 11 + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:points]).to eq(["can't be worth more than 10"]) + + valid_attributes[:points] = 10 + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(true) + end + + it "does not allow topics to be over 20 characters" do + valid_attributes[:topics] = ["fivefivefivefivefiveWHAT", "one"] + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:topics]).to eq(["can't have a length of more than 20 characters"]) + + valid_attributes[:topics] = ["fivefivefivefivefive", "one"] + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(true) + end + end +end diff --git a/gems/bp/spec/challenge_validators/checkbox_challenge_validator_spec.rb b/gems/bp/spec/challenge_validators/checkbox_challenge_validator_spec.rb new file mode 100644 index 0000000..2f40e86 --- /dev/null +++ b/gems/bp/spec/challenge_validators/checkbox_challenge_validator_spec.rb @@ -0,0 +1,118 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::CheckboxChallengeValidator do + describe "validations" do + let(:checkbox_json) do + { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:checkbox], + "title": "People Skills", + "answer": ["Peter Grunde", "Bradford Hamilton"], + "options": [ + "Peter Grunde", + "Bradford Hamilton", + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWho is the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause,duh.\n" + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + } + } + end + + subject { described_class.new(checkbox_json) } + + it "can be valid" do + subject.valid? + expect(subject.valid?).to eq(true) + end + + it "requires an answer" do + checkbox_json.delete(:answer) + + expect(subject.valid?).to eq(false) + expect(subject.errors[:answer]).to eq(["can't be blank", "must have at least one choice"]) + end + + context "options" do + it "cannot be blank" do + checkbox_json.delete(:options) + + expect(subject.valid?).to eq(false) + expect(subject.errors[:options]).to eq(["can't be blank"]) + end + + it "must have at least 2 options" do + checkbox_json[:options] = ["Hello"] + + expect(subject.valid?).to eq(false) + expect(subject.errors[:options]).to eq(["must have at least two choices"]) + end + + it "must have all answers that are listed in the options array" do + checkbox_json[:answer] = ["Peter Grunde", "No such answer"] + + expect(subject.valid?).to eq(false) + expect(subject.errors[:answer]).to eq(["set must exist in the options"]) + end + + context "when there are '\\r' return chars in the options" do + it "should still pass validations" do + checkbox_json[:options].push("Kool Aid Man\r") + checkbox_json[:answer] = ["Kool Aid Man"] + + expect(subject.valid?).to eq(true) + end + end + + context "when there are '\\r' return chars in the answers" do + it "should still pass validations" do + checkbox_json[:options].push("Kool Aid Man") + checkbox_json[:answer] = ["Kool Aid Man\r"] + + expect(subject.valid?).to eq(true) + end + end + + context "when there is a multiline answer" do + it "should still pass validations" do + checkbox_json[:options].push("```java\na bunch of text\n\n split across\n\n multiple lines\n ```") + checkbox_json[:answer] = ["```java\na bunch of text\n\n split across\n\n multiple lines\n ```"] + + expect(subject.valid?).to eq(true) + end + end + + context "when the answer is an 'any' answer" do + it "should still pass validations" do + checkbox_json[:answer] = ["*"] + + expect(subject.valid?).to eq(true) + end + end + end + end +end diff --git a/gems/bp/spec/challenge_validators/code_snippet_challenge_validator_spec.rb b/gems/bp/spec/challenge_validators/code_snippet_challenge_validator_spec.rb new file mode 100644 index 0000000..15a5342 --- /dev/null +++ b/gems/bp/spec/challenge_validators/code_snippet_challenge_validator_spec.rb @@ -0,0 +1,131 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::CodeSnippetChallengeValidator do + describe "validations" do + let(:code_snippet_json) do + { + "id": "123", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + "title": "repeats", + "placeholder": "// Write your function in Javascript below", + "question": { + content: [ + { + "type": "markdown", + "value": "Write a function named repeats that returns true if the first half of the string equals the last half, and false if not." + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "You might have solved this with a while loop, but using recursion is another way to do this." + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + }, + "language": "javascript", + "show_tests": true, + "tests": %{describe('repeats', function() { + it("returns true when given an empty string (which seems strange, but go with it :)", function() { + expect(repeats(""), "Default value is incorrect").to.deep.eq(true) + }) +}) +} + } + end + + it "can be valid" do + challenge_validator = described_class.new(code_snippet_json) + + expect(challenge_validator.valid?).to eq(true) + end + + it "requires that language is one of the approved types" do + code_snippet_json.delete(:language) + challenge_validator = described_class.new(code_snippet_json) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:language]).to eq(["can't be blank", "language must be java, javascript, python2.7, or python3.6, or sql"]) + + code_snippet_json[:language] = "Lua" + challenge_validator = described_class.new(code_snippet_json) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:language]).to eq(["language must be java, javascript, python2.7, or python3.6, or sql"]) + end + + it "requires tests" do + code_snippet_json.delete(:tests) + challenge_validator = described_class.new(code_snippet_json) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:tests]).to eq(["can't be blank"]) + end + + context "when the code snippet is of sql type" do + let(:sql_code_snippet_json) do + { + "id": "123", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + "title": "repeats", + "placeholder": "// Write your function in Javascript below", + "question": { + content: [ + { + "type": "markdown", + "value": "Write a function named repeats that returns true if the first half of the string equals the last half, and false if not." + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "You might have solved this with a while loop, but using recursion is another way to do this." + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + }, + "language": "sql", + "show_tests": true, + "tests": %{describe('repeats', function() { + it("returns true when given an empty string (which seems strange, but go with it :)", function() { + expect(repeats(""), "Default value is incorrect").to.deep.eq(true) + }) + }) + } + } + end + + it "should require the data path to be present" do + challenge_validator = described_class.new(sql_code_snippet_json) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:data_path]).to eq(["can't be blank"]) + end + + it "passes validation with data_path present" do + sql_code_snippet_json[:data_path] = "path" + challenge_validator = described_class.new(sql_code_snippet_json) + + expect(challenge_validator.valid?).to eq(true) + end + end + end +end diff --git a/gems/bp/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb b/gems/bp/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb new file mode 100644 index 0000000..93460f5 --- /dev/null +++ b/gems/bp/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb @@ -0,0 +1,69 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::CustomSnippetChallengeValidator do + describe "validations" do + let(:custom_snippet_json) do + { + "id": "123", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:custom_snippet], + "title": "derp", + "placeholder": "// Write your function in Java below", + "question": { + content: [ + { + "type": "markdown", + "value": "Write a function named repeats that returns true if the first half of the string equals the last half, and false if not." + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "You might have solved this with a while loop, but using recursion is another way to do this." + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + }, + "language": "java", + "docker_directory_path": "/fixtures/valid-custom-snippet-challenge-directory" + } + end + subject { described_class.new(custom_snippet_json.merge(root_directory_path: File.dirname(__dir__))) } + + it "can be valid" do + expect(subject.valid?).to be true + end + + it "validates that language must be present" do + custom_snippet_json.delete(:language) + expect(subject.valid?).to be false + expect(subject.errors[:language]).to eq(["can't be blank"]) + end + + it "validates that docker_directory_path must be present" do + custom_snippet_json.delete(:docker_directory_path) + expect(subject.valid?).to be false + expect(subject.errors[:docker_directory_path]).to eq(["can't be blank"]) + end + + it "validates that docker_directory_path points to an existing directory" do + custom_snippet_json[:docker_directory_path] = "foo_directory" + expect(subject.valid?).to be false + expect(subject.errors[:docker_directory_path]).to eq(["points to a directory that doesn't exist"]) + end + + it "validates that docker_directory_path contains both a Dockerfile and a test.sh" do + custom_snippet_json[:docker_directory_path] = "/fixtures/no-standards-config-test-block-repo" + expect(subject.valid?).to be false + expect(subject.errors[:docker_directory_path]).to eq(["is missing required file 'Dockerfile'", "is missing required file 'test.sh'"]) + end + end +end diff --git a/gems/bp/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb b/gems/bp/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb new file mode 100644 index 0000000..df88362 --- /dev/null +++ b/gems/bp/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb @@ -0,0 +1,107 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::MultipleChoiceChallengeValidator do + describe "validations" do + let(:multiple_choice_json) do + { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:multiple_choice], + "title": "People Skills", + "answer": "Peter Grunde", + "options": [ + "Peter Grunde", + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWho is the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause,duh.\n" + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + } + } + end + + subject { described_class.new(multiple_choice_json) } + + it "can be valid" do + expect(subject.valid?).to eq(true) + end + + it "requires an answer" do + multiple_choice_json.delete(:answer) + + expect(subject.valid?).to eq(false) + expect(subject.errors[:answer]).to eq(["can't be blank", "must exist in the options"]) + end + + context "options" do + it "cannot be blank" do + multiple_choice_json.delete(:options) + + expect(subject.valid?).to eq(false) + expect(subject.errors[:options]).to eq(["can't be blank"]) + end + + it "must have at least 2 options" do + multiple_choice_json[:options] = ["Hello"] + + expect(subject.valid?).to eq(false) + expect(subject.errors[:options]).to eq(["must have at least two choices"]) + end + + it "must have an answer that is listed in the options array" do + multiple_choice_json[:answer] = "No such answer" + + expect(subject.valid?).to eq(false) + expect(subject.errors[:answer]).to eq(["must exist in the options"]) + end + + context "when there are '\\r' return chars in the options" do + it "should still pass validations" do + multiple_choice_json[:options].push("Kool Aid Man\r") + multiple_choice_json[:answer] = "Kool Aid Man" + + expect(subject.valid?).to eq(true) + end + end + + context "when there is a multiline answer" do + it "should still pass validations" do + multiple_choice_json[:options].push("```java\na bunch of text\n\n split across\n\n multiple lines\n ```") + multiple_choice_json[:answer] = "```java\na bunch of text\n\n split across\n\n multiple lines\n ```" + + expect(subject.valid?).to eq(true) + end + end + + context "when the answer is an 'any' answer" do + it "should still pass validations" do + multiple_choice_json[:answer] = "*" + + expect(subject.valid?).to eq(true) + end + end + end + end +end diff --git a/gems/bp/spec/challenge_validators/number_challenge_validator_spec.rb b/gems/bp/spec/challenge_validators/number_challenge_validator_spec.rb new file mode 100644 index 0000000..65a9f33 --- /dev/null +++ b/gems/bp/spec/challenge_validators/number_challenge_validator_spec.rb @@ -0,0 +1,72 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::NumberChallengeValidator do + describe "validations" do + let(:valid_attributes) do + { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:number], + "title": "sum", + "placeholder": "enter answer", + "answer": "3", + "decimal": "2", + "question": { + content: [ + { + "type": "markdown", + "value": "1+2" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "it's the sum" + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + } + } + end + + it "can be valid" do + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(true) + end + + it "requires an answer" do + valid_attributes.delete(:answer) + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:answer]).to eq(["can't be blank"]) + end + + it "require decimal is a number if defined" do + valid_attributes.delete(:decimal) + + challenge_validator = described_class.new(valid_attributes) + expect(challenge_validator.valid?).to eq(true) + + valid_attributes[:decimal] = "" + + challenge_validator = described_class.new(valid_attributes) + expect(challenge_validator.valid?).to eq(true) + + valid_attributes[:decimal] = "asdf" + + challenge_validator = described_class.new(valid_attributes) + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:decimal]).to eq(["is not a number"]) + end + end +end diff --git a/gems/bp/spec/challenge_validators/project_challenge_validator_spec.rb b/gems/bp/spec/challenge_validators/project_challenge_validator_spec.rb new file mode 100644 index 0000000..52b65ff --- /dev/null +++ b/gems/bp/spec/challenge_validators/project_challenge_validator_spec.rb @@ -0,0 +1,56 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::ProjectChallengeValidator do + describe "validations" do + let(:challenge_validator) { described_class.new(valid_attributes) } + let(:valid_attributes) do + { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project], + "title": "js-native-array-methods", + "placeholder": "https://github.com/js-native-array-methods", + "question": { + content: [ + { + "type": "markdown", + "value": "This is a project challenge?" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "Wait for your project to be graded!" + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + }, + "upstream_repo_path": upstream_repo_path + } + end + + context "when upstream_repo_path is present" do + let(:upstream_repo_path) { "https://github.com/gSchool/js-native-array-methods" } + + it "should not be valid" do + expect(challenge_validator.valid?).to eq(false) + end + end + + context "when upstream_repo_path is not present" do + let(:upstream_repo_path) { nil } + + it "should be valid" do + expect(challenge_validator.valid?).to eq(true) + end + end + end +end diff --git a/gems/bp/spec/challenge_validators/short_answer_challenge_validator_spec.rb b/gems/bp/spec/challenge_validators/short_answer_challenge_validator_spec.rb new file mode 100644 index 0000000..9363377 --- /dev/null +++ b/gems/bp/spec/challenge_validators/short_answer_challenge_validator_spec.rb @@ -0,0 +1,53 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::ShortAnswerChallengeValidator do + describe "validations" do + let(:valid_attributes) do + { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:short_answer], + "title": "sum", + "placeholder": "enter answer", + "answer": "much wood", + "question": { + content: [ + { + "type": "markdown", + "value": "how much wood would a woodchuck purchase?" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "Woody Harilyson" + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + } + } + end + + it "can be valid" do + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(true) + end + + it "requires an answer" do + valid_attributes.delete(:answer) + challenge_validator = described_class.new(valid_attributes) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:answer]).to eq(["can't be blank"]) + end + end +end diff --git a/gems/bp/spec/challenge_validators/testable_project_challenge_validator_spec.rb b/gems/bp/spec/challenge_validators/testable_project_challenge_validator_spec.rb new file mode 100644 index 0000000..231b9fb --- /dev/null +++ b/gems/bp/spec/challenge_validators/testable_project_challenge_validator_spec.rb @@ -0,0 +1,296 @@ +require "spec_helper" + +describe BlockParser::ChallengeValidators::TestableProjectChallengeValidator do + before { allow(ENV).to receive(:[]) { "foo" } } + describe "validations" do + let(:challenge_validator) { described_class.new(valid_attributes) } + let(:valid_attributes) do + { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project], + "title": "js-native-array-methods", + "placeholder": "https://github.com/js-native-array-methods", + "question": { + content: [ + { + "type": "markdown", + "value": "This is a project challenge?" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "Wait for your project to be graded!" + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nI AM RUBRIC MAN\n" + } + ] + }, + "upstream_repo_path": upstream_repo_path + } + end + let(:upstream_repo_path) { "https://github.com/gSchool/js-native-array-methods" } + let(:repo_name) { "gSchool/js-native-array-methods" } + + context "when upstream_repo_path is gitlab" do + let(:upstream_repo_path) { "https://gitlab.com/gSchool/js-native-array-methods" } + + it "yields no errors when it contains the required files" do + allow_any_instance_of(Gitlab::Client).to receive(:tree).and_return( + [OpenStruct.new(name: "Dockerfile"), OpenStruct.new(name: "test.sh")] + ) + expect(challenge_validator.valid?).to eq(true) + end + it "should fail validation when a file is not present" do + allow_any_instance_of(Gitlab::Client).to receive(:tree).and_return( + [OpenStruct.new(name: "test.sh")] + ) + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing required file - 'Dockerfile'"]) + end + end + + context "when upstream_repo_path is not present" do + let(:upstream_repo_path) { nil } + + it "should not be valid" do + expect(challenge_validator.valid?).to eq(false) + end + end + + context "when upstream_repo_path is not a github url or gitlab url" do + let!(:upstream_repo_path) { "http://www.google.com" } + + it "should be invalid" do + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq(["must be from Github.com or Gitlab.com"]) + end + end + + context "when repo is on master" do + let(:branch) { "master" } + + context "and upstream repo exists on github" do + it "should be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).and_return(true) + expect(challenge_validator.valid?).to eq(true) + end + end + + context "and repo is not found" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(false) + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo couldn't be found. Verify the repo/branch exists."]) + end + end + + context "and upstream repo is missing Dockerfile" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "Dockerfile", ref: branch).and_raise(Octokit::NotFound) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "test.sh", ref: branch).and_return(true) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing required file - 'Dockerfile'"]) + end + end + + context "upstream repo is missing test.sh" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "Dockerfile", ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "test.sh", ref: branch).and_raise(Octokit::NotFound) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing required file - 'test.sh'"]) + end + end + + context "upstream repo is missing both Dockerfile and test.sh" do + it "should not be valid and return both errors" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "Dockerfile", ref: branch).and_raise(Octokit::NotFound) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "test.sh", ref: branch).and_raise(Octokit::NotFound) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([ + ": upstream test repo is missing required file - 'Dockerfile'", + ": upstream test repo is missing required file - 'test.sh'" + ]) + end + end + + context "and pointing to a subfolder" do + let(:upstream_repo_path) { "https://github.com/gSchool/js-native-array-methods/tree/master/sub-folder" } + let(:path_to_folder) { "sub-folder" } + let(:branch) { "master" } + + context "upstream repo is valid" do + it "should be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: path_to_folder, ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/Dockerfile", ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/test.sh", ref: branch).and_return(true) + + expect(challenge_validator.valid?).to eq(true) + end + end + + context "repo is not found" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(false) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo couldn't be found. Verify the repo/branch exists."]) + end + end + + context "sub folder is not found" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: path_to_folder, ref: branch).and_raise(Octokit::NotFound) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing subfolder(s) '#{path_to_folder}'"]) + end + end + + context "upstream repo is missing Dockerfile" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: path_to_folder, ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/Dockerfile", ref: branch).and_raise(Octokit::NotFound) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/test.sh", ref: branch).and_return(true) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing required file - '#{path_to_folder}/Dockerfile'"]) + end + end + + context "upstream repo is missing test.sh" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository?).with(repo_name).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: path_to_folder, ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/Dockerfile", ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/test.sh", ref: branch).and_raise(Octokit::NotFound) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing required file - '#{path_to_folder}/test.sh'"]) + end + end + end + end + + context "repo is on a branch" do + let(:upstream_repo_path) { "https://github.com/gSchool/js-native-array-methods/tree/sub-branch" } + let(:branch) { "sub-branch" } + + context "upstream repo exists on github" do + it "should be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with(repo_name, branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).and_return(true) + expect(challenge_validator.valid?).to eq(true) + end + end + + context "repo is not found" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with(repo_name, branch).and_raise(Octokit::NotFound) + expect(challenge_validator.valid?).to eq(false) + end + end + + context "upstream repo is missing Dockerfile" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with(repo_name, branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "Dockerfile", ref: branch).and_raise(Octokit::NotFound) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "test.sh", ref: branch).and_return(true) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing required file - 'Dockerfile'"]) + end + end + + context "upstream repo is missing test.sh" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with(repo_name, branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "Dockerfile", ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "test.sh", ref: branch).and_raise(Octokit::NotFound) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing required file - 'test.sh'"]) + end + end + + context "and pointing to a subfolder" do + let(:upstream_repo_path) { "https://github.com/gSchool/js-native-array-methods/tree/sub-branch/sub-folder" } + let(:path_to_folder) { "sub-folder" } + let(:branch) { "sub-branch" } + + context "upstream repo is valid" do + it "should be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with(repo_name, branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: path_to_folder, ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/Dockerfile", ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/test.sh", ref: branch).and_return(true) + + expect(challenge_validator.valid?).to eq(true) + end + end + + context "repo is not found" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with(repo_name, branch).and_return(false) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo couldn't be found. Verify the repo/branch exists."]) + end + end + + context "sub folder is not found" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with(repo_name, branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: path_to_folder, ref: branch).and_raise(Octokit::NotFound) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing subfolder(s) '#{path_to_folder}'"]) + end + end + + context "upstream repo is missing Dockerfile" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with(repo_name, branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: path_to_folder, ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/Dockerfile", ref: branch).and_raise(Octokit::NotFound) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/test.sh", ref: branch).and_return(true) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing required file - '#{path_to_folder}/Dockerfile'"]) + end + end + + context "upstream repo is missing test.sh" do + it "should not be valid" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with(repo_name, branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: path_to_folder, ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/Dockerfile", ref: branch).and_return(true) + allow_any_instance_of(Mocktokit::Client).to receive(:contents).with(repo_name, path: "#{path_to_folder}/test.sh", ref: branch).and_raise(Octokit::NotFound) + + expect(challenge_validator.valid?).to eq(false) + expect(challenge_validator.errors[:upstream_repo_path]).to eq([": upstream test repo is missing required file - '#{path_to_folder}/test.sh'"]) + end + end + end + end + end +end diff --git a/gems/bp/spec/convert_md_to_json_spec.rb b/gems/bp/spec/convert_md_to_json_spec.rb new file mode 100644 index 0000000..c799953 --- /dev/null +++ b/gems/bp/spec/convert_md_to_json_spec.rb @@ -0,0 +1,1702 @@ +require "spec_helper" + +describe BlockParser::ConvertMdToJson do + describe ".convert" do + it "converts plain md to a json representation" do + md_input = "## Hello\nhi" + + expected_output = { + "content": [ + { + "type": "markdown", + "value": "## Hello\nhi" + } + ] + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "converts custom snippet challenge markdown into json" do + md_input = %(## A Challenge + +### !cHaLlEnGe + + * type: custom-snippet +* id: 101 +* standard_uuids: foo123 +* title: some custom snippet +* language: java +* points: 55 +* docker_directory_path: /some/docker/path +* external_id: 2 +* external_source: 1 + +#### !question + +What is the code that will answer this question? + +#### !end-question + +#### !rubric + +What a neat rubric this is. + +#### !end-rubric + +#### !placeholder + +Write your code here + +#### !end-placeholder + +#### !explanation + +Thanks for submitting! Your tests are evaluating. + +#### !end-explanation + +### !eNd-ChAlLeNgE +) + + expected_output = { + "content": [ + { + "type": "markdown", + "value": "## A Challenge\n" + }, + { + "type": "challenge", + "id": "101" + } + ], + "challenges": { + "101": { + "id": "101", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:custom_snippet], + "title": "some custom snippet", + "points": 55, + "placeholder": "Write your code here", + "standard_uuids": ["foo123"], + "language": "java", + "partial_credit": false, + "hints": [], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWhat is the code that will answer this question?\n" + } + ] + }, + "docker_directory_path": "/some/docker/path", + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! Your tests are evaluating.\n" + } + ] + }, + "rubric": { + "content": [ + { + "type": "markdown", + "value": "\nWhat a neat rubric this is.\n" + } + ] + }, + "setup": nil, + "external_id": "2", + "external_source": "1" + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "converts project challenge markdown into json" do + md_input = %{## A Challenge + +### !cHaLlEnGe + + * type: testable-project +* id: 121 +* standard_uuids: foo123, bar456, baz789 +* title: partial derivative +* upstream: www.github.com/hello/world +* validate_fork: true + +#### !question + +Submit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application. + +#### !end-question + +#### !placeholder + +Enter the github URL to your work. + +#### !end-placeholder + +#### !explanation + +Thanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released. + +#### !end-explanation + +### !eNd-ChAlLeNgE +} + + expected_output = { + "content": [ + { + "type": "markdown", + "value": "## A Challenge\n" + }, + { + "type": "challenge", + "id": "121" + } + ], + "challenges": { + "121": { + "id": "121", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:testable_project], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "standard_uuids": ["foo123", "bar456", "baz789"], + "hints": [], + "partial_credit": false, + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "upstream_repo_path": "www.github.com/hello/world", + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nThanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released.\n" + } + ] + }, + "setup": nil, + "validate_fork": true + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "raises an error when multiple stand_uuid lines are present" do + md_input = %{## A Challenge + +### !cHaLlEnGe + + * type: project +* id: 121 +* standard_uuids: foo123, bar456, baz789 +* standard_uuids: foo123, bar456, baz789 +* title: partial derivative + +#### !question + +Submit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application. + +#### !end-question + +#### !placeholder + +Enter the github URL to your work. + +#### !end-placeholder + +#### !explanation + +Thanks for submitting! You will be able to download the [solutions](https://github.com/Galvanize-IT/lms-content-test/tree/master/dsi-example/estimation-sampling/solutions) once they are released. + +#### !end-explanation + +### !eNd-ChAlLeNgE +} + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError) + end + + it "converts number challenge markdown into json" do + md_input = %(### !cHaLlEnGe + +* type: number +* id: 321 +* title: sum numbers +* decimal: 2 + +#### !question + +What is 1 + 2? + +#### !end-question + +#### !placeholder + +Enter your answer here + +#### !end-placeholder + +#### !answer + +3 + +#### !end-answer + +#### !explanation + +Good job at adding numbers! + +#### !end-explanation + +#### !hint + +You `suk` + +#### !end-hint + +### !eNd-ChAlLeNgE +) + + expected_output = { + "content": [ + { + "type": "challenge", + "id": "321" + } + ], + "challenges": { + "321": { + "id": "321", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:number], + "title": "sum numbers", + "decimal": "2", + "placeholder": "Enter your answer here", + "answer": "3", + "partial_credit": false, + "hints": ["You suk"], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWhat is 1 + 2?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nGood job at adding numbers!\n" + } + ] + }, + "setup": nil + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "converts short-answer challenge markdown into json" do + md_input = %(### !challenge +* type: short-answer +* id: 1 +* title: array CFU +* topics: ["\" Cool\"", " 'Beans'", " DUDE ", " dude"] + +##### !question + +What will the following code produce? + +##### !end-question + +##### !placeholder + +Enter your answer here + +##### !end-placeholder + +##### !answer + +Logan is cool + +##### !end-answer + +##### !explanation + +Logan is the coolest! + +##### !end-explanation + +### !end-challenge + +) + + expected_output = { + "content": [ + { + "type": "challenge", + "id": "1" + } + ], + "challenges": { + "1": { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:short_answer], + "title": "array CFU", + "placeholder": "Enter your answer here", + "answer": "Logan is cool", + "hints": [], + partial_credit: false, + "topics": ["cool", "beans", "dude"], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWhat will the following code produce?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nLogan is the coolest!\n" + } + ] + }, + "setup": nil + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "converts local-snippet challenge markdown into json" do + md_input = %(### !challenge +* type: local-snippet +* id: 1 +* title: Grass-fed Snippet + +##### !question + +Fix the bug in the following code. + +##### !end-question + +##### !placeholder + +function returnTrue () { + return false +} + +##### !end-placeholder + +##### !tests + +describe("returnTrue", function() { + it("should return true", function() { + expect(returnTrue()).toEqual(true); + }); +}); + +##### !end-tests + +##### !explanation + +Hard to say. What is true, really? + +##### !end-explanation + +### !end-challenge + +) + + expected_output = { + "content": [ + { + "type": "challenge", + "id": "1" + } + ], + "challenges": { + "1": { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:local_snippet], + "title": "Grass-fed Snippet", + partial_credit: false, + "question": { + content: [ + { + "type": "markdown", + "value": "\nFix the bug in the following code.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nHard to say. What is true, really?\n" + } + ] + }, + "placeholder": "function returnTrue () {\n return false\n}", + "show_tests": false, + "tests": %[\ndescribe("returnTrue", function() {\n it("should return true", function() {\n expect(returnTrue()).toEqual(true);\n });\n});\n], + "setup": nil, + "hints": [] + } + } + } + + # Iterate for easier to read test failures + result = described_class.convert(md_input) + expected_output[:challenges][:"1"].each do |k, v| + expect(v).to eq(result[:challenges][:"1"][k]) + end + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "converts sql code snippet challenge markdown into json" do + md_input = %(# !challenge + +* type: code-snippet +* language: sql +* id: 123 +* title: Absence of a join with Left Outer Join +* data_path: ./sql/food_trucks.sql + +##### !question + +Given a `users` table with a primary key of `id` and a `trucks` table with a foreign key that references the `users.id` column, select all user information from users who do not own trucks, ordered by their last name, `users.last`. + +##### !end-question + +##### !placeholder + +```sql +-- Write your query +``` + +##### !end-placeholder + +##### !tests + +SELECT users.* FROM users LEFT OUTER JOIN trucks ON trucks.owner_id = users.id WHERE owner_id IS NULL ORDER BY users.last; + +##### !end-tests + +#### !explanation + +`LEFT OUTER JOIN` can connect table A to table B, and if a record in table A does not have a matching record in table B, it will still yield a record for it, _however all table B data will contain null values_. A `WHERE` clause that specifies a column can be used with `IS NULL` to only select those columns when they are null. + +#### !end-explanation + +# !end-challenge) + + expected_output = { + content: [ + { + type: "challenge", + id: "123" + } + ], + challenges: { + "123": { + "id": "123", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + "title": "Absence of a join with Left Outer Join", + "data_path": "./sql/food_trucks.sql", + "placeholder": "-- Write your query", + "hints": [], + partial_credit: false, + "question": { + "content": [ + { + "type": "markdown", + "value": "\nGiven a `users` table with a primary key of `id` and a `trucks` table with a foreign key that references the `users.id` column, select all user information from users who do not own trucks, ordered by their last name, `users.last`.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\n`LEFT OUTER JOIN` can connect table A to table B, and if a record in table A does not have a matching record in table B, it will still yield a record for it, _however all table B data will contain null values_. A `WHERE` clause that specifies a column can be used with `IS NULL` to only select those columns when they are null.\n" + } + ] + }, + "language": "sql", + "setup": nil, + "show_tests": false, + "tests": %(\nSELECT users.* FROM users LEFT OUTER JOIN trucks ON trucks.owner_id = users.id WHERE owner_id IS NULL ORDER BY users.last;\n) + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "converts multiple choice challenge markdown into json" do + md_input = %(### !challenge + +* type: multiple-choice +* id: 1 +* title: People Skills + +##### !question + +### People Skills + +Who is the coolest? + +##### !end-question + +##### !options + +* Peter Grunde +* Dr. No +* James Bond +* La Chaiffre +* Jaws + +##### !end-options + +##### !answer + +Peter Grunde + +##### !end-answer + +##### !explanation + +Because, duh. + +##### !end-explanation + +### !end-challenge + +) + + expected_output = { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:multiple_choice], + "title": "People Skills", + "placeholder": nil, + "hints": [], + partial_credit: false, + "question": { + content: [ + { + "type": "markdown", + "value": "\n### People Skills\n\nWho is the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause, duh.\n" + } + ] + }, + "setup": nil, + "options": [ + "Peter Grunde", + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "answer": "Peter Grunde" + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + context "when !setup tag is present" do + let(:md_input) do + %{### !challenge +* type: code-snippet +* language: javascript +* id: 123 +* title: repeats + +### !question +Write a function named repeats that returns true if the first half of the string equals the last half, and false if not. +### !end-question + +### !placeholder +```js +function repeats(input) { +// Write your function in Javascript below +} +``` +### !end-placeholder + +### !setup +```js +(function () { +this.cStub = sinon.stub(console, "log"); +})() +``` +### !end-setup + +### !tests +```js +describe('repeats', function() { + + it("returns true when given an empty string (which seems strange, but go with it :)", function() { + expect(repeats(""), "Default value is incorrect").to.deep.eq(true) + }) + + it("returns true when the second half of the string equals the first", function() { + expect(repeats("bahbah")).to.deep.eq(true) + expect(repeats("nananananananana")).to.deep.eq(true) + }) + + it("returns false when the second half of the string does not equal the first", function() { + expect(repeats("bahba")).to.deep.eq(false) + expect(repeats("nananananann")).to.deep.eq(false) + }) + + it("does not use .repeat", function() { + expect(repeats.toString()).to.not.match(/\.repeat/) + }) + +}) +``` +### !end-tests + +### !explanation +You might have solved this with a while loop, but using recursion is another way to do this. +### !end-explanation + +### !end-challenge +} + end + it "converts javascript code snippet challenges markdown into json" do + expected_output = { + content: [ + { + type: "challenge", + id: "123" + } + ], + challenges: { + "123": { + "id": "123", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + "title": "repeats", + "placeholder": "function repeats(input) {\n// Write your function in Javascript below\n}", + "hints": [], + partial_credit: false, + "question": { + content: [ + { + "type": "markdown", + "value": "Write a function named repeats that returns true if the first half of the string equals the last half, and false if not." + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "You might have solved this with a while loop, but using recursion is another way to do this." + } + ] + }, + "language": "javascript", + "setup": "(function () {\nthis.cStub = sinon.stub(console, \"log\");\n})()\n", + "show_tests": false, + "tests": %{describe('repeats', function() { + + it("returns true when given an empty string (which seems strange, but go with it :)", function() { + expect(repeats(""), "Default value is incorrect").to.deep.eq(true) + }) + + it("returns true when the second half of the string equals the first", function() { + expect(repeats("bahbah")).to.deep.eq(true) + expect(repeats("nananananananana")).to.deep.eq(true) + }) + + it("returns false when the second half of the string does not equal the first", function() { + expect(repeats("bahba")).to.deep.eq(false) + expect(repeats("nananananann")).to.deep.eq(false) + }) + + it("does not use .repeat", function() { + expect(repeats.toString()).to.not.match(/\.repeat/) + }) + +}) +} + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "prevents two setup tags within a single challenge markdown block" do + md_input.gsub!("### !end-challenge\n", "### !setup\n ### !end-challenge\n") + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError) + end + end + + it "converts python code snippet challenges markdown into json" do + md_input = %{### !challenge +* type: code-snippet +* language: python2.7 +* id: 123 +* title: repeats + +### !question +Write a function named repeats that returns true if the first half of the string equals the last half, and false if not. +### !end-question + +### !placeholder +```python +def filter_by_class(X, y, label): + ''' + INPUT: 2 dimensional numpy array, numpy array, object + OUTPUT: 2 dimensional numpy array + + Return the rows from X whose corresponding label from y is the given label. + ''' + pass +``` +### !end-placeholder + +### !tests +```python +def test_filter_by_class1(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "a") + answer = np.array([[1, 2, 3], [7, 8, 9]]) + assert np.array_equal(result, answer) + +def test_filter_by_class2(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "b") + answer = np.array([[10, 11, 12]]) + assert np.array_equal(result, answer) +``` +### !end-tests + +### !explanation +You might have solved this with a while loop, but using recursion is another way to do this. +### !end-explanation + +### !end-challenge +} + + expected_output = { + content: [ + { + type: "challenge", + id: "123" + } + ], + challenges: { + "123": { + "id": "123", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + "title": "repeats", + partial_credit: false, + "placeholder": "def filter_by_class(X, y, label): + ''' + INPUT: 2 dimensional numpy array, numpy array, object + OUTPUT: 2 dimensional numpy array + + Return the rows from X whose corresponding label from y is the given label. + ''' + pass", + "hints": [], + "question": { + content: [ + { + "type": "markdown", + "value": "Write a function named repeats that returns true if the first half of the string equals the last half, and false if not." + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "You might have solved this with a while loop, but using recursion is another way to do this." + } + ] + }, + "language": "python2.7", + "setup": nil, + "show_tests": false, + "tests": %{def test_filter_by_class1(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "a") + answer = np.array([[1, 2, 3], [7, 8, 9]]) + assert np.array_equal(result, answer) + +def test_filter_by_class2(): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "b") + answer = np.array([[10, 11, 12]]) + assert np.array_equal(result, answer) +} + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + context "when md file includes valid callout blocks" do + let(:md_input) do + %(### !callout-danger + # test + test + ### !end-callout + ) + end + + it "returns a callout content object" do + expected_output = { + "content": [ + { + "type": "callout", + "class": "callout-danger", + "value": "# test\ntest" + } + ] + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + end + + context "when a vimeo tag includes valid transcript information" do + let(:md_input) do + %(### !vimeo + * id: 214062173 + * transcript_project: 24099 + * transcript_file: 1795859 + * transcript_plugin: 11378 + ### !end-vimeo + ) + end + + it "returns a vimeo content object with transcript information" do + expected_output = { + "content": [ + { + "type": "vimeo", + "id": "214062173", + "transcript_project": "24099", + "transcript_file": "1795859", + "transcript_plugin": "11378" + } + ] + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + end + + context "when a vimeo includes transcript tags, but not a vimeo ID" do + let(:md_input) do + %(### !vimeo + * transcript_project: 24099 + * transcript_file: 1795859 + * transcript_plugin: 11378 + ### !end-vimeo + ) + end + + it "raises an error" do + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Vimeo tag missing ID") + end + end + + context "when a vimeo tag includes some, but not all of the required transcript tags" do + let(:md_input) do + %(### !vimeo + * id: 214062173 + * transcript_project: 24099 + * transcript_file: 1795859 + ### !end-vimeo + ) + end + + it "raises an error" do + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Vimeo tags with transcripts must include the project, file, and plugin") + end + end + + context "when a vimeo tag does not include transcript information" do + let(:md_input) do + %( + ### !vimeo + * id: 214062173 + ### !end-vimeo + ) + end + + it "returns a vimeo content object without transcript information" do + expected_output = { + "content": [ + { + "type": "vimeo", + "id": "214062173" + } + ] + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + end + + context "when a there are more than 10 vimeo videos" do + let(:md_input) do + %( + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ### !vimeo + * id: 214062173 + ### !end-vimeo + ) + end + + it "raises an error" do + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "There can only be up to 10 vimeo videos in single page") + end + end + + it "handles multiple challenges in the md" do + md_input = %(### !challenge +* type: project +* id: 1 +* title: partial derivative 1 +#### !question +This is question 1 +#### !end-question + +#### !explanation +This is explanation 1 +#### !end-explanation +### !end-challenge +### !challenge +* type: project +* id: 2 +* title: partial derivative 2 +#### !question +This is question 2 +#### !end-question + +#### !explanation +This is explanation 2 +#### !end-explanation +### !end-challenge +) + + expected_output = { + "content": [ + { + "type": "challenge", + "id": "1" + }, + { + "type": "challenge", + "id": "2" + } + ], + "challenges": { + "1": { + "id": "1", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "partial derivative 1", + "placeholder": nil, + "hints": [], + partial_credit: false, + "question": { + content: [ + { + "type": "markdown", + "value": "This is question 1" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "This is explanation 1" + } + ] + }, + "setup": nil + }, + "2": { + "id": "2", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:project], + "title": "partial derivative 2", + "placeholder": nil, + "hints": [], + partial_credit: false, + "question": { + content: [ + { + "type": "markdown", + "value": "This is question 2" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "This is explanation 2" + } + ] + }, + "setup": nil + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "ignores !instructor tags" do + md_input = %(### !instructor +some text +### !end-instructor +) + expect(described_class.convert(md_input)).to eq(content: []) + end + + it "parses hints correctly" do + md_input = %(### !cHaLlEnGe + +* type: number +* id: 321 +* title: sum numbers +* decimal: 2 + +#### !question + +What is 1 + 2? + +#### !end-question + +#### !placeholder + +Enter your answer here + +#### !end-placeholder + +#### !answer + +3 + +#### !end-answer + +#### !explanation + +Good job at adding numbers! + +#### !end-explanation + +#### !hint + +You suk + +#### !end-hint + +#### !hint + +Cant help you + +#### !end-hint + +#### !hint + +```js +function sukLess ()=> { return 'code more' } +``` + +#### !end-hint + +### !eNd-ChAlLeNgE +) + + expected_output = { + "content": [ + { + "type": "challenge", + "id": "321" + } + ], + "challenges": { + "321": { + "id": "321", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:number], + "title": "sum numbers", + "decimal": "2", + "placeholder": "Enter your answer here", + "answer": "3", + partial_credit: false, + "hints": ["You suk", "Cant help you", "
    function sukLess ()=> { return 'code more' }\n
    "], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWhat is 1 + 2?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nGood job at adding numbers!\n" + } + ] + }, + "setup": nil + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + it "parses hints correctly even when there is no lang provided" do + md_input = %(### !cHaLlEnGe + +* type: number +* id: 321 +* title: sum numbers +* decimal: 2 + +#### !question + +What is 1 + 2? + +#### !end-question + +#### !placeholder + +Enter your answer here + +#### !end-placeholder + +#### !answer + +3 + +#### !end-answer + +#### !explanation + +Good job at adding numbers! + +#### !end-explanation + +#### !hint + +You suk + +#### !end-hint + +#### !hint + +Cant help you + +#### !end-hint + +#### !hint + +``` +function sukLess ()=> { return 'code more' } +``` + +#### !end-hint + +### !eNd-ChAlLeNgE +) + + expected_output = { + "content": [ + { + "type": "challenge", + "id": "321" + } + ], + "challenges": { + "321": { + "id": "321", + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:number], + "title": "sum numbers", + "decimal": "2", + "placeholder": "Enter your answer here", + "answer": "3", + partial_credit: false, + "hints": ["You suk", "Cant help you", "
    function sukLess ()=> { return 'code more' }\n
    "], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWhat is 1 + 2?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nGood job at adding numbers!\n" + } + ] + }, + "setup": nil + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + + context "parse errors" do + it "raises an MdParseError when the input cannot be split into lines" do + md_input = "some-invalid-string" + expect(md_input).to receive(:split).and_raise(ArgumentError) + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "file does not appear to be a valid markdown file") + end + + context "inside parsing of challenges" do + it "raises an exception if there is a missing end-challenge tag" do + md_input = "### !challenge\n" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Didn't find !end-challenge tag.") + end + + it "raises an exception if there is a missing end-challenge tag with multiple challenge delimiters" do + md_input = "### !challenge\n### !challenge\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found second !challenge tag before finding !end-challenge.") + end + + it "raises an exception if there is a !end-placeholder tag without a !placeholder tag" do + md_input = "### !challenge\n### !end-placeholder\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !end-placeholder' within a !challenge tag.") + end + + it "raises an exception if there is a !end-question tag without a !question tag" do + md_input = "### !challenge\n### !end-question\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !end-question' within a !challenge tag.") + end + + it "raises an exception if there is a !end-explanation tag without a !explanation tag" do + md_input = "### !challenge\n### !end-explanation\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !end-explanation' within a !challenge tag.") + end + + it "raises an exception if there is a !end-options tag without a !options tag" do + md_input = "### !challenge\n### !end-options\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !end-options' within a !challenge tag.") + end + + it "raises an exception if there is an !end-hint tag without a !hint tag" do + md_input = "### !challenge\n### !end-hint\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !end-hint' within a !challenge tag.") + end + + it "raises an exception if there is an unknown tag" do + md_input = "### !challenge\n### !bob\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !bob' within a !challenge tag.") + end + + context "while parsing meta data" do + it "raises an exception if there is an unknown meta data key" do + md_input = "### !challenge\n* foo: bar\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid meta data key '* foo: bar' within a !challenge tag.") + end + end + + context "inside parsing question" do + it "raises an exception if there is no !end-question tag after the !question tag" do + md_input = "### !challenge\n### !question\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Didn't find !end-question tag.") + end + + it "raises an exception if any non !end-question tag is found" do + md_input = "### !challenge\n### !question\n### !placeholder\n### !end-question\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !placeholder' within a !question tag.") + end + end + + context "inside parsing options" do + it "raises an exception if there is no !end-options tag after the !options tag" do + md_input = "### !challenge\n### !options\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Didn't find !end-options tag.") + end + + it "raises an exception if any non !end-options tag is found" do + md_input = "### !challenge\n### !options\n### !placeholder\n### !end-options\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !placeholder' within a !options tag.") + end + end + + context "inside parsing placeholder" do + it "raises an exception if there is no !end-placeholder tag after the !placeholder tag" do + md_input = "### !challenge\n### !placeholder\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Didn't find !end-placeholder tag.") + end + + it "raises an exception if any non !end-placeholder tag is found" do + md_input = "### !challenge\n### !placeholder\n### !question\n### !end-placeholder\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !question' within a !placeholder tag.") + end + end + + context "inside parsing explanation" do + it "raises an exception if there is no !end-explanation tag after the !explanation tag" do + md_input = "### !challenge\n### !explanation\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Didn't find !end-explanation tag.") + end + + it "raises an exception if any non !end-explanation tag is found" do + md_input = "### !challenge\n### !explanation\n### !question\n### !end-explanation\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !question' within a !explanation tag.") + end + end + + context "inside parsing hint" do + it "raises an exception if there is no !end-hint tag after the !hint tag" do + md_input = "### !challenge\n### !hint\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Didn't find !end-hint tag.") + end + + it "raises an exception if any non !end-hint tag is found" do + md_input = "### !challenge\n### !hint\n### !question\n### !end-hint\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !question' within a !hint tag.") + end + end + end + + context "outside of challenge parsing" do + it "raises an exception if there is a lone end-challenge" do + md_input = "### !end-challenge\n" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag 'end-challenge' without a starting !challenge tag.") + end + + it "raises an exception if there is a lone end-instructor tag" do + md_input = "### !end-instructor\n" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag 'end-instructor' without a starting !instructor tag.") + end + + it "raises an exception if there is a lone end-vimeo tag" do + md_input = "### !end-vimeo\n" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag 'end-vimeo' without a starting !vimeo tag.") + end + + it "raises an exception if there is a !question tag outside of a !challenge" do + md_input = "### !question\n### !challenge\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !question' outside of a !challenge tag.") + end + + it "raises an exception if there is a !end-question tag outside of a !challenge" do + md_input = "### !end-question\n### !challenge\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !end-question' outside of a !challenge tag.") + end + + it "raises an exception if there is a !placeholder tag outside of a !challenge" do + md_input = "### !placeholder\n### !challenge\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !placeholder' outside of a !challenge tag.") + end + + it "raises an exception if there is a !end-placeholder tag outside of a !challenge" do + md_input = "### !end-placeholder\n### !challenge\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !end-placeholder' outside of a !challenge tag.") + end + + it "raises an exception if there is a !explanation tag outside of a !challenge" do + md_input = "### !explanation\n### !challenge\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !explanation' outside of a !challenge tag.") + end + + it "raises an exception if there is a !end-explanation tag outside of a !challenge" do + md_input = "### !end-explanation\n### !challenge\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !end-explanation' outside of a !challenge tag.") + end + + it "raises an exception if there is a unknown tag outside of a !challenge" do + md_input = "### !bob\n### !challenge\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found invalid tag '### !bob' outside of a !challenge tag.") + end + + it "raises an exception if there is a challenge without an id" do + md_input = "### !challenge\n* title: foo\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found !challenge without an id.") + end + + it "raises an exception if there is a duplicate challenge id found" do + md_input = "### !challenge\n* id: 1\n### !end-challenge\n### !challenge\n* id: 1\n### !end-challenge" + + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Found duplicate challenge id: 1.") + end + end + + it "raises an exception if there is a missing end-instructor tag" do + md_input = %(### !instructor +) + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Missing !end-instructor tag for existing !instructor tag") + end + + it "raises an exception if there is a missing end-vimeo tag" do + md_input = %(### !vimeo +) + expect do + described_class.convert(md_input) + end.to raise_error(described_class::MdParseError, "Didn't find !end-vimeo tag.") + end + end + end + + describe "show_tests" do + let(:id) { "444" } + let(:md_input) do + %( +### !cHaLlEnGe + +* type: code-snippet +* id: #{id} +* title: Shown Tests +* language: javascript +* show_tests: true + +#### !question + +Return true + +#### !end-question + +#### !placeholder +```js +function f () {} +``` +#### !end-placeholder + +### !tests +```js +describe('hmm', function() {}) +``` +### !end-tests + +### !eNd-ChAlLeNgE) + end + + it "accepts the show_tests option" do + expected_output = { + "content": [ + { + "type": "challenge", + "id": id + } + ], + challenges: { + id.to_sym => { + "id": id, + "type": BlockParser::ChallengeValidators::Challenge::TYPES[:code_snippet], + "title": "Shown Tests", + "placeholder": "function f () {}", + "hints": [], + partial_credit: false, + "question": { + content: [ + { + "type": "markdown", + "value": "\nReturn true\n" + } + ] + }, + "show_tests": true, + "language": "javascript", + "setup": nil, + "tests": "describe('hmm', function() {})\n" + } + } + } + + expect(described_class.convert(md_input)).to eq(expected_output) + end + end +end diff --git a/gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md b/gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md new file mode 100644 index 0000000..af9b3f7 --- /dev/null +++ b/gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md @@ -0,0 +1,128 @@ +### !challenge + +* type: paragraph +* id: twenty-mc-double-billions +* title: Submit your project + +##### !question + +### Question + +How many words do you know now? + +##### !end-question + +##### !placeholder + +Write them all here + +##### !end-placeholder + +##### !explanation + +An instructor will review and grade this. Good Luck! + +##### !end-explanation + +##### !rubric + +Better do good + +##### !end-rubric + +### !end-challenge + +### !challenge + +# !challenge + +* type: code-snippet +* language: javascript +* id: 384ccd8d-0b4a-4586-b17e-048b99b0f333 +* title: repeats +* standard_uuids: W0018-V1 + +##### !question + +### JS Snippet + +Write a function named repeats that returns true if the first half of the string equals the last half, and false if not. + +##### !end-question + +##### !setup + +``` +var expect = require('chai').expect; + +function repeats(input) { +``` + +##### !end-setup + +##### !placeholder + +``` + +//function repeats(input) { +// return true if the first half of the string equals the last half, false if not + + if (input.length % 2 !== 0) { + return false; + } + var firstHalf = input.substr(0, input.length/2); + var secondHalf = input.substr(input.length/2); + return firstHalf == secondHalf; +} +``` + +##### !end-placeholder + +##### !tests + +``` +describe('repeats', function() { + + it("returns true when given an empty string (which seems strange, but go with it :) )", function() { + expect(repeats(""), "Default value is incorrect").to.deep.eq(true) + }) + + it("returns true when the second half of the string equals the first", function() { + expect(repeats("bahbah")).to.deep.eq(true) + expect(repeats("nananananananana")).to.deep.eq(true) + }) + + it("returns false when the second half of the string does not equal the first", function() { + expect(repeats("bahba")).to.deep.eq(false) + expect(repeats("nananananann")).to.deep.eq(false) + }) + + it("does not use .repeat", function() { + expect(repeats.toString()).to.not.match(/\.repeat/) + }) + +}) +``` + +##### !end-tests + +##### !explanation + +Be sure that you code checked for input of an string of odd-length. + +Here is one possible solution: + +``` +function repeats(myString) { + if (myString.length % 2 !== 0) { + return false; + } + var firstHalf = myString.substr(0, myString.length/2); + var secondHalf = myString.substr(myString.length/2); + return firstHalf == secondHalf; +} +``` + +##### !end-explanation + +# !end-challenge \ No newline at end of file diff --git a/gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml b/gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml new file mode 100644 index 0000000..8ce6623 --- /dev/null +++ b/gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml @@ -0,0 +1,14 @@ +--- +Standards: + - + Title: bar + UID: abc123 + Description: foobar + SuccessCriteria: + - Foo + - Bar + ContentFiles: + - + UID: none + Type: Checkpoint + Path: /bad-challenges.md diff --git a/gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml b/gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml new file mode 100644 index 0000000..2a83ef5 --- /dev/null +++ b/gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml @@ -0,0 +1,14 @@ +--- +Standards: + - + Title: bar + UID: abc123 + Description: foobar + SuccessCriteria: + - Foo + - Bar + ContentFiles: + - + UID: none + Type: Checkpoint + Path: /no-challenges.md diff --git a/gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md b/gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md new file mode 100644 index 0000000..15319ba --- /dev/null +++ b/gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md @@ -0,0 +1 @@ +# I ain't got no challenges! diff --git a/gems/bp/spec/fixtures/invalid-config-test-block-repo/config.yaml b/gems/bp/spec/fixtures/invalid-config-test-block-repo/config.yaml new file mode 100644 index 0000000..4726ad3 --- /dev/null +++ b/gems/bp/spec/fixtures/invalid-config-test-block-repo/config.yaml @@ -0,0 +1,3 @@ +--- +- Title: CNE Intro +BARF \ No newline at end of file diff --git a/gems/bp/spec/fixtures/no-lessons-nor-checkpoints/config.yaml b/gems/bp/spec/fixtures/no-lessons-nor-checkpoints/config.yaml new file mode 100644 index 0000000..afb4da2 --- /dev/null +++ b/gems/bp/spec/fixtures/no-lessons-nor-checkpoints/config.yaml @@ -0,0 +1,14 @@ +--- +Title: Block Name +Standards: + - + Title: No Lessons Nor Checkpoints + UID: xe81925b-4ef0-4ded-ae20-8fb01ce3b3f2 + Description: Do the first thing V10 + SuccessCriteria: + - TEST + ContentFiles: + - + Type: Resource + UID: 11e55988-5990-4a15-bf68-f8d729bedf41 + Path: /resources.md diff --git a/gems/bp/spec/fixtures/no-lessons-nor-checkpoints/resources.md b/gems/bp/spec/fixtures/no-lessons-nor-checkpoints/resources.md new file mode 100644 index 0000000..2ddd5bd --- /dev/null +++ b/gems/bp/spec/fixtures/no-lessons-nor-checkpoints/resources.md @@ -0,0 +1 @@ +Intentionally left blank. diff --git a/gems/bp/spec/fixtures/no-standards-config-test-block-repo/config.yaml b/gems/bp/spec/fixtures/no-standards-config-test-block-repo/config.yaml new file mode 100644 index 0000000..315d6e9 --- /dev/null +++ b/gems/bp/spec/fixtures/no-standards-config-test-block-repo/config.yaml @@ -0,0 +1,2 @@ +--- +Title: CNE Intro diff --git a/gems/bp/spec/fixtures/sample-iframe.md b/gems/bp/spec/fixtures/sample-iframe.md new file mode 100644 index 0000000..76755c4 --- /dev/null +++ b/gems/bp/spec/fixtures/sample-iframe.md @@ -0,0 +1,5 @@ + diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/config.yml b/gems/bp/spec/fixtures/test-block-repo-yml/config.yml new file mode 100644 index 0000000..0e833fc --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-repo-yml/config.yml @@ -0,0 +1,13 @@ +--- +Standards: + - + Title: bar + UID: abc123 + Description: foobar + SuccessCriteria: + - Foo + - Bar + ContentFiles: + - + Type: Lesson + Path: /markdown-smoketest.md diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/dummy.pdf b/gems/bp/spec/fixtures/test-block-repo-yml/dummy.pdf new file mode 100644 index 0000000000000000000000000000000000000000..774c2ea70c55104973794121eae56bcad918da97 GIT binary patch literal 13264 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 literal 0 HcmV?d00001 diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/folder/sibling.md b/gems/bp/spec/fixtures/test-block-repo-yml/folder/sibling.md new file mode 100644 index 0000000..0600109 --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-repo-yml/folder/sibling.md @@ -0,0 +1,3 @@ +I am the sibling + +* [parent link](../markdown-smoketest.md) diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/folder/target.md b/gems/bp/spec/fixtures/test-block-repo-yml/folder/target.md new file mode 100644 index 0000000..083c50b --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-repo-yml/folder/target.md @@ -0,0 +1,3 @@ +nested target + +* [sibling](sibling.md) diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png b/gems/bp/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..64319ca905483ea07335fb5f229576fec27d7036 GIT binary patch literal 4134 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 + +### Syntax Highlighting +Inline `code` has `back-ticks around` it. + +```javascript +var s = "JavaScript syntax highlighting"; +alert(s); +``` + +```python +s = "Python syntax highlighting" +print s +``` + +``` +For data point in training set: + calculate distance from data point to new_value +Order distances in increasing order and take the first k +Make the prediction + +No language indicated, so no syntax highlighting. +But let's throw in a tag. +``` + +### Maths +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ + +### Blockquotes + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +Quote break. + +> This is a very long line that will still be quoted properly when it wraps. + + + +### Tables + +| Tables | Are | Cool | +| ------------- |---------------| ------| +| enterprise | dsi | wdi | + +### Instructor tags (nothing should show up after this line) + +### !instructor + +# H1 + +Text + +![image](images/galvanize-logo.png) + +[relative link](target.md) + +Inline `code` has `back-ticks around` it. + +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +### !end-instructor diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/target.md b/gems/bp/spec/fixtures/test-block-repo-yml/target.md new file mode 100644 index 0000000..2901278 --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-repo-yml/target.md @@ -0,0 +1 @@ +Top level target diff --git a/gems/bp/spec/fixtures/test-block-repo/README.md b/gems/bp/spec/fixtures/test-block-repo/README.md new file mode 100644 index 0000000..49b8920 --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-repo/README.md @@ -0,0 +1,22 @@ +This is the first line + + + + + +Also second line + + + + + + +But also 3 lines + + + + + + + +Should not make the cut \ No newline at end of file diff --git a/gems/bp/spec/fixtures/test-block-repo/config.yaml b/gems/bp/spec/fixtures/test-block-repo/config.yaml new file mode 100644 index 0000000..03af23e --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-repo/config.yaml @@ -0,0 +1,18 @@ +--- +Standards: + - + Title: bar + UID: abc123 + Description: foobar + SuccessCriteria: + - Foo + - Bar + ContentFiles: + - + Type: Lesson + UID: xyz456 + Path: /markdown-smoketest.md + - + Type: Lesson + UID: xyz456 + Path: /ipynb-test.ipynb diff --git a/gems/bp/spec/fixtures/test-block-repo/folder/sibling.md b/gems/bp/spec/fixtures/test-block-repo/folder/sibling.md new file mode 100644 index 0000000..0600109 --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-repo/folder/sibling.md @@ -0,0 +1,3 @@ +I am the sibling + +* [parent link](../markdown-smoketest.md) diff --git a/gems/bp/spec/fixtures/test-block-repo/folder/target.md b/gems/bp/spec/fixtures/test-block-repo/folder/target.md new file mode 100644 index 0000000..083c50b --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-repo/folder/target.md @@ -0,0 +1,3 @@ +nested target + +* [sibling](sibling.md) diff --git a/gems/bp/spec/fixtures/test-block-repo/images/galvanize-logo.png b/gems/bp/spec/fixtures/test-block-repo/images/galvanize-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..64319ca905483ea07335fb5f229576fec27d7036 GIT binary patch literal 4134 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 + +### Syntax Highlighting +Inline `code` has `back-ticks around` it. + +```javascript +var s = "JavaScript syntax highlighting"; +alert(s); +``` + +```python +s = "Python syntax highlighting" +print s +``` + +``` +For data point in training set: + calculate distance from data point to new_value +Order distances in increasing order and take the first k +Make the prediction + +No language indicated, so no syntax highlighting. +But let's throw in a tag. +``` + +### Maths +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ + +### Blockquotes + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +Quote break. + +> This is a very long line that will still be quoted properly when it wraps. + + + +### Tables + +| Tables | Are | Cool | +| ------------- |---------------| ------| +| enterprise | dsi | wdi | + +### Instructor tags (nothing should show up after this line) + +### !instructor + +# H1 + +Text + +![image](images/galvanize-logo.png) + +[relative link](target.md) + +Inline `code` has `back-ticks around` it. + +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +### !end-instructor diff --git a/gems/bp/spec/fixtures/test-block-repo/target.md b/gems/bp/spec/fixtures/test-block-repo/target.md new file mode 100644 index 0000000..2901278 --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-repo/target.md @@ -0,0 +1 @@ +Top level target diff --git a/gems/bp/spec/fixtures/test-block-two-configs/config.yaml b/gems/bp/spec/fixtures/test-block-two-configs/config.yaml new file mode 100644 index 0000000..3f6ef9d --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-two-configs/config.yaml @@ -0,0 +1,2 @@ +--- +- Title: CNE Intro \ No newline at end of file diff --git a/gems/bp/spec/fixtures/test-block-two-configs/config.yml b/gems/bp/spec/fixtures/test-block-two-configs/config.yml new file mode 100644 index 0000000..3f6ef9d --- /dev/null +++ b/gems/bp/spec/fixtures/test-block-two-configs/config.yml @@ -0,0 +1,2 @@ +--- +- Title: CNE Intro \ No newline at end of file diff --git a/gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile b/gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh b/gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh new file mode 100644 index 0000000..e69de29 diff --git a/gems/bp/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml b/gems/bp/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml new file mode 100644 index 0000000..3f6ef9d --- /dev/null +++ b/gems/bp/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml @@ -0,0 +1,2 @@ +--- +- Title: CNE Intro \ No newline at end of file diff --git a/gems/bp/spec/parse_directory_spec.rb b/gems/bp/spec/parse_directory_spec.rb new file mode 100644 index 0000000..9a32ded --- /dev/null +++ b/gems/bp/spec/parse_directory_spec.rb @@ -0,0 +1,320 @@ +require "spec_helper" + +describe BlockParser::ParseDirectory do + describe ".execute" do + describe "errors" do + subject { described_class.new(path: path, asset_uploader: Freeloader.new).execute } + + context "when the path does not exist" do + let(:path) { File.join(File.dirname(__FILE__), "tmp", SecureRandom.hex) } + + it "returns errors" do + expect(subject[:errors]).to include("Directory does not exist") + end + end + + context "when the path exists, but does not contain a config.yaml file" do + let(:path) { File.join(File.dirname(__FILE__), "tmp") } + + it "returns errors" do + expect(subject[:errors]).to include("Root directory does not contain a config.yaml or config.yml file") + end + end + + context "when the path exists and includes a config.yaml file" do + context "in valid format" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "test-block-repo") } + before { allow_any_instance_of(BlockParser::ParseStandards).to receive(:execute).and_return(errors: [], standards_attributes: []) } + + it "returns no errors" do + expect(subject[:errors]).to eq([]) + end + end + + context "the yaml is valid, but does not return a hash" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "valid-yaml-array-test-block-repo") } + before { allow_any_instance_of(BlockParser::ParseStandards).to receive(:execute).and_return(errors: [], standards_attributes: []) } + + it "returns errors" do + expect(subject[:errors]).to include("config.yaml must be a hash") + end + end + + context "the Standard references a content files" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "test-block-repo") } + + context "when content file does not exist" do + before { allow_any_instance_of(BlockParser::ParseStandards).to receive(:execute).and_return(errors: [], standards_attributes: [{ content_files_attributes: [{ path: "/non-existing.md" }] }]) } + + it "returns errors" do + expect(subject[:errors]).to eq(["content file in config.yaml must exist"]) + end + end + + context "when content file does exist" do + before { allow_any_instance_of(BlockParser::ParseStandards).to receive(:execute).and_return(errors: [], standards_attributes: [{ content_files_attributes: [{ path: "markdown-smoketest.md" }] }]) } + + it "returns no errors" do + expect(subject[:errors]).to eq([]) + end + end + end + + context "when the standards cannot be parsed" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "test-block-repo") } + before do + allow_any_instance_of(BlockParser::ParseStandards).to receive(:execute).and_return( + errors: ["parse standards error"], standards_attributes: [] + ) + end + + it "returns errors" do + expect(subject[:errors]).to include("parse standards error") + end + end + + context "when a checkpoint does not have any challenges" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "checkpoint-without-challenges-block-repo") } + + it "returns errors" do + expect(subject[:errors]).to include("/no-challenges.md: checkpoint must have at least one challenge and all challenges must be valid") + end + end + + context "when there are no lessons nor checkpoints" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "no-lessons-nor-checkpoints") } + + it "returns errors" do + expect(subject[:errors]).to include("Standard #1: Must have at least one lesson or checkpoint") + end + end + + context "when a markdown file contains invalid challenges" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "checkpoint-with-bad-challenge-tag") } + + it "returns errors" do + expect(subject[:errors]).to include("/bad-challenges.md: Could not parse markdown: Found second !challenge tag before finding !end-challenge.") + expect(subject[:errors]).to include("/bad-challenges.md: checkpoint must have at least one challenge and all challenges must be valid") + end + end + + context "when no standards key is present" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "no-standards-config-test-block-repo") } + + it "returns errors" do + expect(subject[:errors]).to include("config.yaml must have a key of 'Standards'") + end + end + + context "invalid syntax" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "invalid-config-test-block-repo") } + + it "returns errors" do + expect(subject[:errors]).to include( + "Could not parse config.yaml: could not find expected ':' while scanning a simple key at line 3 column 1" + ) + end + end + end + + context "when the path given has both config.yaml and config.yml" do + let(:path) { File.join(File.dirname(__FILE__), "fixtures", "test-block-two-configs") } + + it "returns errors" do + expect(subject[:errors]).to include("Found both a config.yaml and a config.yml. only one config file is allowed.") + end + end + end + + describe "block_hash" do + it "returns the block_hash" do + allow(Open3).to receive(:capture3).and_return(["", "", OpenStruct.new(success?: true)]) + allow_any_instance_of(BlockParser::ParseStandards).to receive(:execute).and_return( + errors: [], + warnings: [], + standards_attributes: [ + { + content_files_attributes: [ + { path: "/ipynb-test.ipynb", content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:lesson], uid: "abc123", default_visibility: "visible" }, + { path: "/target.md", content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:checkpoint], uid: "1234", autoscore: true, default_visibility: "visible" } + ] + } + ] + ) + + results = BlockParser::ParseDirectory.new( + path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo"), + asset_uploader: Freeloader.new + ).execute + + expect(results[:readme_text]).to eq("This is the first line\nAlso second line\nBut also 3 lines") + expect(results[:block_hash]).to eq( + [ + { + standard: { + content_files_attributes: [ + { + path: "/ipynb-test.ipynb", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:lesson], + uid: "abc123", + default_visibility: "visible" + }, + { + path: "/target.md", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:checkpoint], + uid: "1234", + autoscore: true, + default_visibility: "visible" + } + ] + }, + content_file_attribute_hashes: [ + { + title: "Ipynb Test.Ipynb", + html: "

    Introduction

    \n\n

    To open this notebook so you can run this code, navigate to this file's directory at the comandline and then type jupyter notebook

    \n\n

    This notebook here is meant to get you oriented with one of the tools that we will be using for our pre-class material. The notes and exercises will be built into IPython notebooks (like this one). Before coming to each class, you should work through the materials in the pre-class folder and play around/follow along with the code in those notebooks. You should run the code provided, and additionally modify it to learn what happens when you change certain/different pieces.

    \n\n

    Effectively learning to program means having your hands-on the keyboard often. It means playing with code, breaking code, reading errors, and reading google. It means banging your head against the keyboard, but getting to revel in that time spent banging your head when you're program finally works. To that end, this notebook here will hopefully give you a taste of that.

    \n\n

    You might not know exactly what all the lines of code in this notebook do. But, don't worry, because you will know all about these ideas in a week. For now, it's going to be good to get used to your environment and reading and writing some simple Python code. Besides, there's a good chance you'll know what's going on in most of these exercises.

    \n\n

    You'll start off by type some working code, and playing around with it. Then, we'll walk you through an example of working with some broken code (don't worry, it won't be too bad). Finally, you'll finish up by learning how to move the working code into a script, and you'll run you're very first Python program!

    \n\n

    For all of the exercises below, we've included the exact number of cells for you to complete the assignment in. However, if you want more to experiment with, at any time you can click the + button in the upper-left hand corner of the notebook, and a new cell will be created below the one that is currently selected. If you would like to delete a cell you've created for experimentation, simply select the cell and click the edit tab at the top of the notebook. Then, select "Delete Cells".

    \n\n

    Part 1 - Working with Working Code

    \n\n

    For the first part, type in the following code. Type it one line at a time into a cell, and then run that cell (click the button at the top to do this, or try Shift + Enter). Don't copy and paste it, but instead type it - fingers on the keyboard style. Building up your programming chops will come through muscle memory, and tonight will get you started with that. You will be writing your own programs soon enough!

    \n\n

    Type this code out and run it one line at at time:

    \n\n
    my_first_var = 'Wohoo!'\nprint(my_first_var)\n\nmy_second_var = "I'm typing code."\nprint(my_second_var)\n\nmy_third_var = "Even though this doesn't feel like code, I've been told it is code."\nprint(my_third_var) \n\nmy_combined_var = my_first_var + ' ' + my_second_var + ' ' + my_third_var \nprint(my_combined_var)\n
    \n\n

    We've included a cell below for each one of the lines above.

    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n

    Above, we just worked with text. Let's try working with some numbers now.

    \n\n

    Type this code out line by line:

    \n\n
    my_first_num = 3\nprint(my_first_num)\n\nmy_second_num = 13\nprint(my_second_num)\n\nquotient = my_second_num // my_first_num\nremainder = my_second_num % my_first_num\n\nprint(quotient, remainder)\n
    \n\n

    We've included a cell below for each one of the lines above.

    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n

    Part 2 - Working with Error Messages

    \n\n

    Now, let's work with some code that will break (sorry). This can and will happen all the time when writing code. Half the battle of being a good programmer is learning how to read through error messages and fix your code. Google will be your best friend through this. If you ask any software engineer or programmer you may know, she/he will be likely to tell you the same.

    \n\n

    Type out the code below, again line by line. When you encounter the error, we'll walk you through it.

    \n\n
    my_str_var = 'Hello. My favorite number is '\nprint(my_str_var)\n\nmy_fav_num = 3\nprint(my_fav_num)\n\nfull_str = my_str_var + my_fav_num \nprint(full_str)\n
    \n\n

    There is a cell below for each one of the lines above.

    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n

    By now, you'll have encountered the following error:

    \n\n
    TypeError: cannot concatenate 'str' and 'int' objects\n
    \n\n

    We've said Google will be your best friend. Let's put that to the test. Take that error (the entire thing) and paste it into Google. The first link should be this page, a stackoverflow post explaining what this error means and how to fix it. Reading through the first five posts there, we see that we can fix it if we simply throw a str around the variable my_fav_num above. This means the line

    \n\n

    full_str = my_str_var + my_fav_num

    \n\n

    will become:

    \n\n

    full_str = my_str_var + str(my_fav_num).

    \n\n

    Make this change above, and then re-run all your code.

    \n\n
    \n
    \n\n

    Notice, too, that you could put that line in the cell below and it would still work. This is because the notebook's state is the same no matter what cell you're in. This is true throughout the life of a notebook session. So you could actually insert a cell at the top of the notebook and run the same line! However, this is a very dangerous thing to do! If you inserted that line in a cell at the top of the notebook, and then came back to it later after it was shutdown, it would not run. This is because the variables my_str_var and my_fav_num wouldn't have been defined yet. You can try this if you want. Insert the that line at the top of the notebook, then click the Kernel tab at the top of the notebook and select "Restart". This put's the notebook in a state as though you just opened it. Try running that top line now. It won't work. But, if you run the cells that define my_str_var and my_fav_num it will!

    \n\n

    Part 3 - Moving to a Script

    \n\n

    Now that you have some functioning code here in a notebook, it's time for you to turn it into a Python script!

    \n\n

    Aside: Python is known as a scripting language, which means that you technically write scripts, not programs. This distinction is normally not important, and as a result you will frequently hear people say they wrote a Python program (and that's accepted language).

    \n\n

    Python scripts live in .py files. So, what you're going to need to do is re-write all of the code from the exercise above in a file. This will be done in your text editor (Atom if you followed the advice from the introduction).

    \n\n

    Start a new file and name it my_first_program.py. Inside of the file, re-type all of the lines of code from the working version of the example from above. Once you're done, save it in today's folder.

    \n\n

    Now you have a python script you can run! This can be done in one of two ways. The first is by navigating to the directory that contains my_first_program.py and then typing python my_first_program.py at the command line. You should see the statements that were printed above appear in the console.

    \n\n

    The other way to run a python script is from IPython. To do this from the directory that contains my_first_program.py, type ipython. This will start the IPython console, which allows you to interact with Python code in much the same way as we have done in this notebook. Now, to run your script. Simply type run my_first_program.py into IPython and watch the same output appear on the screen!

    \n\n
    \n
    \n", + challenges: [], + path: "/ipynb-test.ipynb", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:lesson], + uid: "abc123", + default_visibility: "visible", + autoscore: nil, + max_checkpoint_submissions: nil, + time_limit: nil, + warnings: [] + }, + { + title: "Target", + html: "

    Top level target

    \n", + challenges: [], + path: "/target.md", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:checkpoint], + uid: "1234", + autoscore: true, + default_visibility: "visible", + max_checkpoint_submissions: nil, + time_limit: nil, + warnings: [] + } + ] + } + ] + ) + end + + it "returns the block_hash when repo has config.yml file" do + allow_any_instance_of(BlockParser::ParseStandards).to receive(:execute).and_return( + errors: [], + warnings: [], + standards_attributes: [{ content_files_attributes: [ + { + path: "/target.md", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:checkpoint], + uid: "1234", + autoscore: true, + default_visibility: "hidden", + max_checkpoint_submissions: 3, + time_limit: 30 + } + ] }] + ) + + results = BlockParser::ParseDirectory.new( + path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo-yml"), + asset_uploader: Freeloader.new + ).execute + + expect(results[:block_hash]).to eq( + [ + { + standard: { + content_files_attributes: [ + { + path: "/target.md", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:checkpoint], + uid: "1234", + autoscore: true, + default_visibility: "hidden", + max_checkpoint_submissions: 3, + time_limit: 30 + } + ] + }, + content_file_attribute_hashes: [ + { + title: "Target", + html: "

    Top level target

    \n", + challenges: [], + path: "/target.md", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:checkpoint], + uid: "1234", + autoscore: true, + default_visibility: "hidden", + max_checkpoint_submissions: 3, + time_limit: 30, + warnings: [] + } + ] + } + ] + ) + end + + it "it calls out to the asset uploader when the directory contains a pdf" do + allow_any_instance_of(BlockParser::ParseStandards).to receive(:execute).and_return( + errors: [], + warnings: [], + standards_attributes: [{ content_files_attributes: [ + { + path: "/dummy.pdf", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:lesson], + uid: "1234", + autoscore: false, + default_visibility: "hidden", + max_checkpoint_submissions: nil + } + ] }] + ) + allow_any_instance_of(Freeloader).to receive(:find_or_create_content).and_return("url_save") + + results = BlockParser::ParseDirectory.new( + path: File.join(File.dirname(__FILE__), "fixtures", "test-block-repo-yml"), + asset_uploader: Freeloader.new + ).execute + + expect(results[:block_hash]).to eq( + [ + { + standard: { + content_files_attributes: [ + { + path: "/dummy.pdf", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:lesson], + uid: "1234", + autoscore: false, + default_visibility: "hidden", + max_checkpoint_submissions: nil + } + ] + }, + content_file_attribute_hashes: [ + { + title: "Dummy.Pdf", + html: "url_save", + challenges: [], + path: "/dummy_pdf", + content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:lesson], + uid: "1234", + autoscore: false, + default_visibility: "hidden", + max_checkpoint_submissions: nil, + time_limit: nil, + warnings: [] + } + ] + } + ] + ) + end + end + end +end diff --git a/gems/bp/spec/parse_markdown_file_spec.rb b/gems/bp/spec/parse_markdown_file_spec.rb new file mode 100644 index 0000000..7e50b14 --- /dev/null +++ b/gems/bp/spec/parse_markdown_file_spec.rb @@ -0,0 +1,29 @@ +require "spec_helper" + +describe BlockParser::ParseMarkdownFile do + let(:asset_uploader) { Freeloader.new } + let(:root_directory_path) { File.join(File.dirname(__FILE__), "fixtures", "test-block-repo") } + subject do + described_class.execute( + file_body: file_body, + asset_uploader: asset_uploader, + root_directory_path: root_directory_path, + current_content_file_path: File.join(root_directory_path, "markdown-smoketest.md"), + content_file_paths: + Dir[File.join(File.join(File.dirname(__FILE__), "fixtures", "test-block-repo"), "**/*")]. + reject { |fn| File.directory?(fn) }. + select { |file_path| file_path[-3..-1] == ".md" } + ) + end + + context "when passed the markdown smoketest" do + let(:file_body) { File.read(File.join(File.dirname(__FILE__), "fixtures", "test-block-repo", "markdown-smoketest.md")) } + + it "parses the markdown smoketest without errors" do + expect(subject[:errors]).to eq([]) + expect(subject[:html]).to include("

    Markdown Smoketest

    ") + expect(subject[:html]).to include("") + expect(subject[:title]).to include("Markdown Smoketest") + end + end +end diff --git a/gems/bp/spec/parse_standards_spec.rb b/gems/bp/spec/parse_standards_spec.rb new file mode 100644 index 0000000..5f0e834 --- /dev/null +++ b/gems/bp/spec/parse_standards_spec.rb @@ -0,0 +1,368 @@ +require "spec_helper" + +describe BlockParser::ParseStandards do + describe "#execute" do + subject { described_class.new(standards, File.join(File.dirname(__FILE__), "fixtures", "test-block-repo")).execute } + + context "when standards is nil" do + let(:standards) { nil } + + it "returns an error" do + expect(subject[:errors]).to include("Configuration must have at least one standard") + end + end + + context "when there are no standards" do + let(:standards) { [] } + + it "returns an error" do + expect(subject[:errors]).to include("Configuration must have at least one standard") + end + end + + context "when the standard is not a hash" do + let(:standards) { ["foo"] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1 must be a hash") + end + end + + context "when the standard does not contain a title" do + let(:standards) { [{ "foo" => "bar" }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1 must have Title") + end + end + + context "when the standard does not contain a uid" do + let(:standards) { [{ "foo" => "bar" }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1 must have UID") + end + end + + context "when the standard does not contain a description" do + let(:standards) { [{ "foo" => "bar" }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1 must have Description") + end + end + + context "when the standard does not contain success criteria" do + let(:standards) { [{ "foo" => "bar" }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1 must have at least one SuccessCriteria") + end + end + + context "when the standard does not contain content files" do + let(:standards) { [{ "foo" => "bar" }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1 must have at least one ContentFile") + end + end + + describe "ContentFile Validation" do + context "when the ContentFile does not have a path" do + let(:standards) { [{ "foo" => "bar", "ContentFiles" => [{ "Type" => "foo" }] }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1: ContentFile #1 must have a path starting with /") + end + end + + context "when the ContentFile does not have a path starting with /" do + let(:standards) { [{ "foo" => "bar", "ContentFiles" => [{ "Type" => "foo", "Path" => "foo.md" }] }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1: ContentFile #1 must have a path starting with /") + end + end + + context "when the ContentFile is not a markdown or ipynb file" do + let(:standards) { [{ "foo" => "bar", "ContentFiles" => [{ "Type" => "foo", "Path" => "/foo.js" }] }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1: Content File referenced at /foo.js must be a .md, .ipynb, or .pdf file") + end + end + + context "when the ContentFile does not exist at the given path" do + let(:standards) { [{ "foo" => "bar", "ContentFiles" => [{ "Type" => "foo", "Path" => "/food.md" }] }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1: Content File referenced at /food.md does not exist") + end + end + + context "when ContentFile is a checkpoint and does not have a UID" do + let(:standards) { [{ "foo" => "bar", "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/markdown-smoketest.md" }] }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1: Content File referenced at /markdown-smoketest.md does not have a UID") + end + end + end + + context "when the standard does not contain a checkpoint" do + let(:standards) do + [ + { + "Title" => "foo", "UID" => "abc123", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [ + { "Type" => "Lesson", "UID" => "a", "Path" => "/lessoN.md" } + ] + } + ] + end + + it "does not return errors" do + expect(subject[:errors]).to eq([]) + end + end + + context "when the standard contains two checkpoints" do + let(:standards) { [{ "foo" => "bar", "ContentFiles" => [{ "Type" => "Checkpoint" }, { "Type" => "Checkpoint" }] }] } + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1 may not have more than one Checkpoint.") + end + end + + context "when the standard contains a TimeLimit checkpoint with non-numbers" do + let(:standards) { [{ "foo" => "bar", "ContentFiles" => [{ "Path" => "/target.md", "UID" => "abc12", "Type" => "Checkpoint", "TimeLimit" => "1 day" }] }] } + + it "returns an error" do + expect(subject[:errors]).to include("Checkpoint abc12 TimeLimit must be a number only (number specifies minutes)") + end + end + + context "when there are more than two checkpoints with the same UID" do + let(:standards) do + [ + { + "Title" => "foo", + "UID" => "abczxc", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/markdown-smoketest.md", "UID" => "1234" }] + }, + { + "Title" => "bar", + "UID" => "abc123", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/target.md", "UID" => "1234" }] + } + ] + end + + it "returns an error" do + expect(subject[:errors]).to include("Checkpoint UID 1234 used more than once") + end + end + + context "when there are two standards with the same UID" do + let(:standards) do + [ + { + "Title" => "foo", + "UID" => "abc123", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/foo1.md" }] + }, + { + "Title" => "bar", + "UID" => "abc123", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/foo2.md" }] + }, + { + "Title" => "baz", + "UID" => "abc125", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/foo3.md" }] + }, + { + "Title" => "fizz", + "UID" => "abc125", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/foo4.md" }] + }, + { + "Title" => "buzz", + "UID" => "abc127", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/foo5.md" }] + } + ] + end + + it "returns an error" do + expect(subject[:errors]).to include("Duplicate Standard UID used for standards with titles: foo, bar") + expect(subject[:errors]).to include("Duplicate Standard UID used for standards with titles: baz, fizz") + end + end + + context "when there are two content files with the same UID" do + let(:standards) do + [ + { + "Title" => "foo", "UID" => "abc123", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [ + { "Type" => "Lesson", "UID" => "a", "Path" => "/lessoN.md" }, + { "Type" => "Checkpoint", "UID" => "b", "Path" => "/markdown-smoketest.md" } + ] + }, + { + "Title" => "bar", + "UID" => "abc124", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [ + { "Type" => "Lesson", "UID" => "a", "Path" => "/target.md" }, + { "Type" => "Checkpoint", "UID" => "c", "Path" => "/folder/target.md" } + ] + } + ] + end + + it "returns an error" do + expect(subject[:errors]).to include("Duplicate Content File UID: a") + end + end + + context "when there are Lessons without a UID" do + let(:standards) do + [ + { + "Title" => "bar", + "UID" => "abc124", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [ + { "Type" => "Lesson", "Path" => "/target.md" }, + { "Type" => "Checkpoint", "UID" => "c", "Path" => "/folder/target.md" } + ] + } + ] + end + + it "returns an error" do + expect(subject[:errors]).to include("Standard #1: Content File referenced at /target.md does not have a UID") + end + end + + context "when a content file is used more than once" do + let(:standards) do + [ + { + "Title" => "foo", + "UID" => "abc123", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/target.md" }] + }, + { + "Title" => "bar", + "UID" => "abc124", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/target.md" }] + }, + { + "Title" => "bar", + "UID" => "abc124", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/lessoN.md" }] + } + ] + end + + it "returns an error" do + expect(subject[:errors]).to include("ContentFile /target.md used more than once") + end + end + + context "when a content file is marked as autoscored" do + let(:standards) do + [ + { + "Title" => "bar", + "UID" => "abc123", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/target.md", "UID" => "1234", "Autoscore" => true }] + } + ] + end + + it "indicates the content file is autoscored" do + expect(subject[:standards_attributes][0][:content_files_attributes][0][:autoscore]).to eq(true) + end + end + + context "when a content file is marked with default_visibility 'hidden'" do + let(:standards) do + [ + { + "Title" => "bar", + "UID" => "abc123", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/target.md", "UID" => "1234", "Autoscore" => true, "DefaultVisibility" => "HiDDeN" }] + } + ] + end + + it "indicates the content file's default_visibility" do + expect(subject[:standards_attributes][0][:content_files_attributes][0][:default_visibility]).to eq("hidden") + end + end + + context "when a content file is does not provide DefaultVisibility attribute" do + let(:standards) do + [ + { + "Title" => "bar", + "UID" => "abc123", + "Description" => "A short description.", + "SuccessCriteria" => ["Foo"], + "ContentFiles" => [{ "Type" => "Checkpoint", "Path" => "/target.md", "UID" => "1234", "Autoscore" => true }] + } + ] + end + + it "indicates the content file's default_visibility" do + expect(subject[:standards_attributes][0][:content_files_attributes][0][:default_visibility]).to eq("visible") + end + end + + context "when the standard is valid" do + let(:standards) { [{ "Title" => "bar", "UID" => "abc123", "Description" => "A short description.", "SuccessCriteria" => ["Foo"], "ContentFiles" => [{ "Type" => "Lesson", "Path" => "/ipynb-test.ipynb", "UID" => "abc123" }, { "Type" => "Checkpoint", "Path" => "/target.md", "UID" => "1234" }] }] } + + it "returns standard attributes" do + expect(subject[:errors]).to eq([]) + expect(subject[:standards_attributes][0]).to eq( + title: "bar", + uid: "abc123", + description: "A short description.", + success_criteria: ["Foo"], + content_files_attributes: [ + { content_file_type: described_class::CONTENT_FILE_TYPES[:lesson], path: "/ipynb-test.ipynb", uid: "abc123", autoscore: false, default_visibility: "visible" }, + { content_file_type: described_class::CONTENT_FILE_TYPES[:checkpoint], path: "/target.md", uid: "1234", autoscore: false, default_visibility: "visible" } + ] + ) + end + end + end +end diff --git a/gems/bp/spec/spec_helper.rb b/gems/bp/spec/spec_helper.rb new file mode 100644 index 0000000..1d31ebc --- /dev/null +++ b/gems/bp/spec/spec_helper.rb @@ -0,0 +1,23 @@ +require "bundler/setup" +require "block_parser" +require "byebug" + +Dir[File.dirname(__FILE__) + "/support/**/*.rb"].sort.each { |file| require file } + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expose_dsl_globally = true + + config.expect_with :rspec do |c| + c.syntax = :expect + end + + config.before(:each) do + allow(Octokit::Client).to receive(:new).and_return(Mocktokit::Client.new) + end +end diff --git a/gems/bp/spec/support/freeloader.rb b/gems/bp/spec/support/freeloader.rb new file mode 100644 index 0000000..2cfee29 --- /dev/null +++ b/gems/bp/spec/support/freeloader.rb @@ -0,0 +1,8 @@ +class Freeloader + def initialize(*_args) + end + + def find_or_create_content(*_args) + "conent_url" + end +end diff --git a/gems/bp/spec/support/mocktokit.rb b/gems/bp/spec/support/mocktokit.rb new file mode 100644 index 0000000..06fbc64 --- /dev/null +++ b/gems/bp/spec/support/mocktokit.rb @@ -0,0 +1,31 @@ +module Mocktokit + class Client + def branch(*_args) + nil + end + + def repository(org_name) + org = org_name.split("/")[0] + name = org_name.split("/")[1] + + { + name: name, + organization: { + login: org + } + } + end + + def repository?(*_args) + true + end + + def content(*_args) + nil + end + + def contents(*_args) + nil + end + end +end diff --git a/gems/bp/spec/tmp/.keep b/gems/bp/spec/tmp/.keep new file mode 100644 index 0000000..58069b8 --- /dev/null +++ b/gems/bp/spec/tmp/.keep @@ -0,0 +1 @@ +keep me \ No newline at end of file diff --git a/gems/bp/validate-block b/gems/bp/validate-block new file mode 100755 index 0000000..25eac24 --- /dev/null +++ b/gems/bp/validate-block @@ -0,0 +1,11 @@ +#!/bin/bash + +dir_resolve() { + local dir=`dirname "$1"` + local file=`basename "$1"` + pushd "$dir" &>/dev/null || return $? # On error, return error code + echo "`pwd -P`/$file" # output full, link-resolved path with filename + popd &> /dev/null +} + +docker run --rm -v $(dir_resolve $1):/block -it block-parser -- GitLab From 176cacf9d9b89d37adcee6b52bb4a8466f4f4b96 Mon Sep 17 00:00:00 2001 From: Benton Gallun Date: Mon, 21 Sep 2020 18:35:23 -0700 Subject: [PATCH 051/287] change file name --- gems/{bp => block-parser}/Dockerfile | 0 gems/{bp => block-parser}/Gemfile | 0 gems/{bp => block-parser}/Gemfile.lock | 0 gems/{bp => block-parser}/README.md | 0 gems/{bp => block-parser}/Rakefile | 0 gems/{bp => block-parser}/bin/console | 0 gems/{bp => block-parser}/bin/parse_block | 0 gems/{bp => block-parser}/bin/setup | 0 gems/{bp => block-parser}/block_parser-0.1.0.gem | Bin gems/{bp => block-parser}/block_parser.gemspec | 0 gems/{bp => block-parser}/build | 0 gems/{bp => block-parser}/lib/block_parser.rb | 0 .../lib/block_parser/build_challenge.rb | 0 .../lib/block_parser/build_html.rb | 0 .../lib/block_parser/build_html_from_md_json.rb | 0 .../lib/block_parser/build_image_link.rb | 0 .../lib/block_parser/build_link.rb | 0 .../build_title_from_filename_and_html.rb | 0 .../block_parser/challenge_validators/challenge.rb | 0 .../challenge_validators/challenge_validator.rb | 0 .../checkbox_challenge_validator.rb | 0 .../code_snippet_challenge_validator.rb | 0 .../custom_snippet_challenge_validator.rb | 0 .../local_snippet_challenge_validator.rb | 0 .../multiple_choice_challenge_validator.rb | 0 .../number_challenge_validator.rb | 0 .../paragraph_challenge_validator.rb | 0 .../poll_challenge_validator.rb | 0 .../project_challenge_validator.rb | 0 .../short_answer_challenge_validator.rb | 0 .../testable_project_challenge_validator.rb | 0 .../lib/block_parser/convert_md_to_json.rb | 0 .../lib/block_parser/parse_directory.rb | 0 .../lib/block_parser/parse_markdown_file.rb | 0 .../lib/block_parser/parse_standards.rb | 0 .../lib/block_parser/version.rb | 0 .../{bp => block-parser}/pkg/block_parser-0.1.0.gem | Bin .../spec/build_challenge_spec.rb | 0 .../spec/build_html_from_md_json_spec.rb | 0 gems/{bp => block-parser}/spec/build_html_spec.rb | 0 .../spec/build_image_link_spec.rb | 0 gems/{bp => block-parser}/spec/build_link_spec.rb | 0 .../spec/build_title_from_filename_and_html_spec.rb | 0 .../spec/challenge_validators/challenge_spec.rb | 0 .../challenge_validator_spec.rb | 0 .../checkbox_challenge_validator_spec.rb | 0 .../code_snippet_challenge_validator_spec.rb | 0 .../custom_snippet_challenge_validator_spec.rb | 0 .../multiple_choice_challenge_validator_spec.rb | 0 .../number_challenge_validator_spec.rb | 0 .../project_challenge_validator_spec.rb | 0 .../short_answer_challenge_validator_spec.rb | 0 .../testable_project_challenge_validator_spec.rb | 0 .../spec/convert_md_to_json_spec.rb | 0 .../bad-challenges.md | 0 .../checkpoint-with-bad-challenge-tag/config.yaml | 0 .../config.yaml | 0 .../no-challenges.md | 0 .../invalid-config-test-block-repo/config.yaml | 0 .../fixtures/no-lessons-nor-checkpoints/config.yaml | 0 .../no-lessons-nor-checkpoints/resources.md | 0 .../no-standards-config-test-block-repo/config.yaml | 0 .../spec/fixtures/sample-iframe.md | 0 .../spec/fixtures/test-block-repo-yml/config.yml | 0 .../spec/fixtures/test-block-repo-yml/dummy.pdf | Bin .../fixtures/test-block-repo-yml/folder/sibling.md | 0 .../fixtures/test-block-repo-yml/folder/target.md | 0 .../test-block-repo-yml/images/galvanize-logo.png | Bin .../test-block-repo-yml/images/register_klass.gif | Bin .../spec/fixtures/test-block-repo-yml/lessoN.md | 0 .../test-block-repo-yml/markdown-smoketest.md | 0 .../spec/fixtures/test-block-repo-yml/target.md | 0 .../spec/fixtures/test-block-repo/README.md | 0 .../spec/fixtures/test-block-repo/config.yaml | 0 .../spec/fixtures/test-block-repo/folder/sibling.md | 0 .../spec/fixtures/test-block-repo/folder/target.md | 0 .../test-block-repo/images/galvanize-logo.png | Bin .../test-block-repo/images/register_klass.gif | Bin .../spec/fixtures/test-block-repo/ipynb-test.ipynb | 0 .../spec/fixtures/test-block-repo/ipynb-test.md | 0 .../spec/fixtures/test-block-repo/lessoN.md | 0 .../fixtures/test-block-repo/markdown-smoketest.md | 0 .../spec/fixtures/test-block-repo/target.md | 0 .../fixtures/test-block-two-configs/config.yaml | 0 .../spec/fixtures/test-block-two-configs/config.yml | 0 .../Dockerfile | 0 .../test.sh | 0 .../valid-yaml-array-test-block-repo/config.yaml | 0 .../spec/parse_directory_spec.rb | 0 .../spec/parse_markdown_file_spec.rb | 0 .../spec/parse_standards_spec.rb | 0 gems/{bp => block-parser}/spec/spec_helper.rb | 0 .../{bp => block-parser}/spec/support/freeloader.rb | 0 gems/{bp => block-parser}/spec/support/mocktokit.rb | 0 gems/{bp => block-parser}/spec/tmp/.keep | 0 gems/{bp => block-parser}/validate-block | 0 96 files changed, 0 insertions(+), 0 deletions(-) rename gems/{bp => block-parser}/Dockerfile (100%) rename gems/{bp => block-parser}/Gemfile (100%) rename gems/{bp => block-parser}/Gemfile.lock (100%) rename gems/{bp => block-parser}/README.md (100%) rename gems/{bp => block-parser}/Rakefile (100%) rename gems/{bp => block-parser}/bin/console (100%) rename gems/{bp => block-parser}/bin/parse_block (100%) rename gems/{bp => block-parser}/bin/setup (100%) rename gems/{bp => block-parser}/block_parser-0.1.0.gem (100%) rename gems/{bp => block-parser}/block_parser.gemspec (100%) rename gems/{bp => block-parser}/build (100%) rename gems/{bp => block-parser}/lib/block_parser.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/build_challenge.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/build_html.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/build_html_from_md_json.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/build_image_link.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/build_link.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/build_title_from_filename_and_html.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/challenge.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/number_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/poll_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/project_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/convert_md_to_json.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/parse_directory.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/parse_markdown_file.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/parse_standards.rb (100%) rename gems/{bp => block-parser}/lib/block_parser/version.rb (100%) rename gems/{bp => block-parser}/pkg/block_parser-0.1.0.gem (100%) rename gems/{bp => block-parser}/spec/build_challenge_spec.rb (100%) rename gems/{bp => block-parser}/spec/build_html_from_md_json_spec.rb (100%) rename gems/{bp => block-parser}/spec/build_html_spec.rb (100%) rename gems/{bp => block-parser}/spec/build_image_link_spec.rb (100%) rename gems/{bp => block-parser}/spec/build_link_spec.rb (100%) rename gems/{bp => block-parser}/spec/build_title_from_filename_and_html_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/challenge_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/challenge_validator_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/checkbox_challenge_validator_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/code_snippet_challenge_validator_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/number_challenge_validator_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/project_challenge_validator_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/short_answer_challenge_validator_spec.rb (100%) rename gems/{bp => block-parser}/spec/challenge_validators/testable_project_challenge_validator_spec.rb (100%) rename gems/{bp => block-parser}/spec/convert_md_to_json_spec.rb (100%) rename gems/{bp => block-parser}/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md (100%) rename gems/{bp => block-parser}/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml (100%) rename gems/{bp => block-parser}/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml (100%) rename gems/{bp => block-parser}/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md (100%) rename gems/{bp => block-parser}/spec/fixtures/invalid-config-test-block-repo/config.yaml (100%) rename gems/{bp => block-parser}/spec/fixtures/no-lessons-nor-checkpoints/config.yaml (100%) rename gems/{bp => block-parser}/spec/fixtures/no-lessons-nor-checkpoints/resources.md (100%) rename gems/{bp => block-parser}/spec/fixtures/no-standards-config-test-block-repo/config.yaml (100%) rename gems/{bp => block-parser}/spec/fixtures/sample-iframe.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo-yml/config.yml (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo-yml/dummy.pdf (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo-yml/folder/sibling.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo-yml/folder/target.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo-yml/images/register_klass.gif (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo-yml/lessoN.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo-yml/markdown-smoketest.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo-yml/target.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/README.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/config.yaml (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/folder/sibling.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/folder/target.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/images/galvanize-logo.png (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/images/register_klass.gif (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/ipynb-test.ipynb (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/ipynb-test.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/lessoN.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/markdown-smoketest.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-repo/target.md (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-two-configs/config.yaml (100%) rename gems/{bp => block-parser}/spec/fixtures/test-block-two-configs/config.yml (100%) rename gems/{bp => block-parser}/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile (100%) rename gems/{bp => block-parser}/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh (100%) rename gems/{bp => block-parser}/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml (100%) rename gems/{bp => block-parser}/spec/parse_directory_spec.rb (100%) rename gems/{bp => block-parser}/spec/parse_markdown_file_spec.rb (100%) rename gems/{bp => block-parser}/spec/parse_standards_spec.rb (100%) rename gems/{bp => block-parser}/spec/spec_helper.rb (100%) rename gems/{bp => block-parser}/spec/support/freeloader.rb (100%) rename gems/{bp => block-parser}/spec/support/mocktokit.rb (100%) rename gems/{bp => block-parser}/spec/tmp/.keep (100%) rename gems/{bp => block-parser}/validate-block (100%) diff --git a/gems/bp/Dockerfile b/gems/block-parser/Dockerfile similarity index 100% rename from gems/bp/Dockerfile rename to gems/block-parser/Dockerfile diff --git a/gems/bp/Gemfile b/gems/block-parser/Gemfile similarity index 100% rename from gems/bp/Gemfile rename to gems/block-parser/Gemfile diff --git a/gems/bp/Gemfile.lock b/gems/block-parser/Gemfile.lock similarity index 100% rename from gems/bp/Gemfile.lock rename to gems/block-parser/Gemfile.lock diff --git a/gems/bp/README.md b/gems/block-parser/README.md similarity index 100% rename from gems/bp/README.md rename to gems/block-parser/README.md diff --git a/gems/bp/Rakefile b/gems/block-parser/Rakefile similarity index 100% rename from gems/bp/Rakefile rename to gems/block-parser/Rakefile diff --git a/gems/bp/bin/console b/gems/block-parser/bin/console similarity index 100% rename from gems/bp/bin/console rename to gems/block-parser/bin/console diff --git a/gems/bp/bin/parse_block b/gems/block-parser/bin/parse_block similarity index 100% rename from gems/bp/bin/parse_block rename to gems/block-parser/bin/parse_block diff --git a/gems/bp/bin/setup b/gems/block-parser/bin/setup similarity index 100% rename from gems/bp/bin/setup rename to gems/block-parser/bin/setup diff --git a/gems/bp/block_parser-0.1.0.gem b/gems/block-parser/block_parser-0.1.0.gem similarity index 100% rename from gems/bp/block_parser-0.1.0.gem rename to gems/block-parser/block_parser-0.1.0.gem diff --git a/gems/bp/block_parser.gemspec b/gems/block-parser/block_parser.gemspec similarity index 100% rename from gems/bp/block_parser.gemspec rename to gems/block-parser/block_parser.gemspec diff --git a/gems/bp/build b/gems/block-parser/build similarity index 100% rename from gems/bp/build rename to gems/block-parser/build diff --git a/gems/bp/lib/block_parser.rb b/gems/block-parser/lib/block_parser.rb similarity index 100% rename from gems/bp/lib/block_parser.rb rename to gems/block-parser/lib/block_parser.rb diff --git a/gems/bp/lib/block_parser/build_challenge.rb b/gems/block-parser/lib/block_parser/build_challenge.rb similarity index 100% rename from gems/bp/lib/block_parser/build_challenge.rb rename to gems/block-parser/lib/block_parser/build_challenge.rb diff --git a/gems/bp/lib/block_parser/build_html.rb b/gems/block-parser/lib/block_parser/build_html.rb similarity index 100% rename from gems/bp/lib/block_parser/build_html.rb rename to gems/block-parser/lib/block_parser/build_html.rb diff --git a/gems/bp/lib/block_parser/build_html_from_md_json.rb b/gems/block-parser/lib/block_parser/build_html_from_md_json.rb similarity index 100% rename from gems/bp/lib/block_parser/build_html_from_md_json.rb rename to gems/block-parser/lib/block_parser/build_html_from_md_json.rb diff --git a/gems/bp/lib/block_parser/build_image_link.rb b/gems/block-parser/lib/block_parser/build_image_link.rb similarity index 100% rename from gems/bp/lib/block_parser/build_image_link.rb rename to gems/block-parser/lib/block_parser/build_image_link.rb diff --git a/gems/bp/lib/block_parser/build_link.rb b/gems/block-parser/lib/block_parser/build_link.rb similarity index 100% rename from gems/bp/lib/block_parser/build_link.rb rename to gems/block-parser/lib/block_parser/build_link.rb diff --git a/gems/bp/lib/block_parser/build_title_from_filename_and_html.rb b/gems/block-parser/lib/block_parser/build_title_from_filename_and_html.rb similarity index 100% rename from gems/bp/lib/block_parser/build_title_from_filename_and_html.rb rename to gems/block-parser/lib/block_parser/build_title_from_filename_and_html.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/challenge.rb b/gems/block-parser/lib/block_parser/challenge_validators/challenge.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/challenge.rb rename to gems/block-parser/lib/block_parser/challenge_validators/challenge.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/number_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/number_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/number_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/number_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/poll_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/poll_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/poll_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/poll_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/project_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/project_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/project_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/project_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb similarity index 100% rename from gems/bp/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb rename to gems/block-parser/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb diff --git a/gems/bp/lib/block_parser/convert_md_to_json.rb b/gems/block-parser/lib/block_parser/convert_md_to_json.rb similarity index 100% rename from gems/bp/lib/block_parser/convert_md_to_json.rb rename to gems/block-parser/lib/block_parser/convert_md_to_json.rb diff --git a/gems/bp/lib/block_parser/parse_directory.rb b/gems/block-parser/lib/block_parser/parse_directory.rb similarity index 100% rename from gems/bp/lib/block_parser/parse_directory.rb rename to gems/block-parser/lib/block_parser/parse_directory.rb diff --git a/gems/bp/lib/block_parser/parse_markdown_file.rb b/gems/block-parser/lib/block_parser/parse_markdown_file.rb similarity index 100% rename from gems/bp/lib/block_parser/parse_markdown_file.rb rename to gems/block-parser/lib/block_parser/parse_markdown_file.rb diff --git a/gems/bp/lib/block_parser/parse_standards.rb b/gems/block-parser/lib/block_parser/parse_standards.rb similarity index 100% rename from gems/bp/lib/block_parser/parse_standards.rb rename to gems/block-parser/lib/block_parser/parse_standards.rb diff --git a/gems/bp/lib/block_parser/version.rb b/gems/block-parser/lib/block_parser/version.rb similarity index 100% rename from gems/bp/lib/block_parser/version.rb rename to gems/block-parser/lib/block_parser/version.rb diff --git a/gems/bp/pkg/block_parser-0.1.0.gem b/gems/block-parser/pkg/block_parser-0.1.0.gem similarity index 100% rename from gems/bp/pkg/block_parser-0.1.0.gem rename to gems/block-parser/pkg/block_parser-0.1.0.gem diff --git a/gems/bp/spec/build_challenge_spec.rb b/gems/block-parser/spec/build_challenge_spec.rb similarity index 100% rename from gems/bp/spec/build_challenge_spec.rb rename to gems/block-parser/spec/build_challenge_spec.rb diff --git a/gems/bp/spec/build_html_from_md_json_spec.rb b/gems/block-parser/spec/build_html_from_md_json_spec.rb similarity index 100% rename from gems/bp/spec/build_html_from_md_json_spec.rb rename to gems/block-parser/spec/build_html_from_md_json_spec.rb diff --git a/gems/bp/spec/build_html_spec.rb b/gems/block-parser/spec/build_html_spec.rb similarity index 100% rename from gems/bp/spec/build_html_spec.rb rename to gems/block-parser/spec/build_html_spec.rb diff --git a/gems/bp/spec/build_image_link_spec.rb b/gems/block-parser/spec/build_image_link_spec.rb similarity index 100% rename from gems/bp/spec/build_image_link_spec.rb rename to gems/block-parser/spec/build_image_link_spec.rb diff --git a/gems/bp/spec/build_link_spec.rb b/gems/block-parser/spec/build_link_spec.rb similarity index 100% rename from gems/bp/spec/build_link_spec.rb rename to gems/block-parser/spec/build_link_spec.rb diff --git a/gems/bp/spec/build_title_from_filename_and_html_spec.rb b/gems/block-parser/spec/build_title_from_filename_and_html_spec.rb similarity index 100% rename from gems/bp/spec/build_title_from_filename_and_html_spec.rb rename to gems/block-parser/spec/build_title_from_filename_and_html_spec.rb diff --git a/gems/bp/spec/challenge_validators/challenge_spec.rb b/gems/block-parser/spec/challenge_validators/challenge_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/challenge_spec.rb rename to gems/block-parser/spec/challenge_validators/challenge_spec.rb diff --git a/gems/bp/spec/challenge_validators/challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/challenge_validator_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/challenge_validator_spec.rb rename to gems/block-parser/spec/challenge_validators/challenge_validator_spec.rb diff --git a/gems/bp/spec/challenge_validators/checkbox_challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/checkbox_challenge_validator_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/checkbox_challenge_validator_spec.rb rename to gems/block-parser/spec/challenge_validators/checkbox_challenge_validator_spec.rb diff --git a/gems/bp/spec/challenge_validators/code_snippet_challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/code_snippet_challenge_validator_spec.rb rename to gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb diff --git a/gems/bp/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb rename to gems/block-parser/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb diff --git a/gems/bp/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb rename to gems/block-parser/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb diff --git a/gems/bp/spec/challenge_validators/number_challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/number_challenge_validator_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/number_challenge_validator_spec.rb rename to gems/block-parser/spec/challenge_validators/number_challenge_validator_spec.rb diff --git a/gems/bp/spec/challenge_validators/project_challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/project_challenge_validator_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/project_challenge_validator_spec.rb rename to gems/block-parser/spec/challenge_validators/project_challenge_validator_spec.rb diff --git a/gems/bp/spec/challenge_validators/short_answer_challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/short_answer_challenge_validator_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/short_answer_challenge_validator_spec.rb rename to gems/block-parser/spec/challenge_validators/short_answer_challenge_validator_spec.rb diff --git a/gems/bp/spec/challenge_validators/testable_project_challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/testable_project_challenge_validator_spec.rb similarity index 100% rename from gems/bp/spec/challenge_validators/testable_project_challenge_validator_spec.rb rename to gems/block-parser/spec/challenge_validators/testable_project_challenge_validator_spec.rb diff --git a/gems/bp/spec/convert_md_to_json_spec.rb b/gems/block-parser/spec/convert_md_to_json_spec.rb similarity index 100% rename from gems/bp/spec/convert_md_to_json_spec.rb rename to gems/block-parser/spec/convert_md_to_json_spec.rb diff --git a/gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md b/gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md similarity index 100% rename from gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md rename to gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md diff --git a/gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml b/gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml similarity index 100% rename from gems/bp/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml rename to gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml diff --git a/gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml b/gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml similarity index 100% rename from gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml rename to gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml diff --git a/gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md b/gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md similarity index 100% rename from gems/bp/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md rename to gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md diff --git a/gems/bp/spec/fixtures/invalid-config-test-block-repo/config.yaml b/gems/block-parser/spec/fixtures/invalid-config-test-block-repo/config.yaml similarity index 100% rename from gems/bp/spec/fixtures/invalid-config-test-block-repo/config.yaml rename to gems/block-parser/spec/fixtures/invalid-config-test-block-repo/config.yaml diff --git a/gems/bp/spec/fixtures/no-lessons-nor-checkpoints/config.yaml b/gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/config.yaml similarity index 100% rename from gems/bp/spec/fixtures/no-lessons-nor-checkpoints/config.yaml rename to gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/config.yaml diff --git a/gems/bp/spec/fixtures/no-lessons-nor-checkpoints/resources.md b/gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/resources.md similarity index 100% rename from gems/bp/spec/fixtures/no-lessons-nor-checkpoints/resources.md rename to gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/resources.md diff --git a/gems/bp/spec/fixtures/no-standards-config-test-block-repo/config.yaml b/gems/block-parser/spec/fixtures/no-standards-config-test-block-repo/config.yaml similarity index 100% rename from gems/bp/spec/fixtures/no-standards-config-test-block-repo/config.yaml rename to gems/block-parser/spec/fixtures/no-standards-config-test-block-repo/config.yaml diff --git a/gems/bp/spec/fixtures/sample-iframe.md b/gems/block-parser/spec/fixtures/sample-iframe.md similarity index 100% rename from gems/bp/spec/fixtures/sample-iframe.md rename to gems/block-parser/spec/fixtures/sample-iframe.md diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/config.yml b/gems/block-parser/spec/fixtures/test-block-repo-yml/config.yml similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo-yml/config.yml rename to gems/block-parser/spec/fixtures/test-block-repo-yml/config.yml diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/dummy.pdf b/gems/block-parser/spec/fixtures/test-block-repo-yml/dummy.pdf similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo-yml/dummy.pdf rename to gems/block-parser/spec/fixtures/test-block-repo-yml/dummy.pdf diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/folder/sibling.md b/gems/block-parser/spec/fixtures/test-block-repo-yml/folder/sibling.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo-yml/folder/sibling.md rename to gems/block-parser/spec/fixtures/test-block-repo-yml/folder/sibling.md diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/folder/target.md b/gems/block-parser/spec/fixtures/test-block-repo-yml/folder/target.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo-yml/folder/target.md rename to gems/block-parser/spec/fixtures/test-block-repo-yml/folder/target.md diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png b/gems/block-parser/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png rename to gems/block-parser/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/images/register_klass.gif b/gems/block-parser/spec/fixtures/test-block-repo-yml/images/register_klass.gif similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo-yml/images/register_klass.gif rename to gems/block-parser/spec/fixtures/test-block-repo-yml/images/register_klass.gif diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/lessoN.md b/gems/block-parser/spec/fixtures/test-block-repo-yml/lessoN.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo-yml/lessoN.md rename to gems/block-parser/spec/fixtures/test-block-repo-yml/lessoN.md diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/markdown-smoketest.md b/gems/block-parser/spec/fixtures/test-block-repo-yml/markdown-smoketest.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo-yml/markdown-smoketest.md rename to gems/block-parser/spec/fixtures/test-block-repo-yml/markdown-smoketest.md diff --git a/gems/bp/spec/fixtures/test-block-repo-yml/target.md b/gems/block-parser/spec/fixtures/test-block-repo-yml/target.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo-yml/target.md rename to gems/block-parser/spec/fixtures/test-block-repo-yml/target.md diff --git a/gems/bp/spec/fixtures/test-block-repo/README.md b/gems/block-parser/spec/fixtures/test-block-repo/README.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/README.md rename to gems/block-parser/spec/fixtures/test-block-repo/README.md diff --git a/gems/bp/spec/fixtures/test-block-repo/config.yaml b/gems/block-parser/spec/fixtures/test-block-repo/config.yaml similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/config.yaml rename to gems/block-parser/spec/fixtures/test-block-repo/config.yaml diff --git a/gems/bp/spec/fixtures/test-block-repo/folder/sibling.md b/gems/block-parser/spec/fixtures/test-block-repo/folder/sibling.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/folder/sibling.md rename to gems/block-parser/spec/fixtures/test-block-repo/folder/sibling.md diff --git a/gems/bp/spec/fixtures/test-block-repo/folder/target.md b/gems/block-parser/spec/fixtures/test-block-repo/folder/target.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/folder/target.md rename to gems/block-parser/spec/fixtures/test-block-repo/folder/target.md diff --git a/gems/bp/spec/fixtures/test-block-repo/images/galvanize-logo.png b/gems/block-parser/spec/fixtures/test-block-repo/images/galvanize-logo.png similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/images/galvanize-logo.png rename to gems/block-parser/spec/fixtures/test-block-repo/images/galvanize-logo.png diff --git a/gems/bp/spec/fixtures/test-block-repo/images/register_klass.gif b/gems/block-parser/spec/fixtures/test-block-repo/images/register_klass.gif similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/images/register_klass.gif rename to gems/block-parser/spec/fixtures/test-block-repo/images/register_klass.gif diff --git a/gems/bp/spec/fixtures/test-block-repo/ipynb-test.ipynb b/gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.ipynb similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/ipynb-test.ipynb rename to gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.ipynb diff --git a/gems/bp/spec/fixtures/test-block-repo/ipynb-test.md b/gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/ipynb-test.md rename to gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.md diff --git a/gems/bp/spec/fixtures/test-block-repo/lessoN.md b/gems/block-parser/spec/fixtures/test-block-repo/lessoN.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/lessoN.md rename to gems/block-parser/spec/fixtures/test-block-repo/lessoN.md diff --git a/gems/bp/spec/fixtures/test-block-repo/markdown-smoketest.md b/gems/block-parser/spec/fixtures/test-block-repo/markdown-smoketest.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/markdown-smoketest.md rename to gems/block-parser/spec/fixtures/test-block-repo/markdown-smoketest.md diff --git a/gems/bp/spec/fixtures/test-block-repo/target.md b/gems/block-parser/spec/fixtures/test-block-repo/target.md similarity index 100% rename from gems/bp/spec/fixtures/test-block-repo/target.md rename to gems/block-parser/spec/fixtures/test-block-repo/target.md diff --git a/gems/bp/spec/fixtures/test-block-two-configs/config.yaml b/gems/block-parser/spec/fixtures/test-block-two-configs/config.yaml similarity index 100% rename from gems/bp/spec/fixtures/test-block-two-configs/config.yaml rename to gems/block-parser/spec/fixtures/test-block-two-configs/config.yaml diff --git a/gems/bp/spec/fixtures/test-block-two-configs/config.yml b/gems/block-parser/spec/fixtures/test-block-two-configs/config.yml similarity index 100% rename from gems/bp/spec/fixtures/test-block-two-configs/config.yml rename to gems/block-parser/spec/fixtures/test-block-two-configs/config.yml diff --git a/gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile b/gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile similarity index 100% rename from gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile rename to gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile diff --git a/gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh b/gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh similarity index 100% rename from gems/bp/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh rename to gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh diff --git a/gems/bp/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml b/gems/block-parser/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml similarity index 100% rename from gems/bp/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml rename to gems/block-parser/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml diff --git a/gems/bp/spec/parse_directory_spec.rb b/gems/block-parser/spec/parse_directory_spec.rb similarity index 100% rename from gems/bp/spec/parse_directory_spec.rb rename to gems/block-parser/spec/parse_directory_spec.rb diff --git a/gems/bp/spec/parse_markdown_file_spec.rb b/gems/block-parser/spec/parse_markdown_file_spec.rb similarity index 100% rename from gems/bp/spec/parse_markdown_file_spec.rb rename to gems/block-parser/spec/parse_markdown_file_spec.rb diff --git a/gems/bp/spec/parse_standards_spec.rb b/gems/block-parser/spec/parse_standards_spec.rb similarity index 100% rename from gems/bp/spec/parse_standards_spec.rb rename to gems/block-parser/spec/parse_standards_spec.rb diff --git a/gems/bp/spec/spec_helper.rb b/gems/block-parser/spec/spec_helper.rb similarity index 100% rename from gems/bp/spec/spec_helper.rb rename to gems/block-parser/spec/spec_helper.rb diff --git a/gems/bp/spec/support/freeloader.rb b/gems/block-parser/spec/support/freeloader.rb similarity index 100% rename from gems/bp/spec/support/freeloader.rb rename to gems/block-parser/spec/support/freeloader.rb diff --git a/gems/bp/spec/support/mocktokit.rb b/gems/block-parser/spec/support/mocktokit.rb similarity index 100% rename from gems/bp/spec/support/mocktokit.rb rename to gems/block-parser/spec/support/mocktokit.rb diff --git a/gems/bp/spec/tmp/.keep b/gems/block-parser/spec/tmp/.keep similarity index 100% rename from gems/bp/spec/tmp/.keep rename to gems/block-parser/spec/tmp/.keep diff --git a/gems/bp/validate-block b/gems/block-parser/validate-block similarity index 100% rename from gems/bp/validate-block rename to gems/block-parser/validate-block -- GitLab From 5ed96587f23cbc1ecb4700f700f4e0a9827157c7 Mon Sep 17 00:00:00 2001 From: Benton Gallun Date: Tue, 22 Sep 2020 01:50:11 +0000 Subject: [PATCH 052/287] change image target to registry1.dsop.io/ironbank/opensource/ruby/ruby26:latest --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index cbbb371..6f5acc2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ -FROM bitnami/ruby:2.6.0 - +FROM registry1.dsop.io/ironbank/opensource/ruby/ruby26:latest ENV count 6 # Install what we can through OS package management -- GitLab From 3cd19fe5de275fb2bd683ea86dc851f8e4499559 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Tue, 22 Sep 2020 08:00:13 -1000 Subject: [PATCH 053/287] Use bitnami ruby image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6f5acc2..e78d68a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM registry1.dsop.io/ironbank/opensource/ruby/ruby26:latest +FROM bitnami/ruby:2.6.0 ENV count 6 # Install what we can through OS package management -- GitLab From 5458fe932233558742dd7740b150f3224fe75f3c Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 22 Sep 2020 11:28:00 -1000 Subject: [PATCH 054/287] creating new job runners for code snippets. changing to queue jobs on specific queues per language. --- app/jobs/java_code_evaluation_job.rb | 7 +++ app/jobs/javascript_code_evaluation_job.rb | 7 +++ app/jobs/python_code_evaluation_job.rb | 7 +++ app/services/assessment_service.rb | 54 ++++++++++++++-------- 4 files changed, 57 insertions(+), 18 deletions(-) create mode 100644 app/jobs/java_code_evaluation_job.rb create mode 100644 app/jobs/javascript_code_evaluation_job.rb create mode 100644 app/jobs/python_code_evaluation_job.rb diff --git a/app/jobs/java_code_evaluation_job.rb b/app/jobs/java_code_evaluation_job.rb new file mode 100644 index 0000000..27be584 --- /dev/null +++ b/app/jobs/java_code_evaluation_job.rb @@ -0,0 +1,7 @@ +class JavaCodeEvaluationJob < ApplicationJob + queue_as :java_evaluation + + def perform(setup:, code_to_assess:, tests_to_assess_against:, callback_url:) + puts "it ran" + end +end diff --git a/app/jobs/javascript_code_evaluation_job.rb b/app/jobs/javascript_code_evaluation_job.rb new file mode 100644 index 0000000..2432051 --- /dev/null +++ b/app/jobs/javascript_code_evaluation_job.rb @@ -0,0 +1,7 @@ +class JavascriptCodeEvaluationJob < ApplicationJob + queue_as :javascript_evaluation + + def perform(setup:, code_to_assess:, tests_to_assess_against:, callback_url:) + puts "it ran" + end +end diff --git a/app/jobs/python_code_evaluation_job.rb b/app/jobs/python_code_evaluation_job.rb new file mode 100644 index 0000000..4248a73 --- /dev/null +++ b/app/jobs/python_code_evaluation_job.rb @@ -0,0 +1,7 @@ +class PythonCodeEvaluationJob < ApplicationJob + queue_as :python_evaluation + + def perform(setup:, code_to_assess:, tests_to_assess_against:, callback_url:) + puts "it ran" + end +end diff --git a/app/services/assessment_service.rb b/app/services/assessment_service.rb index 6f2b01c..49f228d 100644 --- a/app/services/assessment_service.rb +++ b/app/services/assessment_service.rb @@ -2,27 +2,45 @@ class AssessmentService GITHUB_HOST = "github.com".freeze def self.evaluate_code_snippet(challenge, submitted_challenge_answer, callback_url) - connection = Faraday.new(url: Rails.application.secrets.assessments_service_domain) - connection.authorization :Token, token: Rails.application.secrets.assessments_api_key + args = { + setup: challenge.setup, + code_to_assess: submitted_challenge_answer.answer, + tests_to_assess_against: challenge.tests, + callback_url: callback_url + } - begin - response = connection.post do |req| - req.url "/assessments" - req.headers["Content-Type"] = "application/json" - req.body = { "code_to_assess": submitted_challenge_answer.answer, - "setup_to_run_before_code": challenge.setup, - "db_checksum": challenge.data_path_checksum, - "tests_to_assess_against": challenge.tests, - "language": challenge.language, - "callback_url": callback_url }.to_json - end - rescue Faraday::ConnectionFailed => e - Honeybadger.notify(e) - submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") and return + if challenge.language == "javascript" + JavascriptCodeEvaluationJob.perform_later(args) + elsif challenge.language == "java" + JavaCodeEvaluationJob.perform_later(args) + elsif challenge.language.include?("python") + PythonCodeEvaluationJob.perform_later(args) + else + puts "Unfamiliar language: #{challenge.language}" end - submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") if response.status != 200 - response + + # connection = Faraday.new(url: Rails.application.secrets.assessments_service_domain) + # connection.authorization :Token, token: Rails.application.secrets.assessments_api_key + # + # begin + # response = connection.post do |req| + # req.url "/assessments" + # req.headers["Content-Type"] = "application/json" + # req.body = { "code_to_assess": submitted_challenge_answer.answer, + # "setup_to_run_before_code": challenge.setup, + # "db_checksum": challenge.data_path_checksum, + # "tests_to_assess_against": challenge.tests, + # "language": challenge.language, + # "callback_url": callback_url }.to_json + # end + # rescue Faraday::ConnectionFailed => e + # Honeybadger.notify(e) + # submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") and return + # end + # submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") if response.status != 200 + # + # response end def self.evaluate_project(submitted_challenge_answer, callback_url) -- GitLab From 24d36c442a14197e7d8de58ca2455788ba36ca0f Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Tue, 22 Sep 2020 12:38:39 -1000 Subject: [PATCH 055/287] Fix s3 endpoints --- app/services/download_s3_repository_service.rb | 2 +- config/initializers/aws.rb | 2 +- config/secrets.yml | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/services/download_s3_repository_service.rb b/app/services/download_s3_repository_service.rb index 5f26fe3..8f41e3c 100644 --- a/app/services/download_s3_repository_service.rb +++ b/app/services/download_s3_repository_service.rb @@ -9,7 +9,7 @@ class DownloadS3RepositoryService < DownloadRepositoryService tmp_zip_file = tmp_zip_path(@tmp_path) File.open(tmp_zip_file, "wb") do |f| - client.get_object(bucket: Rails.application.secrets.s3_bucket_name, + client.get_object(bucket: Rails.application.secrets.glearn_bucket_name, key: @s3_key) { |chunk| f.write(chunk) } end diff --git a/config/initializers/aws.rb b/config/initializers/aws.rb index 606c756..9bc4636 100644 --- a/config/initializers/aws.rb +++ b/config/initializers/aws.rb @@ -35,5 +35,5 @@ end Rails.application.config.aws = if Rails.env.test? AwsMock.new else - Aws::S3::Resource.new(region: "us-west-2").bucket(Rails.application.secrets.s3_bucket_name) + Aws::S3::Resource.new(region: "us-gov-west-1").bucket(Rails.application.secrets.glearn_bucket_name) end diff --git a/config/secrets.yml b/config/secrets.yml index fd7c36f..ec63e6f 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -56,7 +56,6 @@ development: auth_url: http://localhost:5000 auth_webhook_token: 5d3b97936810572da8568e2712ec4557ffeecf0a06f3c77fcee5b5846c3389dc protocol: http:// - s3_bucket_name: forge-development.galvanize.com secret_key_base: 6f494a506dc84ee2752abbe81ec8168d6ce27ae30d6e392ecd6411b8224399089821759983823d433afa7662dfde34b4c4af3c6e7a7343c3d2dc146255c15da4 dev_notify_slack_url: development -- GitLab From 8fddc057cfda4bde1bc3b10682b5169f87fdd338 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 22 Sep 2020 14:10:29 -1000 Subject: [PATCH 056/287] changing s3 region to come from secrets.yml and env file --- .env.example | 3 ++- config/initializers/aws.rb | 2 +- config/secrets.yml | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 100441f..b8be933 100644 --- a/.env.example +++ b/.env.example @@ -10,4 +10,5 @@ STANDARD_EVENT_UIDS="40f3bb48-1d23-4105-a22a-7f07b90bd1e6,cc22ef7e-d9c9-4e3d-ae9 KEYCLOAK_CLIENT_ID=forge KEYCLOAK_CLIENT_SECRET=487b8a4a-e437-4795-9e1e-b68a9c340afb KEYCLOAK_ENDPOINT=http://localhost:8080 -KEYCLOAK_REALM=learn \ No newline at end of file +KEYCLOAK_REALM=learn +S3_REGION=us-gov-west-1 \ No newline at end of file diff --git a/config/initializers/aws.rb b/config/initializers/aws.rb index 9bc4636..9694d1d 100644 --- a/config/initializers/aws.rb +++ b/config/initializers/aws.rb @@ -35,5 +35,5 @@ end Rails.application.config.aws = if Rails.env.test? AwsMock.new else - Aws::S3::Resource.new(region: "us-gov-west-1").bucket(Rails.application.secrets.glearn_bucket_name) + Aws::S3::Resource.new(region: Rails.application.secrets.s3_region).bucket(Rails.application.secrets.glearn_bucket_name) end diff --git a/config/secrets.yml b/config/secrets.yml index ec63e6f..b8dcb24 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -34,6 +34,7 @@ shared: glearn_secret_access_key: <%= ENV['GLEARN_SECRET_ACCESS_KEY'] %> glearn_key_prefix: <%= ENV['GLEARN_KEY_PREFIX'] %> glearn_bucket_name: <%= ENV['GLEARN_BUCKET_NAME'] %> + s3_region: <%= ENV['S3_REGION'] %> dev_notify_slack_url: <%= ENV['DEV_NOTIFY_SLACK_URL'] %> git_download_tokens: github_com: -- GitLab From 3a452d19f999878ecd66806b72c7224d770dcfc4 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 23 Sep 2020 09:53:15 -1000 Subject: [PATCH 057/287] removing pulling roles from keycloak --- app/services/keycloak_resolver_service.rb | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/app/services/keycloak_resolver_service.rb b/app/services/keycloak_resolver_service.rb index 27f7fc3..9ccd1d9 100644 --- a/app/services/keycloak_resolver_service.rb +++ b/app/services/keycloak_resolver_service.rb @@ -5,21 +5,12 @@ class KeycloakResolverService end def find_or_create_user - # default roles to empty array - roles = [] - - # if there are roles in the access token. get the roles from the access token. - if @auth_hash&.extra&.raw_info&.realm_access&.roles - roles = @auth_hash.extra.raw_info.realm_access.roles.to_a - end - existing_user = User.find_by(uid: @auth_hash.uid) if existing_user existing_user.update(first_name: @auth_hash.info.first_name, last_name: @auth_hash.info.last_name, - email: @auth_hash.info.email, - roles: roles + email: @auth_hash.info.email ) existing_user @@ -28,7 +19,7 @@ class KeycloakResolverService first_name: @auth_hash.info.first_name, last_name: @auth_hash.info.last_name, email: @auth_hash.info.email, - roles: roles + roles: [] ) user -- GitLab From 0998dcf4b140968f944e8945caeadae9cbbaf2b3 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 23 Sep 2020 12:46:16 -1000 Subject: [PATCH 058/287] adding edit of global roles on the cohort user screens --- app/controllers/users_controller.rb | 10 ++++++++++ app/views/users/edit.html.haml | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 53d6131..9339cb6 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -7,11 +7,17 @@ class UsersController < ApplicationController def edit authorize(current_cohort) current_cohort_user + user end def update authorize(current_cohort) current_cohort_user.update(roles: [params[:instructor]].compact) + + if (user.admin?) + user.update(roles:[params[:forge_admin], params[:forge_blocks_manager], params[:auth_product_admin]].compact) + end + redirect_to users_cohort_path(current_cohort) end @@ -30,4 +36,8 @@ class UsersController < ApplicationController def current_cohort_user @cohort_user ||= CohortUser.find_by(cohort_id: params[:cohort_id], user_id: params[:id]) end + + def user + @user ||= User.find_by(id: params[:id]) + end end diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index a702522..d917643 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -15,6 +15,18 @@ .form-check = check_box_tag :instructor, "forge.instructor", @cohort_user.roles.include?("forge.instructor"), class: "form-check-input" = label :instructor, "Instructor" + - if @user.admin? + %h5 Global Roles: + .form-group + .form-check + = check_box_tag :forge_admin, "forge.admin", @user.roles.include?("forge.admin"), class: "form-check-input" + = label :forge_admin, "Forge Admin" + .form-check + = check_box_tag :forge_blocks_manager, "forge.blocks_manager", @user.roles.include?("forge.blocks_manager"), class: "form-check-input" + = label :forge_blocks_manager, "Forge Blocks Manager" + .form-check + = check_box_tag :auth_product_admin, "auth.product_admin", @user.roles.include?("auth.product_admin"), class: "form-check-input" + = label :auth_product_admin, "Forge Product Admin" = f.submit "Update Cohort User", class: "btn btn-primary" = link_to "Remove User From Cohort", "#my-modal", class: "btn btn-danger", "data-toggle": "modal", style: "float:right;" .modal{id: "my-modal"} -- GitLab From 4345818c13b74649ae6a7dd81f7931874c064c61 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 23 Sep 2020 17:36:14 -1000 Subject: [PATCH 059/287] fixing labels --- app/views/users/edit.html.haml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index d917643..cdc6a68 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -14,19 +14,19 @@ .form-group .form-check = check_box_tag :instructor, "forge.instructor", @cohort_user.roles.include?("forge.instructor"), class: "form-check-input" - = label :instructor, "Instructor" + = label_tag :instructor, "Instructor" - if @user.admin? %h5 Global Roles: .form-group .form-check = check_box_tag :forge_admin, "forge.admin", @user.roles.include?("forge.admin"), class: "form-check-input" - = label :forge_admin, "Forge Admin" + = label_tag :forge_admin, "Forge Admin" .form-check = check_box_tag :forge_blocks_manager, "forge.blocks_manager", @user.roles.include?("forge.blocks_manager"), class: "form-check-input" - = label :forge_blocks_manager, "Forge Blocks Manager" + = label_tag :forge_blocks_manager, "Forge Blocks Manager" .form-check = check_box_tag :auth_product_admin, "auth.product_admin", @user.roles.include?("auth.product_admin"), class: "form-check-input" - = label :auth_product_admin, "Forge Product Admin" + = label_tag :auth_product_admin, "Forge Product Admin" = f.submit "Update Cohort User", class: "btn btn-primary" = link_to "Remove User From Cohort", "#my-modal", class: "btn btn-danger", "data-toggle": "modal", style: "float:right;" .modal{id: "my-modal"} -- GitLab From 46ff9301c312c2590a7c81526578bfdf42a99869 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 24 Sep 2020 12:30:35 -1000 Subject: [PATCH 060/287] changing image uploader --- app/services/encoded_image_link_service.rb | 16 ++++++++++++++++ app/services/release_helper_service.rb | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 app/services/encoded_image_link_service.rb diff --git a/app/services/encoded_image_link_service.rb b/app/services/encoded_image_link_service.rb new file mode 100644 index 0000000..523d3af --- /dev/null +++ b/app/services/encoded_image_link_service.rb @@ -0,0 +1,16 @@ +class EncodedImageLinkService + class Error < StandardError; end + + def initialize + end + + def find_or_create_content(full_path:, directory: nil) + begin + encoded_string = Base64.encode64(File.open(full_path).read) + image_mime_type = MIME::Types.type_for(full_path).first.content_type + return "data:#{image_mime_type};base64, #{encoded_string}" + rescue StandardError => e + raise(Error, e) + end + end +end diff --git a/app/services/release_helper_service.rb b/app/services/release_helper_service.rb index bdcbee1..b719ae5 100644 --- a/app/services/release_helper_service.rb +++ b/app/services/release_helper_service.rb @@ -6,7 +6,7 @@ module ReleaseHelperService path_results = BlockParser::ParseDirectory.new( path: path, - asset_uploader: S3AssetUploaderService.new + asset_uploader: EncodedImageLinkService.new ).execute if path_results[:warnings].any? -- GitLab From c8fee5f4a4b1359370e149a29ded5c586f956034 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 24 Sep 2020 12:34:49 -1000 Subject: [PATCH 061/287] Fixes for gitlab subprojects --- app/models/block.rb | 9 ++++---- app/services/course_validator.rb | 2 +- .../download_gitlab_repository_service.rb | 3 ++- app/services/git_url_service.rb | 23 +++++++++++++++---- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/app/models/block.rb b/app/models/block.rb index 655e017..778a93f 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -2,7 +2,7 @@ class Block < ApplicationRecord validates :title, :repo_name, presence: true validates :title, uniqueness: true validate :repo_is_unique, on: :create - validate :validate_github_org_and_branch + # validate :validate_github_org_and_branch has_many :releases @@ -45,6 +45,10 @@ class Block < ApplicationRecord errors.add(:repo, "URL cannot include a branch") unless repo_name_only? end + def repo_name_only? + repo_name =~ /^[\w.-]*$/ + end + private def repo_is_unique @@ -53,7 +57,4 @@ class Block < ApplicationRecord end end - def repo_name_only? - repo_name =~ /^[\w.-]*$/ - end end diff --git a/app/services/course_validator.rb b/app/services/course_validator.rb index 0be07f2..08a4d1a 100644 --- a/app/services/course_validator.rb +++ b/app/services/course_validator.rb @@ -133,7 +133,7 @@ class CourseValidator json = JSON.parse( open("https://#{git_url.host}/api/v4/projects?search=#{git_url.repository}", "Private-Token" => token).read - ).find {|project| project["path_with_namespace"] == "#{git_url.organization}/#{git_url.repository}"} + ).find {|project| project["path_with_namespace"] == git_url.full_path} raise OpenURI::HTTPError.new("404 Not Found", nil) if json.nil? and return diff --git a/app/services/download_gitlab_repository_service.rb b/app/services/download_gitlab_repository_service.rb index 02611d4..ec5da41 100644 --- a/app/services/download_gitlab_repository_service.rb +++ b/app/services/download_gitlab_repository_service.rb @@ -54,7 +54,8 @@ class DownloadGitlabRepositoryService < DownloadRepositoryService end def gitlab_project_search_by_repo_name - project_search_url = "https://#{@origin}/api/#{GITLAB_API_VERSION}/projects?search=#{@repo_name}&membership=true" + name = @block.repo_name_only? ? @repo_name : @repo_name.split('/').last + project_search_url = "https://#{@origin}/api/#{GITLAB_API_VERSION}/projects?search=#{name}&membership=true" json = read_endpoint_data(project_search_url, headers). find {|project| project["path_with_namespace"] == "#{@org}/#{@repo_name}"} diff --git a/app/services/git_url_service.rb b/app/services/git_url_service.rb index ea88eaa..65c4bc8 100644 --- a/app/services/git_url_service.rb +++ b/app/services/git_url_service.rb @@ -13,6 +13,15 @@ class GitUrlService @host = URI.parse(@url).host || host + if default_branch? + @repository = url_path[-1] + else + segment = (["blob", "tree", "raw"] & url_path).last + segindex = url_path.index(segment) + @repository = url_path[segindex - 1] + @branch = url_path[segindex + 1] + end + validate_url end @@ -21,15 +30,21 @@ class GitUrlService end def repository - url_path[1] + @repository end def branch - default_branch? ? @default_branch : url_path[3] + default_branch? ? @default_branch : @branch end def path - default_branch? ? File.join(url_path[2..-1]) : File.join(url_path[4..-1].to_a) + segment = default_branch? ? repository : branch + segindex = url_path.index(segment) + url_path[segindex + 1..-1].join('/') + end + + def full_path + url_path.join('/') end private @@ -41,7 +56,7 @@ class GitUrlService def validate_url raise(Invalid, "Url cannot be blank.") if url.strip.empty? raise(Invalid, "Missing organization") if organization.nil? - raise(Invalid, "Missing repository") if repository.nil? + raise(Invalid, "Missing repository") if repository.nil? || url_path.length <= 1 raise(Invalid, "Missing branch") if !default_branch? && url_path[3].nil? end -- GitLab From 3521375a3afb5c2e7dd92deda77df2373556b31a Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 24 Sep 2020 14:10:10 -1000 Subject: [PATCH 062/287] removing public read from s3 --- app/services/s3_asset_uploader_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/s3_asset_uploader_service.rb b/app/services/s3_asset_uploader_service.rb index 0cab0cf..d9a32f4 100644 --- a/app/services/s3_asset_uploader_service.rb +++ b/app/services/s3_asset_uploader_service.rb @@ -24,7 +24,7 @@ class S3AssetUploaderService def new_upload file_content = File.read(@full_path) aws_image_object = Rails.application.config.aws.object(content_key) - aws_image_object.put(body: file_content, acl: "public-read") + aws_image_object.put(body: file_content) aws_image_object.public_url rescue StandardError => e raise(Error, e) -- GitLab From 6427d4737f5b6a96baabcdbcd0d0af4827b79902 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Thu, 24 Sep 2020 15:35:34 -1000 Subject: [PATCH 063/287] Set not version --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e78d68a..0c5eb73 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,11 +20,12 @@ RUN apt-get -y install --no-install-recommends nodejs RUN apt-get -q clean RUN npm cache clean -f RUN npm install -g n -RUN n stable +RUN n 10.15.1 RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10 # Copy in all the application dependencies ADD package.json /usr/src/app/package.json +RUN cd /usr/src/app RUN npm install # Set the user -- GitLab From a45d81cd9afdc53cbccc5610a0e397e2f7309ce8 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Thu, 24 Sep 2020 15:54:22 -1000 Subject: [PATCH 064/287] jumbled stuff around --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0c5eb73..a809810 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,10 +18,10 @@ RUN apt-get -y install --no-install-recommends \ RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - RUN apt-get -y install --no-install-recommends nodejs RUN apt-get -q clean +RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10 RUN npm cache clean -f RUN npm install -g n RUN n 10.15.1 -RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10 # Copy in all the application dependencies ADD package.json /usr/src/app/package.json -- GitLab From 63ea7458c331237cf8502b701ffe1f7ad91734d4 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Thu, 24 Sep 2020 16:20:38 -1000 Subject: [PATCH 065/287] mike is a hacker --- .circleci/config.yml | 4 ++-- .overcommit.yml | 8 ++++---- Dockerfile | 2 +- package.json => package-hidden-from-robot.json | 0 4 files changed, 7 insertions(+), 7 deletions(-) rename package.json => package-hidden-from-robot.json (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 837f2f3..83e82a7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,12 +34,12 @@ jobs: root: . paths: vendor/bundle - restore_cache: - key: v1-yarn-{{ checksum "package.json" }} + key: v1-yarn-{{ checksum "package-hidden-from-robot.json" }} - run: name: Yarn Install JS Dependencies command: yarn install - save_cache: - key: v1-yarn-{{ checksum "package.json" }} + key: v1-yarn-{{ checksum "package-hidden-from-robot.json" }} paths: - ~/forge/node_modules - persist_to_workspace: diff --git a/.overcommit.yml b/.overcommit.yml index 13aa9ce..43a08df 100644 --- a/.overcommit.yml +++ b/.overcommit.yml @@ -718,7 +718,7 @@ PostCheckout: required_executable: 'npm' flags: ['install'] include: - - 'package.json' + - 'package-hidden-from-robot.json' - 'npm-shrinkwrap.json' SubmoduleStatus: @@ -783,7 +783,7 @@ PostCommit: required_executable: 'npm' flags: ['install'] include: - - 'package.json' + - 'package-hidden-from-robot.json' - 'npm-shrinkwrap.json' SubmoduleStatus: @@ -832,7 +832,7 @@ PostMerge: required_executable: 'npm' flags: ['install'] include: - - 'package.json' + - 'package-hidden-from-robot.json' - 'npm-shrinkwrap.json' SubmoduleStatus: @@ -881,7 +881,7 @@ PostRewrite: required_executable: 'npm' flags: ['install'] include: - - 'package.json' + - 'package-hidden-from-robot.json' - 'npm-shrinkwrap.json' SubmoduleStatus: diff --git a/Dockerfile b/Dockerfile index a809810..2be0ef9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ RUN npm install -g n RUN n 10.15.1 # Copy in all the application dependencies -ADD package.json /usr/src/app/package.json +ADD package-hidden-from-robot.json /usr/src/app/package.json RUN cd /usr/src/app RUN npm install diff --git a/package.json b/package-hidden-from-robot.json similarity index 100% rename from package.json rename to package-hidden-from-robot.json -- GitLab From 7b4bd839a4f91e64cf6f5d7b225d8575498f42bb Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Thu, 24 Sep 2020 17:01:32 -1000 Subject: [PATCH 066/287] change back to package.json --- .circleci/config.yml | 4 ++-- .overcommit.yml | 8 ++++---- Dockerfile | 2 +- package-hidden-from-robot.json => package.json | 0 4 files changed, 7 insertions(+), 7 deletions(-) rename package-hidden-from-robot.json => package.json (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 83e82a7..837f2f3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -34,12 +34,12 @@ jobs: root: . paths: vendor/bundle - restore_cache: - key: v1-yarn-{{ checksum "package-hidden-from-robot.json" }} + key: v1-yarn-{{ checksum "package.json" }} - run: name: Yarn Install JS Dependencies command: yarn install - save_cache: - key: v1-yarn-{{ checksum "package-hidden-from-robot.json" }} + key: v1-yarn-{{ checksum "package.json" }} paths: - ~/forge/node_modules - persist_to_workspace: diff --git a/.overcommit.yml b/.overcommit.yml index 43a08df..13aa9ce 100644 --- a/.overcommit.yml +++ b/.overcommit.yml @@ -718,7 +718,7 @@ PostCheckout: required_executable: 'npm' flags: ['install'] include: - - 'package-hidden-from-robot.json' + - 'package.json' - 'npm-shrinkwrap.json' SubmoduleStatus: @@ -783,7 +783,7 @@ PostCommit: required_executable: 'npm' flags: ['install'] include: - - 'package-hidden-from-robot.json' + - 'package.json' - 'npm-shrinkwrap.json' SubmoduleStatus: @@ -832,7 +832,7 @@ PostMerge: required_executable: 'npm' flags: ['install'] include: - - 'package-hidden-from-robot.json' + - 'package.json' - 'npm-shrinkwrap.json' SubmoduleStatus: @@ -881,7 +881,7 @@ PostRewrite: required_executable: 'npm' flags: ['install'] include: - - 'package-hidden-from-robot.json' + - 'package.json' - 'npm-shrinkwrap.json' SubmoduleStatus: diff --git a/Dockerfile b/Dockerfile index 2be0ef9..a809810 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ RUN npm install -g n RUN n 10.15.1 # Copy in all the application dependencies -ADD package-hidden-from-robot.json /usr/src/app/package.json +ADD package.json /usr/src/app/package.json RUN cd /usr/src/app RUN npm install diff --git a/package-hidden-from-robot.json b/package.json similarity index 100% rename from package-hidden-from-robot.json rename to package.json -- GitLab From ee2a71106ad9532d0c13b1d5b1063d2aba411dae Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 24 Sep 2020 18:17:17 -1000 Subject: [PATCH 067/287] chaning node install --- Dockerfile | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index a809810..e2a6d8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,18 +15,20 @@ RUN apt-get -y install --no-install-recommends \ xvfb # Install the right version of nodejs -RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - -RUN apt-get -y install --no-install-recommends nodejs -RUN apt-get -q clean -RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10 -RUN npm cache clean -f -RUN npm install -g n -RUN n 10.15.1 +#RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - +#RUN apt-get -y install --no-install-recommends nodejs +#RUN apt-get -q clean +#RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10 +#RUN npm cache clean -f +#RUN npm install -g n +#RUN n 10.15.1 +RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n +RUN bash n 10.15.1 # Copy in all the application dependencies -ADD package.json /usr/src/app/package.json -RUN cd /usr/src/app -RUN npm install +#ADD package.json /usr/src/app/package.json +#RUN cd /usr/src/app +#RUN npm install # Set the user RUN groupadd -r ruby @@ -42,6 +44,10 @@ ENV RAILS_ENV production ADD . . ADD Gemfile /usr/src/app/Gemfile ADD Gemfile.lock /usr/src/app/Gemfile.lock + +# Run NPM Install +RUN npm install + # RUN gem install bundler --version 1.17.3 RUN gem install rake -v '12.3.1' --source 'https://rubygems.org/' RUN bundle install -- GitLab From 5e190b3af683cc3c650d04660a5a58354866f601 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 24 Sep 2020 19:42:32 -1000 Subject: [PATCH 068/287] fixing all build errors on docker file --- Dockerfile | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index e2a6d8f..5c1766b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,21 +14,13 @@ RUN apt-get -y install --no-install-recommends \ libqt5webkit5-dev \ xvfb -# Install the right version of nodejs -#RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - -#RUN apt-get -y install --no-install-recommends nodejs -#RUN apt-get -q clean -#RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10 -#RUN npm cache clean -f -#RUN npm install -g n -#RUN n 10.15.1 -RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n -RUN bash n 10.15.1 +# Clear off space. +RUN apt-get -q clean -# Copy in all the application dependencies -#ADD package.json /usr/src/app/package.json -#RUN cd /usr/src/app -#RUN npm install +# Install Node.js. +ADD .nvmrc . +RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n +RUN bash n auto # Set the user RUN groupadd -r ruby @@ -42,14 +34,19 @@ ENV RAILS_ENV production # Stuff that changes ADD . . -ADD Gemfile /usr/src/app/Gemfile -ADD Gemfile.lock /usr/src/app/Gemfile.lock # Run NPM Install RUN npm install -# RUN gem install bundler --version 1.17.3 +# Install Rails Dependecies. +RUN gem install bundler:1.17.3 RUN gem install rake -v '12.3.1' --source 'https://rubygems.org/' + +# Run the bundle install RUN bundle install + +# Add the node_modules to the path. ENV PATH /usr/src/app/node_modules/.bin:$PATH + +# Set the entry point. CMD ["./entrypoint-server.sh"] -- GitLab From d2db3a2f086f54ff362707f0500e22cedf071987 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 24 Sep 2020 20:07:16 -1000 Subject: [PATCH 069/287] changing to use yarn instead of npm install --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5c1766b..288b52b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ RUN apt-get -q clean ADD .nvmrc . RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n RUN bash n auto +RUN npm install -g yarn # Set the user RUN groupadd -r ruby @@ -35,8 +36,8 @@ ENV RAILS_ENV production # Stuff that changes ADD . . -# Run NPM Install -RUN npm install +# Run Yarn Install +RUN yarn install # Install Rails Dependecies. RUN gem install bundler:1.17.3 -- GitLab From 3e762f2528112023e10421a4959486459edd654d Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 09:08:49 -1000 Subject: [PATCH 070/287] changing gem file block parser to use relative path. Adding webpack build to docker file --- Dockerfile | 3 +++ Gemfile | 2 +- Gemfile.lock | 5 ++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 288b52b..ac31eef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,6 +46,9 @@ RUN gem install rake -v '12.3.1' --source 'https://rubygems.org/' # Run the bundle install RUN bundle install +# Run Webpack build. +RUN NODE_ENV=production bin/webpack + # Add the node_modules to the path. ENV PATH /usr/src/app/node_modules/.bin:$PATH diff --git a/Gemfile b/Gemfile index ce249cb..028c022 100644 --- a/Gemfile +++ b/Gemfile @@ -60,7 +60,7 @@ gem "dry-validation", "~> 0.12.2" # file processing ### We need to pass git credentials into the docker build command so we can clone the block-parser # gem "block_parser", :git => "https://code.il2.dsop.io/tron/products/learn-lms/ruby-gems/block-parser.git" -gem "block_parser", path: "/usr/src/app/gems/block-parser" +gem "block_parser", path: "./gems/block-parser" # Reduces boot times through caching; required in config/boot.rb gem "bootsnap", ">= 1.1.0" diff --git a/Gemfile.lock b/Gemfile.lock index f922ed5..61a9802 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,5 @@ -GIT - remote: https://code.il2.dsop.io/tron/products/learn-lms/ruby-gems/block-parser.git - revision: bdf3acdb4145805d306fc022f4abc2803a6bacb7 +PATH + remote: gems/block-parser specs: block_parser (0.1.0) activemodel (> 4.2) -- GitLab From a806733340803ce3c65bd14012f67bb3e7e59df8 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 10:08:48 -1000 Subject: [PATCH 071/287] changing port on entry point --- entrypoint-server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 35df35e..6fa9cea 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -1,4 +1,4 @@ #!/bin/sh rm -f tmp/pids/server.pid -RAILS_ENV=production rails server -b 0.0.0.0 -p 3003 +RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 -- GitLab From 6d8bce76e6fd59867db77c4f302af419318b738f Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Fri, 25 Sep 2020 10:27:01 -1000 Subject: [PATCH 072/287] Fixed getting gitlab subprojects in course.yaml --- .../components/cohorts/settings/CohortSettingsResync.tsx | 6 +++--- app/services/resync_course_service.rb | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx b/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx index 2744868..43ad062 100644 --- a/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx +++ b/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx @@ -71,10 +71,10 @@ export default class CohortSettingsResync extends React.Component { else if (job.status === 'pending') { return { state: 'pending', job: job } } - else if (job.status === 'canceled') { + else if (job.status === 'canceled') { return { state: 'canceled', job: job } } - else if (job.status === 'timeout') { + else if (job.status === 'timeout') { return { state: 'timeout', job: job } } else if (! job.archived) { @@ -218,7 +218,7 @@ export default class CohortSettingsResync extends React.Component { var {fileUrl, working, current, buttonText} = this.state var {githubIconPath} = this.props - var urlIsValid = fileUrl.match(new RegExp('^https://github.com\/')) + var urlIsValid = fileUrl.match(new RegExp('^https://')) var disable = !urlIsValid || working || current.state === 'pending' var jobStatusLabel = this.jobStatusLabel() diff --git a/app/services/resync_course_service.rb b/app/services/resync_course_service.rb index 1f41b14..5f0cbe0 100644 --- a/app/services/resync_course_service.rb +++ b/app/services/resync_course_service.rb @@ -16,6 +16,12 @@ class ResyncCourseService git_url.repository.downcase, git_url.host.downcase, git_url.organization.downcase).first + if block.nil? + block = Block.where("lower(repo_name) = ? AND lower(origin) = ? AND lower(org) = ?", + git_url.full_path.gsub(git_url.organization + '/', '').downcase, + git_url.host.downcase, + git_url.organization.downcase).first + end if block.nil? failed_blocks.push( -- GitLab From eb0665caa8c6ef6692b5105ec1b7a22fdf8e726e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 10:38:52 -1000 Subject: [PATCH 073/287] allowing forge admin to see add users button --- app/views/cohorts/users.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/cohorts/users.html.haml b/app/views/cohorts/users.html.haml index 9d63cf7..55e7d0e 100644 --- a/app/views/cohorts/users.html.haml +++ b/app/views/cohorts/users.html.haml @@ -25,7 +25,7 @@ importPath: import_users_work_cohort_path(cohort), importStatusPath: import_users_work_status_cohort_path(cohort) .managebutton - - if current_user.role?(User::ROLES[:product_admin]) || current_user.product_admin_of?(cohort.id) + - if current_user.role?(User::ROLES[:admin]) || current_user.role?(User::ROLES[:product_admin]) || current_user.product_admin_of?(cohort.id) = link_to new_cohort_user_path(cohort.id), class: "lp-style-button", target: "_blank" do Add Users = react_component "SvgRenderer", viewBox: "-8 -8 10 24", style: {fill: "white"}, url: path_to_image("svg/svg-sprite-action-symbol.svg#ic_launch_24px") -- GitLab From 7c4485a4b3be661625395c76527f9bdaeef23326 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Fri, 25 Sep 2020 11:28:23 -1000 Subject: [PATCH 074/287] Fixes for gitlab subprojects --- .../cohorts/blocks/content_files_controller.rb | 2 +- app/services/git_url_service.rb | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/controllers/cohorts/blocks/content_files_controller.rb b/app/controllers/cohorts/blocks/content_files_controller.rb index 98a6b2c..cc30315 100644 --- a/app/controllers/cohorts/blocks/content_files_controller.rb +++ b/app/controllers/cohorts/blocks/content_files_controller.rb @@ -104,7 +104,7 @@ module Cohorts is_resource: current_content_file.resource?, title: current_content_file.title, path: current_content_file.path, - permalink: permalink_url(current_block.repo_name, current_content_file.path), + permalink: permalink_url(CGI.escape(current_block.repo_name), current_content_file.path), type: current_content_file.content_file_type, students_in_cohort: current_cohort.students.map do |s| { id: s.id, full_name: s.full_name, initials: s.initials, profile_image: s.profile_image } diff --git a/app/services/git_url_service.rb b/app/services/git_url_service.rb index 65c4bc8..59f01e9 100644 --- a/app/services/git_url_service.rb +++ b/app/services/git_url_service.rb @@ -44,7 +44,13 @@ class GitUrlService end def full_path - url_path.join('/') + if default_branch? + url_path.join('/') + else + segment = (["blob", "tree", "raw"] & url_path).last + segindex = url_path.index(segment) + url_path[0...segindex].join('/') + end end private -- GitLab From 12e8cabbd47799d1ba222aee736b44ebffba2027 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 13:29:49 -1000 Subject: [PATCH 075/287] removing keycloak gem for API. adding db change to remove not nulls on user fields and added a constraint on email field for user. modified the bulk add to create empty users with cohort users that will get set after first login. removing some keycloak env fields. changing s3 fields. --- Gemfile | 1 - .../api/v1/cohorts/users_controller.rb | 49 +++++-------------- app/models/user.rb | 2 +- app/services/keycloak_resolver_service.rb | 8 +-- .../layouts/_primary_navigation.html.haml | 3 +- config/initializers/keycloak.rb | 11 ----- config/secrets.yml | 14 ++---- ...0149_remove_null_constraints_from_users.rb | 8 +++ db/schema.rb | 10 ++-- 9 files changed, 37 insertions(+), 69 deletions(-) delete mode 100644 config/initializers/keycloak.rb create mode 100644 db/migrate/20200925230149_remove_null_constraints_from_users.rb diff --git a/Gemfile b/Gemfile index fe3d036..f109552 100644 --- a/Gemfile +++ b/Gemfile @@ -43,7 +43,6 @@ gem "apitome" # services gem "omniauth" gem "omniauth-keycloak" -gem "keycloak" gem "honeybadger", "~> 3.1" gem "github_url", "0.2.1" gem "gitlab" diff --git a/app/controllers/api/v1/cohorts/users_controller.rb b/app/controllers/api/v1/cohorts/users_controller.rb index f6cdd88..7d86cb9 100644 --- a/app/controllers/api/v1/cohorts/users_controller.rb +++ b/app/controllers/api/v1/cohorts/users_controller.rb @@ -14,16 +14,16 @@ class Api::V1::Cohorts::UsersController < Api::ApplicationController json_response({ error: "Validation Error: #{param_errors.join(', ')}" }, 400) and return end - if keycloak_user_data - user = find_or_create_user(keycloak_user_data) - - if add_user_to_cohort(user, current_cohort) - json_response({ status: "ok" }, :ok) and return - else - json_response({ error: "User is already in the cohort." }, 400) and return - end + user = find_or_create_user(params[:email]) + + if user.errors.full_messages.any? + json_response({ error: "Validation Error: #{user.errors.full_messages.join(', ')}" }, 400) and return + end + + if add_user_to_cohort(user, current_cohort) + json_response({ status: "ok" }, :ok) and return else - json_response({ error: "User not found in KeyCloak." }, 400) and return + json_response({ error: "User is already in the cohort." }, 400) and return end rescue Keycloak::UserLoginNotFound => e @@ -176,38 +176,13 @@ class Api::V1::Cohorts::UsersController < Api::ApplicationController validation_response end - def keycloak_user_data - if @keycloak_user_data - return @keycloak_user_data - end - - data = Keycloak::Internal.get_user_info(params[:email], - true, - Rails.application.secrets.keycloak_client_id, - Rails.application.secrets.keycloak_client_secret) - - if data - @keycloak_user_data = { - id: data["id"], - first_name: data["firstName"], - last_name: data["lastName"], - email: data["email"] - } - - @keycloak_user_data - end - end - - def find_or_create_user(user_data) - user = User.find_by(uid: user_data[:id]) + def find_or_create_user(email) + user = User.find_by(email: email) if user user else - user = User.create(uid: user_data[:id], - first_name: user_data[:first_name], - last_name: user_data[:last_name], - email: user_data[:email], + user = User.create(email: email, roles: []) user diff --git a/app/models/user.rb b/app/models/user.rb index c5748c5..612af35 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,7 +8,7 @@ class User < ApplicationRecord blocks_manager: "forge.blocks_manager" }.freeze - validates(:first_name, :last_name, :uid, presence: true) + validates(:email, presence: true) validates(:uid, uniqueness: true) has_many :cohort_users diff --git a/app/services/keycloak_resolver_service.rb b/app/services/keycloak_resolver_service.rb index 9ccd1d9..a9aacb1 100644 --- a/app/services/keycloak_resolver_service.rb +++ b/app/services/keycloak_resolver_service.rb @@ -5,12 +5,12 @@ class KeycloakResolverService end def find_or_create_user - existing_user = User.find_by(uid: @auth_hash.uid) + existing_user = User.find_by(email: @auth_hash.info.email) if existing_user - existing_user.update(first_name: @auth_hash.info.first_name, - last_name: @auth_hash.info.last_name, - email: @auth_hash.info.email + existing_user.update(uid: @auth_hash.uid, + first_name: @auth_hash.info.first_name, + last_name: @auth_hash.info.last_name ) existing_user diff --git a/app/views/layouts/_primary_navigation.html.haml b/app/views/layouts/_primary_navigation.html.haml index 638a6dc..d76b7f0 100644 --- a/app/views/layouts/_primary_navigation.html.haml +++ b/app/views/layouts/_primary_navigation.html.haml @@ -47,7 +47,8 @@ = sprite_navigation('#ic_arrow_drop_down_24px') %ul.list %li.item - = link_to("My Account", Rails.application.secrets.keycloak_user_account_url, target: :_blank) + -# = link_to("My Account", "") + %br %span.account-email=current_user.email - if @authorized_api_access %li.item diff --git a/config/initializers/keycloak.rb b/config/initializers/keycloak.rb deleted file mode 100644 index 9347bbe..0000000 --- a/config/initializers/keycloak.rb +++ /dev/null @@ -1,11 +0,0 @@ -# If true, then all request exception will explode in application (this is the default value) -Keycloak.generate_request_exception = false - -# realm name (only if the installation file is not present) -Keycloak.realm = Rails.application.secrets.keycloak_realm - -# realm url (only if the installation file is not present) -Keycloak.auth_server_url = Rails.application.secrets.keycloak_auth_url - -# The introspect of the token will be executed every time the Keycloak::Client.has_role? method is invoked, if this setting is set to true. -Keycloak.validate_token_when_call_has_role = false diff --git a/config/secrets.yml b/config/secrets.yml index b8dcb24..130173d 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -10,17 +10,13 @@ shared: keycloak_client_id: <%= ENV["KEYCLOAK_CLIENT_ID"] %> keycloak_client_secret: <%= ENV["KEYCLOAK_CLIENT_SECRET"] %> keycloak_logout_url: <%= "#{ENV["KEYCLOAK_ENDPOINT"]}/auth/realms/#{ENV["KEYCLOAK_REALM"]}/protocol/openid-connect/logout?redirect_uri=" %> - keycloak_endpoint: <%= ENV["KEYCLOAK_ENDPOINT"] %> - keycloak_realm: <%= ENV["KEYCLOAK_REALM"] %> - keycloak_auth_url: <%= "#{ENV["KEYCLOAK_ENDPOINT"]}/auth" %> - keycloak_user_account_url: <%= "#{ENV["KEYCLOAK_ENDPOINT"]}/auth/realms/#{ENV["KEYCLOAK_REALM"]}/account/?referrer=#{ENV["KEYCLOAK_CLIENT_ID"]}" %> keycloak_client_options: site: <%= ENV["KEYCLOAK_ENDPOINT"] %> realm: <%= ENV["KEYCLOAK_REALM"] %> github_token: <%= ENV["GITHUB_COM_TOKEN"] %> mixpanel_project_token: <%= ENV["MIXPANEL_PROJECT_TOKEN"] %> protocol: <%= ENV['PROTOCOL'] || "http://" %> - s3_bucket_name: <%= ENV["S3_BUCKET_NAME"] %> + s3_bucket_name: <%= ENV["appS3BucketUUID"] %> secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> sendgrid_password: <%= ENV['SENDGRID_PASSWORD'] %> sendgrid_username: <%= ENV['SENDGRID_USERNAME'] %> @@ -30,11 +26,11 @@ shared: aws_sqlsnippets_password: <%= ENV["AWS_SQLSNIPPETS_PASSWORD"] %> aws_sqlsnippets_user: <%= ENV["AWS_SQLSNIPPETS_USER"] %> aws_sqlsnippets_host: <%= ENV["AWS_SQLSNIPPETS_HOST"] %> - glearn_access_key_id: <%= ENV['GLEARN_ACCESS_KEY_ID'] %> - glearn_secret_access_key: <%= ENV['GLEARN_SECRET_ACCESS_KEY'] %> + glearn_access_key_id: <%= ENV['accesskey'] %> + glearn_secret_access_key: <%= ENV['secretkey'] %> glearn_key_prefix: <%= ENV['GLEARN_KEY_PREFIX'] %> - glearn_bucket_name: <%= ENV['GLEARN_BUCKET_NAME'] %> - s3_region: <%= ENV['S3_REGION'] %> + glearn_bucket_name: <%= ENV["appS3BucketUUID"] %> + s3_region: <%= ENV["S3_REGION"] || "us-gov-west-1" %> dev_notify_slack_url: <%= ENV['DEV_NOTIFY_SLACK_URL'] %> git_download_tokens: github_com: diff --git a/db/migrate/20200925230149_remove_null_constraints_from_users.rb b/db/migrate/20200925230149_remove_null_constraints_from_users.rb new file mode 100644 index 0000000..e48424e --- /dev/null +++ b/db/migrate/20200925230149_remove_null_constraints_from_users.rb @@ -0,0 +1,8 @@ +class RemoveNullConstraintsFromUsers < ActiveRecord::Migration[5.2] + def change + change_column( :users, :uid, :string, :null => true) + change_column( :users, :first_name, :string, :null => true) + change_column( :users, :last_name, :string, :null => true) + change_column( :users, :email, :string, :null => false) + end +end diff --git a/db/schema.rb b/db/schema.rb index c8e582b..f5e6ecd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_08_28_165130) do +ActiveRecord::Schema.define(version: 2020_09_25_230149) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -356,10 +356,10 @@ ActiveRecord::Schema.define(version: 2020_08_28_165130) do end create_table "users", force: :cascade do |t| - t.string "uid", null: false - t.string "first_name", null: false - t.string "last_name", null: false - t.string "email" + t.string "uid" + t.string "first_name" + t.string "last_name" + t.string "email", null: false t.string "timezone" t.string "profile_image" t.datetime "created_at", null: false -- GitLab From fd9923bcc4300035f3f3fae932f1b219903dfe91 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Fri, 25 Sep 2020 13:58:12 -1000 Subject: [PATCH 076/287] Set permissions --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index ac31eef..610417a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,6 +34,7 @@ WORKDIR /usr/src/app ENV RAILS_ENV production # Stuff that changes +RUN chmod 755 /usr/src/app ADD . . # Run Yarn Install -- GitLab From 31632954d536ddc4b79eaa2a29916f67812e304e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 14:01:43 -1000 Subject: [PATCH 077/287] removing keycloak exception --- Gemfile.lock | 23 ------------------- .../api/v1/cohorts/users_controller.rb | 2 -- 2 files changed, 25 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f922ed5..dd969a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -118,8 +118,6 @@ GEM debug_inspector (0.0.3) deterministic (0.6.0) diff-lcs (1.3) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) dotenv (2.5.0) dotenv-rails (2.5.0) dotenv (= 2.5.0) @@ -180,9 +178,6 @@ GEM hashdiff (0.4.0) hashie (4.1.0) honeybadger (3.2.0) - http-accept (1.7.0) - http-cookie (1.0.3) - domain_name (~> 0.5) httparty (0.15.6) multi_xml (>= 0.5.2) i18n (1.6.0) @@ -197,7 +192,6 @@ GEM js-routes (1.4.2) railties (>= 3.2) sprockets-rails - json (2.3.1) json-jwt (1.13.0) activesupport (>= 4.2) aes_key_wrap @@ -206,10 +200,6 @@ GEM multi_json (~> 1.0) rspec (>= 2.0, < 4.0) jwt (2.2.2) - keycloak (3.0.0) - json - jwt - rest-client kramdown (2.1.0) launchy (2.4.3) addressable (~> 2.3) @@ -226,9 +216,6 @@ GEM railties (>= 3.0) memory_profiler (0.9.12) method_source (0.9.0) - mime-types (3.3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) mimemagic (0.3.2) mini_mime (1.0.0) mini_portile2 (2.2.0) @@ -238,7 +225,6 @@ GEM multi_xml (0.6.0) multipart-post (2.0.0) mustache (1.1.0) - netrc (0.11.0) nio4r (2.3.1) nokogiri (1.8.0) mini_portile2 (~> 2.2.0) @@ -325,11 +311,6 @@ GEM tilt redcarpet (3.3.4) redis (3.3.5) - rest-client (2.1.0) - http-accept (>= 1.7.0, < 2.0) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) rspec (3.7.0) rspec-core (~> 3.7.0) rspec-expectations (~> 3.7.0) @@ -417,9 +398,6 @@ GEM uglifier (4.0.2) execjs (>= 0.3.0, < 3) underscore-rails (1.8.3) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.7) unicode-display_width (1.6.0) vcr (4.0.0) webmock (3.6.0) @@ -469,7 +447,6 @@ DEPENDENCIES jquery-rails js-routes json_spec - keycloak letter_opener mathjax-rails memory_profiler diff --git a/app/controllers/api/v1/cohorts/users_controller.rb b/app/controllers/api/v1/cohorts/users_controller.rb index 7d86cb9..ad3efca 100644 --- a/app/controllers/api/v1/cohorts/users_controller.rb +++ b/app/controllers/api/v1/cohorts/users_controller.rb @@ -26,8 +26,6 @@ class Api::V1::Cohorts::UsersController < Api::ApplicationController json_response({ error: "User is already in the cohort." }, 400) and return end - rescue Keycloak::UserLoginNotFound => e - json_response({ error: "User does not exist in KeyCloak." }, 400) and return rescue StandardError => e json_response({ error: e.message }, 400) and return end -- GitLab From b9c6d3a5026b68916787d5a2f399c34935bb426d Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 15:03:59 -1000 Subject: [PATCH 078/287] fixing bug in image encoding --- app/services/encoded_image_link_service.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/services/encoded_image_link_service.rb b/app/services/encoded_image_link_service.rb index 523d3af..123d57e 100644 --- a/app/services/encoded_image_link_service.rb +++ b/app/services/encoded_image_link_service.rb @@ -7,8 +7,9 @@ class EncodedImageLinkService def find_or_create_content(full_path:, directory: nil) begin encoded_string = Base64.encode64(File.open(full_path).read) - image_mime_type = MIME::Types.type_for(full_path).first.content_type - return "data:#{image_mime_type};base64, #{encoded_string}" + image_mime_type = Rack::Mime.mime_type(File.extname(full_path)) + + "data:#{image_mime_type};base64, #{encoded_string}" rescue StandardError => e raise(Error, e) end -- GitLab From d1421f8d83fb2c2b431548e6730559ca56996932 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Fri, 25 Sep 2020 15:24:40 -1000 Subject: [PATCH 079/287] Add default for GLEARN_PREFIX --- config/secrets.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/secrets.yml b/config/secrets.yml index 130173d..c07c473 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -28,7 +28,7 @@ shared: aws_sqlsnippets_host: <%= ENV["AWS_SQLSNIPPETS_HOST"] %> glearn_access_key_id: <%= ENV['accesskey'] %> glearn_secret_access_key: <%= ENV['secretkey'] %> - glearn_key_prefix: <%= ENV['GLEARN_KEY_PREFIX'] %> + glearn_key_prefix: <%= ENV['GLEARN_KEY_PREFIX'] || "forge" %> glearn_bucket_name: <%= ENV["appS3BucketUUID"] %> s3_region: <%= ENV["S3_REGION"] || "us-gov-west-1" %> dev_notify_slack_url: <%= ENV['DEV_NOTIFY_SLACK_URL'] %> -- GitLab From 39e9dcff42bb1c7c3df0dbfd76c69ab05412457f Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Fri, 25 Sep 2020 15:51:09 -1000 Subject: [PATCH 080/287] Another try at fixing permissions --- Dockerfile | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 610417a..33a9396 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,26 +24,31 @@ RUN bash n auto RUN npm install -g yarn # Set the user -RUN groupadd -r ruby -RUN useradd --no-log-init -r -g ruby ruby -# USER ruby -RUN whoami +RUN set -ex && \ + groupadd --gid 950 ruby && \ + useradd --uid 950 --gid ruby --shell /bin/bash --create-home ruby +RUN ls -l /home # Setup our environment WORKDIR /usr/src/app ENV RAILS_ENV production # Stuff that changes +RUN chown -R ruby:ruby /usr/src/app RUN chmod 755 /usr/src/app +RUN ls -ld /usr/src/app ADD . . -# Run Yarn Install -RUN yarn install +USER ruby +RUN whoami # Install Rails Dependecies. RUN gem install bundler:1.17.3 RUN gem install rake -v '12.3.1' --source 'https://rubygems.org/' +# Run Yarn Install +RUN yarn install + # Run the bundle install RUN bundle install @@ -54,4 +59,5 @@ RUN NODE_ENV=production bin/webpack ENV PATH /usr/src/app/node_modules/.bin:$PATH # Set the entry point. +#CMD ["tail", "-f", "/dev/null"] CMD ["./entrypoint-server.sh"] -- GitLab From a45e7f8fdc424fcf5f88f64687bc15b3b8b67784 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 16:43:58 -1000 Subject: [PATCH 081/287] turning off ssl for production build. commenting out user setup on docker file. --- Dockerfile | 27 +++++++++++++++------------ config/environments/production.rb | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 33a9396..4fa228d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,34 +24,37 @@ RUN bash n auto RUN npm install -g yarn # Set the user -RUN set -ex && \ - groupadd --gid 950 ruby && \ - useradd --uid 950 --gid ruby --shell /bin/bash --create-home ruby -RUN ls -l /home +#RUN set -ex && \ +# groupadd --gid 950 ruby && \ +# useradd --uid 950 --gid ruby --shell /bin/bash --create-home ruby +#RUN ls -l /home # Setup our environment WORKDIR /usr/src/app ENV RAILS_ENV production # Stuff that changes -RUN chown -R ruby:ruby /usr/src/app -RUN chmod 755 /usr/src/app -RUN ls -ld /usr/src/app +#RUN chown -R ruby:ruby /usr/src/app +#RUN chmod 755 /usr/src/app +#RUN chown -R ruby:ruby /opt/bitnami/ruby +#RUN chmod 755 /opt/bitnami/ruby + +# Copy the files. ADD . . -USER ruby -RUN whoami +# Switch to ruby. +#USER ruby # Install Rails Dependecies. RUN gem install bundler:1.17.3 RUN gem install rake -v '12.3.1' --source 'https://rubygems.org/' -# Run Yarn Install -RUN yarn install - # Run the bundle install RUN bundle install +# Run Yarn Install +RUN yarn install + # Run Webpack build. RUN NODE_ENV=production bin/webpack diff --git a/config/environments/production.rb b/config/environments/production.rb index a268b7b..41f4ff0 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -46,7 +46,7 @@ Rails.application.configure do # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - config.force_ssl = true + config.force_ssl = false # Use the lowest log level to ensure availability of diagnostic information # when problems arise. -- GitLab From f0255e59a4835851b6e377725d6e45a47b7350ef Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 18:16:20 -1000 Subject: [PATCH 082/287] adding jwt gem and new resolver service to part the jwt and create/update users --- Gemfile | 1 + Gemfile.lock | 1 + .../platform_one_auth_resolver_service.rb | 27 +++++++++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 app/services/platform_one_auth_resolver_service.rb diff --git a/Gemfile b/Gemfile index f109552..88eca97 100644 --- a/Gemfile +++ b/Gemfile @@ -41,6 +41,7 @@ gem "browser" gem "apitome" # services +gem 'jwt' gem "omniauth" gem "omniauth-keycloak" gem "honeybadger", "~> 3.1" diff --git a/Gemfile.lock b/Gemfile.lock index dd969a2..36dfc5d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -447,6 +447,7 @@ DEPENDENCIES jquery-rails js-routes json_spec + jwt letter_opener mathjax-rails memory_profiler diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb new file mode 100644 index 0000000..22d0f00 --- /dev/null +++ b/app/services/platform_one_auth_resolver_service.rb @@ -0,0 +1,27 @@ +class PlatformOneAuthResolverService + def initialize(jwt_token) + @payload = JWT.decode(jwt_token, nil, false)[0] + end + + def find_or_create_user + existing_user = User.find_by(email: @payload.email) + + if existing_user + existing_user.update(uid: @payload["sub"], + first_name: @payload["given_name"], + last_name: @payload["family_name"] + ) + + existing_user + else + user = User.create(uid: @payload["sub"], + first_name: @payload["given_name"], + last_name: @payload["family_name"], + email: @payload["email"], + roles: [] + ) + + user + end + end +end -- GitLab From 645ff1a1ef42f770e63a4f0b0ef4d04882428f4e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 18:17:00 -1000 Subject: [PATCH 083/287] fixing bug --- app/services/platform_one_auth_resolver_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index 22d0f00..05f913e 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -4,7 +4,7 @@ class PlatformOneAuthResolverService end def find_or_create_user - existing_user = User.find_by(email: @payload.email) + existing_user = User.find_by(email: @payload["email"]) if existing_user existing_user.update(uid: @payload["sub"], -- GitLab From 9a9bef65574ec528848fbe7c8811383612123680 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 18:56:36 -1000 Subject: [PATCH 084/287] attempt at removing keycloak code and replacing with P1 jwt auth --- Gemfile | 2 -- app/controllers/sessions_controller.rb | 26 ++++++++++++++------------ config/initializers/omniauth.rb | 6 ------ config/routes.rb | 1 - 4 files changed, 14 insertions(+), 21 deletions(-) delete mode 100644 config/initializers/omniauth.rb diff --git a/Gemfile b/Gemfile index 88eca97..7ad8728 100644 --- a/Gemfile +++ b/Gemfile @@ -42,8 +42,6 @@ gem "apitome" # services gem 'jwt' -gem "omniauth" -gem "omniauth-keycloak" gem "honeybadger", "~> 3.1" gem "github_url", "0.2.1" gem "gitlab" diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 3bd600b..852edbc 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,24 +1,20 @@ class SessionsController < ActionController::Base + def new + if !current_user + @user = PlatformOneAuthResolverService.new(token).find_or_create_user + session[:user_uid] = @user.uid + end - def create - @user = KeycloakResolverService.new(auth_hash).find_or_create_user - session[:user_uid] = @user.uid redirect_to '/' end - def new - # Redirect back to root if signed in, otherwise kick off the defined omniauth strategy. - session[:after_auth_params] = params - redirect_to current_user ? '/' : '/auth/keycloakopenid' - end - def failure? redirect_to '/' end def destroy session[:user_uid] = nil - redirect_to "#{Rails.application.secrets.keycloak_logout_url}#{root_url}" + redirect_to '/' end protected @@ -27,7 +23,13 @@ class SessionsController < ActionController::Base @current_user ||= User.find_by(uid: session[:user_uid]) unless session[:user_uid].nil? end - def auth_hash - request.env['omniauth.auth'] + def token + if !request.env["Authorization"].nil? + pattern = /^Bearer / + header = request.env["Authorization"] + header.gsub(pattern, '') if header&.match(pattern) + elsif !request.query_parameters["token"].nil? + request.query_parameters["token"] + end end end diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb deleted file mode 100644 index 7cb4457..0000000 --- a/config/initializers/omniauth.rb +++ /dev/null @@ -1,6 +0,0 @@ -OmniAuth.config.allowed_request_methods = [:post, :get] - -Rails.application.config.middleware.use OmniAuth::Builder do - provider :keycloak_openid, Rails.application.secrets.keycloak_client_id, Rails.application.secrets.keycloak_client_secret, - client_options: Rails.application.secrets.keycloak_client_options -end diff --git a/config/routes.rb b/config/routes.rb index 60296a1..bf3c21c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -9,7 +9,6 @@ Rails.application.routes.draw do get "api_token", to: "application#api_token", as: "api_token" get "api_interactions", to: "application#api_interactions", as: "api_interactions" - get '/auth/:provider/callback', to: 'sessions#create' get '/sign_in', to: 'sessions#new' get '/sign_out', to: 'sessions#destroy', as: "sign_out" -- GitLab From 6ee45a363a7bd3b0d3a5d14478ca1d46bdf9f351 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 25 Sep 2020 23:33:42 -1000 Subject: [PATCH 085/287] commenting out seed data --- db/seeds.rb | 216 ++++++++++++++++++++++++++-------------------------- 1 file changed, 108 insertions(+), 108 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index a2cf07d..ab31218 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -6,114 +6,114 @@ # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) # -["learn-example-block", "learn-test", "blocks-test", "blocks-test-copy"].each do |repo| - block = Block.find_or_create_by(title: repo, repo_name: repo) - release = Release.create(notes: "Block Used for Learn V2 testing", block_id: block.id, github_sha: "pending") - CreateReleaseJob.perform_now(pending_release_id: release.id) -end +# ["learn-example-block", "learn-test", "blocks-test", "blocks-test-copy"].each do |repo| +# block = Block.find_or_create_by(title: repo, repo_name: repo) +# release = Release.create(notes: "Block Used for Learn V2 testing", block_id: block.id, github_sha: "pending") +# CreateReleaseJob.perform_now(pending_release_id: release.id) +# end Block.find_or_create_by(title: "preview", repo_name: "preview") -# these values are specific to galvanize-auth-staging values -cohort = Cohort.create(uid: "9871fef341f9ef250b", - name: "Story Testing", - product_type: "Other", - starts_on: Time.new("2018-02-02").utc, - mode: "Percentage", - allow_paired_submissions: true -) - -instructor_user = User.create(uid: "003V000000bNlMaIAK", - first_name: "Erin", - last_name: "Hough", - email: "erin.hough@galvanize.com", - timezone: "America/Denver", - roles: "{auth.admin, - auth.product_admin, - forge.admin, - forge.blocks_manager}", - last_viewed_cohort_id: cohort.id) - -student_user = User.create(uid: "003n000000SnC61AAF", - first_name: "Chauncey", - last_name: "Huffelfeffer", - email: "dev+learnstudent@galvanize.com", - timezone: "America/Denver", - roles: "{}", - last_viewed_cohort_id: cohort.id) - -CohortUser.create(cohort: cohort, user: instructor_user, roles: "{forge.instructor}") -CohortUser.create(cohort: cohort, user: student_user, roles: "{}") - -erin_student_user = User.create(uid: "003V000000cVzsKIAS", - first_name: "Erin", - last_name: "student", - email: "erin.hough+student@galvanize.com", - timezone: "America/Denver", - roles: "{}", - last_viewed_cohort_id: cohort.id) - -CohortUser.create(cohort: cohort, user: erin_student_user, roles: "{}") - - steve_student_user = User.create(uid: "003V000000ZlEYdIAN", - first_name: "Steve", - last_name: "Student", - email: "steve.perella+student@galvanize.com", - timezone: "America/Denver", - roles: "{}", - last_viewed_cohort_id: cohort.id) - -CohortUser.create(cohort: cohort, user: steve_student_user, roles: "{}") - -dev_auth_user = User.create( - uid: "003V000000VLPZCIA5 ", - first_name: "Dev", - last_name: "Auth", - email: "dev+auth@galvanize.com", - timezone: "America/Denver", - roles: "{auth.app_admin,auth.admin,auth.product_admin,members.admin,talent.admin,forge.admin,forge.blocks_manager}", - last_viewed_cohort_id: cohort.id -) - -sebp = Cohort.create( - uid: "01t0a000004hRLqAAM", - name: "Software Engineering Basic Prep", - product_type: "WDI", - starts_on: Time.new("2018-02-02").utc, - mode: "Mastery" -) - -dsip = Cohort.create( - uid: "01tj0000003h8lMAAQ", - name: "Data Science Immersive Prep", - product_type: "DSI", - starts_on: Time.new("2018-02-02").utc, - mode: "Mastery" -) - -CohortUser.create(cohort: sebp, user: dev_auth_user) -CohortUser.create(cohort: dsip, user: dev_auth_user) -CohortUser.create(cohort: sebp, user: instructor_user) -CohortUser.create(cohort: dsip, user: instructor_user) - -# sync initial cohort curriculum -course_url = "https://github.com/gSchool/learn-course-files/blob/master/test/learn-test.yaml" -result = ResyncJobResult.start(cohort.id, course_url) -CourseValidator.run(url: course_url).and_then do |results| - ResyncCourseService.run(cohort_id: cohort.id, - course_url: course_url, - sections: results[:course][:sections], - default_unit_visibility: results[:course][:default_unit_visibility]) -end.match do - success do - result.update(status: "success") - end - - failure do |error| - result.update(status: "failure", data: { - error_type: error.type, - course_config_url: course_url, - error_data: error.as_json["table"].without("type") - }) - end -end +# # these values are specific to galvanize-auth-staging values +# cohort = Cohort.create(uid: "9871fef341f9ef250b", +# name: "Story Testing", +# product_type: "Other", +# starts_on: Time.new("2018-02-02").utc, +# mode: "Percentage", +# allow_paired_submissions: true +# ) +# +# instructor_user = User.create(uid: "003V000000bNlMaIAK", +# first_name: "Erin", +# last_name: "Hough", +# email: "erin.hough@galvanize.com", +# timezone: "America/Denver", +# roles: "{auth.admin, +# auth.product_admin, +# forge.admin, +# forge.blocks_manager}", +# last_viewed_cohort_id: cohort.id) +# +# student_user = User.create(uid: "003n000000SnC61AAF", +# first_name: "Chauncey", +# last_name: "Huffelfeffer", +# email: "dev+learnstudent@galvanize.com", +# timezone: "America/Denver", +# roles: "{}", +# last_viewed_cohort_id: cohort.id) +# +# CohortUser.create(cohort: cohort, user: instructor_user, roles: "{forge.instructor}") +# CohortUser.create(cohort: cohort, user: student_user, roles: "{}") +# +# erin_student_user = User.create(uid: "003V000000cVzsKIAS", +# first_name: "Erin", +# last_name: "student", +# email: "erin.hough+student@galvanize.com", +# timezone: "America/Denver", +# roles: "{}", +# last_viewed_cohort_id: cohort.id) +# +# CohortUser.create(cohort: cohort, user: erin_student_user, roles: "{}") +# +# steve_student_user = User.create(uid: "003V000000ZlEYdIAN", +# first_name: "Steve", +# last_name: "Student", +# email: "steve.perella+student@galvanize.com", +# timezone: "America/Denver", +# roles: "{}", +# last_viewed_cohort_id: cohort.id) +# +# CohortUser.create(cohort: cohort, user: steve_student_user, roles: "{}") +# +# dev_auth_user = User.create( +# uid: "003V000000VLPZCIA5 ", +# first_name: "Dev", +# last_name: "Auth", +# email: "dev+auth@galvanize.com", +# timezone: "America/Denver", +# roles: "{auth.app_admin,auth.admin,auth.product_admin,members.admin,talent.admin,forge.admin,forge.blocks_manager}", +# last_viewed_cohort_id: cohort.id +# ) +# +# sebp = Cohort.create( +# uid: "01t0a000004hRLqAAM", +# name: "Software Engineering Basic Prep", +# product_type: "WDI", +# starts_on: Time.new("2018-02-02").utc, +# mode: "Mastery" +# ) +# +# dsip = Cohort.create( +# uid: "01tj0000003h8lMAAQ", +# name: "Data Science Immersive Prep", +# product_type: "DSI", +# starts_on: Time.new("2018-02-02").utc, +# mode: "Mastery" +# ) +# +# CohortUser.create(cohort: sebp, user: dev_auth_user) +# CohortUser.create(cohort: dsip, user: dev_auth_user) +# CohortUser.create(cohort: sebp, user: instructor_user) +# CohortUser.create(cohort: dsip, user: instructor_user) +# +# # sync initial cohort curriculum +# course_url = "https://github.com/gSchool/learn-course-files/blob/master/test/learn-test.yaml" +# result = ResyncJobResult.start(cohort.id, course_url) +# CourseValidator.run(url: course_url).and_then do |results| +# ResyncCourseService.run(cohort_id: cohort.id, +# course_url: course_url, +# sections: results[:course][:sections], +# default_unit_visibility: results[:course][:default_unit_visibility]) +# end.match do +# success do +# result.update(status: "success") +# end +# +# failure do |error| +# result.update(status: "failure", data: { +# error_type: error.type, +# course_config_url: course_url, +# error_data: error.as_json["table"].without("type") +# }) +# end +# end -- GitLab From 6945d32b81eda053dc5087ac42cb8ff9f54bd6f7 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Sat, 26 Sep 2020 17:32:27 -1000 Subject: [PATCH 086/287] Test changes --- .gitignore | 3 +- Gemfile | 4 +- Gemfile.lock | 516 --------------------------------------------------- 3 files changed, 5 insertions(+), 518 deletions(-) delete mode 100644 Gemfile.lock diff --git a/.gitignore b/.gitignore index cbbbfd1..70a3437 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ tags .zshrc .solargraph.yml .generators -.rakeTasks \ No newline at end of file +.rakeTasks +/coverage diff --git a/Gemfile b/Gemfile index fe3d036..2a33252 100644 --- a/Gemfile +++ b/Gemfile @@ -55,7 +55,8 @@ gem "solid_use_case", "~> 2.2.0" gem "dry-validation", "~> 0.12.2" # file processing -gem "block_parser", :git => "https://code.il2.dsop.io/tron/products/learn-lms/ruby-gems/block-parser.git" +#gem "block_parser", :git => "https://code.il2.dsop.io/tron/products/learn-lms/ruby-gems/block-parser.git" +gem "block_parser", path: "/Users/csakamaki/Projects/LEARN/RubyGems/block-parser" # Reduces boot times through caching; required in config/boot.rb gem "bootsnap", ">= 1.1.0" @@ -93,4 +94,5 @@ group :test do gem "webmock" gem "rails-controller-testing" gem "rspec_api_documentation" + gem 'simplecov', require: false, group: :test end diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index f922ed5..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,516 +0,0 @@ -GIT - remote: https://code.il2.dsop.io/tron/products/learn-lms/ruby-gems/block-parser.git - revision: bdf3acdb4145805d306fc022f4abc2803a6bacb7 - specs: - block_parser (0.1.0) - activemodel (> 4.2) - github-markdown (= 0.6.9) - github-markup (= 1.6.1) - github_url (= 0.2.1) - gitlab (= 4.14.1) - nokogiri (= 1.8.0) - octokit (= 4.3.0) - psych (= 2.2.4) - redcarpet (= 3.3.4) - rspec_junit_formatter (> 0.2.3) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (5.2.1) - actionpack (= 5.2.1) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailer (5.2.1) - actionpack (= 5.2.1) - actionview (= 5.2.1) - activejob (= 5.2.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (5.2.1) - actionview (= 5.2.1) - activesupport (= 5.2.1) - rack (~> 2.0) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.1) - activesupport (= 5.2.1) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.1) - activesupport (= 5.2.1) - globalid (>= 0.3.6) - activemodel (5.2.1) - activesupport (= 5.2.1) - activerecord (5.2.1) - activemodel (= 5.2.1) - activesupport (= 5.2.1) - arel (>= 9.0) - activestorage (5.2.1) - actionpack (= 5.2.1) - activerecord (= 5.2.1) - marcel (~> 0.3.1) - activesupport (5.2.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.4.0) - aes_key_wrap (1.1.0) - analytics-ruby (2.0.13) - apitome (0.3.0) - kramdown - railties - arel (9.0.0) - ast (2.4.0) - autoprefixer-rails (7.2.5) - execjs - aws-sdk (2.6.50) - aws-sdk-resources (= 2.6.50) - aws-sdk-core (2.6.50) - aws-sigv4 (~> 1.0) - jmespath (~> 1.0) - aws-sdk-resources (2.6.50) - aws-sdk-core (= 2.6.50) - aws-sigv4 (1.0.2) - babel-source (5.8.35) - babel-transpiler (0.7.0) - babel-source (>= 4.0, < 6) - execjs (~> 2.0) - barnes (0.0.7) - multi_json (~> 1) - statsd-ruby (~> 1.1) - better_errors (2.5.0) - coderay (>= 1.0.0) - erubi (>= 1.0.0) - rack (>= 0.9.0) - bindata (2.4.8) - binding_of_caller (0.8.0) - debug_inspector (>= 0.0.1) - bootsnap (1.3.1) - msgpack (~> 1.0) - bootstrap (4.0.0) - autoprefixer-rails (>= 6.0.3) - popper_js (>= 1.12.9, < 2) - sass (>= 3.5.2) - browser (2.5.2) - builder (3.2.3) - byebug (9.1.0) - capybara (2.16.1) - addressable - mini_mime (>= 0.1.3) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - xpath (~> 2.0) - childprocess (0.8.0) - ffi (~> 1.0, >= 1.0.11) - coderay (1.1.2) - concurrent-ruby (1.1.5) - connection_pool (2.2.1) - crack (0.4.3) - safe_yaml (~> 1.0.0) - crass (1.0.4) - database_cleaner (1.6.2) - debug_inspector (0.0.3) - deterministic (0.6.0) - diff-lcs (1.3) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.5.0) - dotenv-rails (2.5.0) - dotenv (= 2.5.0) - railties (>= 3.2, < 6.0) - dry-configurable (0.7.0) - concurrent-ruby (~> 1.0) - dry-container (0.6.0) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (0.4.7) - concurrent-ruby (~> 1.0) - dry-equalizer (0.2.1) - dry-inflector (0.1.2) - dry-logic (0.4.2) - dry-container (~> 0.2, >= 0.2.6) - dry-core (~> 0.2) - dry-equalizer (~> 0.2) - dry-types (0.13.2) - concurrent-ruby (~> 1.0) - dry-container (~> 0.3) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.2) - dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 0.4, >= 0.4.2) - dry-validation (0.12.2) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (~> 0.2, >= 0.2.1) - dry-equalizer (~> 0.2) - dry-logic (~> 0.4, >= 0.4.0) - dry-types (~> 0.13.1) - erubi (1.7.1) - execjs (2.7.0) - factory_bot (4.8.2) - activesupport (>= 3.0.0) - factory_bot_rails (4.8.2) - factory_bot (~> 4.8.2) - railties (>= 3.0.0) - faraday (0.9.2) - multipart-post (>= 1.2, < 3) - ffi (1.9.18) - flamegraph (0.9.5) - font-awesome-rails (4.7.0.4) - railties (>= 3.2, < 6.0) - foreman (0.84.0) - thor (~> 0.19.1) - github-markdown (0.6.9) - github-markup (1.6.1) - github_url (0.2.1) - gitlab (4.14.1) - httparty (~> 0.14, >= 0.14.0) - terminal-table (~> 1.5, >= 1.5.1) - globalid (0.4.1) - activesupport (>= 4.2.0) - haml (5.0.4) - temple (>= 0.8.0) - tilt - hashdiff (0.4.0) - hashie (4.1.0) - honeybadger (3.2.0) - http-accept (1.7.0) - http-cookie (1.0.3) - domain_name (~> 0.5) - httparty (0.15.6) - multi_xml (>= 0.5.2) - i18n (1.6.0) - concurrent-ruby (~> 1.0) - iniparse (1.4.4) - jaro_winkler (1.5.3) - jmespath (1.3.1) - jquery-rails (4.3.1) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) - js-routes (1.4.2) - railties (>= 3.2) - sprockets-rails - json (2.3.1) - json-jwt (1.13.0) - activesupport (>= 4.2) - aes_key_wrap - bindata - json_spec (1.1.5) - multi_json (~> 1.0) - rspec (>= 2.0, < 4.0) - jwt (2.2.2) - keycloak (3.0.0) - json - jwt - rest-client - kramdown (2.1.0) - launchy (2.4.3) - addressable (~> 2.3) - letter_opener (1.7.0) - launchy (~> 2.2) - loofah (2.2.2) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.0) - mini_mime (>= 0.1.1) - marcel (0.3.2) - mimemagic (~> 0.3.2) - mathjax-rails (2.6.1) - railties (>= 3.0) - memory_profiler (0.9.12) - method_source (0.9.0) - mime-types (3.3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) - mimemagic (0.3.2) - mini_mime (1.0.0) - mini_portile2 (2.2.0) - minitest (5.11.3) - msgpack (1.2.4) - multi_json (1.13.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - mustache (1.1.0) - netrc (0.11.0) - nio4r (2.3.1) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) - oauth2 (1.4.4) - faraday (>= 0.8, < 2.0) - jwt (>= 1.0, < 3.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (>= 1.2, < 3) - octokit (4.3.0) - sawyer (~> 0.7.0, >= 0.5.3) - omniauth (1.9.1) - hashie (>= 3.4.6) - rack (>= 1.6.2, < 3) - omniauth-keycloak (1.2.0) - json-jwt (~> 1.12) - omniauth (~> 1.9.0) - omniauth-oauth2 (~> 1.6.0) - omniauth-oauth2 (1.6.0) - oauth2 (~> 1.1) - omniauth (~> 1.9) - overcommit (0.41.0) - childprocess (~> 0.6, >= 0.6.3) - iniparse (~> 1.4) - pagy (3.7.1) - parallel (1.17.0) - parser (2.6.3.0) - ast (~> 2.4.0) - pg (0.21.0) - popper_js (1.12.9) - psych (2.2.4) - puma (3.12.1) - pundit (1.1.0) - activesupport (>= 3.0.0) - rack (2.0.3) - rack-attack (6.2.1) - rack (>= 1.0, < 3) - rack-mini-profiler (1.0.0) - rack (>= 1.2.0) - rack-protection (2.0.0) - rack - rack-proxy (0.6.4) - rack - rack-test (0.8.2) - rack (>= 1.0, < 3) - rails (5.2.1) - actioncable (= 5.2.1) - actionmailer (= 5.2.1) - actionpack (= 5.2.1) - actionview (= 5.2.1) - activejob (= 5.2.1) - activemodel (= 5.2.1) - activerecord (= 5.2.1) - activestorage (= 5.2.1) - activesupport (= 5.2.1) - bundler (>= 1.3.0) - railties (= 5.2.1) - sprockets-rails (>= 2.0.0) - rails-controller-testing (1.0.2) - actionpack (~> 5.x, >= 5.0.1) - actionview (~> 5.x, >= 5.0.1) - activesupport (~> 5.x) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (5.2.1) - actionpack (= 5.2.1) - activesupport (= 5.2.1) - method_source - rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rainbow (3.0.0) - rake (12.3.1) - rb-fsevent (0.10.2) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - react-rails (2.4.5) - babel-transpiler (>= 0.7.0) - connection_pool - execjs - railties (>= 3.2) - tilt - redcarpet (3.3.4) - redis (3.3.5) - rest-client (2.1.0) - http-accept (>= 1.7.0, < 2.0) - http-cookie (>= 1.0.2, < 2.0) - mime-types (>= 1.16, < 4.0) - netrc (~> 0.8) - rspec (3.7.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-core (3.7.0) - rspec-support (~> 3.7.0) - rspec-expectations (3.7.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-mocks (3.7.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-rails (3.7.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-support (~> 3.7.0) - rspec-support (3.7.0) - rspec_api_documentation (6.1.0) - activesupport (>= 3.0.0) - mustache (~> 1.0, >= 0.99.4) - rspec (~> 3.0) - rspec_junit_formatter (0.3.0) - rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.72.0) - jaro_winkler (~> 1.5.1) - parallel (~> 1.10) - parser (>= 2.6) - rainbow (>= 2.2.2, < 4.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.7) - ruby-progressbar (1.10.1) - rubyzip (1.2.1) - safe_yaml (1.0.5) - sass (3.5.3) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) - sawyer (0.7.0) - addressable (>= 2.3.5, < 2.5) - faraday (~> 0.8, < 0.10) - scout_apm (2.4.19) - selenium-webdriver (3.8.0) - childprocess (~> 0.5) - rubyzip (~> 1.0) - shoulda-matchers (3.1.2) - activesupport (>= 4.0.0) - sidekiq (5.0.5) - concurrent-ruby (~> 1.0) - connection_pool (~> 2.2, >= 2.2.0) - rack-protection (>= 1.5.0) - redis (>= 3.3.4, < 5) - solid_use_case (2.2.0) - deterministic (~> 0.6.0) - sprockets (3.7.1) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - stackprof (0.2.12) - statsd-ruby (1.4.0) - temple (0.8.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thor (0.19.4) - thread_safe (0.3.6) - tilt (2.0.8) - timecop (0.9.1) - ts_routes (1.0.1) - railties (>= 5.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - uglifier (4.0.2) - execjs (>= 0.3.0, < 3) - underscore-rails (1.8.3) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.6.0) - vcr (4.0.0) - webmock (3.6.0) - addressable (>= 2.3.6) - crack (>= 0.3.2) - hashdiff (>= 0.4.0, < 2.0.0) - webpacker (3.5.5) - activesupport (>= 4.2) - rack-proxy (>= 0.6.1) - railties (>= 4.2) - websocket-driver (0.7.0) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) - xpath (2.1.0) - nokogiri (~> 1.3) - zip-zip (0.3) - rubyzip (>= 1.0.0) - -PLATFORMS - ruby - -DEPENDENCIES - analytics-ruby (~> 2.0.0) - apitome - aws-sdk (~> 2.6.5) - barnes - better_errors - binding_of_caller - block_parser! - bootsnap (>= 1.1.0) - bootstrap (= 4.0.0) - browser - byebug - capybara - database_cleaner - dotenv-rails - dry-validation (~> 0.12.2) - factory_bot_rails - flamegraph - font-awesome-rails - foreman - github_url (= 0.2.1) - gitlab - haml - honeybadger (~> 3.1) - httparty - jquery-rails - js-routes - json_spec - keycloak - letter_opener - mathjax-rails - memory_profiler - octokit - omniauth - omniauth-keycloak - overcommit - pagy - pg (~> 0.18) - puma (~> 3.12.0) - pundit - rack-attack - rack-mini-profiler - rails (~> 5.2.1) - rails-controller-testing - react-rails (= 2.4.5) - redis (~> 3.0) - rspec-rails - rspec_api_documentation - rspec_junit_formatter - rubocop - rubyzip (>= 1.0.0) - sass-rails (~> 5.0) - scout_apm - selenium-webdriver - shoulda-matchers - sidekiq - solid_use_case (~> 2.2.0) - stackprof - timecop - ts_routes - tzinfo-data - uglifier (>= 1.3.0) - underscore-rails (= 1.8.3) - vcr - webmock - webpacker (~> 3.5) - zip-zip - -RUBY VERSION - ruby 2.6.0p0 - -BUNDLED WITH - 1.17.3 -- GitLab From 88c1c9c8488a51c1b062553a2d42d7d3ea924c3c Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Sat, 26 Sep 2020 17:52:15 -1000 Subject: [PATCH 087/287] fix --- Gemfile.lock | 527 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 527 insertions(+) create mode 100644 Gemfile.lock diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..3971a89 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,527 @@ +PATH + remote: /Users/csakamaki/Projects/LEARN/RubyGems/block-parser + specs: + block_parser (0.1.0) + activemodel (> 4.2) + github-markdown (= 0.6.9) + github-markup (= 1.6.1) + github_url (= 0.2.1) + gitlab (= 4.14.1) + nokogiri (= 1.8.0) + octokit (= 4.3.0) + psych (= 2.2.4) + redcarpet (= 3.3.4) + rspec_junit_formatter (> 0.2.3) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.4.3) + actionpack (= 5.2.4.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.4.3) + actionpack (= 5.2.4.3) + actionview (= 5.2.4.3) + activejob (= 5.2.4.3) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.4.3) + actionview (= 5.2.4.3) + activesupport (= 5.2.4.3) + rack (~> 2.0, >= 2.0.8) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.4.3) + activesupport (= 5.2.4.3) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.4.3) + activesupport (= 5.2.4.3) + globalid (>= 0.3.6) + activemodel (5.2.4.3) + activesupport (= 5.2.4.3) + activerecord (5.2.4.3) + activemodel (= 5.2.4.3) + activesupport (= 5.2.4.3) + arel (>= 9.0) + activestorage (5.2.4.3) + actionpack (= 5.2.4.3) + activerecord (= 5.2.4.3) + marcel (~> 0.3.1) + activesupport (5.2.4.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.4.0) + aes_key_wrap (1.1.0) + analytics-ruby (2.0.13) + apitome (0.3.0) + kramdown + railties + arel (9.0.0) + ast (2.4.0) + autoprefixer-rails (9.7.4) + execjs + aws-eventstream (1.1.0) + aws-sdk (2.6.50) + aws-sdk-resources (= 2.6.50) + aws-sdk-core (2.6.50) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-resources (2.6.50) + aws-sdk-core (= 2.6.50) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) + barnes (0.0.7) + multi_json (~> 1) + statsd-ruby (~> 1.1) + better_errors (2.5.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + bindata (2.4.8) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.4.5) + msgpack (~> 1.0) + bootstrap (4.0.0) + autoprefixer-rails (>= 6.0.3) + popper_js (>= 1.12.9, < 2) + sass (>= 3.5.2) + browser (2.5.2) + builder (3.2.4) + byebug (11.0.1) + capybara (3.33.0) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (~> 1.5) + xpath (~> 3.2) + childprocess (0.8.0) + ffi (~> 1.0, >= 1.0.11) + coderay (1.1.2) + concurrent-ruby (1.1.6) + connection_pool (2.2.2) + crack (0.4.3) + safe_yaml (~> 1.0.0) + crass (1.0.6) + database_cleaner (1.8.5) + debug_inspector (0.0.3) + deterministic (0.6.0) + diff-lcs (1.4.4) + docile (1.3.2) + domain_name (0.5.20190701) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.5.0) + dotenv-rails (2.5.0) + dotenv (= 2.5.0) + railties (>= 3.2, < 6.0) + dry-configurable (0.7.0) + concurrent-ruby (~> 1.0) + dry-container (0.6.0) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (0.4.7) + concurrent-ruby (~> 1.0) + dry-equalizer (0.2.1) + dry-inflector (0.1.2) + dry-logic (0.4.2) + dry-container (~> 0.2, >= 0.2.6) + dry-core (~> 0.2) + dry-equalizer (~> 0.2) + dry-types (0.13.2) + concurrent-ruby (~> 1.0) + dry-container (~> 0.3) + dry-core (~> 0.4, >= 0.4.4) + dry-equalizer (~> 0.2) + dry-inflector (~> 0.1, >= 0.1.2) + dry-logic (~> 0.4, >= 0.4.2) + dry-validation (0.12.2) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (~> 0.2, >= 0.2.1) + dry-equalizer (~> 0.2) + dry-logic (~> 0.4, >= 0.4.0) + dry-types (~> 0.13.1) + erubi (1.9.0) + execjs (2.7.0) + factory_bot (6.1.0) + activesupport (>= 5.0.0) + factory_bot_rails (6.1.0) + factory_bot (~> 6.1.0) + railties (>= 5.0.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + ffi (1.12.2) + flamegraph (0.9.5) + font-awesome-rails (4.7.0.4) + railties (>= 3.2, < 6.0) + foreman (0.84.0) + thor (~> 0.19.1) + github-markdown (0.6.9) + github-markup (1.6.1) + github_url (0.2.1) + gitlab (4.14.1) + httparty (~> 0.14, >= 0.14.0) + terminal-table (~> 1.5, >= 1.5.1) + globalid (0.4.2) + activesupport (>= 4.2.0) + haml (5.0.4) + temple (>= 0.8.0) + tilt + hashdiff (1.0.1) + hashie (4.1.0) + honeybadger (3.2.0) + http-accept (1.7.0) + http-cookie (1.0.3) + domain_name (~> 0.5) + httparty (0.18.0) + mime-types (~> 3.0) + multi_xml (>= 0.5.2) + i18n (1.8.3) + concurrent-ruby (~> 1.0) + iniparse (1.4.4) + jmespath (1.4.0) + jquery-rails (4.3.1) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + js-routes (1.4.2) + railties (>= 3.2) + sprockets-rails + json (2.3.1) + json-jwt (1.13.0) + activesupport (>= 4.2) + aes_key_wrap + bindata + json_spec (1.1.5) + multi_json (~> 1.0) + rspec (>= 2.0, < 4.0) + jwt (2.2.2) + keycloak (3.0.0) + json + jwt + rest-client + kramdown (2.1.0) + launchy (2.4.3) + addressable (~> 2.3) + letter_opener (1.7.0) + launchy (~> 2.2) + loofah (2.4.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + mathjax-rails (2.6.1) + railties (>= 3.0) + memory_profiler (0.9.12) + method_source (1.0.0) + mime-types (3.3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2020.0512) + mimemagic (0.3.5) + mini_mime (1.0.2) + mini_portile2 (2.2.0) + minitest (5.14.1) + msgpack (1.3.3) + multi_json (1.15.0) + multi_xml (0.6.0) + multipart-post (2.1.1) + mustache (1.1.0) + netrc (0.11.0) + nio4r (2.5.2) + nokogiri (1.8.0) + mini_portile2 (~> 2.2.0) + oauth2 (1.4.4) + faraday (>= 0.8, < 2.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + octokit (4.3.0) + sawyer (~> 0.7.0, >= 0.5.3) + omniauth (1.9.1) + hashie (>= 3.4.6) + rack (>= 1.6.2, < 3) + omniauth-keycloak (1.2.0) + json-jwt (~> 1.12) + omniauth (~> 1.9.0) + omniauth-oauth2 (~> 1.6.0) + omniauth-oauth2 (1.6.0) + oauth2 (~> 1.1) + omniauth (~> 1.9) + overcommit (0.41.0) + childprocess (~> 0.6, >= 0.6.3) + iniparse (~> 1.4) + pagy (3.7.1) + parallel (1.19.1) + parser (2.7.1.2) + ast (~> 2.4.0) + pg (0.21.0) + popper_js (1.12.9) + psych (2.2.4) + puma (3.12.6) + pundit (1.1.0) + activesupport (>= 3.0.0) + rack (2.2.3) + rack-attack (6.2.1) + rack (>= 1.0, < 3) + rack-mini-profiler (1.0.0) + rack (>= 1.2.0) + rack-protection (2.0.5) + rack + rack-proxy (0.6.5) + rack + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.4.3) + actioncable (= 5.2.4.3) + actionmailer (= 5.2.4.3) + actionpack (= 5.2.4.3) + actionview (= 5.2.4.3) + activejob (= 5.2.4.3) + activemodel (= 5.2.4.3) + activerecord (= 5.2.4.3) + activestorage (= 5.2.4.3) + activesupport (= 5.2.4.3) + bundler (>= 1.3.0) + railties (= 5.2.4.3) + sprockets-rails (>= 2.0.0) + rails-controller-testing (1.0.4) + actionpack (>= 5.0.1.x) + actionview (>= 5.0.1.x) + activesupport (>= 5.0.1.x) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (5.2.4.3) + actionpack (= 5.2.4.3) + activesupport (= 5.2.4.3) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rainbow (3.0.0) + rake (13.0.1) + rb-fsevent (0.10.3) + rb-inotify (0.10.1) + ffi (~> 1.0) + react-rails (2.4.5) + babel-transpiler (>= 0.7.0) + connection_pool + execjs + railties (>= 3.2) + tilt + redcarpet (3.3.4) + redis (3.3.5) + regexp_parser (1.7.1) + rest-client (2.1.0) + http-accept (>= 1.7.0, < 2.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + rexml (3.2.4) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.2) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-rails (4.0.1) + actionpack (>= 4.2) + activesupport (>= 4.2) + railties (>= 4.2) + rspec-core (~> 3.9) + rspec-expectations (~> 3.9) + rspec-mocks (~> 3.9) + rspec-support (~> 3.9) + rspec-support (3.9.3) + rspec_api_documentation (6.1.0) + activesupport (>= 3.0.0) + mustache (~> 1.0, >= 0.99.4) + rspec (~> 3.0) + rspec_junit_formatter (0.4.1) + rspec-core (>= 2, < 4, != 2.12.0) + rubocop (0.83.0) + parallel (~> 1.10) + parser (>= 2.7.0.1) + rainbow (>= 2.2.2, < 4.0) + rexml + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 2.0) + ruby-progressbar (1.10.1) + rubyzip (2.3.0) + safe_yaml (1.0.5) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.1.0) + railties (>= 5.2.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sawyer (0.7.0) + addressable (>= 2.3.5, < 2.5) + faraday (~> 0.8, < 0.10) + scout_apm (2.4.19) + selenium-webdriver (3.142.7) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) + shoulda-matchers (3.1.2) + activesupport (>= 4.0.0) + sidekiq (5.2.7) + connection_pool (~> 2.2, >= 2.2.2) + rack (>= 1.5.0) + rack-protection (>= 1.5.0) + redis (>= 3.3.5, < 5) + simplecov (0.17.1) + docile (~> 1.1) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + solid_use_case (2.2.0) + deterministic (~> 0.6.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + stackprof (0.2.12) + statsd-ruby (1.4.0) + temple (0.8.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thor (0.19.4) + thread_safe (0.3.6) + tilt (2.0.10) + timecop (0.9.1) + ts_routes (1.0.1) + railties (>= 5.0) + tzinfo (1.2.7) + thread_safe (~> 0.1) + uglifier (4.0.2) + execjs (>= 0.3.0, < 3) + underscore-rails (1.8.3) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.7) + unicode-display_width (1.7.0) + vcr (6.0.0) + webmock (3.8.3) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + webpacker (3.5.5) + activesupport (>= 4.2) + rack-proxy (>= 0.6.1) + railties (>= 4.2) + websocket-driver (0.7.3) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + zip-zip (0.3) + rubyzip (>= 1.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + analytics-ruby (~> 2.0.0) + apitome + aws-sdk (~> 2.6.5) + barnes + better_errors + binding_of_caller + block_parser! + bootsnap (>= 1.1.0) + bootstrap (= 4.0.0) + browser + byebug + capybara + database_cleaner + dotenv-rails + dry-validation (~> 0.12.2) + factory_bot_rails + flamegraph + font-awesome-rails + foreman + github_url (= 0.2.1) + gitlab + haml + honeybadger (~> 3.1) + httparty + jquery-rails + js-routes + json_spec + keycloak + letter_opener + mathjax-rails + memory_profiler + octokit + omniauth + omniauth-keycloak + overcommit + pagy + pg (~> 0.18) + puma (~> 3.12.0) + pundit + rack-attack + rack-mini-profiler + rails (~> 5.2.1) + rails-controller-testing + react-rails (= 2.4.5) + redis (~> 3.0) + rspec-rails + rspec_api_documentation + rspec_junit_formatter + rubocop + rubyzip (>= 1.0.0) + sass-rails (~> 5.0) + scout_apm + selenium-webdriver + shoulda-matchers + sidekiq + simplecov + solid_use_case (~> 2.2.0) + stackprof + timecop + ts_routes + tzinfo-data + uglifier (>= 1.3.0) + underscore-rails (= 1.8.3) + vcr + webmock + webpacker (~> 3.5) + zip-zip + +RUBY VERSION + ruby 2.6.0p0 + +BUNDLED WITH + 2.1.4 -- GitLab From ea5c865d93aa85583c8f877b2c90e69e4eaffd5f Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Sat, 26 Sep 2020 18:19:59 -1000 Subject: [PATCH 088/287] fix Gemfile.lock --- Gemfile.lock | 290 +++++++++++++++++++++++++-------------------------- 1 file changed, 142 insertions(+), 148 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3971a89..44fc3d8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,43 +16,43 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (5.2.4.3) - actionpack (= 5.2.4.3) + actioncable (5.2.1) + actionpack (= 5.2.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.4.3) - actionpack (= 5.2.4.3) - actionview (= 5.2.4.3) - activejob (= 5.2.4.3) + actionmailer (5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.4.3) - actionview (= 5.2.4.3) - activesupport (= 5.2.4.3) - rack (~> 2.0, >= 2.0.8) + actionpack (5.2.1) + actionview (= 5.2.1) + activesupport (= 5.2.1) + rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.4.3) - activesupport (= 5.2.4.3) + actionview (5.2.1) + activesupport (= 5.2.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.4.3) - activesupport (= 5.2.4.3) + activejob (5.2.1) + activesupport (= 5.2.1) globalid (>= 0.3.6) - activemodel (5.2.4.3) - activesupport (= 5.2.4.3) - activerecord (5.2.4.3) - activemodel (= 5.2.4.3) - activesupport (= 5.2.4.3) + activemodel (5.2.1) + activesupport (= 5.2.1) + activerecord (5.2.1) + activemodel (= 5.2.1) + activesupport (= 5.2.1) arel (>= 9.0) - activestorage (5.2.4.3) - actionpack (= 5.2.4.3) - activerecord (= 5.2.4.3) + activestorage (5.2.1) + actionpack (= 5.2.1) + activerecord (= 5.2.1) marcel (~> 0.3.1) - activesupport (5.2.4.3) + activesupport (5.2.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -65,9 +65,8 @@ GEM railties arel (9.0.0) ast (2.4.0) - autoprefixer-rails (9.7.4) + autoprefixer-rails (7.2.5) execjs - aws-eventstream (1.1.0) aws-sdk (2.6.50) aws-sdk-resources (= 2.6.50) aws-sdk-core (2.6.50) @@ -75,8 +74,7 @@ GEM jmespath (~> 1.0) aws-sdk-resources (2.6.50) aws-sdk-core (= 2.6.50) - aws-sigv4 (1.2.2) - aws-eventstream (~> 1, >= 1.0.2) + aws-sigv4 (1.0.2) babel-source (5.8.35) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) @@ -91,35 +89,34 @@ GEM bindata (2.4.8) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.4.5) + bootsnap (1.3.1) msgpack (~> 1.0) bootstrap (4.0.0) autoprefixer-rails (>= 6.0.3) popper_js (>= 1.12.9, < 2) sass (>= 3.5.2) browser (2.5.2) - builder (3.2.4) - byebug (11.0.1) - capybara (3.33.0) + builder (3.2.3) + byebug (9.1.0) + capybara (2.16.1) addressable mini_mime (>= 0.1.3) - nokogiri (~> 1.8) - rack (>= 1.6.0) - rack-test (>= 0.6.3) - regexp_parser (~> 1.5) - xpath (~> 3.2) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) childprocess (0.8.0) ffi (~> 1.0, >= 1.0.11) coderay (1.1.2) - concurrent-ruby (1.1.6) - connection_pool (2.2.2) + concurrent-ruby (1.1.5) + connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) - crass (1.0.6) - database_cleaner (1.8.5) + crass (1.0.4) + database_cleaner (1.6.2) debug_inspector (0.0.3) deterministic (0.6.0) - diff-lcs (1.4.4) + diff-lcs (1.3) docile (1.3.2) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) @@ -154,16 +151,16 @@ GEM dry-equalizer (~> 0.2) dry-logic (~> 0.4, >= 0.4.0) dry-types (~> 0.13.1) - erubi (1.9.0) + erubi (1.7.1) execjs (2.7.0) - factory_bot (6.1.0) - activesupport (>= 5.0.0) - factory_bot_rails (6.1.0) - factory_bot (~> 6.1.0) - railties (>= 5.0.0) + factory_bot (4.8.2) + activesupport (>= 3.0.0) + factory_bot_rails (4.8.2) + factory_bot (~> 4.8.2) + railties (>= 3.0.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) - ffi (1.12.2) + ffi (1.9.18) flamegraph (0.9.5) font-awesome-rails (4.7.0.4) railties (>= 3.2, < 6.0) @@ -175,24 +172,24 @@ GEM gitlab (4.14.1) httparty (~> 0.14, >= 0.14.0) terminal-table (~> 1.5, >= 1.5.1) - globalid (0.4.2) + globalid (0.4.1) activesupport (>= 4.2.0) haml (5.0.4) temple (>= 0.8.0) tilt - hashdiff (1.0.1) + hashdiff (0.4.0) hashie (4.1.0) honeybadger (3.2.0) http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) - httparty (0.18.0) - mime-types (~> 3.0) + httparty (0.15.6) multi_xml (>= 0.5.2) - i18n (1.8.3) + i18n (1.6.0) concurrent-ruby (~> 1.0) iniparse (1.4.4) - jmespath (1.4.0) + jaro_winkler (1.5.3) + jmespath (1.3.1) jquery-rails (4.3.1) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) @@ -218,31 +215,31 @@ GEM addressable (~> 2.3) letter_opener (1.7.0) launchy (~> 2.2) - loofah (2.4.0) + loofah (2.2.2) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.1) + mail (2.7.0) mini_mime (>= 0.1.1) - marcel (0.3.3) + marcel (0.3.2) mimemagic (~> 0.3.2) mathjax-rails (2.6.1) railties (>= 3.0) memory_profiler (0.9.12) - method_source (1.0.0) + method_source (0.9.0) mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2020.0512) - mimemagic (0.3.5) - mini_mime (1.0.2) + mimemagic (0.3.2) + mini_mime (1.0.0) mini_portile2 (2.2.0) - minitest (5.14.1) - msgpack (1.3.3) - multi_json (1.15.0) + minitest (5.11.3) + msgpack (1.2.4) + multi_json (1.13.1) multi_xml (0.6.0) - multipart-post (2.1.1) + multipart-post (2.0.0) mustache (1.1.0) netrc (0.11.0) - nio4r (2.5.2) + nio4r (2.3.1) nokogiri (1.8.0) mini_portile2 (~> 2.2.0) oauth2 (1.4.4) @@ -267,59 +264,59 @@ GEM childprocess (~> 0.6, >= 0.6.3) iniparse (~> 1.4) pagy (3.7.1) - parallel (1.19.1) - parser (2.7.1.2) + parallel (1.17.0) + parser (2.6.3.0) ast (~> 2.4.0) pg (0.21.0) popper_js (1.12.9) psych (2.2.4) - puma (3.12.6) + puma (3.12.1) pundit (1.1.0) activesupport (>= 3.0.0) - rack (2.2.3) + rack (2.0.3) rack-attack (6.2.1) rack (>= 1.0, < 3) rack-mini-profiler (1.0.0) rack (>= 1.2.0) - rack-protection (2.0.5) + rack-protection (2.0.0) rack - rack-proxy (0.6.5) + rack-proxy (0.6.4) rack - rack-test (1.1.0) + rack-test (0.8.2) rack (>= 1.0, < 3) - rails (5.2.4.3) - actioncable (= 5.2.4.3) - actionmailer (= 5.2.4.3) - actionpack (= 5.2.4.3) - actionview (= 5.2.4.3) - activejob (= 5.2.4.3) - activemodel (= 5.2.4.3) - activerecord (= 5.2.4.3) - activestorage (= 5.2.4.3) - activesupport (= 5.2.4.3) + rails (5.2.1) + actioncable (= 5.2.1) + actionmailer (= 5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + activemodel (= 5.2.1) + activerecord (= 5.2.1) + activestorage (= 5.2.1) + activesupport (= 5.2.1) bundler (>= 1.3.0) - railties (= 5.2.4.3) + railties (= 5.2.1) sprockets-rails (>= 2.0.0) - rails-controller-testing (1.0.4) - actionpack (>= 5.0.1.x) - actionview (>= 5.0.1.x) - activesupport (>= 5.0.1.x) + rails-controller-testing (1.0.2) + actionpack (~> 5.x, >= 5.0.1) + actionview (~> 5.x, >= 5.0.1) + activesupport (~> 5.x) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (5.2.4.3) - actionpack (= 5.2.4.3) - activesupport (= 5.2.4.3) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.1) + actionpack (= 5.2.1) + activesupport (= 5.2.1) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) rainbow (3.0.0) - rake (13.0.1) - rb-fsevent (0.10.3) - rb-inotify (0.10.1) - ffi (~> 1.0) + rake (12.3.1) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) react-rails (2.4.5) babel-transpiler (>= 0.7.0) connection_pool @@ -328,57 +325,55 @@ GEM tilt redcarpet (3.3.4) redis (3.3.5) - regexp_parser (1.7.1) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.2.4) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.2) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.2) + rspec (3.7.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-core (3.7.0) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.7.0) + rspec-mocks (3.7.0) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-rails (4.0.1) - actionpack (>= 4.2) - activesupport (>= 4.2) - railties (>= 4.2) - rspec-core (~> 3.9) - rspec-expectations (~> 3.9) - rspec-mocks (~> 3.9) - rspec-support (~> 3.9) - rspec-support (3.9.3) + rspec-support (~> 3.7.0) + rspec-rails (3.7.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-support (~> 3.7.0) + rspec-support (3.7.0) rspec_api_documentation (6.1.0) activesupport (>= 3.0.0) mustache (~> 1.0, >= 0.99.4) rspec (~> 3.0) - rspec_junit_formatter (0.4.1) + rspec_junit_formatter (0.3.0) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.83.0) + rubocop (0.72.0) + jaro_winkler (~> 1.5.1) parallel (~> 1.10) - parser (>= 2.7.0.1) + parser (>= 2.6) rainbow (>= 2.2.2, < 4.0) - rexml ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) + unicode-display_width (>= 1.4.0, < 1.7) ruby-progressbar (1.10.1) - rubyzip (2.3.0) + rubyzip (1.2.1) safe_yaml (1.0.5) - sass (3.7.4) + sass (3.5.3) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.1.0) - railties (>= 5.2.0) + sass-rails (5.0.7) + railties (>= 4.0.0, < 6) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) @@ -387,24 +382,23 @@ GEM addressable (>= 2.3.5, < 2.5) faraday (~> 0.8, < 0.10) scout_apm (2.4.19) - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) - rubyzip (>= 1.2.2) + selenium-webdriver (3.8.0) + childprocess (~> 0.5) + rubyzip (~> 1.0) shoulda-matchers (3.1.2) activesupport (>= 4.0.0) - sidekiq (5.2.7) - connection_pool (~> 2.2, >= 2.2.2) - rack (>= 1.5.0) + sidekiq (5.0.5) + concurrent-ruby (~> 1.0) + connection_pool (~> 2.2, >= 2.2.0) rack-protection (>= 1.5.0) - redis (>= 3.3.5, < 5) - simplecov (0.17.1) + redis (>= 3.3.4, < 5) + simplecov (0.19.0) docile (~> 1.1) - json (>= 1.8, < 3) - simplecov-html (~> 0.10.0) - simplecov-html (0.10.2) + simplecov-html (~> 0.11) + simplecov-html (0.12.3) solid_use_case (2.2.0) deterministic (~> 0.6.0) - sprockets (3.7.2) + sprockets (3.7.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.1) @@ -418,11 +412,11 @@ GEM unicode-display_width (~> 1.1, >= 1.1.1) thor (0.19.4) thread_safe (0.3.6) - tilt (2.0.10) + tilt (2.0.8) timecop (0.9.1) ts_routes (1.0.1) railties (>= 5.0) - tzinfo (1.2.7) + tzinfo (1.2.5) thread_safe (~> 0.1) uglifier (4.0.2) execjs (>= 0.3.0, < 3) @@ -430,9 +424,9 @@ GEM unf (0.1.4) unf_ext unf_ext (0.0.7.7) - unicode-display_width (1.7.0) - vcr (6.0.0) - webmock (3.8.3) + unicode-display_width (1.6.0) + vcr (4.0.0) + webmock (3.6.0) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -440,11 +434,11 @@ GEM activesupport (>= 4.2) rack-proxy (>= 0.6.1) railties (>= 4.2) - websocket-driver (0.7.3) + websocket-driver (0.7.0) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - xpath (3.2.0) - nokogiri (~> 1.8) + websocket-extensions (0.1.3) + xpath (2.1.0) + nokogiri (~> 1.3) zip-zip (0.3) rubyzip (>= 1.0.0) @@ -524,4 +518,4 @@ RUBY VERSION ruby 2.6.0p0 BUNDLED WITH - 2.1.4 + 1.17.3 -- GitLab From 842f62e32a281df147ba78d69c3866b13ef296f9 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Sat, 26 Sep 2020 22:00:55 -1000 Subject: [PATCH 089/287] Tests for cohort_users --- app/controllers/users_controller.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 53d6131..d053711 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,5 +1,6 @@ class UsersController < ApplicationController def new + byebug authorize(Cohort) @cohort_id = params[:cohort_id] end @@ -11,8 +12,12 @@ class UsersController < ApplicationController def update authorize(current_cohort) - current_cohort_user.update(roles: [params[:instructor]].compact) - redirect_to users_cohort_path(current_cohort) + if current_cohort_user.update(roles: [params[:instructor]].compact) + redirect_to users_cohort_path(current_cohort) + else + flash[:error] = current_cohort_user.errors.full_messages.join(", ") + render :edit + end end def destroy -- GitLab From cfe4b5767f87cdc0c2b2a1ef55089e365bc7fa1b Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Mon, 28 Sep 2020 08:38:43 -1000 Subject: [PATCH 090/287] Set envs --- config/database.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/config/database.yml b/config/database.yml index 2068095..b1c7d3f 100644 --- a/config/database.yml +++ b/config/database.yml @@ -2,10 +2,10 @@ default: &default adapter: postgresql encoding: unicode pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> - host: <%= ENV["POSTGRES_HOST"] || "localhost" %> - username: <%= ENV["POSTGRES_USER"] || "auth_user" %> - password: <%= ENV["POSTGRES_PASSWORD"] || "postgres" %> - database: <%= ENV["POSTGRES_DB"] || "auth_db" %> + host: <%= ENV["PGHOST"] || "localhost" %> + username: <%= ENV["POSTGRES_USER"] || "learn_admin_user" %> + password: <%= ENV["APP_DB_ADMIN_PASSWORD"] || "postgres" %> + database: <%= ENV["POSTGRES_DB"] || "learn_db" %> min_messages: warning pool: 5 timeout: 5000 -- GitLab From 99614ca4df49e519c55c9df74856445097cf2823 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Mon, 28 Sep 2020 09:02:04 -1000 Subject: [PATCH 091/287] added db:migrate --- entrypoint-server.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 6fa9cea..3df1bbc 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -1,4 +1,5 @@ #!/bin/sh rm -f tmp/pids/server.pid +bundle exec rails db:migrate RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 -- GitLab From eb477fd3d02fe145a5f4ae4a64b728ec971ab8c2 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Mon, 28 Sep 2020 09:39:52 -1000 Subject: [PATCH 092/287] Write to stderr --- config/environments/production.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 41f4ff0..52b5b46 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -48,9 +48,15 @@ Rails.application.configure do # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = false + # Enable stdout logger + 
config.logger = Logger.new(STDOUT) + + 

# Set log level
 + config.log_level = :ERROR + # Use the lowest log level to ensure availability of diagnostic information # when problems arise. - config.log_level = :debug + # config.log_level = :debug # Prepend all log lines with the following tags. config.log_tags = [:request_id] -- GitLab From 0deebd504804a7eb57ea6f06501a3b90264f37a0 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 10:02:47 -1000 Subject: [PATCH 093/287] adding check for production env to not allow token in query params --- app/controllers/sessions_controller.rb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 852edbc..a837b85 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -24,12 +24,18 @@ class SessionsController < ActionController::Base end def token - if !request.env["Authorization"].nil? + if Rails.env == 'production' pattern = /^Bearer / header = request.env["Authorization"] header.gsub(pattern, '') if header&.match(pattern) - elsif !request.query_parameters["token"].nil? - request.query_parameters["token"] + else + if !request.env["Authorization"].nil? + pattern = /^Bearer / + header = request.env["Authorization"] + header.gsub(pattern, '') if header&.match(pattern) + elsif !request.query_parameters["token"].nil? + request.query_parameters["token"] + end end end end -- GitLab From 3227442e639776662bf530f61d7c53310f70f063 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 10:10:16 -1000 Subject: [PATCH 094/287] removing keycloak settings --- .env.example | 9 ++++--- app/services/keycloak_resolver_service.rb | 29 ----------------------- config/secrets.yml | 6 ----- 3 files changed, 4 insertions(+), 40 deletions(-) delete mode 100644 app/services/keycloak_resolver_service.rb diff --git a/.env.example b/.env.example index b8be933..2ad408f 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,7 @@ ASSESSMENTS_CALLBACK_TOKEN= LEARN_FIND_COHORT_URL="http://localhost:3002/cohorts/find" DS_PREP_UID="01t0a000005He03AAC" STANDARD_EVENT_UIDS="40f3bb48-1d23-4105-a22a-7f07b90bd1e6,cc22ef7e-d9c9-4e3d-ae9f-39f3477c5861" -KEYCLOAK_CLIENT_ID=forge -KEYCLOAK_CLIENT_SECRET=487b8a4a-e437-4795-9e1e-b68a9c340afb -KEYCLOAK_ENDPOINT=http://localhost:8080 -KEYCLOAK_REALM=learn -S3_REGION=us-gov-west-1 \ No newline at end of file +S3_REGION=us-gov-west-1 +accesskey= +secretkey= +appS3BucketUUID= \ No newline at end of file diff --git a/app/services/keycloak_resolver_service.rb b/app/services/keycloak_resolver_service.rb deleted file mode 100644 index a9aacb1..0000000 --- a/app/services/keycloak_resolver_service.rb +++ /dev/null @@ -1,29 +0,0 @@ -class KeycloakResolverService - - def initialize(auth_hash) - @auth_hash = auth_hash - end - - def find_or_create_user - existing_user = User.find_by(email: @auth_hash.info.email) - - if existing_user - existing_user.update(uid: @auth_hash.uid, - first_name: @auth_hash.info.first_name, - last_name: @auth_hash.info.last_name - ) - - existing_user - else - user = User.create(uid: @auth_hash.uid, - first_name: @auth_hash.info.first_name, - last_name: @auth_hash.info.last_name, - email: @auth_hash.info.email, - roles: [] - ) - - user - end - end - -end diff --git a/config/secrets.yml b/config/secrets.yml index c07c473..e0048ab 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -7,12 +7,6 @@ shared: auth_client_secret: <%= ENV["AUTH_CLIENT_SECRET"] %> auth_url: <%= ENV["AUTH_URL"] %> auth_webhook_token: <%= ENV["AUTH_WEBHOOK_TOKEN"] %> - keycloak_client_id: <%= ENV["KEYCLOAK_CLIENT_ID"] %> - keycloak_client_secret: <%= ENV["KEYCLOAK_CLIENT_SECRET"] %> - keycloak_logout_url: <%= "#{ENV["KEYCLOAK_ENDPOINT"]}/auth/realms/#{ENV["KEYCLOAK_REALM"]}/protocol/openid-connect/logout?redirect_uri=" %> - keycloak_client_options: - site: <%= ENV["KEYCLOAK_ENDPOINT"] %> - realm: <%= ENV["KEYCLOAK_REALM"] %> github_token: <%= ENV["GITHUB_COM_TOKEN"] %> mixpanel_project_token: <%= ENV["MIXPANEL_PROJECT_TOKEN"] %> protocol: <%= ENV['PROTOCOL'] || "http://" %> -- GitLab From 8645df4b275555b0b8838c60eac17808dbd9f634 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 10:13:44 -1000 Subject: [PATCH 095/287] removing weird characters --- config/environments/production.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 52b5b46..028aac6 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -48,10 +48,10 @@ Rails.application.configure do # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. config.force_ssl = false - # Enable stdout logger - 
config.logger = Logger.new(STDOUT) + # Enable stdout logger. + config.logger = Logger.new(STDOUT) - 

# Set log level
 + # Set log level. config.log_level = :ERROR # Use the lowest log level to ensure availability of diagnostic information -- GitLab From 8b09a0e2316cd2b45b99a1db10bf4457c046a9a8 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 11:28:51 -1000 Subject: [PATCH 096/287] changing auth header name --- app/controllers/sessions_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 852edbc..1ef778b 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -24,9 +24,9 @@ class SessionsController < ActionController::Base end def token - if !request.env["Authorization"].nil? + if !request.env["HTTP_AUTHORIZATION"].nil? pattern = /^Bearer / - header = request.env["Authorization"] + header = request.env["HTTP_AUTHORIZATION"] header.gsub(pattern, '') if header&.match(pattern) elsif !request.query_parameters["token"].nil? request.query_parameters["token"] -- GitLab From 66ecae6a3903b104c0b68c0402ee4f5ae52b8f27 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 12:05:30 -1000 Subject: [PATCH 097/287] changing log level. adding logs --- app/services/platform_one_auth_resolver_service.rb | 5 +++++ config/environments/production.rb | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index 05f913e..1f5b8c9 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -1,6 +1,7 @@ class PlatformOneAuthResolverService def initialize(jwt_token) @payload = JWT.decode(jwt_token, nil, false)[0] + puts @payload end def find_or_create_user @@ -21,6 +22,10 @@ class PlatformOneAuthResolverService roles: [] ) + unless user.valid? + puts user.errors.full_messages + end + user end end diff --git a/config/environments/production.rb b/config/environments/production.rb index 028aac6..48c5407 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -51,12 +51,9 @@ Rails.application.configure do # Enable stdout logger. config.logger = Logger.new(STDOUT) - # Set log level. - config.log_level = :ERROR - # Use the lowest log level to ensure availability of diagnostic information # when problems arise. - # config.log_level = :debug + config.log_level = :debug # Prepend all log lines with the following tags. config.log_tags = [:request_id] -- GitLab From 5cb3554fea4d26dcc4de5eb17a0154f9281d908d Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 12:38:08 -1000 Subject: [PATCH 098/287] user certificate parsing --- .../platform_one_auth_resolver_service.rb | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index 1f5b8c9..b01078f 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -1,24 +1,37 @@ class PlatformOneAuthResolverService + class UserCertificateError < StandardError + end + def initialize(jwt_token) @payload = JWT.decode(jwt_token, nil, false)[0] - puts @payload + user_certificate = @payload["usercertificate"] + user_parts = user_certificate.split(".") + + if user_parts.nil? || user_parts.length < 3 + raise UserCertificateError + end + + @dod_id = user_parts.last + @last_name = user_parts[0] + @first_name = user_parts[1] + @email = @payload["email"] || "#{@first_name}.#{@last_name}@changeme.com" end def find_or_create_user - existing_user = User.find_by(email: @payload["email"]) + existing_user = User.find_by(email: @email) if existing_user - existing_user.update(uid: @payload["sub"], - first_name: @payload["given_name"], - last_name: @payload["family_name"] + existing_user.update(uid: @dod_id, + first_name: @first_name, + last_name: @last_name ) existing_user else - user = User.create(uid: @payload["sub"], - first_name: @payload["given_name"], - last_name: @payload["family_name"], - email: @payload["email"], + user = User.create(uid: @dod_id, + first_name: @first_name, + last_name: @last_name, + email: @email, roles: [] ) -- GitLab From cedc55d24d37ce9dc8ab417717ed0314edce2d3d Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 13:53:04 -1000 Subject: [PATCH 099/287] change to titleize the names from the jwt token. change to set email to blank string instead of fake email --- app/services/platform_one_auth_resolver_service.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index b01078f..b24dc26 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -2,6 +2,9 @@ class PlatformOneAuthResolverService class UserCertificateError < StandardError end + class CreateUserError < StandardError + end + def initialize(jwt_token) @payload = JWT.decode(jwt_token, nil, false)[0] user_certificate = @payload["usercertificate"] @@ -12,9 +15,9 @@ class PlatformOneAuthResolverService end @dod_id = user_parts.last - @last_name = user_parts[0] - @first_name = user_parts[1] - @email = @payload["email"] || "#{@first_name}.#{@last_name}@changeme.com" + @last_name = user_parts[0].titleize + @first_name = user_parts[1].titleize + @email = @payload["email"] || "" end def find_or_create_user @@ -37,6 +40,7 @@ class PlatformOneAuthResolverService unless user.valid? puts user.errors.full_messages + raise CreateUserError end user -- GitLab From 6f79f174605d5410529cb62ae091b4e21b35bc90 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 13:57:29 -1000 Subject: [PATCH 100/287] updating bundle lock file --- Gemfile.lock | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6360384..b82f726 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,7 +58,6 @@ GEM minitest (~> 5.1) tzinfo (~> 1.1) addressable (2.4.0) - aes_key_wrap (1.1.0) analytics-ruby (2.0.13) apitome (0.3.0) kramdown @@ -86,7 +85,6 @@ GEM coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) - bindata (2.4.8) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) bootsnap (1.3.1) @@ -175,7 +173,6 @@ GEM temple (>= 0.8.0) tilt hashdiff (0.4.0) - hashie (4.1.0) honeybadger (3.2.0) httparty (0.15.6) multi_xml (>= 0.5.2) @@ -191,10 +188,6 @@ GEM js-routes (1.4.2) railties (>= 3.2) sprockets-rails - json-jwt (1.13.0) - activesupport (>= 4.2) - aes_key_wrap - bindata json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) @@ -227,24 +220,8 @@ GEM nio4r (2.3.1) nokogiri (1.8.0) mini_portile2 (~> 2.2.0) - oauth2 (1.4.4) - faraday (>= 0.8, < 2.0) - jwt (>= 1.0, < 3.0) - multi_json (~> 1.3) - multi_xml (~> 0.5) - rack (>= 1.2, < 3) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) - omniauth (1.9.1) - hashie (>= 3.4.6) - rack (>= 1.6.2, < 3) - omniauth-keycloak (1.2.0) - json-jwt (~> 1.12) - omniauth (~> 1.9.0) - omniauth-oauth2 (~> 1.6.0) - omniauth-oauth2 (1.6.0) - oauth2 (~> 1.1) - omniauth (~> 1.9) overcommit (0.41.0) childprocess (~> 0.6, >= 0.6.3) iniparse (~> 1.4) @@ -451,8 +428,6 @@ DEPENDENCIES mathjax-rails memory_profiler octokit - omniauth - omniauth-keycloak overcommit pagy pg (~> 0.18) -- GitLab From 88c8ae608229f8cd3aa9af72e0a873cc72a82952 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 14:18:39 -1000 Subject: [PATCH 101/287] adding log --- app/services/platform_one_auth_resolver_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index b24dc26..7fe812e 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -7,6 +7,7 @@ class PlatformOneAuthResolverService def initialize(jwt_token) @payload = JWT.decode(jwt_token, nil, false)[0] + puts @payload user_certificate = @payload["usercertificate"] user_parts = user_certificate.split(".") -- GitLab From 0fd7655007c39e293880fd38c65ae32c9c92389f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 14:55:04 -1000 Subject: [PATCH 102/287] adding handling for non CAC users. change db to allow empty emails --- app/models/user.rb | 2 +- .../platform_one_auth_resolver_service.rb | 32 +++++++++++-------- ...0929004708_remove_null_email_from_users.rb | 5 +++ db/schema.rb | 4 +-- 4 files changed, 27 insertions(+), 16 deletions(-) create mode 100644 db/migrate/20200929004708_remove_null_email_from_users.rb diff --git a/app/models/user.rb b/app/models/user.rb index 612af35..38dde44 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,7 +8,7 @@ class User < ApplicationRecord blocks_manager: "forge.blocks_manager" }.freeze - validates(:email, presence: true) + # validates(:email, presence: true) validates(:uid, uniqueness: true) has_many :cohort_users diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index 7fe812e..2d74bae 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -8,31 +8,37 @@ class PlatformOneAuthResolverService def initialize(jwt_token) @payload = JWT.decode(jwt_token, nil, false)[0] puts @payload - user_certificate = @payload["usercertificate"] - user_parts = user_certificate.split(".") + @email = @payload["email"] || "" - if user_parts.nil? || user_parts.length < 3 - raise UserCertificateError - end + if @payload["usercertificate"].nil? || @payload["usercertificate"].empty? + @uid = @payload["sub"] + @first_name = "" + @last_name = "" + else + user_certificate = @payload["usercertificate"] + user_parts = user_certificate.split(".") - @dod_id = user_parts.last - @last_name = user_parts[0].titleize - @first_name = user_parts[1].titleize - @email = @payload["email"] || "" + if user_parts.nil? || user_parts.length < 3 + raise UserCertificateError + end + + @uid = user_parts.last + @last_name = user_parts[0].titleize + @first_name = user_parts[1].titleize + end end def find_or_create_user - existing_user = User.find_by(email: @email) + existing_user = User.find_by(uid: @uid) if existing_user - existing_user.update(uid: @dod_id, - first_name: @first_name, + existing_user.update(first_name: @first_name, last_name: @last_name ) existing_user else - user = User.create(uid: @dod_id, + user = User.create(uid: @uid, first_name: @first_name, last_name: @last_name, email: @email, diff --git a/db/migrate/20200929004708_remove_null_email_from_users.rb b/db/migrate/20200929004708_remove_null_email_from_users.rb new file mode 100644 index 0000000..1610586 --- /dev/null +++ b/db/migrate/20200929004708_remove_null_email_from_users.rb @@ -0,0 +1,5 @@ +class RemoveNullEmailFromUsers < ActiveRecord::Migration[5.2] + def change + change_column( :users, :email, :string, :null => true) + end +end diff --git a/db/schema.rb b/db/schema.rb index f5e6ecd..f205c10 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_09_25_230149) do +ActiveRecord::Schema.define(version: 2020_09_29_004708) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -359,7 +359,7 @@ ActiveRecord::Schema.define(version: 2020_09_25_230149) do t.string "uid" t.string "first_name" t.string "last_name" - t.string "email", null: false + t.string "email" t.string "timezone" t.string "profile_image" t.datetime "created_at", null: false -- GitLab From d386e69683fb2da310df1064e5fbfed019a1ba96 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 28 Sep 2020 15:26:43 -1000 Subject: [PATCH 103/287] changing to use keycloak id for everyone --- app/services/platform_one_auth_resolver_service.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index 2d74bae..a32d5d1 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -8,10 +8,10 @@ class PlatformOneAuthResolverService def initialize(jwt_token) @payload = JWT.decode(jwt_token, nil, false)[0] puts @payload + @uid = @payload["sub"] @email = @payload["email"] || "" if @payload["usercertificate"].nil? || @payload["usercertificate"].empty? - @uid = @payload["sub"] @first_name = "" @last_name = "" else @@ -22,7 +22,6 @@ class PlatformOneAuthResolverService raise UserCertificateError end - @uid = user_parts.last @last_name = user_parts[0].titleize @first_name = user_parts[1].titleize end -- GitLab From b37ddc331a024ced236cddc5ae2ddaa88decc700 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Mon, 28 Sep 2020 18:45:01 -1000 Subject: [PATCH 104/287] Run webpack --- entrypoint-server.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 3df1bbc..74d5445 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -1,5 +1,7 @@ #!/bin/sh +/usr/src/app/bin/webpack + rm -f tmp/pids/server.pid bundle exec rails db:migrate RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 -- GitLab From 45810e76d08ee6e49577eeddbf053fa38f145295 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Tue, 29 Sep 2020 10:08:27 -1000 Subject: [PATCH 105/287] Debug mode, on --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4fa228d..3abf0b0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -62,5 +62,5 @@ RUN NODE_ENV=production bin/webpack ENV PATH /usr/src/app/node_modules/.bin:$PATH # Set the entry point. -#CMD ["tail", "-f", "/dev/null"] -CMD ["./entrypoint-server.sh"] +CMD ["tail", "-f", "/dev/null"] +#CMD ["./entrypoint-server.sh"] -- GitLab From 98f96c46634cd595dc2375817930c1f21b674e36 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 11:07:23 -1000 Subject: [PATCH 106/287] fixing build issues. adding build files to git ignore. --- .gitignore | 4 +++- Dockerfile | 7 +++++-- app/controllers/cohorts_controller.rb | 2 +- config/environments/production.rb | 5 ++--- entrypoint-server.sh | 2 -- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index cbbbfd1..7aadd35 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,6 @@ tags .zshrc .solargraph.yml .generators -.rakeTasks \ No newline at end of file +.rakeTasks +/public/assets/ +!/public/assets/images diff --git a/Dockerfile b/Dockerfile index 4fa228d..1148353 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,11 +52,14 @@ RUN gem install rake -v '12.3.1' --source 'https://rubygems.org/' # Run the bundle install RUN bundle install +# Precompile assets +RUN bundle exec rake assets:precompile + # Run Yarn Install RUN yarn install -# Run Webpack build. -RUN NODE_ENV=production bin/webpack +# Asset Clean +RUN bundle exec rake assets:clean # Add the node_modules to the path. ENV PATH /usr/src/app/node_modules/.bin:$PATH diff --git a/app/controllers/cohorts_controller.rb b/app/controllers/cohorts_controller.rb index 159fdd6..a8a564a 100644 --- a/app/controllers/cohorts_controller.rb +++ b/app/controllers/cohorts_controller.rb @@ -64,7 +64,7 @@ class CohortsController < ApplicationController learn_base_url: ENV["LEARN_BASE_URL"], cohort_types: cohort_types, cohort_campuses: cohort_campuses, - new_auth_product_url: Rails.application.secrets.auth_url + "/admin/products/new" + new_auth_product_url: "" } ) end diff --git a/config/environments/production.rb b/config/environments/production.rb index 48c5407..9e68ea7 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -23,15 +23,14 @@ Rails.application.configure do # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. - config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + config.public_file_server.enabled = true # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. - # config.assets.compile = false - config.assets.compile = true + config.assets.compile = false # Generate digests for assets URLs. config.assets.digest = true diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 74d5445..3df1bbc 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -1,7 +1,5 @@ #!/bin/sh -/usr/src/app/bin/webpack - rm -f tmp/pids/server.pid bundle exec rails db:migrate RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 -- GitLab From 25e041b029813920fa967dfe077f3965563b62c0 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 11:09:10 -1000 Subject: [PATCH 107/287] switching to startup the app. --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7de8b07..1148353 100644 --- a/Dockerfile +++ b/Dockerfile @@ -65,5 +65,5 @@ RUN bundle exec rake assets:clean ENV PATH /usr/src/app/node_modules/.bin:$PATH # Set the entry point. -CMD ["tail", "-f", "/dev/null"] -#CMD ["./entrypoint-server.sh"] +#CMD ["tail", "-f", "/dev/null"] +CMD ["./entrypoint-server.sh"] -- GitLab From d47a562d94beda96b39e6befae79b9d577e42ab4 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 11:34:43 -1000 Subject: [PATCH 108/287] fixing issue on other tabs loading --- app/controllers/cohorts_controller.rb | 8 ++++---- app/models/user.rb | 2 +- app/views/cohorts/users.html.haml | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/controllers/cohorts_controller.rb b/app/controllers/cohorts_controller.rb index a8a564a..916e7db 100644 --- a/app/controllers/cohorts_controller.rb +++ b/app/controllers/cohorts_controller.rb @@ -102,7 +102,7 @@ class CohortsController < ApplicationController locals: { cohort: current_cohort, release_count: release_count, - auth_product_url: Rails.application.secrets.auth_url + "/admin/products/#{current_cohort.uid}" + auth_product_url: "" } ) end @@ -127,7 +127,7 @@ class CohortsController < ApplicationController visibilities: visibilities ) end, - auth_product_url: Rails.application.secrets.auth_url + "/admin/products/#{current_cohort.uid}" + auth_product_url: "" } ) end @@ -315,7 +315,7 @@ class CohortsController < ApplicationController end, unattached_block_release_options: ReleaseFinder.latest_for_all_blocks_not_attached_to_cohort(current_cohort.id), - auth_product_url: Rails.application.secrets.auth_url + "/admin/products/#{current_cohort.uid}" + auth_product_url: "" } ) end @@ -339,7 +339,7 @@ class CohortsController < ApplicationController students: current_cohort.cohort_users.student.map { |s| { uid: s.user.uid, name: s.user.full_name, profile_image: s.user.profile_image }}, release_count: release_count, pairings: Pairing.where(cohort_id: current_cohort.id).order(created_at: :desc), - auth_product_url: "#{Rails.application.secrets.auth_url}/admin/products/#{current_cohort.uid}" + auth_product_url: "" } ) end diff --git a/app/models/user.rb b/app/models/user.rb index 38dde44..c56665e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -120,6 +120,6 @@ class User < ApplicationRecord end def auth_edit_url - "#{Rails.application.secrets.auth_url}/admin/users/#{uid}/edit" + "" end end diff --git a/app/views/cohorts/users.html.haml b/app/views/cohorts/users.html.haml index 55e7d0e..aff6bbc 100644 --- a/app/views/cohorts/users.html.haml +++ b/app/views/cohorts/users.html.haml @@ -45,7 +45,7 @@ %td.action-cell yes %div.user-action - = react_component "cohorts/settings/UserCohortKebab", studentId: instructor.id, studentUid: instructor.uid, cohortId: cohort.id, authUrl: Rails.application.secrets.auth_url + = react_component "cohorts/settings/UserCohortKebab", studentId: instructor.id, studentUid: instructor.uid, cohortId: cohort.id, authUrl: "" - cohort.students.order(first_name: :asc).each do |student| %tr.cohortsetuprow %td.cohortusername= student.full_name @@ -54,4 +54,4 @@ %td.action-cell no %div.user-action - = react_component "cohorts/settings/UserCohortKebab", studentId: student.id, studentUid: student.uid, cohortId: cohort.id, authUrl: Rails.application.secrets.auth_url + = react_component "cohorts/settings/UserCohortKebab", studentId: student.id, studentUid: student.uid, cohortId: cohort.id, authUrl: "" -- GitLab From 5a58d116928e47fe5849e1cc54fc862cc33bae4b Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 11:57:24 -1000 Subject: [PATCH 109/287] adding startup of sidekiq --- entrypoint-server.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 3df1bbc..23730d1 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -2,4 +2,5 @@ rm -f tmp/pids/server.pid bundle exec rails db:migrate +bundle exec sidekiq & RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 -- GitLab From 1e0562ac6cb6ccbc95466a014678189d134569d1 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 12:22:57 -1000 Subject: [PATCH 110/287] changing api app controller current user. --- app/controllers/api/application_controller.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/application_controller.rb b/app/controllers/api/application_controller.rb index 128bcd8..24c4ae6 100644 --- a/app/controllers/api/application_controller.rb +++ b/app/controllers/api/application_controller.rb @@ -37,11 +37,13 @@ class Api::ApplicationController < ActionController::API end def current_user - @current_user ||= if request.headers["Authorization"] - User.find_by(api_token: bearer_token) - else - User.find_by(uid: session[:user_uid]) - end + # @current_user ||= if request.headers["Authorization"] + # User.find_by(api_token: bearer_token) + # else + # User.find_by(uid: session[:user_uid]) + # end + + @current_user ||= User.find_by(uid: session[:user_uid]) end def current_cohort -- GitLab From f9ed26f2bedb3c1f8429fa43b56f4cc57b4a9627 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 14:38:58 -1000 Subject: [PATCH 111/287] changing sidekiq to run in production mode and log to file. --- .gitignore | 1 + entrypoint-server.sh | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7aadd35..6b52443 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ tags .rakeTasks /public/assets/ !/public/assets/images +log.txt \ No newline at end of file diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 23730d1..588f9ad 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -2,5 +2,5 @@ rm -f tmp/pids/server.pid bundle exec rails db:migrate -bundle exec sidekiq & -RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 +RAILS_ENV=production bundle exec sidekiq > log.txt 2>&1 & +RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 \ No newline at end of file -- GitLab From 43c214fc6ae2a131d95c13b6511fc672a312c389 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Tue, 29 Sep 2020 15:30:41 -1000 Subject: [PATCH 112/287] Install redis --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 1148353..8439451 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ ENV count 6 # Install what we can through OS package management RUN apt-get -y update RUN apt-get -y install --no-install-recommends \ + redis-tools \ curl \ software-properties-common \ postgresql-client\ -- GitLab From d86b031b8c33baca3cf50f309a2fd96dd2f78369 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 16:19:27 -1000 Subject: [PATCH 113/287] changes for minio --- config/initializers/aws.rb | 10 +++++++++- config/secrets.yml | 5 +++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/config/initializers/aws.rb b/config/initializers/aws.rb index 9694d1d..b3f02f1 100644 --- a/config/initializers/aws.rb +++ b/config/initializers/aws.rb @@ -35,5 +35,13 @@ end Rails.application.config.aws = if Rails.env.test? AwsMock.new else - Aws::S3::Resource.new(region: Rails.application.secrets.s3_region).bucket(Rails.application.secrets.glearn_bucket_name) + options = { + :region => Rails.application.secrets.s3_region + } + + if !Rails.application.secrets.minio_endpoint_url.nil? + options[:endpoint] = Rails.application.secrets.minio_endpoint_url + end + + Aws::S3::Resource.new(options).bucket(Rails.application.secrets.glearn_bucket_name) end diff --git a/config/secrets.yml b/config/secrets.yml index ce9959d..0898743 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -10,7 +10,7 @@ shared: github_token: <%= ENV["GITHUB_COM_TOKEN"] %> mixpanel_project_token: <%= ENV["MIXPANEL_PROJECT_TOKEN"] %> protocol: <%= ENV['PROTOCOL'] || "http://" %> - s3_bucket_name: <%= ENV["appS3BucketUUID"] %> + s3_bucket_name: <%= ENV["APP_S3_BUCKET"] %> secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> sendgrid_password: <%= ENV['SENDGRID_PASSWORD'] %> sendgrid_username: <%= ENV['SENDGRID_USERNAME'] %> @@ -23,9 +23,10 @@ shared: glearn_access_key_id: <%= ENV['accesskey'] %> glearn_secret_access_key: <%= ENV['secretkey'] %> glearn_key_prefix: <%= ENV['GLEARN_KEY_PREFIX'] || "forge" %> - glearn_bucket_name: <%= ENV["appS3BucketUUID"] %> + glearn_bucket_name: <%= ENV["APP_S3_BUCKET"] %> s3_region: <%= ENV["S3_REGION"] || "us-gov-west-1" %> dev_notify_slack_url: <%= ENV['DEV_NOTIFY_SLACK_URL'] %> + minio_endpoint_url: <%= ENV["MINIO_ENDPOINT_URL"] %> git_download_tokens: github_com: type: github -- GitLab From 3a8002ec21db4af8cf9cf921374261e355f17cd6 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 16:40:53 -1000 Subject: [PATCH 114/287] changing sidekiq production mode parameter. --- entrypoint-server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 588f9ad..42dd16f 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -2,5 +2,5 @@ rm -f tmp/pids/server.pid bundle exec rails db:migrate -RAILS_ENV=production bundle exec sidekiq > log.txt 2>&1 & +bundle exec sidekiq -e production > log.txt 2>&1 & RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 \ No newline at end of file -- GitLab From f3a51f30f91c4ecf8364811fe1d8938cbb2b1121 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 16:45:31 -1000 Subject: [PATCH 115/287] changing sidekiq command --- entrypoint-server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 42dd16f..1106a52 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -2,5 +2,5 @@ rm -f tmp/pids/server.pid bundle exec rails db:migrate -bundle exec sidekiq -e production > log.txt 2>&1 & +bundle exec sidekiq -d -e production -L sidekiq.log -C config/sidekiq.yml RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 \ No newline at end of file -- GitLab From 0919fc429c5bccb5bb05f570bfd59ba402d19d33 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 18:03:44 -1000 Subject: [PATCH 116/287] fixing aws module setup for minio --- .gitignore | 3 ++- config/initializers/aws.rb | 30 +++++++++++++++++------------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 6b52443..d8bcd2a 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ tags .rakeTasks /public/assets/ !/public/assets/images -log.txt \ No newline at end of file +log.txt +sidekiq.log \ No newline at end of file diff --git a/config/initializers/aws.rb b/config/initializers/aws.rb index b3f02f1..6961a43 100644 --- a/config/initializers/aws.rb +++ b/config/initializers/aws.rb @@ -32,16 +32,20 @@ class AwsMock end end -Rails.application.config.aws = if Rails.env.test? - AwsMock.new - else - options = { - :region => Rails.application.secrets.s3_region - } - - if !Rails.application.secrets.minio_endpoint_url.nil? - options[:endpoint] = Rails.application.secrets.minio_endpoint_url - end - - Aws::S3::Resource.new(options).bucket(Rails.application.secrets.glearn_bucket_name) - end +if Rails.env.test? + Rails.application.config.aws = AwsMock.new +else + options = { + :access_key_id => Rails.application.secrets.glearn_access_key_id, + :secret_access_key => Rails.application.secrets.glearn_secret_access_key + } + + if !Rails.application.secrets.minio_endpoint_url.nil? + options[:endpoint] = Rails.application.secrets.minio_endpoint_url + options[:force_path_style] = true + else + options[:region] = Rails.application.secrets.s3_region + end + + Rails.application.config.aws = Aws::S3::Resource.new(options).bucket(Rails.application.secrets.glearn_bucket_name) +end \ No newline at end of file -- GitLab From aab820b4be2e5da15229518bf27cd998372a7046 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 29 Sep 2020 18:54:06 -1000 Subject: [PATCH 117/287] adding region back in --- config/initializers/aws.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/config/initializers/aws.rb b/config/initializers/aws.rb index 6961a43..bf80004 100644 --- a/config/initializers/aws.rb +++ b/config/initializers/aws.rb @@ -37,14 +37,13 @@ if Rails.env.test? else options = { :access_key_id => Rails.application.secrets.glearn_access_key_id, - :secret_access_key => Rails.application.secrets.glearn_secret_access_key + :secret_access_key => Rails.application.secrets.glearn_secret_access_key, + :region => Rails.application.secrets.s3_region } if !Rails.application.secrets.minio_endpoint_url.nil? options[:endpoint] = Rails.application.secrets.minio_endpoint_url options[:force_path_style] = true - else - options[:region] = Rails.application.secrets.s3_region end Rails.application.config.aws = Aws::S3::Resource.new(options).bucket(Rails.application.secrets.glearn_bucket_name) -- GitLab From f1bd7dad17311a6c81a112290b61488b7c54f35a Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 30 Sep 2020 12:45:34 -1000 Subject: [PATCH 118/287] changing to use email again for looking up existing users. --- .../platform_one_auth_resolver_service.rb | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index a32d5d1..d6d45b6 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -9,30 +9,18 @@ class PlatformOneAuthResolverService @payload = JWT.decode(jwt_token, nil, false)[0] puts @payload @uid = @payload["sub"] - @email = @payload["email"] || "" - - if @payload["usercertificate"].nil? || @payload["usercertificate"].empty? - @first_name = "" - @last_name = "" - else - user_certificate = @payload["usercertificate"] - user_parts = user_certificate.split(".") - - if user_parts.nil? || user_parts.length < 3 - raise UserCertificateError - end - - @last_name = user_parts[0].titleize - @first_name = user_parts[1].titleize - end + @email = @payload["email"] + @first_name = @payload["given_name"] + @last_name = @payload["family_name"] end def find_or_create_user - existing_user = User.find_by(uid: @uid) + existing_user = User.find_by(email: @email) if existing_user - existing_user.update(first_name: @first_name, - last_name: @last_name + existing_user.update(uid: @uid, + first_name: @first_name, + last_name: @last_name, ) existing_user -- GitLab From 1b2e9e7d24973080b6f0e2a469fe14502171d00c Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Wed, 30 Sep 2020 16:06:06 -1000 Subject: [PATCH 119/287] Revert to old method of starting sidekiq --- entrypoint-server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 1106a52..95515d9 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -2,5 +2,5 @@ rm -f tmp/pids/server.pid bundle exec rails db:migrate -bundle exec sidekiq -d -e production -L sidekiq.log -C config/sidekiq.yml +bundle exec sidekiq -e production -L sidekiq.log & RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 \ No newline at end of file -- GitLab From 996d2b77cb7d4c2c8f6c42d99c5ef81aeae30b6d Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 30 Sep 2020 17:29:12 -1000 Subject: [PATCH 120/287] adding secrets for webhooks url --- config/secrets.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/secrets.yml b/config/secrets.yml index 0898743..890cc21 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -1,5 +1,6 @@ shared: - actionmailer_host: <%= ENV['HEROKU_APP_NAME'].present? ? "#{ENV['HEROKU_APP_NAME']}.herokuapp.com" : ENV['ACTIONMAILER_HOST'] %> + actionmailer_host: <%= ENV['ACTIONMAILER_HOST'] %> + actionmailer_port: <%= ENV['ACTIONMAILER_PORT'] %> assessments_api_key: <%= ENV['ASSESSMENTS_API_KEY'] %> assessments_callback_token: <%= ENV['ASSESSMENTS_CALLBACK_TOKEN'] %> assessments_service_domain: <%= ENV['ASSESSMENTS_SERVICE_DOMAIN'] %> -- GitLab From ceac82aaf146bd23d7a3277a49441ff755f2703d Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 30 Sep 2020 22:38:21 -1000 Subject: [PATCH 121/287] removing reference to learn base url --- app/controllers/cohorts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/cohorts_controller.rb b/app/controllers/cohorts_controller.rb index 916e7db..5aa9eff 100644 --- a/app/controllers/cohorts_controller.rb +++ b/app/controllers/cohorts_controller.rb @@ -61,7 +61,7 @@ class CohortsController < ApplicationController all_other_cohorts: all_other_cohorts, own_cohorts: own_cohorts, cohort_id_counts: cohort_id_counts, - learn_base_url: ENV["LEARN_BASE_URL"], + learn_base_url: "", cohort_types: cohort_types, cohort_campuses: cohort_campuses, new_auth_product_url: "" -- GitLab From 66284061b8e7b46ef15528de776e767e6a143d26 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 1 Oct 2020 11:49:58 -1000 Subject: [PATCH 122/287] fixing bug on cohorts where user does not have a name yet --- app/models/user.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index c56665e..a307bbf 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,8 +8,8 @@ class User < ApplicationRecord blocks_manager: "forge.blocks_manager" }.freeze - # validates(:email, presence: true) - validates(:uid, uniqueness: true) + validates(:email, presence: true) + validates(:uid, uniqueness: true, allow_nil: true) has_many :cohort_users has_many :cohorts, through: :cohort_users @@ -35,7 +35,11 @@ class User < ApplicationRecord end def initials - "#{first_name[0]}#{last_name[0]}".upcase + if first_name.nil? || last_name.nil? + "" + else + "#{first_name[0]}#{last_name[0]}".upcase + end end def instructor_of?(cohort_id) -- GitLab From eccfd36805bd5e0c74b0a6edef7b1f1f8186ad29 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 1 Oct 2020 16:19:11 -1000 Subject: [PATCH 123/287] changing the way the auth header is read. --- app/controllers/sessions_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 9ee7c56..e2f99ce 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -26,12 +26,12 @@ class SessionsController < ActionController::Base def token if Rails.env == 'production' pattern = /^Bearer / - header = request.env["HTTP_AUTHORIZATION"] + header = request.headers["Authorization"] header.gsub(pattern, '') if header&.match(pattern) else - if !request.env["HTTP_AUTHORIZATION"].nil? + if !request.headers["Authorization"].nil? pattern = /^Bearer / - header = request.env["HTTP_AUTHORIZATION"] + header = request.headers["Authorization"] header.gsub(pattern, '') if header&.match(pattern) elsif !request.query_parameters["token"].nil? request.query_parameters["token"] -- GitLab From efe379995874620ee5d8cf15f614411a7e86b003 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 1 Oct 2020 16:49:49 -1000 Subject: [PATCH 124/287] changing header name for bearer token --- app/controllers/api/application_controller.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/application_controller.rb b/app/controllers/api/application_controller.rb index 24c4ae6..8c6e56e 100644 --- a/app/controllers/api/application_controller.rb +++ b/app/controllers/api/application_controller.rb @@ -37,13 +37,11 @@ class Api::ApplicationController < ActionController::API end def current_user - # @current_user ||= if request.headers["Authorization"] - # User.find_by(api_token: bearer_token) - # else - # User.find_by(uid: session[:user_uid]) - # end - - @current_user ||= User.find_by(uid: session[:user_uid]) + @current_user ||= if request.headers["X-LEARN-API-TOKEN"] + User.find_by(api_token: bearer_token) + else + User.find_by(uid: session[:user_uid]) + end end def current_cohort @@ -58,7 +56,7 @@ class Api::ApplicationController < ActionController::API def bearer_token pattern = /^Bearer / - header = request.headers["Authorization"] + header = request.headers["X-LEARN-API-TOKEN"] header.gsub(pattern, "") if header&.match(pattern) end -- GitLab From af52384f59428698b1273a3ca169be41420963b5 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 1 Oct 2020 20:05:56 -1000 Subject: [PATCH 125/287] fixing bug on cohort user edit using wrong user to determine if global roles can be editted. --- app/controllers/users_controller.rb | 2 +- app/views/users/edit.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 9339cb6..8ab5b37 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -14,7 +14,7 @@ class UsersController < ApplicationController authorize(current_cohort) current_cohort_user.update(roles: [params[:instructor]].compact) - if (user.admin?) + if (current_user.admin?) user.update(roles:[params[:forge_admin], params[:forge_blocks_manager], params[:auth_product_admin]].compact) end diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index cdc6a68..de0f522 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -15,7 +15,7 @@ .form-check = check_box_tag :instructor, "forge.instructor", @cohort_user.roles.include?("forge.instructor"), class: "form-check-input" = label_tag :instructor, "Instructor" - - if @user.admin? + - if @current_user.admin? %h5 Global Roles: .form-group .form-check -- GitLab From fde70bb8214f3459d6ad6a8f7962f73986e2caed Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Mon, 5 Oct 2020 13:45:05 -1000 Subject: [PATCH 126/287] Testing & doc changes for new API authorization key --- config/secrets.yml | 1 - doc/api.md | 2 +- doc/api/cohorts/creating_a_new_cohort.json | 4 ++-- doc/api/cohorts/viewing_curriculum_details.json | 2 +- doc/api/content/update_content_visibility.json | 2 +- doc/api/enrollments/creating_a_user_and_their_enrollment.json | 2 +- doc/api/enrollments/unenrolling_a_user_from_a_cohort.json | 4 ++-- doc/api/enrollments/updating_a_user's_enrollment_roles.json | 4 ++-- doc/api/enrollments/viewing_cohort_enrollments.json | 4 ++-- doc/api/units/update_unit_visibility.json | 2 +- doc/api/users/regenerate_user_token.json | 2 +- scripts/sh/cohort_curriculum.sh | 2 +- scripts/sh/content_file_mark_hidden.sh | 2 +- scripts/sh/content_file_mark_visible.sh | 2 +- scripts/sh/unit_mark_hidden.sh | 2 +- scripts/sh/unit_mark_visible.sh | 2 +- 16 files changed, 19 insertions(+), 20 deletions(-) diff --git a/config/secrets.yml b/config/secrets.yml index 890cc21..f66ca92 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -59,7 +59,6 @@ test: auth_url: http://localhost:5000 assessments_callback_token: yep assessments_service_domain: http://test-assessments-server/assessments - auth_url: http://localhost:5000 secret_key_base: 4a87e39a69c39c2d133099a329efab6c39b428b807c3e89601e2196cfa1ca79c2560530283d0a845e95b939bd07acfc3bc3686c36dfde8aaf8fc57ea2d1ca6e4 segment_write_key_server: test_key segment_write_key_client: test_key diff --git a/doc/api.md b/doc/api.md index 02fbd41..7631cd6 100644 --- a/doc/api.md +++ b/doc/api.md @@ -8,7 +8,7 @@ Future work might add the ability to publish curriculum and manage cohort creati Authentication ============== -Each Learn admin and instructor account will have a token that can be used to access the API. The token can be found in the UI under the account dropdown, or by going directly to https://learn-2.galvanize.com/api_token. The token must be passed in the header of API requests as `Authorization: Bearer {token}` +Each Learn admin and instructor account will have a token that can be used to access the API. The token can be found in the UI under the account dropdown, or by going directly to https://learn.apps.il2.dsop.io/api_token. The token must be passed in the header of API requests as `X-LEARN-API-TOKEN: Bearer {token}` The authorization scope for each token will match the user’s permissions. For example, if a user is allowed to update a cohort’s visibility in the UI, that user’s token will be allowed to make API requests to do the same. diff --git a/doc/api/cohorts/creating_a_new_cohort.json b/doc/api/cohorts/creating_a_new_cohort.json index e080c07..d062b17 100644 --- a/doc/api/cohorts/creating_a_new_cohort.json +++ b/doc/api/cohorts/creating_a_new_cohort.json @@ -60,7 +60,7 @@ "request_headers": { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": "Bearer token", + "X-LEARN-API-TOKEN": "Bearer token", "Host": "example.org", "Cookie": "" }, @@ -83,4 +83,4 @@ "curl": null } ] -} \ No newline at end of file +} diff --git a/doc/api/cohorts/viewing_curriculum_details.json b/doc/api/cohorts/viewing_curriculum_details.json index 6b55981..826cf79 100644 --- a/doc/api/cohorts/viewing_curriculum_details.json +++ b/doc/api/cohorts/viewing_curriculum_details.json @@ -24,7 +24,7 @@ "request_headers": { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": "Bearer token", + "X-LEARN-API-TOKEN": "Bearer token", "Host": "example.org", "Cookie": "" }, diff --git a/doc/api/content/update_content_visibility.json b/doc/api/content/update_content_visibility.json index b731347..d8c15a2 100644 --- a/doc/api/content/update_content_visibility.json +++ b/doc/api/content/update_content_visibility.json @@ -42,7 +42,7 @@ "request_headers": { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": "Bearer token", + "X-LEARN-API-TOKEN": "Bearer token", "Host": "example.org", "Cookie": "" }, diff --git a/doc/api/enrollments/creating_a_user_and_their_enrollment.json b/doc/api/enrollments/creating_a_user_and_their_enrollment.json index 2ac9988..73ea749 100644 --- a/doc/api/enrollments/creating_a_user_and_their_enrollment.json +++ b/doc/api/enrollments/creating_a_user_and_their_enrollment.json @@ -54,7 +54,7 @@ "request_headers": { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": "Bearer token", + "X-LEARN-API-TOKEN": "Bearer token", "Host": "example.org", "Cookie": "" }, diff --git a/doc/api/enrollments/unenrolling_a_user_from_a_cohort.json b/doc/api/enrollments/unenrolling_a_user_from_a_cohort.json index fe3e684..e1905e5 100644 --- a/doc/api/enrollments/unenrolling_a_user_from_a_cohort.json +++ b/doc/api/enrollments/unenrolling_a_user_from_a_cohort.json @@ -30,7 +30,7 @@ "request_headers": { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": "Bearer token", + "X-LEARN-API-TOKEN": "Bearer token", "Host": "example.org", "Cookie": "" }, @@ -53,4 +53,4 @@ "curl": null } ] -} \ No newline at end of file +} diff --git a/doc/api/enrollments/updating_a_user's_enrollment_roles.json b/doc/api/enrollments/updating_a_user's_enrollment_roles.json index 7239916..7f547cf 100644 --- a/doc/api/enrollments/updating_a_user's_enrollment_roles.json +++ b/doc/api/enrollments/updating_a_user's_enrollment_roles.json @@ -42,7 +42,7 @@ "request_headers": { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": "Bearer token", + "X-LEARN-API-TOKEN": "Bearer token", "Host": "example.org", "Cookie": "" }, @@ -65,4 +65,4 @@ "curl": null } ] -} \ No newline at end of file +} diff --git a/doc/api/enrollments/viewing_cohort_enrollments.json b/doc/api/enrollments/viewing_cohort_enrollments.json index 5514f83..3ade291 100644 --- a/doc/api/enrollments/viewing_cohort_enrollments.json +++ b/doc/api/enrollments/viewing_cohort_enrollments.json @@ -24,7 +24,7 @@ "request_headers": { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": "Bearer token", + "X-LEARN-API-TOKEN": "Bearer token", "Host": "example.org", "Cookie": "" }, @@ -47,4 +47,4 @@ "curl": null } ] -} \ No newline at end of file +} diff --git a/doc/api/units/update_unit_visibility.json b/doc/api/units/update_unit_visibility.json index ecf0cbf..2afc576 100644 --- a/doc/api/units/update_unit_visibility.json +++ b/doc/api/units/update_unit_visibility.json @@ -42,7 +42,7 @@ "request_headers": { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": "Bearer token", + "X-LEARN-API-TOKEN": "Bearer token", "Host": "example.org", "Cookie": "" }, diff --git a/doc/api/users/regenerate_user_token.json b/doc/api/users/regenerate_user_token.json index c1abe63..8fcfaeb 100644 --- a/doc/api/users/regenerate_user_token.json +++ b/doc/api/users/regenerate_user_token.json @@ -19,7 +19,7 @@ "request_headers": { "Content-Type": "application/json", "Accept": "application/json", - "Authorization": "Bearer token", + "X-LEARN-API-TOKEN": "Bearer token", "Host": "example.org", "Cookie": "" }, diff --git a/scripts/sh/cohort_curriculum.sh b/scripts/sh/cohort_curriculum.sh index e16b457..27ab4f6 100755 --- a/scripts/sh/cohort_curriculum.sh +++ b/scripts/sh/cohort_curriculum.sh @@ -5,6 +5,6 @@ BASE_URL= # Example: http://localhost:3003 or https://galvanize-forge-staging-pr COHORT_ID=1 # Change for different cohorts curl --request GET \ - -H "Authorization: Bearer ${API_TOKEN}" \ + -H "X-LEARN-API-TOKEN: Bearer ${API_TOKEN}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" ${BASE_URL}/api/v1/cohorts/${COHORT_ID}/curriculum -k -v diff --git a/scripts/sh/content_file_mark_hidden.sh b/scripts/sh/content_file_mark_hidden.sh index da46ff2..a9c8c81 100644 --- a/scripts/sh/content_file_mark_hidden.sh +++ b/scripts/sh/content_file_mark_hidden.sh @@ -7,7 +7,7 @@ BLOCK_ID= # Change for different blocks UID= # Change for different content uid curl --request PATCH \ - -H "Authorization: Bearer ${API_TOKEN}" \ + -H "X-LEARN-API-TOKEN: Bearer ${API_TOKEN}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" ${BASE_URL}/api/v1/cohorts/${COHORT_ID}/blocks/${BLOCK_ID}/content_files/${UID} -k -v \ --data '{"visible": false }' diff --git a/scripts/sh/content_file_mark_visible.sh b/scripts/sh/content_file_mark_visible.sh index 909809c..ec4f767 100644 --- a/scripts/sh/content_file_mark_visible.sh +++ b/scripts/sh/content_file_mark_visible.sh @@ -7,7 +7,7 @@ BLOCK_ID= # Change for different blocks UID= # Change for different content uid curl --request PATCH \ - -H "Authorization: Bearer ${API_TOKEN}" \ + -H "X-LEARN-API-TOKEN: Bearer ${API_TOKEN}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" ${BASE_URL}/api/v1/cohorts/${COHORT_ID}/blocks/${BLOCK_ID}/content_files/${UID} -k -v \ --data '{"visible": true }' diff --git a/scripts/sh/unit_mark_hidden.sh b/scripts/sh/unit_mark_hidden.sh index 9864020..f5da9cb 100644 --- a/scripts/sh/unit_mark_hidden.sh +++ b/scripts/sh/unit_mark_hidden.sh @@ -7,7 +7,7 @@ BLOCK_ID= # Change for different blocks UID= # Change for different content uid curl --request PATCH \ - -H "Authorization: Bearer ${API_TOKEN}" \ + -H "X-LEARN-API-TOKEN: Bearer ${API_TOKEN}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" ${BASE_URL}/api/v1/cohorts/${COHORT_ID}/blocks/${BLOCK_ID}/units/${UID} -k -v \ --data '{"visible": false }' diff --git a/scripts/sh/unit_mark_visible.sh b/scripts/sh/unit_mark_visible.sh index dff7750..893027c 100644 --- a/scripts/sh/unit_mark_visible.sh +++ b/scripts/sh/unit_mark_visible.sh @@ -7,7 +7,7 @@ BLOCK_ID= # Change for different blocks UID= # Change for different content uid curl --request PATCH \ - -H "Authorization: Bearer ${API_TOKEN}" \ + -H "X-LEARN-API-TOKEN: Bearer ${API_TOKEN}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" ${BASE_URL}/api/v1/cohorts/${COHORT_ID}/blocks/${BLOCK_ID}/units/${UID} -k -v \ --data '{"visible": true }' -- GitLab From d15ba1fd753c233200dc6dab7f731de6082337a6 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 5 Oct 2020 17:14:39 -1000 Subject: [PATCH 127/287] upgrading to ruby 2.6.6 and bundler 2.1.4 --- .ruby-version | 2 +- Dockerfile | 27 +++++---------------------- Dockerfile.base | 8 +++----- Gemfile | 2 +- Gemfile.lock | 4 ++-- 5 files changed, 12 insertions(+), 31 deletions(-) diff --git a/.ruby-version b/.ruby-version index e70b452..338a5b5 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.6.0 +2.6.6 diff --git a/Dockerfile b/Dockerfile index 8439451..d35d8b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ -FROM bitnami/ruby:2.6.0 -ENV count 6 +FROM bitnami/ruby:2.6.6 # Install what we can through OS package management RUN apt-get -y update @@ -24,31 +23,16 @@ RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n RUN bash n auto RUN npm install -g yarn -# Set the user -#RUN set -ex && \ -# groupadd --gid 950 ruby && \ -# useradd --uid 950 --gid ruby --shell /bin/bash --create-home ruby -#RUN ls -l /home - # Setup our environment WORKDIR /usr/src/app -ENV RAILS_ENV production - -# Stuff that changes -#RUN chown -R ruby:ruby /usr/src/app -#RUN chmod 755 /usr/src/app -#RUN chown -R ruby:ruby /opt/bitnami/ruby -#RUN chmod 755 /opt/bitnami/ruby - -# Copy the files. ADD . . -# Switch to ruby. -#USER ruby +# Set the Rails environment variable. +ENV RAILS_ENV production # Install Rails Dependecies. -RUN gem install bundler:1.17.3 -RUN gem install rake -v '12.3.1' --source 'https://rubygems.org/' +RUN gem install bundler:2.1.4 +RUN gem install rake:13.0.1 --source 'https://rubygems.org/' # Run the bundle install RUN bundle install @@ -66,5 +50,4 @@ RUN bundle exec rake assets:clean ENV PATH /usr/src/app/node_modules/.bin:$PATH # Set the entry point. -#CMD ["tail", "-f", "/dev/null"] CMD ["./entrypoint-server.sh"] diff --git a/Dockerfile.base b/Dockerfile.base index cbbb371..dc797ed 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -1,6 +1,4 @@ -FROM bitnami/ruby:2.6.0 - -ENV count 6 +FROM bitnami/ruby:2.6.6 # Install what we can through OS package management RUN apt-get -y update @@ -42,8 +40,8 @@ ENV RAILS_ENV production ADD . . ADD Gemfile /usr/src/app/Gemfile ADD Gemfile.lock /usr/src/app/Gemfile.lock -# RUN gem install bundler --version 1.17.3 -RUN gem install rake -v '12.3.1' --source 'https://rubygems.org/' +# RUN gem install bundler --version 2.1.4 +RUN gem install rake -v '13.0.1' --source 'https://rubygems.org/' RUN bundle install ENV PATH /usr/src/app/node_modules/.bin:$PATH CMD ["./entrypoint-server.sh"] diff --git a/Gemfile b/Gemfile index 7df3f20..f76b087 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source "https://rubygems.org" -ruby "2.6.0" +ruby "2.6.6" git_source(:github) do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") diff --git a/Gemfile.lock b/Gemfile.lock index b82f726..f0f90e3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -462,7 +462,7 @@ DEPENDENCIES zip-zip RUBY VERSION - ruby 2.6.0p0 + ruby 2.6.6p146 BUNDLED WITH - 1.17.3 + 2.1.4 -- GitLab From 9083a3cf99e58e210e29ef2877326e93fc570f0d Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Mon, 5 Oct 2020 17:43:20 -1000 Subject: [PATCH 128/287] Several changes to testing --- app/controllers/users_controller.rb | 12 ++++++++++-- app/services/platform_one_auth_resolver_service.rb | 1 - 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 8ab5b37..941c048 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -12,10 +12,18 @@ class UsersController < ApplicationController def update authorize(current_cohort) - current_cohort_user.update(roles: [params[:instructor]].compact) + unless current_cohort_user.update(roles: [params[:instructor]].compact) + flash[:error] = current_cohort_user.errors.full_messages.join(", ") + render :edit + return + end if (current_user.admin?) - user.update(roles:[params[:forge_admin], params[:forge_blocks_manager], params[:auth_product_admin]].compact) + unless user.update(roles:[params[:forge_admin], params[:forge_blocks_manager], params[:auth_product_admin]].compact) + flash[:error] = user.errors.full_messages.join(", ") + render :edit + return + end end redirect_to users_cohort_path(current_cohort) diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index d6d45b6..7ad8286 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -7,7 +7,6 @@ class PlatformOneAuthResolverService def initialize(jwt_token) @payload = JWT.decode(jwt_token, nil, false)[0] - puts @payload @uid = @payload["sub"] @email = @payload["email"] @first_name = @payload["given_name"] -- GitLab From b91d53f6e16823093b058847ee54235bb4fa42ff Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 6 Oct 2020 12:18:53 -1000 Subject: [PATCH 132/287] adding new users listing and edit for global roles. removing global role edit from cohort user edit form --- app/controllers/users_controller.rb | 32 ++++++++++++++----- app/policies/user_policy.rb | 6 ++++ .../layouts/_primary_navigation.html.haml | 2 ++ app/views/users/edit.html.haml | 12 ------- app/views/users/edit_user.html.haml | 26 +++++++++++++++ app/views/users/index.html.haml | 20 ++++++++++++ config/routes.rb | 7 ++++ 7 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 app/views/users/edit_user.html.haml create mode 100644 app/views/users/index.html.haml diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 941c048..c5cd2e0 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,4 +1,28 @@ class UsersController < ApplicationController + def index + authorize(current_user) + @users = User.all.limit(20) + render :index + end + + def edit_user + authorize(current_user) + user + render :edit_user + end + + def update_user + authorize(current_user) + + unless user.update(roles:[params[:forge_admin], params[:forge_blocks_manager], params[:auth_product_admin]].compact) + flash[:error] = user.errors.full_messages.join(", ") + render :edit + return + end + + redirect_to users_path + end + def new authorize(Cohort) @cohort_id = params[:cohort_id] @@ -18,14 +42,6 @@ class UsersController < ApplicationController return end - if (current_user.admin?) - unless user.update(roles:[params[:forge_admin], params[:forge_blocks_manager], params[:auth_product_admin]].compact) - flash[:error] = user.errors.full_messages.join(", ") - render :edit - return - end - end - redirect_to users_cohort_path(current_cohort) end diff --git a/app/policies/user_policy.rb b/app/policies/user_policy.rb index a0b8ee9..c1b93ba 100644 --- a/app/policies/user_policy.rb +++ b/app/policies/user_policy.rb @@ -3,6 +3,10 @@ class UserPolicy < ApplicationPolicy user == record end + def edit_user? + user.admin? + end + def view_challenges? user.instructor_or_admin?(record.cohort_id) end @@ -19,6 +23,8 @@ class UserPolicy < ApplicationPolicy user.onboarder? end + alias index? edit_user? + alias update_user? edit_user? alias regenerate_api_token? content_file_api? alias release_polling? content_file_api? alias learn_cli_credentials? content_file_api? diff --git a/app/views/layouts/_primary_navigation.html.haml b/app/views/layouts/_primary_navigation.html.haml index d76b7f0..6c55bfb 100644 --- a/app/views/layouts/_primary_navigation.html.haml +++ b/app/views/layouts/_primary_navigation.html.haml @@ -30,6 +30,8 @@ - if current_user.admin? %li.item = link_to("Cohorts", cohorts_path) + %li.item + = link_to("Users", users_path) .notifications= react_component "Notifications", isMobileDevice: @is_mobile_device .item .navigation-dropdown diff --git a/app/views/users/edit.html.haml b/app/views/users/edit.html.haml index de0f522..761e786 100644 --- a/app/views/users/edit.html.haml +++ b/app/views/users/edit.html.haml @@ -15,18 +15,6 @@ .form-check = check_box_tag :instructor, "forge.instructor", @cohort_user.roles.include?("forge.instructor"), class: "form-check-input" = label_tag :instructor, "Instructor" - - if @current_user.admin? - %h5 Global Roles: - .form-group - .form-check - = check_box_tag :forge_admin, "forge.admin", @user.roles.include?("forge.admin"), class: "form-check-input" - = label_tag :forge_admin, "Forge Admin" - .form-check - = check_box_tag :forge_blocks_manager, "forge.blocks_manager", @user.roles.include?("forge.blocks_manager"), class: "form-check-input" - = label_tag :forge_blocks_manager, "Forge Blocks Manager" - .form-check - = check_box_tag :auth_product_admin, "auth.product_admin", @user.roles.include?("auth.product_admin"), class: "form-check-input" - = label_tag :auth_product_admin, "Forge Product Admin" = f.submit "Update Cohort User", class: "btn btn-primary" = link_to "Remove User From Cohort", "#my-modal", class: "btn btn-danger", "data-toggle": "modal", style: "float:right;" .modal{id: "my-modal"} diff --git a/app/views/users/edit_user.html.haml b/app/views/users/edit_user.html.haml new file mode 100644 index 0000000..95a6087 --- /dev/null +++ b/app/views/users/edit_user.html.haml @@ -0,0 +1,26 @@ +.container + %h1 Edit User +%br +.container + = form_for @user, :url => { :controller => 'users', :action => :update_user } do |f| + .form-group + = label :first_name, :title, "First Name" + = text_field_tag :first_name, @user.first_name, class: "form-control", readonly: true + .form-group + = label :last_name, :title, "Last Name" + = text_field_tag :last_name, @user.last_name, class: "form-control", readonly: true + .form-group + = label :email, :title, "Email" + = text_field_tag :email, @user.email, class: "form-control", readonly: true + %h5 Global Roles: + .form-group + .form-check + = check_box_tag :forge_admin, "forge.admin", @user.roles.include?("forge.admin"), class: "form-check-input" + = label_tag :forge_admin, "Forge Admin" + .form-check + = check_box_tag :forge_blocks_manager, "forge.blocks_manager", @user.roles.include?("forge.blocks_manager"), class: "form-check-input" + = label_tag :forge_blocks_manager, "Forge Blocks Manager" + .form-check + = check_box_tag :auth_product_admin, "auth.product_admin", @user.roles.include?("auth.product_admin"), class: "form-check-input" + = label_tag :auth_product_admin, "Forge Product Admin" + = f.submit "Update User", class: "btn btn-primary" \ No newline at end of file diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml new file mode 100644 index 0000000..ac9b223 --- /dev/null +++ b/app/views/users/index.html.haml @@ -0,0 +1,20 @@ +.container + %h1 Users + %br + - if @users.present? + %table.table.table-hover + %thead + %tr + %th First + %th Last + %th Email + %th + %tbody + - @users.each do |u| + %tr + %td= u.first_name + %td= u.last_name + %td= u.email + %td{ style: "text-align: right;" } + %a{href: edit_user_user_path(u)} + %button.lp-style-button.-small Edit \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index bf3c21c..b453d74 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -44,6 +44,13 @@ Rails.application.routes.draw do match "*path", via: :all, to: "application#not_found_or_invalid_version" end + resources :users, only: %i[ index ] do + member do + get :edit_user + patch :update_user + end + end + resources :blocks, only: %i[index create new show] do member do get :release_polling -- GitLab From 526a1df2045c57aca384cfb0bf62efa5e4b6171b Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 6 Oct 2020 16:33:26 -1000 Subject: [PATCH 133/287] ordering by email. --- app/controllers/users_controller.rb | 2 +- app/views/users/index.html.haml | 33 ++++++++++++++--------------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index c5cd2e0..659d1d0 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,7 +1,7 @@ class UsersController < ApplicationController def index authorize(current_user) - @users = User.all.limit(20) + @users = User.order('LOWER(email)') render :index end diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml index ac9b223..7207085 100644 --- a/app/views/users/index.html.haml +++ b/app/views/users/index.html.haml @@ -1,20 +1,19 @@ .container %h1 Users %br - - if @users.present? - %table.table.table-hover - %thead - %tr - %th First - %th Last - %th Email - %th - %tbody - - @users.each do |u| - %tr - %td= u.first_name - %td= u.last_name - %td= u.email - %td{ style: "text-align: right;" } - %a{href: edit_user_user_path(u)} - %button.lp-style-button.-small Edit \ No newline at end of file + %table.table.table-hover + %thead + %tr + %th First + %th Last + %th Email + %th + %tbody + - @users.each do |u| + %tr + %td= u.first_name + %td= u.last_name + %td= u.email + %td{ style: "text-align: right;" } + %a{href: edit_user_user_path(u)} + %button.lp-style-button.-small Edit \ No newline at end of file -- GitLab From 38d64c7a540f24b6f1df9b38e224fc8c618564f4 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 6 Oct 2020 21:53:45 -1000 Subject: [PATCH 134/287] adding search feature to user listing --- app/javascript/api.d.ts | 8 + .../components/users/UsersIndex.tsx | 200 ++++++++++++++++++ app/models/user.rb | 6 +- app/views/users/index.html.haml | 29 +-- 4 files changed, 223 insertions(+), 20 deletions(-) create mode 100644 app/javascript/components/users/UsersIndex.tsx diff --git a/app/javascript/api.d.ts b/app/javascript/api.d.ts index 51881ec..74b5d40 100644 --- a/app/javascript/api.d.ts +++ b/app/javascript/api.d.ts @@ -28,6 +28,14 @@ declare namespace Api { profile_image: string } + type User = { + id: number, + first_name: string, + last_name: string, + full_name: string, + email: string + } + type ContentFile = { id: number uid: string diff --git a/app/javascript/components/users/UsersIndex.tsx b/app/javascript/components/users/UsersIndex.tsx new file mode 100644 index 0000000..0be44c9 --- /dev/null +++ b/app/javascript/components/users/UsersIndex.tsx @@ -0,0 +1,200 @@ +import React, { Component } from 'react' +import SvgRenderer from '../SvgRenderer' + +type Props = { + users: Api.User[] + arrowDropUpPath: string + arrowDropDownPath: string +} + +type State = { + sort: 'first_name' | 'last_name' | 'email' + sortDirectionInverted: boolean + search: string + users: Api.User[] +} + +export default class UsersIndex extends Component { + constructor(props: Props) { + super(props); + + this.state = { + sort: 'email', + sortDirectionInverted: false, + search: '', + users: this.props.users + }; + + this.sortedUsers = this.sortedUsers.bind(this); + this.userTable = this.userTable.bind(this); + this.updateSortSelection = this.updateSortSelection.bind(this); + this.userSorter = this.userSorter.bind(this); + this.applySort = this.applySort.bind(this); + this.buildSortIcon = this.buildSortIcon.bind(this); + this.searchInput = this.searchInput.bind(this); + this.applySearch = this.applySearch.bind(this); + } + + goToPage = (pageObj: any) => { + window.location.href = + `${window.location.href.split('?')[0]}?page=${pageObj.selected + 1}` + } + + updateSortSelection(sortType: any) { + if (this.state.sort === sortType) { + this.setState({ sortDirectionInverted: !this.state.sortDirectionInverted }); + } else { + this.setState({ sort: sortType }); + } + } + + applySort(userSet: Api.User[]) { + let comparatorFunction = this.userSorter(this.state.sort); + let sortedUsers = userSet.sort(comparatorFunction); + + if (this.state.search !== '') { + sortedUsers = sortedUsers.filter((user) => { + if (!user.first_name) { + user.first_name = ''; + } + + if (!user.last_name) { + user.last_name = ''; + } + + return user.email.toLowerCase().includes(this.state.search.trim().toLowerCase()) + || user.first_name.toLowerCase().includes(this.state.search.trim().toLowerCase()) + || user.last_name.toLowerCase().includes(this.state.search.trim().toLowerCase()); + }); + } + + return sortedUsers; + } + + userSorter(key: string) { + return ( + (a: Api.User, b: Api.User) => { + if (this.state.sortDirectionInverted) { + b = [a, a = b][0]; + } + if ((a as any)[key] === null || (a as any)[key] === '') { + return 1; + } else if ((b as any)[key] === null || (b as any)[key] === '') { + return -1; + } else if ((a as any)[key].toLowerCase() === (b as any)[key].toLowerCase()) { + return 0; + } else if ((a as any)[key].toLowerCase() < (b as any)[key].toLowerCase()) { + return -1; + } else if ((a as any)[key].toLowerCase() > (b as any)[key].toLowerCase()) { + return 1; + } + + return 0 + } + ); + } + + sortedUsers() { + return ( + this.applySort(this.state.users).map((user:Api.User) => { + return ( + + + + + + + ); + }) + ); + } + + userTable(userList: any[]) { + return ( +
    +
    {user.first_name}{user.last_name}{user.email} + Edit +
    + + + + + + + + + + { userList.length == 0 ? this.noUsersMessage() : userList } + +
    + First Name{this.buildSortIcon('first_name')} + + Last Name{this.buildSortIcon('last_name')} + + Email{this.buildSortIcon('email')} +
    + + ); + } + + buildSortIcon(sortType: string) { + if (this.state.sort === sortType) { + if (this.state.sortDirectionInverted) { + return ( + + + + ) + } else { + return ( + + + + ) + } + } + } + + noUsersMessage() { + return ( + + +

    No results match your search

    + + + ); + } + + searchInput() { + return ( +
    +

    Search

    + +
    + ); + } + + applySearch(e: React.ChangeEvent) { + this.setState({ search: (e.target as HTMLInputElement).value }); + } + + render() { + return ( +
    +
    +
    + { this.searchInput() } + { this.userTable(this.sortedUsers())} +
    +
    +
    + ); + } +} diff --git a/app/models/user.rb b/app/models/user.rb index a307bbf..6f3563c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -31,7 +31,11 @@ class User < ApplicationRecord end def full_name - "#{first_name} #{last_name}" + if first_name.nil? || last_name.nil? + "" + else + "#{first_name} #{last_name}" + end end def initials diff --git a/app/views/users/index.html.haml b/app/views/users/index.html.haml index 7207085..44b30c6 100644 --- a/app/views/users/index.html.haml +++ b/app/views/users/index.html.haml @@ -1,19 +1,10 @@ -.container - %h1 Users - %br - %table.table.table-hover - %thead - %tr - %th First - %th Last - %th Email - %th - %tbody - - @users.each do |u| - %tr - %td= u.first_name - %td= u.last_name - %td= u.email - %td{ style: "text-align: right;" } - %a{href: edit_user_user_path(u)} - %button.lp-style-button.-small Edit \ No newline at end of file +.cohorts + .settingscontainer + .settingsrow + .selectedsettingstab + %h1 + Users + = react_component "users/UsersIndex", + users: @users, + arrowDropDownPath: path_to_image("svg/svg-sprite-navigation-symbol.svg#ic_arrow_drop_down_24px"), + arrowDropUpPath: path_to_image("svg/svg-sprite-navigation-symbol.svg#ic_arrow_drop_up_24px") -- GitLab From 613c8dfc06a45cecb5d5089f7557502100d507c2 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Wed, 7 Oct 2020 12:14:14 -1000 Subject: [PATCH 135/287] Initial work on getting forge to work with new webpacker --- .browserslistrc | 1 + .nvmrc | 2 +- Gemfile | 2 +- Gemfile.lock | 12 +- app/assets/javascripts/application.js | 1 - .../components/blocks/BlocksIndex-v2.tsx | 4 +- .../components/blocks/BlocksNewModal.tsx | 3 +- .../components/blocks/BlocksNewRelease.tsx | 3 +- .../components/blocks/BlocksStats.tsx | 16 +- .../challenge_block/ChallengeBlock.tsx | 32 +- .../checkpoints/CheckpointSubmissionShow.tsx | 8 +- .../cohort_submissions/CohortSubmissions.tsx | 6 +- .../cohorts/pairing/StudentItem.tsx | 2 +- .../cohorts/settings/CohortSettingsResync.tsx | 2 +- .../SubmissionsDashboard.tsx | 2 +- .../components/content_files/PDFRenderer.tsx | 5 +- .../checkpoints/CheckpointActionBar.tsx | 6 +- .../components/shared/Button/Button.tsx | 4 +- .../components/shared/Pill/Pill.tsx | 24 +- .../shared/UniversalList/UniversalList.tsx | 3 +- .../shared/UniversalRow/UniversalRow.tsx | 4 +- .../standards/MasteryScoringBlock.tsx | 2 +- .../standards/StandardScoreButton.tsx | 2 +- .../standards/StandardScoringBlock.tsx | 2 +- babel.config.js | 70 + .babelrc => babelrc.sav | 0 bin/webpack | 7 +- bin/webpack-dev-server | 7 +- config/webpacker.yml | 28 +- package.json | 97 +- package.json.sav | 83 + postcss.config.js | 12 + yarn.lock | 6835 ++++++++++------- 33 files changed, 4526 insertions(+), 2761 deletions(-) create mode 100644 .browserslistrc create mode 100644 babel.config.js rename .babelrc => babelrc.sav (100%) create mode 100644 package.json.sav create mode 100644 postcss.config.js diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..e94f814 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1 @@ +defaults diff --git a/.nvmrc b/.nvmrc index f20cfd9..9306ff9 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -10.15.1 \ No newline at end of file +14.8.0 diff --git a/Gemfile b/Gemfile index 1de72bf..d744d89 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem "rack-attack" # assets gem "ts_routes" -gem "webpacker", "~> 3.5" +gem "webpacker", "~> 5.2.1" gem "bootstrap", "4.0.0" gem "font-awesome-rails" gem "sass-rails", "~> 5.0" diff --git a/Gemfile.lock b/Gemfile.lock index 0dbb773..34ea2ba 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -243,7 +243,7 @@ GEM rack (>= 1.2.0) rack-protection (2.0.0) rack - rack-proxy (0.6.4) + rack-proxy (0.6.5) rack rack-test (0.8.2) rack (>= 1.0, < 3) @@ -343,6 +343,7 @@ GEM selenium-webdriver (3.8.0) childprocess (~> 0.5) rubyzip (~> 1.0) + semantic_range (2.3.0) shoulda-matchers (3.1.2) activesupport (>= 4.0.0) sidekiq (5.0.5) @@ -385,10 +386,11 @@ GEM addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) - webpacker (3.5.5) - activesupport (>= 4.2) + webpacker (5.2.1) + activesupport (>= 5.2) rack-proxy (>= 0.6.1) - railties (>= 4.2) + railties (>= 5.2) + semantic_range (>= 2.3.0) websocket-driver (0.7.0) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) @@ -464,7 +466,7 @@ DEPENDENCIES underscore-rails (= 1.8.3) vcr webmock - webpacker (~> 3.5) + webpacker (~> 5.2.1) zip-zip RUBY VERSION diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 8cc897f..fb0297b 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -4,4 +4,3 @@ //= require bootstrap-sprockets //= require bootstrap-datepicker //= require hopscotch -//= require gsap diff --git a/app/javascript/components/blocks/BlocksIndex-v2.tsx b/app/javascript/components/blocks/BlocksIndex-v2.tsx index e4d000f..f809c90 100644 --- a/app/javascript/components/blocks/BlocksIndex-v2.tsx +++ b/app/javascript/components/blocks/BlocksIndex-v2.tsx @@ -8,7 +8,7 @@ import BlocksStats from './BlocksStats'; import BlocksNewModal from './BlocksNewModal'; import BlocksNewRelease from './BlocksNewRelease'; -const ReactTooltip = require('react-tooltip'); +import ReactTooltip from "react-tooltip"; export const MODAL_STATE = { NEW_BLOCK: 'newBlockModalVisible', @@ -85,7 +85,7 @@ class BlocksIndex extends React.Component { mainTitleLink={Routes.blockPath(block.id)} rowClickable={true} subTitle={block.repo_url.includes('github') ? block.repo_url && - <> + <> {block.repo_url.split('/').slice(-2).join(' / ')} diff --git a/app/javascript/components/blocks/BlocksNewModal.tsx b/app/javascript/components/blocks/BlocksNewModal.tsx index fb49141..da0e602 100644 --- a/app/javascript/components/blocks/BlocksNewModal.tsx +++ b/app/javascript/components/blocks/BlocksNewModal.tsx @@ -23,7 +23,7 @@ type State = { urlErrorMsg: string } -class BlocksNewModal extends React.Component { +export default class BlocksNewModal extends React.Component { constructor(props: Props) { super(props); @@ -184,4 +184,3 @@ class BlocksNewModal extends React.Component { } } -export default BlocksNewModal; diff --git a/app/javascript/components/blocks/BlocksNewRelease.tsx b/app/javascript/components/blocks/BlocksNewRelease.tsx index 398a923..9dab196 100644 --- a/app/javascript/components/blocks/BlocksNewRelease.tsx +++ b/app/javascript/components/blocks/BlocksNewRelease.tsx @@ -14,7 +14,7 @@ type State = { releaseNotesText: string } -class BlocksNewRelease extends React.Component { +export default class BlocksNewRelease extends React.Component { constructor(props: Props) { super(props); @@ -79,4 +79,3 @@ class BlocksNewRelease extends React.Component { } } -export default BlocksNewRelease; diff --git a/app/javascript/components/blocks/BlocksStats.tsx b/app/javascript/components/blocks/BlocksStats.tsx index e31caf0..440e92e 100644 --- a/app/javascript/components/blocks/BlocksStats.tsx +++ b/app/javascript/components/blocks/BlocksStats.tsx @@ -11,7 +11,7 @@ type statsProps = { verticalAlign?: boolean, } -const BlocksStats = ({ block, displayUser, showTooltip, verbose, verticalAlign }: statsProps) => { +export default ({ block, displayUser = true, showTooltip = true, verbose = false, verticalAlign = false }: statsProps) => { const timezone = window.moment.tz.guess(); const timeAgo = block.last_update ? `published ${window.moment(block.last_update).tz(timezone).fromNow()}` : ''; const classes = verticalAlign ? "block-stats__stats-row vertical" : "block-stats__stats-row"; @@ -49,11 +49,9 @@ const BlocksStats = ({ block, displayUser, showTooltip, verbose, verticalAlign } ) } -export default BlocksStats; - -BlocksStats.defaultProps = { - displayUser: true, - showTooltip: true, - verbose: false, - verticalAlign: false -} +// BlocksStats.defaultProps = { +// displayUser: true, +// showTooltip: true, +// verbose: false, +// verticalAlign: false +// } diff --git a/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx b/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx index 5d3cc61..0bfcf1e 100644 --- a/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx +++ b/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx @@ -17,7 +17,7 @@ import ChallengeLocalTestResults from './ChallengeLocalTestResults' import Pill from '../../shared/Pill/Pill' import { TestResult } from '../local/run-local-challenge' -const ReactTooltip = require('react-tooltip') +import ReactTooltip from "react-tooltip"; type Props = { challenge: Api.ChallengeWithSubmittedChallengeAnswersPresenter @@ -344,10 +344,10 @@ export default class ChallengeBlock extends React.Component { if (contentFileType === "checkpoint") { if (challenge.challenge_type === "multiple-choice" || challenge.challenge_type === "checkbox") { this.saveDraft(e.target.value) - } else if (challenge.challenge_type === "testable-project" || - challenge.challenge_type === "project" || - challenge.challenge_type === "paragraph" || - challenge.challenge_type === "short-answer" || + } else if (challenge.challenge_type === "testable-project" || + challenge.challenge_type === "project" || + challenge.challenge_type === "paragraph" || + challenge.challenge_type === "short-answer" || challenge.challenge_type === "number" ) { if (this.state.draftTimeout) { clearTimeout(this.state.draftTimeout) @@ -366,7 +366,7 @@ export default class ChallengeBlock extends React.Component { } updateHasUnsavedWork = (inputVal: string) => { - let bool = false; + let bool = false; if (this.currentSubmission()) { bool = this.currentSubmission().answer !== inputVal; @@ -405,7 +405,7 @@ export default class ChallengeBlock extends React.Component { .map(entry => entry[0]); this.setState({draftTimeout: setTimeout(() => { this.saveDraft(answer); }, 100)}); } - + if (!onUpdateAnswered) return; Object.values(this.state.checkboxInputs).forEach((checkValue) => { if (checkValue) { @@ -420,7 +420,7 @@ export default class ChallengeBlock extends React.Component { const { onUpdateAnswered, contentFileType, challenge } = this.props this.setState({ input: value }); if (!onUpdateAnswered) return; - + if (contentFileType === "checkpoint" && challenge.challenge_type === 'code-snippet' && (this.isAnsweredIgnorePlaceholder(value) && this.inputIsChanged(value))) { if (this.state.draftTimeout) { clearTimeout(this.state.draftTimeout); @@ -679,8 +679,8 @@ export default class ChallengeBlock extends React.Component { const { challenge: { topics }, contentFileType, challenge } = this.props const { draftTimestamp } = this.state - const hideSubmit = (contentFileType === "checkpoint" && !(challenge.challenge_type == "testable-project" || - challenge.challenge_type == "code-snippet" || + const hideSubmit = (contentFileType === "checkpoint" && !(challenge.challenge_type == "testable-project" || + challenge.challenge_type == "code-snippet" || challenge.challenge_type == "local-snippet") ) return ( @@ -717,8 +717,8 @@ export default class ChallengeBlock extends React.Component { const { showSaved } = this.state const disabledClass = showSaved && this.buttonText() === 'SAVED' ? '-disabled' : '' // On checkpoints, show the submit button if its "testable-project" || "code-snippet" || "local-snippet" - const hideSubmit = (contentFileType === "checkpoint" && !(challenge.challenge_type == "testable-project" || - challenge.challenge_type == "code-snippet" || + const hideSubmit = (contentFileType === "checkpoint" && !(challenge.challenge_type == "testable-project" || + challenge.challenge_type == "code-snippet" || challenge.challenge_type == "local-snippet") ) specificClass = `${specificClass} ${disabledClass}`; @@ -780,7 +780,7 @@ export default class ChallengeBlock extends React.Component { if ( currentSubmission == undefined) return null let { points, max_points } = currentSubmission; - + // handle legacy submissions which might not have maxPoints or points if (max_points === undefined || max_points === null) max_points = 1; if (points === undefined || points === null) { @@ -807,7 +807,7 @@ export default class ChallengeBlock extends React.Component { } else { // This could possible be removed, "saftey coding" let currentSubmission = this.currentSubmission() let { points, max_points } = currentSubmission; - + // handle legacy submissions which might not have maxPoints or points if (max_points === undefined || max_points === null) max_points = 1; if (points === undefined || points === null) { @@ -959,7 +959,7 @@ export default class ChallengeBlock extends React.Component { challenge.topics.length > 0 && contentFileType === 'checkpoint' - let challengeTopics + let challengeTopics if (challenge.topics) { challengeTopics = challenge.topics.map((topic: any, i: number) => ( { )} {this.timeline()}
    diff --git a/app/javascript/components/checkpoints/CheckpointSubmissionShow.tsx b/app/javascript/components/checkpoints/CheckpointSubmissionShow.tsx index 54e26ac..73d2744 100644 --- a/app/javascript/components/checkpoints/CheckpointSubmissionShow.tsx +++ b/app/javascript/components/checkpoints/CheckpointSubmissionShow.tsx @@ -10,7 +10,7 @@ import ChallengeDetailActivities from '../challenges/challenge_block/ChallengeDe import CheckpointSubmissionChallenges from './CheckpointSubmissionChallenges' import CheckpointSubmissionStudentNameBar from './CheckpointSubmissionStudentNameBar' -const ReactTooltip = require('react-tooltip') +import ReactTooltip from "react-tooltip"; type Props = { challenges: Api.ChallengeWithSubmittedChallengeAnswersPresenter[] @@ -55,7 +55,7 @@ export default class CheckpointSubmissionShow extends React.Component { http('PATCH', submissionUrl,{ - body: { + body: { checkpoint_submission: { state: "done" } @@ -294,7 +294,7 @@ export default class CheckpointSubmissionShow extends React.Component ) } - + return (
    diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx b/app/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx index f803bd3..9acd96e 100644 --- a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx +++ b/app/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx @@ -11,7 +11,7 @@ import ProgressThresholdsModal from '../../shared/ProgressThresholdsModal/Progre import ProgressThresholdsKey from '../../shared/ProgressThresholdsKey/ProgressThresholdsKey' import SettingsCog from '../../shared/Icons/SettingsCog' -const ReactTooltip = require('react-tooltip') +import ReactTooltip from "react-tooltip"; type contentFileId = number type standardId = number @@ -320,7 +320,7 @@ export default class CohortSubmissions extends React.Component { block.standards.forEach((standard) => { if (standard.id === standardId) { if (this.state.open[standard.id]) { - + // TODO: Update to not modify state directly this.state.standardsLoading[standard.id] = false; this.state.open[standard.id] = false; @@ -331,7 +331,7 @@ export default class CohortSubmissions extends React.Component { this.setState({ data: blocks }); } else { const oldContentFilesLength = standard.content_files.length; - + // TODO: Update to not modify state directly this.state.standardsLoading[standard.id] = true; this.state.open[standard.id] = true; diff --git a/app/javascript/components/cohorts/pairing/StudentItem.tsx b/app/javascript/components/cohorts/pairing/StudentItem.tsx index 7984662..b079b89 100644 --- a/app/javascript/components/cohorts/pairing/StudentItem.tsx +++ b/app/javascript/components/cohorts/pairing/StudentItem.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react' import { DraggableProvided } from 'react-beautiful-dnd' import UserAvatar from '../../UserAvatar'; -const ReactTooltip = require('react-tooltip') +import ReactTooltip from "react-tooltip"; type Props = { index: number diff --git a/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx b/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx index 43ad062..1d37c35 100644 --- a/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx +++ b/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as Routes from '../../../generated/routes' import http from '../../../lib/http' -const ReactTooltip = require('react-tooltip') +import ReactTooltip from "react-tooltip"; type Job = Api.Setup.ResyncJob type SuccessfulJob = Api.Setup.SuccessfulResyncJob diff --git a/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx b/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx index dd9a4ec..8057255 100644 --- a/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx +++ b/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx @@ -9,7 +9,7 @@ import SortDropdown from '../../SortDropdown' import HeaderStandardContainer from './HeaderStandardContainer' import SubmissionsDashboardTable from './SubmissionsDashboardTable' -const ReactTooltip = require('react-tooltip') +import ReactTooltip from "react-tooltip"; type Props = any type State = any diff --git a/app/javascript/components/content_files/PDFRenderer.tsx b/app/javascript/components/content_files/PDFRenderer.tsx index e75fafe..ecc7265 100644 --- a/app/javascript/components/content_files/PDFRenderer.tsx +++ b/app/javascript/components/content_files/PDFRenderer.tsx @@ -1,5 +1,8 @@ import * as React from 'react'; -import { Document, Page, pdfjs } from 'react-pdf/dist/entry.webpack'; +//import { Document, Page, pdfjs } from 'react-pdf/dist/umd/entry.webpack'; +//import { Document, Page, pdfjs } from 'react-pdf/dist/esm/entry.webpack'; +import { Document, Page } from 'react-pdf/dist/umd/entry.webpack'; +import { pdfjs } from 'react-pdf'; pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; diff --git a/app/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx b/app/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx index 37a9580..7552538 100644 --- a/app/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx +++ b/app/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx @@ -5,7 +5,7 @@ import { cohortCheckpointSubmissionPath } from '../../../generated/routes' import CheckpointPairs from './CheckpointPairs' import PairAvatars from './PairAvatars' -const ReactTooltip = require('react-tooltip') +import ReactTooltip from "react-tooltip"; type Props = { timeLimit: number | null @@ -112,7 +112,7 @@ export default class CheckpointActionBar extends React.Component { render() { - const { + const { onSubmitCheckpoint, onSaveAndExit, timeLimit, @@ -189,4 +189,4 @@ export default class CheckpointActionBar extends React.Component { ) } -} \ No newline at end of file +} diff --git a/app/javascript/components/shared/Button/Button.tsx b/app/javascript/components/shared/Button/Button.tsx index f1eea72..27b98c9 100644 --- a/app/javascript/components/shared/Button/Button.tsx +++ b/app/javascript/components/shared/Button/Button.tsx @@ -11,7 +11,7 @@ type Props = { className?: string } -const Button = ({ format, color, style, onClick, disabled, text, children, className }: Props) => { +export default Button = ({ format, color, style, onClick, disabled, text, children, className }: Props) => { let classes = `button-wrapper button-${format}-${color} ${(className ? className : '')}`; return (
    ) } } - -- GitLab From 64b1bd2642a5beaed9d7f1820daac19a1bb45d18 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Wed, 7 Oct 2020 17:31:50 -1000 Subject: [PATCH 137/287] Re-enabled but downgraded react-pdf --- .../components/content_files/PDFRenderer.tsx | 37 +- package-lock.json | 14172 ++++++++++++++++ package.json | 2 +- 3 files changed, 14189 insertions(+), 22 deletions(-) create mode 100644 package-lock.json diff --git a/app/javascript/components/content_files/PDFRenderer.tsx b/app/javascript/components/content_files/PDFRenderer.tsx index 544aaf9..b9ff2c6 100644 --- a/app/javascript/components/content_files/PDFRenderer.tsx +++ b/app/javascript/components/content_files/PDFRenderer.tsx @@ -1,11 +1,7 @@ import React from 'react'; -// TODO: Figure out why the react-pdf stuff is not working -//import { Document, Page, pdfjs } from 'react-pdf/dist/esm/entry.webpack'; -//import { Document, Page, pdfjs } from 'react-pdf/dist/umd/entry.webpack'; -//import { Document, Page, pdfjs } from 'react-pdf'; +import { Document, Page, pdfjs } from 'react-pdf/dist/entry.webpack'; -//pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; -//pdfjs.GlobalWorkerOptions.workerSrc = "pdfjs-dist/build/pdf.worker.js"; +pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; type Props = { contentFileHtml: string @@ -37,23 +33,22 @@ export default class PDFRenderer extends React.Component { )}
    - {/*TODO: Figure out why the react-pdf stuff is not working*/} - {/**/} + { - // Array.from( - // new Array(this.state.numPages), - // (_el, index) => ( - // - // ), - // ) + Array.from( + new Array(this.state.numPages), + (_el, index) => ( + + ), + ) } - {/**/} +
    ) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..382cf49 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,14172 @@ +{ + "name": "forge", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/compat-data": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", + "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", + "requires": { + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "semver": "^5.5.0" + } + }, + "@babel/core": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", + "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.6", + "@babel/helper-module-transforms": "^7.11.0", + "@babel/helpers": "^7.10.4", + "@babel/parser": "^7.11.5", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.11.5", + "@babel/types": "^7.11.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + } + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", + "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "requires": { + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", + "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", + "requires": { + "@babel/compat-data": "^7.10.4", + "browserslist": "^4.12.0", + "invariant": "^2.2.4", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", + "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.10.5", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", + "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-regex": "^7.10.4", + "regexpu-core": "^4.7.0" + } + }, + "@babel/helper-define-map": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", + "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", + "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", + "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/template": "^7.10.4", + "@babel/types": "^7.11.0", + "lodash": "^4.17.19" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", + "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" + }, + "@babel/helper-regex": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", + "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", + "requires": { + "lodash": "^4.17.19" + } + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.11.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", + "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-replace-supers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", + "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", + "requires": { + "@babel/helper-member-expression-to-functions": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", + "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", + "requires": { + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", + "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + }, + "@babel/helper-wrap-function": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", + "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helpers": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", + "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", + "requires": { + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==" + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", + "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", + "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", + "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", + "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", + "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.0" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", + "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", + "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", + "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", + "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-transform-parameters": "^7.10.4" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", + "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", + "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", + "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", + "requires": { + "@babel/helper-create-class-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", + "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", + "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", + "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", + "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", + "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", + "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.11.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", + "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", + "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.10.4", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", + "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", + "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", + "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", + "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", + "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", + "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", + "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", + "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", + "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", + "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", + "requires": { + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", + "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", + "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", + "requires": { + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.10.5", + "@babel/helper-plugin-utils": "^7.10.4", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", + "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", + "requires": { + "@babel/helper-module-transforms": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", + "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", + "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", + "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.10.4" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", + "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", + "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", + "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", + "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-runtime": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.5.tgz", + "integrity": "sha512-9aIoee+EhjySZ6vY5hnLjigHzunBlscx9ANKutkeWTJTx6m5Rbq6Ic01tLvO54lSusR+BxV7u4UDdCmXv5aagg==", + "requires": { + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "resolve": "^1.8.1", + "semver": "^5.5.1" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", + "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", + "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", + "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-regex": "^7.10.4" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", + "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", + "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", + "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", + "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/preset-env": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", + "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", + "requires": { + "@babel/compat-data": "^7.11.0", + "@babel/helper-compilation-targets": "^7.10.4", + "@babel/helper-module-imports": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/plugin-proposal-async-generator-functions": "^7.10.4", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-dynamic-import": "^7.10.4", + "@babel/plugin-proposal-export-namespace-from": "^7.10.4", + "@babel/plugin-proposal-json-strings": "^7.10.4", + "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", + "@babel/plugin-proposal-numeric-separator": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.11.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", + "@babel/plugin-proposal-optional-chaining": "^7.11.0", + "@babel/plugin-proposal-private-methods": "^7.10.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", + "@babel/plugin-syntax-async-generators": "^7.8.0", + "@babel/plugin-syntax-class-properties": "^7.10.4", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.0", + "@babel/plugin-syntax-top-level-await": "^7.10.4", + "@babel/plugin-transform-arrow-functions": "^7.10.4", + "@babel/plugin-transform-async-to-generator": "^7.10.4", + "@babel/plugin-transform-block-scoped-functions": "^7.10.4", + "@babel/plugin-transform-block-scoping": "^7.10.4", + "@babel/plugin-transform-classes": "^7.10.4", + "@babel/plugin-transform-computed-properties": "^7.10.4", + "@babel/plugin-transform-destructuring": "^7.10.4", + "@babel/plugin-transform-dotall-regex": "^7.10.4", + "@babel/plugin-transform-duplicate-keys": "^7.10.4", + "@babel/plugin-transform-exponentiation-operator": "^7.10.4", + "@babel/plugin-transform-for-of": "^7.10.4", + "@babel/plugin-transform-function-name": "^7.10.4", + "@babel/plugin-transform-literals": "^7.10.4", + "@babel/plugin-transform-member-expression-literals": "^7.10.4", + "@babel/plugin-transform-modules-amd": "^7.10.4", + "@babel/plugin-transform-modules-commonjs": "^7.10.4", + "@babel/plugin-transform-modules-systemjs": "^7.10.4", + "@babel/plugin-transform-modules-umd": "^7.10.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", + "@babel/plugin-transform-new-target": "^7.10.4", + "@babel/plugin-transform-object-super": "^7.10.4", + "@babel/plugin-transform-parameters": "^7.10.4", + "@babel/plugin-transform-property-literals": "^7.10.4", + "@babel/plugin-transform-regenerator": "^7.10.4", + "@babel/plugin-transform-reserved-words": "^7.10.4", + "@babel/plugin-transform-shorthand-properties": "^7.10.4", + "@babel/plugin-transform-spread": "^7.11.0", + "@babel/plugin-transform-sticky-regex": "^7.10.4", + "@babel/plugin-transform-template-literals": "^7.10.4", + "@babel/plugin-transform-typeof-symbol": "^7.10.4", + "@babel/plugin-transform-unicode-escapes": "^7.10.4", + "@babel/plugin-transform-unicode-regex": "^7.10.4", + "@babel/preset-modules": "^0.1.3", + "@babel/types": "^7.11.5", + "browserslist": "^4.12.0", + "core-js-compat": "^3.6.2", + "invariant": "^2.2.2", + "levenary": "^1.1.1", + "semver": "^5.5.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", + "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/runtime-corejs3": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", + "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", + "dev": true, + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@csstools/convert-colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", + "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" + }, + "@eslint/eslintrc": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", + "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + } + } + }, + "@npmcli/move-file": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", + "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "requires": { + "mkdirp": "^1.0.4" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, + "@rails/webpacker": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@rails/webpacker/-/webpacker-5.2.1.tgz", + "integrity": "sha512-rO0kOv0o4ESB8ZnKX+b54ZKogNJGWSMULGmsJacREfm9SahKEQwXBeHNsqSGtS9NAPsU6YUFhGKRd4i/kbMNrQ==", + "requires": { + "@babel/core": "^7.11.1", + "@babel/plugin-proposal-class-properties": "^7.10.4", + "@babel/plugin-proposal-object-rest-spread": "^7.10.1", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-destructuring": "^7.10.1", + "@babel/plugin-transform-regenerator": "^7.10.1", + "@babel/plugin-transform-runtime": "^7.11.0", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "babel-loader": "^8.1.0", + "babel-plugin-dynamic-import-node": "^2.3.3", + "babel-plugin-macros": "^2.8.0", + "case-sensitive-paths-webpack-plugin": "^2.3.0", + "compression-webpack-plugin": "^4.0.0", + "core-js": "^3.6.5", + "css-loader": "^3.5.3", + "file-loader": "^6.0.0", + "flatted": "^3.0.4", + "glob": "^7.1.6", + "js-yaml": "^3.14.0", + "mini-css-extract-plugin": "^0.9.0", + "node-sass": "^4.14.1", + "optimize-css-assets-webpack-plugin": "^5.0.3", + "path-complete-extname": "^1.0.0", + "pnp-webpack-plugin": "^1.6.4", + "postcss-flexbugs-fixes": "^4.2.1", + "postcss-import": "^12.0.1", + "postcss-loader": "^3.0.0", + "postcss-preset-env": "^6.7.0", + "postcss-safe-parser": "^4.0.2", + "regenerator-runtime": "^0.13.7", + "sass-loader": "^8.0.2", + "style-loader": "^1.2.1", + "terser-webpack-plugin": "^4.0.0", + "webpack": "^4.44.1", + "webpack-assets-manifest": "^3.1.1", + "webpack-cli": "^3.3.12", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + } + } + } + } + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/json-schema": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "@types/lodash": { + "version": "4.14.137", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.137.tgz", + "integrity": "sha512-g4rNK5SRKloO+sUGbuO7aPtwbwzMgjK+bm9BBhLD7jGUiGR7zhwYEhSln/ihgYQBeIJ5j7xjyaYzrWTcu3UotQ==", + "dev": true + }, + "@types/lodash-es": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.3.tgz", + "integrity": "sha512-iHI0i7ZAL1qepz1Y7f3EKg/zUMDwDfTzitx+AlHhJJvXwenP682ZyGbgPSc5Ej3eEAKVbNWKFuwOadCj5vBbYQ==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "dev": true + }, + "@types/node": { + "version": "12.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", + "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==" + }, + "@types/node-fetch": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", + "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "@types/pdfjs-dist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/pdfjs-dist/-/pdfjs-dist-2.1.2.tgz", + "integrity": "sha512-tUMIcX3z8M8EXA0SA4YIZcgZ6r/Rb2wbwvM9AKJhE/MPh5rEC90Sg8lMyVX6hzMaToR4jWEvN7IUA1bvoVpctA==" + }, + "@types/prop-types": { + "version": "15.7.1", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", + "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==" + }, + "@types/q": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", + "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" + }, + "@types/rc-slider": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/@types/rc-slider/-/rc-slider-8.6.6.tgz", + "integrity": "sha512-2Q3vwKrSm3PbgiMNwzxMkOaMtcAGi0xQ8WPeVKoabk1vNYHiVR44DMC3mr9jC2lhbxCBgGBJWF9sBhmnSDQ8Bg==", + "dev": true, + "requires": { + "@types/rc-tooltip": "*", + "@types/react": "*" + }, + "dependencies": { + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", + "dev": true + } + } + }, + "@types/rc-tooltip": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@types/rc-tooltip/-/rc-tooltip-3.7.1.tgz", + "integrity": "sha512-H+pW9+H42rlb3PBjcxuXhQre1ldr0gdDVbVfCj0Pf4Mdjd1plti9ekt1yJ+gGig3bRFLpCknWDmfOkAjKY+S+Q==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react": { + "version": "16.9.51", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.51.tgz", + "integrity": "sha512-lQa12IyO+DMlnSZ3+AGHRUiUcpK47aakMMoBG8f7HGxJT8Yfe+WE128HIXaHOHVPReAW0oDS3KAI0JI2DDe1PQ==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "@types/react-addons-css-transition-group": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/react-addons-css-transition-group/-/react-addons-css-transition-group-15.0.5.tgz", + "integrity": "sha512-UIJt5HQDOzRI7AOmnGnc2OZA0N3p7r6yMsxZ3T0+dyGPB3zWiKOPKrMkJr9tyuY3kHKPm26GyihcJKNJdMY8CQ==", + "dev": true, + "requires": { + "@types/react": "*", + "@types/react-addons-transition-group": "*" + }, + "dependencies": { + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", + "dev": true + } + } + }, + "@types/react-addons-transition-group": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/@types/react-addons-transition-group/-/react-addons-transition-group-15.0.4.tgz", + "integrity": "sha512-0S2cKn9OLYr6N36oRH4ybzidkgQ0UGhuvrFvU3tdktJfrx3muu7MgfIWG434wKg7rcysBEfpmQaNpGteEtx6vw==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, + "@types/react-beautiful-dnd": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz", + "integrity": "sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg==", + "dev": true, + "requires": { + "@types/react": "*" + }, + "dependencies": { + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", + "dev": true + } + } + }, + "@types/react-copy-to-clipboard": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz", + "integrity": "sha512-iideNPRyroENqsOFh1i2Dv3zkviYS9r/9qD9Uh3Z9NNoAAqqa2x53i7iGndGNnJFIo20wIu7Hgh77tx1io8bgw==", + "dev": true, + "requires": { + "@types/react": "*" + }, + "dependencies": { + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", + "dev": true + } + } + }, + "@types/react-datepicker": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-3.1.1.tgz", + "integrity": "sha512-vwNrgaIMJThvvwmtnA8jSVVJZ0FNgljQrq1jDA4MtYJIDmVmd9NNrFaXf9u2JqR2nS+8Kvi8OVs/tnAbUqZhHw==", + "dev": true, + "requires": { + "@types/react": "*", + "date-fns": "^2.0.1", + "popper.js": "^1.14.1" + }, + "dependencies": { + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", + "dev": true + }, + "popper.js": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz", + "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==", + "dev": true + } + } + }, + "@types/react-dom": { + "version": "16.9.8", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", + "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", + "dev": true, + "requires": { + "@types/react": "*" + }, + "dependencies": { + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", + "dev": true + } + } + }, + "@types/react-paginate": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@types/react-paginate/-/react-paginate-6.2.1.tgz", + "integrity": "sha512-+q8k1N0WzbMyOCsIEH/p5D6/KQD8dXYLzfvSvriYn//94icd2sqhAL2rWXkgwGvqHGCSTU9AoHtsWCJxPfquUQ==", + "requires": { + "@types/react": "*" + }, + "dependencies": { + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==" + } + } + }, + "@types/react-pdf": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/react-pdf/-/react-pdf-4.0.6.tgz", + "integrity": "sha512-ZmtUA31L5AaF9PilB8cJ3PuGOHIiyWcHnCir7KOux1cDjfVH6VxiN/j7CcyX98U9hwlyN81bkqxeNWNuEc0a4w==", + "requires": { + "@types/pdfjs-dist": "*", + "@types/react": "*" + }, + "dependencies": { + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==" + } + } + }, + "@types/react-textarea-autosize": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@types/react-textarea-autosize/-/react-textarea-autosize-4.3.5.tgz", + "integrity": "sha512-PiDL83kPMTolyZAWW3lyzO6ktooTb9tFTntVy7CA83/qFLWKLJ5bLeRboy6J6j3b1e8h2Eec6gBTEOOJRjV14A==", + "dev": true, + "requires": { + "@types/react": "*" + }, + "dependencies": { + "@types/react": { + "version": "16.9.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", + "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", + "dev": true, + "requires": { + "@types/prop-types": "*", + "csstype": "^2.2.0" + } + }, + "csstype": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", + "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", + "dev": true + } + } + }, + "@types/vimeo__player": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@types/vimeo__player/-/vimeo__player-2.9.1.tgz", + "integrity": "sha512-L6XHWenPkN+WHFWmo/fhA70kTQnNUxOs3bQS3nF/FK3kv+UzEVRqPEkxJNUZWExwrhOKaTKzplZHmxYwmr0SJA==", + "dev": true + }, + "@vimeo/player": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@vimeo/player/-/player-2.14.0.tgz", + "integrity": "sha512-hTeROVnkcyboRjetPi9tasryrjDPVsmr/73clwbYsBpSk8k5q1ygFyvkWXQShv9Rc+hvXM5RJXPd7i7Wh7dqcA==", + "requires": { + "native-promise-only": "0.8.1", + "weakmap-polyfill": "2.0.1" + } + }, + "@webassemblyjs/ast": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", + "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "requires": { + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", + "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==" + }, + "@webassemblyjs/helper-api-error": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", + "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==" + }, + "@webassemblyjs/helper-buffer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", + "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==" + }, + "@webassemblyjs/helper-code-frame": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", + "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", + "requires": { + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/helper-fsm": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", + "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==" + }, + "@webassemblyjs/helper-module-context": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", + "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "requires": { + "@webassemblyjs/ast": "1.9.0" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", + "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==" + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", + "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", + "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", + "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", + "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==" + }, + "@webassemblyjs/wasm-edit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", + "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/helper-wasm-section": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-opt": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "@webassemblyjs/wast-printer": "1.9.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", + "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", + "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-buffer": "1.9.0", + "@webassemblyjs/wasm-gen": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", + "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-wasm-bytecode": "1.9.0", + "@webassemblyjs/ieee754": "1.9.0", + "@webassemblyjs/leb128": "1.9.0", + "@webassemblyjs/utf8": "1.9.0" + } + }, + "@webassemblyjs/wast-parser": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", + "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/floating-point-hex-parser": "1.9.0", + "@webassemblyjs/helper-api-error": "1.9.0", + "@webassemblyjs/helper-code-frame": "1.9.0", + "@webassemblyjs/helper-fsm": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", + "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/wast-parser": "1.9.0", + "@xtuc/long": "4.2.2" + } + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dev": true, + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + }, + "acorn-jsx": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", + "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", + "dev": true + }, + "acorn-node": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", + "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", + "requires": { + "acorn": "^7.0.0", + "acorn-walk": "^7.0.0", + "xtend": "^4.0.2" + } + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" + }, + "add-dom-event-listener": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", + "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", + "requires": { + "object-assign": "4.x" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + }, + "ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true + }, + "ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + }, + "array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "array-includes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "array.prototype.flatmap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz", + "integrity": "sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "requires": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "requires": { + "object-assign": "^4.1.1", + "util": "0.10.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" + }, + "util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "requires": { + "inherits": "2.0.1" + } + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "ast-types": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", + "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=" + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" + }, + "async-foreach": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "autoprefixer": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.6.tgz", + "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", + "dev": true, + "requires": { + "browserslist": "^2.11.3", + "caniuse-lite": "^1.0.30000805", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^6.0.17", + "postcss-value-parser": "^3.2.3" + }, + "dependencies": { + "browserslist": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000792", + "electron-to-chromium": "^1.3.30" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + }, + "axe-core": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.5.tgz", + "integrity": "sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==", + "dev": true + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "babel-helper-builder-react-jsx": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", + "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "esutils": "^2.0.2" + } + }, + "babel-loader": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", + "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", + "requires": { + "find-cache-dir": "^2.1.0", + "loader-utils": "^1.4.0", + "mkdirp": "^0.5.3", + "pify": "^4.0.1", + "schema-utils": "^2.6.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "requires": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "babel-plugin-syntax-flow": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", + "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=" + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, + "babel-plugin-transform-flow-strip-types": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", + "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", + "requires": { + "babel-plugin-syntax-flow": "^6.18.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-react-display-name": { + "version": "6.25.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", + "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-react-jsx": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", + "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", + "requires": { + "babel-helper-builder-react-jsx": "^6.24.1", + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-react-jsx-self": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", + "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", + "requires": { + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-react-jsx-source": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", + "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", + "requires": { + "babel-plugin-syntax-jsx": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-preset-flow": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", + "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", + "requires": { + "babel-plugin-transform-flow-strip-types": "^6.22.0" + } + }, + "babel-preset-react": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", + "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", + "requires": { + "babel-plugin-syntax-jsx": "^6.3.13", + "babel-plugin-transform-react-display-name": "^6.23.0", + "babel-plugin-transform-react-jsx": "^6.24.1", + "babel-plugin-transform-react-jsx-self": "^6.22.0", + "babel-plugin-transform-react-jsx-source": "^6.22.0", + "babel-preset-flow": "^6.23.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + } + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base62": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz", + "integrity": "sha1-e0F0wvlESXU7EcJlHAg9qEGnsIQ=" + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "bn.js": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", + "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + } + } + }, + "bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "requires": { + "array-flatten": "^2.1.0", + "deep-equal": "^1.0.1", + "dns-equal": "^1.0.0", + "dns-txt": "^2.0.2", + "multicast-dns": "^6.0.1", + "multicast-dns-service-types": "^1.1.0" + } + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "brace": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", + "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-pack": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", + "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", + "requires": { + "JSONStream": "^1.0.3", + "combine-source-map": "~0.8.0", + "defined": "^1.0.0", + "safe-buffer": "^5.1.1", + "through2": "^2.0.0", + "umd": "^3.0.0" + } + }, + "browser-resolve": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", + "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", + "requires": { + "resolve": "^1.17.0" + } + }, + "browserify": { + "version": "16.5.2", + "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.5.2.tgz", + "integrity": "sha512-TkOR1cQGdmXU9zW4YukWzWVSJwrxmNdADFbqbE3HFgQWe5wqZmOawqZ7J/8MPCwk/W8yY7Y0h+7mOtcZxLP23g==", + "requires": { + "JSONStream": "^1.0.3", + "assert": "^1.4.0", + "browser-pack": "^6.0.1", + "browser-resolve": "^2.0.0", + "browserify-zlib": "~0.2.0", + "buffer": "~5.2.1", + "cached-path-relative": "^1.0.0", + "concat-stream": "^1.6.0", + "console-browserify": "^1.1.0", + "constants-browserify": "~1.0.0", + "crypto-browserify": "^3.0.0", + "defined": "^1.0.0", + "deps-sort": "^2.0.0", + "domain-browser": "^1.2.0", + "duplexer2": "~0.1.2", + "events": "^2.0.0", + "glob": "^7.1.0", + "has": "^1.0.0", + "htmlescape": "^1.1.0", + "https-browserify": "^1.0.0", + "inherits": "~2.0.1", + "insert-module-globals": "^7.0.0", + "labeled-stream-splicer": "^2.0.0", + "mkdirp-classic": "^0.5.2", + "module-deps": "^6.2.3", + "os-browserify": "~0.3.0", + "parents": "^1.0.1", + "path-browserify": "~0.0.0", + "process": "~0.11.0", + "punycode": "^1.3.2", + "querystring-es3": "~0.2.0", + "read-only-stream": "^2.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.1.4", + "shasum": "^1.0.0", + "shell-quote": "^1.6.1", + "stream-browserify": "^2.0.0", + "stream-http": "^3.0.0", + "string_decoder": "^1.1.1", + "subarg": "^1.0.0", + "syntax-error": "^1.1.1", + "through2": "^2.0.0", + "timers-browserify": "^1.0.1", + "tty-browserify": "0.0.1", + "url": "~0.11.0", + "util": "~0.10.1", + "vm-browserify": "^1.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "resolve": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", + "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "requires": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "browserify-cache-api": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/browserify-cache-api/-/browserify-cache-api-3.0.1.tgz", + "integrity": "sha1-liR+hT8Gj9bg1FzHPwuyzZd47wI=", + "requires": { + "async": "^1.5.2", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + } + } + }, + "browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "requires": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "requires": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "browserify-incremental": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/browserify-incremental/-/browserify-incremental-3.1.1.tgz", + "integrity": "sha1-BxPLdYckemMqnwjPG9FpuHi2Koo=", + "requires": { + "JSONStream": "^0.10.0", + "browserify-cache-api": "^3.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + }, + "dependencies": { + "JSONStream": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz", + "integrity": "sha1-dDSdDYlSK3HzDwoD/5vSDKbxKsA=", + "requires": { + "jsonparse": "0.0.5", + "through": ">=2.2.7 <3" + } + }, + "jsonparse": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", + "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=" + } + } + }, + "browserify-rsa": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", + "requires": { + "bn.js": "^4.1.0", + "randombytes": "^2.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "requires": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "requires": { + "pako": "~1.0.5" + } + }, + "browserslist": { + "version": "4.14.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz", + "integrity": "sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==", + "requires": { + "caniuse-lite": "^1.0.30001135", + "electron-to-chromium": "^1.3.571", + "escalade": "^3.1.0", + "node-releases": "^1.1.61" + } + }, + "buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", + "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" + }, + "builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true + }, + "cacache": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", + "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", + "requires": { + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cached-path-relative": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", + "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==" + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "camelcase-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + } + } + }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "caniuse-lite": { + "version": "1.0.30001144", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001144.tgz", + "integrity": "sha512-4GQTEWNMnVZVOFG3BK0xvGeaDAtiPAbG2N8yuMXuXzx/c2Vd4XoMPO8+E918zeXn5IF0FRVtGShBfkfQea2wHQ==" + }, + "case-sensitive-paths-webpack-plugin": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz", + "integrity": "sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chain-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz", + "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } + } + } + } + }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, + "chrome-trace-event": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", + "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "coa": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", + "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", + "q": "^1.1.2" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", + "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", + "dev": true, + "requires": { + "clone": "^1.0.2", + "color-convert": "^1.3.0", + "color-string": "^0.3.0" + }, + "dependencies": { + "color-string": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", + "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "dev": true, + "requires": { + "color-name": "^1.0.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + }, + "dependencies": { + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + } + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "color-string": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" + }, + "combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + }, + "dependencies": { + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=" + }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=" + } + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" + }, + "commoner": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.8.tgz", + "integrity": "sha1-NPw2cs0kOT6LtH5wyqApOBH08sU=", + "requires": { + "commander": "^2.5.0", + "detective": "^4.3.1", + "glob": "^5.0.15", + "graceful-fs": "^4.1.2", + "iconv-lite": "^0.4.5", + "mkdirp": "^0.5.0", + "private": "^0.1.6", + "q": "^1.1.2", + "recast": "^0.11.17" + }, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" + }, + "detective": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "requires": { + "acorn": "^5.2.1", + "defined": "^1.0.0" + } + }, + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "component-classes": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz", + "integrity": "sha1-xkI5TDYYpNiwuJGe/Mu9kw5c1pE=", + "requires": { + "component-indexof": "0.0.3" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "component-indexof": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", + "integrity": "sha1-EdCRMSI5648yyPJa6csAL/6NPCQ=" + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "requires": { + "mime-db": ">= 1.43.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", + "dev": true + } + } + }, + "compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "requires": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "compression-webpack-plugin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-4.0.1.tgz", + "integrity": "sha512-0mg6PgwTsUe5LEcUrOu3ob32vraDx2VdbMGAT1PARcOV+UJWDYZFdkSo6RbHoGQ061mmmkC7XpRKOlvwm/gzJQ==", + "requires": { + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "schema-utils": "^2.7.0", + "serialize-javascript": "^4.0.0", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "confusing-browser-globals": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", + "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", + "dev": true + }, + "connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "requires": { + "date-now": "^0.1.4" + } + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "dev": true + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "copy-to-clipboard": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz", + "integrity": "sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==", + "requires": { + "toggle-selection": "^1.0.6" + } + }, + "core-js": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + }, + "core-js-compat": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", + "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", + "requires": { + "browserslist": "^4.8.5", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" + } + } + }, + "core-js-pure": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "dependencies": { + "parse-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + } + } + }, + "create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "requires": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "requires": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "create-react-context": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", + "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", + "requires": { + "gud": "^1.0.0", + "warning": "^4.0.3" + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "requires": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + } + }, + "css-animation": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz", + "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", + "requires": { + "babel-runtime": "6.x", + "component-classes": "^1.2.5" + } + }, + "css-blank-pseudo": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "requires": { + "postcss": "^7.0.5" + } + }, + "css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "requires": { + "tiny-invariant": "^1.0.6" + } + }, + "css-color-function": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/css-color-function/-/css-color-function-1.3.3.tgz", + "integrity": "sha1-jtJMLAIFBzM5+voAS8jBQfzLKC4=", + "dev": true, + "requires": { + "balanced-match": "0.1.0", + "color": "^0.11.0", + "debug": "^3.1.0", + "rgb": "~0.1.0" + }, + "dependencies": { + "balanced-match": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.1.0.tgz", + "integrity": "sha1-tQS9BYabOSWd0MXvw12EMXbczEo=", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "css-color-names": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" + }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + } + }, + "css-has-pseudo": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", + "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "requires": { + "camelcase": "^5.3.1", + "cssesc": "^3.0.0", + "icss-utils": "^4.1.1", + "loader-utils": "^1.2.3", + "normalize-path": "^3.0.0", + "postcss": "^7.0.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "requires": { + "postcss": "^7.0.5" + } + }, + "css-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", + "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", + "requires": { + "boolbase": "^1.0.0", + "css-what": "^3.2.1", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" + }, + "css-tree": { + "version": "1.0.0-alpha.37", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", + "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", + "requires": { + "mdn-data": "2.0.4", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "css-unit-converter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", + "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==", + "dev": true + }, + "css-what": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.1.tgz", + "integrity": "sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==" + }, + "cssdb": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", + "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==" + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" + }, + "cssnano": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", + "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", + "requires": { + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.7", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "cssnano-preset-default": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", + "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", + "requires": { + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.1", + "postcss-colormin": "^4.0.3", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.2", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.11", + "postcss-merge-rules": "^4.0.3", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.2", + "postcss-minify-params": "^4.0.2", + "postcss-minify-selectors": "^4.0.2", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.2", + "postcss-normalize-positions": "^4.0.2", + "postcss-normalize-repeat-style": "^4.0.2", + "postcss-normalize-string": "^4.0.2", + "postcss-normalize-timing-functions": "^4.0.2", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.2", + "postcss-ordered-values": "^4.1.2", + "postcss-reduce-initial": "^4.0.3", + "postcss-reduce-transforms": "^4.0.2", + "postcss-svgo": "^4.0.2", + "postcss-unique-selectors": "^4.0.1" + }, + "dependencies": { + "postcss-calc": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", + "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", + "requires": { + "postcss": "^7.0.27", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + } + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "requires": { + "postcss": "^7.0.0" + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" + }, + "csso": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", + "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", + "requires": { + "css-tree": "1.0.0-alpha.39" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.39", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", + "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", + "requires": { + "mdn-data": "2.0.6", + "source-map": "^0.6.1" + } + }, + "mdn-data": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", + "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "csstype": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", + "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==", + "dev": true + }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "requires": { + "array-find-index": "^1.0.1" + } + }, + "cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" + }, + "damerau-levenshtein": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", + "dev": true + }, + "dash-ast": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", + "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-fns": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", + "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==" + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" + }, + "del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "globby": "^6.1.0", + "is-path-cwd": "^2.0.0", + "is-path-in-cwd": "^2.0.0", + "p-map": "^2.0.0", + "pify": "^4.0.1", + "rimraf": "^2.6.3" + }, + "dependencies": { + "p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "dev": true + }, + "deps-sort": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", + "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", + "requires": { + "JSONStream": "^1.0.3", + "shasum": "^1.0.0", + "subarg": "^1.0.0", + "through2": "^2.0.0" + } + }, + "des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "requires": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", + "dev": true + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" + }, + "detect-node": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", + "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", + "dev": true + }, + "detective": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", + "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", + "requires": { + "acorn-node": "^1.6.1", + "defined": "^1.0.0", + "minimist": "^1.1.1" + } + }, + "diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "requires": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "dns-packet": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", + "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", + "dev": true, + "requires": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "requires": { + "buffer-indexof": "^1.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "dom-align": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.0.tgz", + "integrity": "sha512-YkoezQuhp3SLFGdOlr5xkqZ640iXrnHAwVYcDg8ZKRUtO7mSzSC2BA5V0VuyAwPSJA4CLIc6EDDJh4bEsD2+zA==" + }, + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + }, + "dependencies": { + "domelementtype": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", + "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==" + } + } + }, + "domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "requires": { + "is-obj": "^2.0.0" + } + }, + "dropzone": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.7.2.tgz", + "integrity": "sha512-m217bJHtf0J1IiKn4Tv6mnu1h5QvQNBnKZ39gma7hzGQhIZMxYq1vYEHs4AVd4ThFwmALys+52NAOD4zdLTG4w==" + }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.578", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.578.tgz", + "integrity": "sha512-z4gU6dA1CbBJsAErW5swTGAaU2TBzc2mPAonJb00zqW1rOraDo2zfBMDRvaz9cVic+0JEZiYbHWPw/fTaZlG2Q==" + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "^1.4.0" + } + }, + "enhanced-resolve": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", + "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + } + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + }, + "dependencies": { + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + } + } + }, + "entities": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", + "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" + }, + "errno": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", + "requires": { + "prr": "~1.0.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.18.0-next.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", + "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-negative-zero": "^2.0.0", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + }, + "dependencies": { + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", + "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "eslint": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.10.0.tgz", + "integrity": "sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@eslint/eslintrc": "^0.1.3", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^1.3.0", + "espree": "^7.3.0", + "esquery": "^1.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash": "^4.17.19", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "eslint-config-airbnb": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz", + "integrity": "sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg==", + "dev": true, + "requires": { + "eslint-config-airbnb-base": "^14.2.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2" + } + }, + "eslint-config-airbnb-base": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz", + "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==", + "dev": true, + "requires": { + "confusing-browser-globals": "^1.0.9", + "object.assign": "^4.1.0", + "object.entries": "^1.1.2" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + } + }, + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + } + } + }, + "eslint-plugin-import": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", + "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.4", + "eslint-module-utils": "^2.6.0", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.1", + "read-pkg-up": "^2.0.0", + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.0" + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz", + "integrity": "sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "aria-query": "^4.2.2", + "array-includes": "^3.1.1", + "ast-types-flow": "^0.0.7", + "axe-core": "^3.5.4", + "axobject-query": "^2.1.2", + "damerau-levenshtein": "^1.0.6", + "emoji-regex": "^9.0.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1", + "language-tags": "^1.0.5" + }, + "dependencies": { + "emoji-regex": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.0.0.tgz", + "integrity": "sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w==", + "dev": true + } + } + }, + "eslint-plugin-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", + "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "dev": true + }, + "eslint-plugin-react": { + "version": "7.21.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.3.tgz", + "integrity": "sha512-OI4GwTCqyIb4ipaOEGLWdaOHCXZZydStAsBEPB2e1ZfNM37bojpgO1BoOQbFb0eLVz3QLDx7b+6kYcrxCuJfhw==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1", + "object.entries": "^1.1.2", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.17.0", + "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "eslint-plugin-standard": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", + "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + }, + "espree": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", + "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.3.0" + } + }, + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "events": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", + "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==" + }, + "eventsource": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", + "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", + "dev": true, + "requires": { + "original": "^1.0.0" + } + }, + "evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "requires": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, + "file-loader": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.0.tgz", + "integrity": "sha512-26qPdHyTsArQ6gU4P1HJbAbnFTyT2r0pG7czh1GFAd9TZbj0n94wWbupgixZH/ET/meqi2/5+F7DhW4OAXD+Lg==", + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.7.1" + }, + "dependencies": { + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + } + } + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true + }, + "filename-reserved-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", + "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=" + }, + "filenamify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", + "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=", + "requires": { + "filename-reserved-regex": "^1.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" + } + }, + "filenamify-url": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz", + "integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=", + "requires": { + "filenamify": "^1.0.0", + "humanize-url": "^1.0.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "findup-sync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", + "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", + "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==" + }, + "flatten": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", + "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" + }, + "flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "requires": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "follow-redirects": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", + "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", + "dev": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "dev": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "optional": true + }, + "fstream": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", + "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "gaze": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", + "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", + "requires": { + "globule": "^1.0.0" + } + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" + }, + "get-assigned-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", + "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-stdin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "gh-pages": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.2.0.tgz", + "integrity": "sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA==", + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify-url": "^1.0.0", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "requires": { + "global-prefix": "^3.0.0" + }, + "dependencies": { + "global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "requires": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + } + } + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "globule": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", + "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", + "requires": { + "glob": "~7.1.1", + "lodash": "~4.17.10", + "minimatch": "~3.0.2" + } + }, + "graceful-fs": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", + "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" + }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, + "handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" + }, + "highlightjs": { + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/highlightjs/-/highlightjs-9.16.2.tgz", + "integrity": "sha512-FK1vmMj8BbEipEy8DLIvp71t5UsC7n2D6En/UfM/91PCwmOpj6f2iu0Y0coRC62KSRHHC+dquM2xMULV/X7NFg==" + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, + "hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" + }, + "html-comment-regex": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" + }, + "html-entities": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", + "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", + "dev": true + }, + "htmlescape": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=" + }, + "http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + } + } + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-proxy-middleware": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", + "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", + "dev": true, + "requires": { + "http-proxy": "^1.17.0", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" + }, + "humanize-url": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz", + "integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=", + "requires": { + "normalize-url": "^1.0.0", + "strip-url-auth": "^1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "requires": { + "postcss": "^7.0.14" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "immutability-helper": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-3.1.1.tgz", + "integrity": "sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ==" + }, + "import-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", + "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", + "requires": { + "import-from": "^2.1.0" + } + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + } + } + }, + "import-from": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", + "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", + "requires": { + "resolve-from": "^3.0.0" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "in-publish": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", + "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==" + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" + }, + "indexes-of": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" + }, + "infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", + "requires": { + "source-map": "~0.5.3" + } + }, + "insert-module-globals": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz", + "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==", + "requires": { + "JSONStream": "^1.0.3", + "acorn-node": "^1.5.2", + "combine-source-map": "^0.8.0", + "concat-stream": "^1.6.1", + "is-buffer": "^1.1.0", + "path-is-absolute": "^1.0.1", + "process": "~0.11.0", + "through2": "^2.0.0", + "undeclared-identifiers": "^1.1.2", + "xtend": "^4.0.0" + } + }, + "internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, + "requires": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + } + }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", + "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "dev": true + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, + "is-absolute-url": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", + "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arguments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", + "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" + }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-finite": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", + "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", + "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true + }, + "is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "requires": { + "is-path-inside": "^2.1.0" + } + }, + "is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "requires": { + "path-is-inside": "^1.0.2" + } + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-svg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", + "requires": { + "html-comment-regex": "^1.1.0" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isnumeric": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/isnumeric/-/isnumeric-0.2.0.tgz", + "integrity": "sha1-ojR7o2DeGeM9D/1ZD933dVy/LmQ=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jest-worker": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", + "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-base64": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", + "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", + "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jstransform": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz", + "integrity": "sha1-tMSb9j8WLBCLA0g5moc3xxOwqDo=", + "requires": { + "base62": "0.1.1", + "esprima-fb": "13001.1001.0-dev-harmony-fb", + "source-map": "0.1.31" + }, + "dependencies": { + "esprima-fb": { + "version": "13001.1001.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-13001.1001.0-dev-harmony-fb.tgz", + "integrity": "sha1-YzrNtA2b1NuKHB1owGqUKVn60rA=" + }, + "source-map": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz", + "integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE=", + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "jsx-ast-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", + "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "object.assign": "^4.1.0" + } + }, + "killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "labeled-stream-splicer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", + "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", + "requires": { + "inherits": "^2.0.1", + "stream-splicer": "^2.0.0" + } + }, + "language-subtag-registry": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz", + "integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, + "last-call-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", + "requires": { + "lodash": "^4.17.5", + "webpack-sources": "^1.1.0" + } + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + }, + "levenary": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", + "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", + "requires": { + "leven": "^3.1.0" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "lodash-es": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", + "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" + }, + "lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" + }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.has": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", + "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" + }, + "lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "requires": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "requires": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" + }, + "loglevel": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.0.tgz", + "integrity": "sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-cancellable-promise": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-1.0.0.tgz", + "integrity": "sha512-+YO6Grg2uy/z8Mv3uV90OP6yAUHIF43YGgEFbejmBrK9VWFsVO6DvzFMcopXr9wCNg3/QIltIKiSCROC7zFB2g==" + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + } + } + }, + "make-event-props": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.2.0.tgz", + "integrity": "sha512-BmWFkm/jZzVH9A0tEBdkjAARUz/eha+5IRyfOndeSMKRadkgR5DawoBHoRwLxkYmjJOI5bHkXKpaZocxj+dKgg==" + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "marked": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.0.tgz", + "integrity": "sha512-tiRxakgbNPBr301ihe/785NntvYyhxlqcL3YaC8CaxJQh7kiaEtrN9B/eK2I2943Yjkh5gw25chYFDQhOMCwMA==" + }, + "math-expression-evaluator": { + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.22.tgz", + "integrity": "sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ==", + "dev": true + }, + "md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "mdn-data": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", + "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "dev": true + }, + "memoize-one": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", + "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "meow": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-type": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "read-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "merge-class-names": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/merge-class-names/-/merge-class-names-1.3.0.tgz", + "integrity": "sha512-k0Qaj36VBpKgdc8c188LEZvo6v/zzry/FUufwopWbMSp6/knfVFU/KIB55/hJjeIpg18IH2WskXJCRnM/1BrdQ==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "requires": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "mini-css-extract-plugin": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", + "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", + "requires": { + "loader-utils": "^1.1.0", + "normalize-url": "1.9.1", + "schema-utils": "^1.0.0", + "webpack-sources": "^1.1.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", + "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "requires": { + "minipass": "^3.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "module-deps": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", + "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", + "requires": { + "JSONStream": "^1.0.3", + "browser-resolve": "^2.0.0", + "cached-path-relative": "^1.0.2", + "concat-stream": "~1.6.0", + "defined": "^1.0.0", + "detective": "^5.2.0", + "duplexer2": "^0.1.2", + "inherits": "^2.0.1", + "parents": "^1.0.0", + "readable-stream": "^2.0.2", + "resolve": "^1.4.0", + "stream-combiner2": "^1.1.1", + "subarg": "^1.0.0", + "through2": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "moment-timezone": { + "version": "0.5.31", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", + "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", + "requires": { + "moment": ">= 2.9.0" + }, + "dependencies": { + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "requires": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + } + }, + "multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node-ensure": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", + "integrity": "sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=" + }, + "node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "dev": true + }, + "node-gyp": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", + "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "tar": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", + "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", + "requires": { + "block-stream": "*", + "fstream": "^1.0.12", + "inherits": "2" + } + } + } + }, + "node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "requires": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + }, + "dependencies": { + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "events": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", + "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==" + }, + "stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "timers-browserify": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", + "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", + "requires": { + "setimmediate": "^1.0.4" + } + }, + "tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" + }, + "util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + } + } + }, + "node-releases": { + "version": "1.1.61", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", + "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==" + }, + "node-sass": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", + "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", + "requires": { + "async-foreach": "^0.1.3", + "chalk": "^1.1.1", + "cross-spawn": "^3.0.0", + "gaze": "^1.0.0", + "get-stdin": "^4.0.1", + "glob": "^7.0.3", + "in-publish": "^2.0.0", + "lodash": "^4.17.15", + "meow": "^3.7.0", + "mkdirp": "^0.5.1", + "nan": "^2.13.2", + "node-gyp": "^3.8.0", + "npmlog": "^4.0.0", + "request": "^2.88.0", + "sass-graph": "2.2.5", + "stdout-stream": "^1.4.0", + "true-case-path": "^1.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "cross-spawn": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + }, + "normalize-url": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", + "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", + "requires": { + "object-assign": "^4.0.1", + "prepend-http": "^1.0.0", + "query-string": "^4.1.0", + "sort-keys": "^1.0.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-is": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.3.tgz", + "integrity": "sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", + "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.0", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.entries": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5", + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "objectify-array": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/objectify-array/-/objectify-array-2.1.0.tgz", + "integrity": "sha512-tG8ndq75CyLdsVSB5e3Xp6ajVi0oC3LsR0lMiGx3imtYWrGNnpdPzP/Tv3UQsRO2OCvpqUedQyZAv20OrlK2WA==" + }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onecolor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/onecolor/-/onecolor-3.1.0.tgz", + "integrity": "sha512-YZSypViXzu3ul5LMu/m6XjJ9ol8qAy9S2VjHl5E6UlhUH1KGKWabyEJifn0Jjpw23bYDzC2ucKMPGiH5kfwSGQ==", + "dev": true + }, + "opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimize-css-assets-webpack-plugin": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz", + "integrity": "sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A==", + "requires": { + "cssnano": "^4.1.10", + "last-call-webpack-plugin": "^3.0.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "original": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", + "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", + "dev": true, + "requires": { + "url-parse": "^1.4.3" + } + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-retry": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", + "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", + "dev": true, + "requires": { + "retry": "^0.12.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "requires": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", + "requires": { + "path-platform": "~0.11.15" + } + }, + "parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "requires": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" + }, + "path-complete-extname": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/path-complete-extname/-/path-complete-extname-1.0.0.tgz", + "integrity": "sha512-CVjiWcMRdGU8ubs08YQVzhutOR5DEfO97ipRIlOGMK5Bek5nQySknBpuxVAVJ36hseTNs+vdIcv57ZrWxH7zvg==" + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "^2.0.0" + } + }, + "pbkdf2": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", + "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", + "requires": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "pdfjs-dist": { + "version": "2.1.266", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.1.266.tgz", + "integrity": "sha512-Jy7o1wE3NezPxozexSbq4ltuLT0Z21ew/qrEiAEeUZzHxMHGk4DUV1D7RuCXg5vJDvHmjX1YssN+we9QfRRgXQ==", + "requires": { + "node-ensure": "^0.0.0", + "worker-loader": "^2.0.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "requires": { + "pinkie": "^2.0.0" + } + }, + "pixrem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pixrem/-/pixrem-4.0.1.tgz", + "integrity": "sha1-LaSh3m7EQjxfw3lOkwuB1EkOxoY=", + "dev": true, + "requires": { + "browserslist": "^2.0.0", + "postcss": "^6.0.0", + "reduce-css-calc": "^1.2.7" + }, + "dependencies": { + "browserslist": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000792", + "electron-to-chromium": "^1.3.30" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + } + } + }, + "pleeease-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pleeease-filters/-/pleeease-filters-4.0.0.tgz", + "integrity": "sha1-ZjKy+wVkjSdY2GU4T7zteeHMrsc=", + "dev": true, + "requires": { + "onecolor": "^3.0.4", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "pnp-webpack-plugin": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", + "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", + "requires": { + "ts-pnp": "^1.1.6" + } + }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "postcss": { + "version": "7.0.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", + "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-apply": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/postcss-apply/-/postcss-apply-0.8.0.tgz", + "integrity": "sha1-FOVEu7XLbxweBIhXll15rgZrE0M=", + "dev": true, + "requires": { + "babel-runtime": "^6.23.0", + "balanced-match": "^0.4.2", + "postcss": "^6.0.0" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-attribute-case-insensitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-2.0.0.tgz", + "integrity": "sha1-lNxCLI+QmX8WvTOjZUu77AhJY7Q=", + "dev": true, + "requires": { + "postcss": "^6.0.0", + "postcss-selector-parser": "^2.2.3" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "postcss-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", + "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-calc": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-6.0.2.tgz", + "integrity": "sha512-fiznXjEN5T42Qm7qqMCVJXS3roaj9r4xsSi+meaBVe7CJBl8t/QLOXu02Z2E6oWAMWIvCuF6JrvzFekmVEbOKA==", + "dev": true, + "requires": { + "css-unit-converter": "^1.1.1", + "postcss": "^7.0.2", + "postcss-selector-parser": "^2.2.2", + "reduce-css-calc": "^2.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", + "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "reduce-css-calc": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz", + "integrity": "sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA==", + "dev": true, + "requires": { + "css-unit-converter": "^1.1.1", + "postcss-value-parser": "^3.3.0" + } + } + } + }, + "postcss-color-function": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-color-function/-/postcss-color-function-4.1.0.tgz", + "integrity": "sha512-2/fuv6mP5Lt03XbRpVfMdGC8lRP1sykme+H1bR4ARyOmSMB8LPSjcL6EAI1iX6dqUF+jNEvKIVVXhan1w/oFDQ==", + "dev": true, + "requires": { + "css-color-function": "~1.3.3", + "postcss": "^6.0.23", + "postcss-message-helpers": "^2.0.0", + "postcss-value-parser": "^3.3.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-functional-notation": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", + "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-gray": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-4.1.0.tgz", + "integrity": "sha512-L4iLKQLdqChz6ZOgGb6dRxkBNw78JFYcJmBz1orHpZoeLtuhDDGegRtX9gSyfoCIM7rWZ3VNOyiqqvk83BEN+w==", + "dev": true, + "requires": { + "color": "^2.0.1", + "postcss": "^6.0.14", + "postcss-message-helpers": "^2.0.0", + "reduce-function-call": "^1.0.2" + }, + "dependencies": { + "color": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color/-/color-2.0.1.tgz", + "integrity": "sha512-ubUCVVKfT7r2w2D3qtHakj8mbmKms+tThR8gI8zEYCbUBl8/voqFGt3kgBqGwXAopgXybnkuOq+qMYCRrp4cXw==", + "dev": true, + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-hex-alpha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-3.0.0.tgz", + "integrity": "sha1-HlPmyKyyN5Vej9CLfs2xuLgwn5U=", + "dev": true, + "requires": { + "color": "^1.0.3", + "postcss": "^6.0.1", + "postcss-message-helpers": "^2.0.0" + }, + "dependencies": { + "color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", + "integrity": "sha1-5I6DLYXxTvaU+0aIEcLVz+cptV0=", + "dev": true, + "requires": { + "color-convert": "^1.8.2", + "color-string": "^1.4.0" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-hsl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hsl/-/postcss-color-hsl-2.0.0.tgz", + "integrity": "sha1-EnA2ZvoxBDDj8wpFTawThjF9WEQ=", + "dev": true, + "requires": { + "postcss": "^6.0.1", + "postcss-value-parser": "^3.3.0", + "units-css": "^0.4.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-hwb": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-hwb/-/postcss-color-hwb-3.0.0.tgz", + "integrity": "sha1-NAKxnvTYSXVAwftQcr6YY8qVVx4=", + "dev": true, + "requires": { + "color": "^1.0.3", + "postcss": "^6.0.1", + "postcss-message-helpers": "^2.0.0", + "reduce-function-call": "^1.0.2" + }, + "dependencies": { + "color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", + "integrity": "sha1-5I6DLYXxTvaU+0aIEcLVz+cptV0=", + "dev": true, + "requires": { + "color-convert": "^1.8.2", + "color-string": "^1.4.0" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-mod-function": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", + "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-rebeccapurple": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-3.1.0.tgz", + "integrity": "sha512-212hJUk9uSsbwO5ECqVjmh/iLsmiVL1xy9ce9TVf+X3cK/ZlUIlaMdoxje/YpsL9cmUH3I7io+/G2LyWx5rg1g==", + "dev": true, + "requires": { + "postcss": "^6.0.22", + "postcss-values-parser": "^1.5.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "postcss-values-parser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-1.5.0.tgz", + "integrity": "sha512-3M3p+2gMp0AH3da530TlX8kiO1nxdTnc3C6vr8dMxRLIlh8UYkz0/wcwptSXjhtx2Fr0TySI7a+BHDQ8NL7LaQ==", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-rgb": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rgb/-/postcss-color-rgb-2.0.0.tgz", + "integrity": "sha1-FFOcinExSUtILg3RzCZf9lFLUmM=", + "dev": true, + "requires": { + "postcss": "^6.0.1", + "postcss-value-parser": "^3.3.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-rgba-fallback": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-rgba-fallback/-/postcss-color-rgba-fallback-3.0.0.tgz", + "integrity": "sha1-N9XJNToHoJJwkSqCYGu0Kg1wLAQ=", + "dev": true, + "requires": { + "postcss": "^6.0.6", + "postcss-value-parser": "^3.3.0", + "rgb-hex": "^2.1.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-colormin": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", + "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", + "requires": { + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "color": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", + "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", + "requires": { + "color-convert": "^1.9.1", + "color-string": "^1.5.2" + } + } + } + }, + "postcss-convert-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-cssnext": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-cssnext/-/postcss-cssnext-3.1.0.tgz", + "integrity": "sha512-awPDhI4OKetcHCr560iVCoDuP6e/vn0r6EAqdWPpAavJMvkBSZ6kDpSN4b3mB3Ti57hQMunHHM8Wvx9PeuYXtA==", + "dev": true, + "requires": { + "autoprefixer": "^7.1.1", + "caniuse-api": "^2.0.0", + "chalk": "^2.0.1", + "pixrem": "^4.0.0", + "pleeease-filters": "^4.0.0", + "postcss": "^6.0.5", + "postcss-apply": "^0.8.0", + "postcss-attribute-case-insensitive": "^2.0.0", + "postcss-calc": "^6.0.0", + "postcss-color-function": "^4.0.0", + "postcss-color-gray": "^4.0.0", + "postcss-color-hex-alpha": "^3.0.0", + "postcss-color-hsl": "^2.0.0", + "postcss-color-hwb": "^3.0.0", + "postcss-color-rebeccapurple": "^3.0.0", + "postcss-color-rgb": "^2.0.0", + "postcss-color-rgba-fallback": "^3.0.0", + "postcss-custom-media": "^6.0.0", + "postcss-custom-properties": "^6.1.0", + "postcss-custom-selectors": "^4.0.1", + "postcss-font-family-system-ui": "^3.0.0", + "postcss-font-variant": "^3.0.0", + "postcss-image-set-polyfill": "^0.3.5", + "postcss-initial": "^2.0.0", + "postcss-media-minmax": "^3.0.0", + "postcss-nesting": "^4.0.1", + "postcss-pseudo-class-any-link": "^4.0.0", + "postcss-pseudoelements": "^5.0.0", + "postcss-replace-overflow-wrap": "^2.0.0", + "postcss-selector-matches": "^3.0.1", + "postcss-selector-not": "^3.0.1" + }, + "dependencies": { + "browserslist": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000792", + "electron-to-chromium": "^1.3.30" + } + }, + "caniuse-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-2.0.0.tgz", + "integrity": "sha1-sd21pZZrFvSNxJmERNS7xsfZ2DQ=", + "dev": true, + "requires": { + "browserslist": "^2.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-custom-media": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-6.0.0.tgz", + "integrity": "sha1-vlMnhBEOyylQRPtTlaGABushpzc=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-custom-properties": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-6.3.1.tgz", + "integrity": "sha512-zoiwn4sCiUFbr4KcgcNZLFkR6gVQom647L+z1p/KBVHZ1OYwT87apnS42atJtx6XlX2yI7N5fjXbFixShQO2QQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^6.0.18" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-custom-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-4.0.1.tgz", + "integrity": "sha1-eBOC+UxS5yfvXKR3bqKt9JphE4I=", + "dev": true, + "requires": { + "postcss": "^6.0.1", + "postcss-selector-matches": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-dir-pseudo-class": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", + "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-discard-comments": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", + "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-duplicates": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-empty": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-discard-overridden": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-double-position-gradients": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", + "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", + "requires": { + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-env-function": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", + "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-flexbugs-fixes": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz", + "integrity": "sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ==", + "requires": { + "postcss": "^7.0.26" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-focus-visible": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-focus-within": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-font-family-system-ui": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-family-system-ui/-/postcss-font-family-system-ui-3.0.0.tgz", + "integrity": "sha512-58G/hTxMSSKlIRpcPUjlyo6hV2MEzvcVO2m4L/T7Bb2fJTG4DYYfQjQeRvuimKQh1V1sOzCIz99g+H2aFNtlQw==", + "dev": true, + "requires": { + "postcss": "^6.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-font-variant": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-3.0.0.tgz", + "integrity": "sha1-CMzIj2BQuoLtjvLMdsDGprQfGD4=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-gap-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-image-set-function": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", + "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-image-set-polyfill": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/postcss-image-set-polyfill/-/postcss-image-set-polyfill-0.3.5.tgz", + "integrity": "sha1-Dxk0E3AM8fgr05Bm7wFtZaShgYE=", + "dev": true, + "requires": { + "postcss": "^6.0.1", + "postcss-media-query-parser": "^0.2.3" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-import": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "requires": { + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-initial": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-2.0.0.tgz", + "integrity": "sha1-cnFfczbgu3k1HZnuZcSiU6hEG6Q=", + "dev": true, + "requires": { + "lodash.template": "^4.2.4", + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-lab-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", + "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-load-config": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", + "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + } + } + }, + "postcss-loader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", + "requires": { + "loader-utils": "^1.1.0", + "postcss": "^7.0.0", + "postcss-load-config": "^2.0.0", + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "postcss-logical": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-media-minmax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-3.0.0.tgz", + "integrity": "sha1-Z1JWA3pD70C8Twdgv9BtTcadSNI=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", + "dev": true + }, + "postcss-merge-longhand": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", + "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", + "requires": { + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" + } + }, + "postcss-merge-rules": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", + "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", + "vendors": "^1.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-message-helpers": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", + "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", + "dev": true + }, + "postcss-minify-font-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-minify-gradients": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", + "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-minify-params": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", + "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", + "requires": { + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-minify-selectors": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", + "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", + "requires": { + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "requires": { + "postcss": "^7.0.5" + } + }, + "postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "requires": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + } + }, + "postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "requires": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "postcss-nesting": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-4.2.1.tgz", + "integrity": "sha512-IkyWXICwagCnlaviRexi7qOdwPw3+xVVjgFfGsxmztvRVaNxAlrypOIKqDE5mxY+BVxnId1rnUKBRQoNE2VDaA==", + "dev": true, + "requires": { + "postcss": "^6.0.11" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-charset": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", + "requires": { + "postcss": "^7.0.0" + } + }, + "postcss-normalize-display-values": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", + "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-positions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", + "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", + "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-string": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", + "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", + "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-normalize-url": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", + "requires": { + "is-absolute-url": "^2.0.0", + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "is-absolute-url": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", + "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" + }, + "normalize-url": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", + "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-ordered-values": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", + "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-overflow-shorthand": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-page-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-place": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", + "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-preset-env": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", + "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", + "requires": { + "autoprefixer": "^9.6.1", + "browserslist": "^4.6.4", + "caniuse-lite": "^1.0.30000981", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.4.0", + "postcss": "^7.0.17", + "postcss-attribute-case-insensitive": "^4.0.1", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.3", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.8", + "postcss-custom-properties": "^8.0.11", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" + }, + "dependencies": { + "autoprefixer": { + "version": "9.8.6", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", + "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", + "requires": { + "browserslist": "^4.12.0", + "caniuse-lite": "^1.0.30001109", + "colorette": "^1.2.1", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.32", + "postcss-value-parser": "^4.1.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + } + } + }, + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" + }, + "postcss-attribute-case-insensitive": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", + "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^6.0.2" + } + }, + "postcss-color-gray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", + "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-color-hex-alpha": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", + "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", + "requires": { + "postcss": "^7.0.14", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-color-rebeccapurple": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", + "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", + "requires": { + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + } + }, + "postcss-custom-media": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", + "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", + "requires": { + "postcss": "^7.0.14" + } + }, + "postcss-custom-properties": { + "version": "8.0.11", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", + "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", + "requires": { + "postcss": "^7.0.17", + "postcss-values-parser": "^2.0.1" + } + }, + "postcss-custom-selectors": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", + "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-font-variant": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", + "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-initial": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", + "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", + "requires": { + "lodash.template": "^4.5.0", + "postcss": "^7.0.2" + } + }, + "postcss-media-minmax": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-nesting": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", + "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-pseudo-class-any-link": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", + "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", + "requires": { + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", + "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", + "requires": { + "cssesc": "^2.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "postcss-replace-overflow-wrap": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", + "requires": { + "postcss": "^7.0.2" + } + }, + "postcss-selector-matches": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", + "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-selector-not": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", + "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", + "requires": { + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-pseudo-class-any-link": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-4.0.0.tgz", + "integrity": "sha1-kVKgYT00UHIFE+iJKFS65C0O5o4=", + "dev": true, + "requires": { + "postcss": "^6.0.1", + "postcss-selector-parser": "^2.2.3" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "postcss-selector-parser": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", + "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "dev": true, + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-pseudoelements": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudoelements/-/postcss-pseudoelements-5.0.0.tgz", + "integrity": "sha1-7vGU6NUkZFylIKlJ6V5RjoEkAss=", + "dev": true, + "requires": { + "postcss": "^6.0.0" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-reduce-initial": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", + "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", + "requires": { + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", + "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", + "requires": { + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + } + }, + "postcss-replace-overflow-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-2.0.0.tgz", + "integrity": "sha1-eU22+qVPjbEAhUOSqTr0V2i04ls=", + "dev": true, + "requires": { + "postcss": "^6.0.1" + }, + "dependencies": { + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-safe-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", + "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", + "requires": { + "postcss": "^7.0.26" + }, + "dependencies": { + "postcss": { + "version": "7.0.35", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", + "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-selector-matches": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz", + "integrity": "sha1-5WNAEeE5UIgYYbvdWMLQER/8lqs=", + "dev": true, + "requires": { + "balanced-match": "^0.4.2", + "postcss": "^6.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-selector-not": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz", + "integrity": "sha1-Lk2y8JZTNsAefOx9tsYN/3ZzNdk=", + "dev": true, + "requires": { + "balanced-match": "^0.4.2", + "postcss": "^6.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-selector-parser": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", + "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", + "requires": { + "cssesc": "^3.0.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1", + "util-deprecate": "^1.0.2" + } + }, + "postcss-svgo": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", + "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", + "requires": { + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" + } + }, + "postcss-unique-selectors": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", + "requires": { + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", + "uniqs": "^2.0.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" + }, + "postcss-values-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", + "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", + "requires": { + "flatten": "^1.0.2", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "requires": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + }, + "dependencies": { + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + } + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + }, + "query-string": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", + "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", + "requires": { + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "raf": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", + "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", + "requires": { + "performance-now": "^2.1.0" + } + }, + "raf-schd": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz", + "integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==" + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "requires": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dev": true, + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "dev": true + } + } + }, + "rc-align": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.5.tgz", + "integrity": "sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==", + "requires": { + "babel-runtime": "^6.26.0", + "dom-align": "^1.7.0", + "prop-types": "^15.5.8", + "rc-util": "^4.0.4" + } + }, + "rc-animate": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.11.1.tgz", + "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==", + "requires": { + "babel-runtime": "6.x", + "classnames": "^2.2.6", + "css-animation": "^1.3.2", + "prop-types": "15.x", + "raf": "^3.4.0", + "rc-util": "^4.15.3", + "react-lifecycles-compat": "^3.0.4" + } + }, + "rc-slider": { + "version": "github:Galvanize-IT/slider#c569ca3b11979aced8306e6c02f37466cb7cd365", + "from": "github:Galvanize-IT/slider#c569ca3b11979aced8306e6c02f37466cb7cd365", + "requires": { + "babel-runtime": "6.x", + "classnames": "^2.2.5", + "gh-pages": "^2.0.1", + "prop-types": "^15.5.4", + "rc-tooltip": "^3.7.0", + "rc-util": "^4.0.4", + "shallowequal": "^1.0.1", + "warning": "^4.0.3" + } + }, + "rc-tooltip": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-3.7.3.tgz", + "integrity": "sha512-dE2ibukxxkrde7wH9W8ozHKUO4aQnPZ6qBHtrTH9LoO836PjDdiaWO73fgPB05VfJs9FbZdmGPVEbXCeOP99Ww==", + "requires": { + "babel-runtime": "6.x", + "prop-types": "^15.5.8", + "rc-trigger": "^2.2.2" + } + }, + "rc-trigger": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.6.5.tgz", + "integrity": "sha512-m6Cts9hLeZWsTvWnuMm7oElhf+03GOjOLfTuU0QmdB9ZrW7jR2IpI5rpNM7i9MvAAlMAmTx5Zr7g3uu/aMvZAw==", + "requires": { + "babel-runtime": "6.x", + "classnames": "^2.2.6", + "prop-types": "15.x", + "rc-align": "^2.4.0", + "rc-animate": "2.x", + "rc-util": "^4.4.0", + "react-lifecycles-compat": "^3.0.4" + } + }, + "rc-util": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", + "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", + "requires": { + "add-dom-event-listener": "^1.1.0", + "prop-types": "^15.5.10", + "react-is": "^16.12.0", + "react-lifecycles-compat": "^3.0.4", + "shallowequal": "^1.1.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, + "react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "react-addons-css-transition-group": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-addons-css-transition-group/-/react-addons-css-transition-group-15.6.2.tgz", + "integrity": "sha1-nkN2vPQLUhfRTsaFUwgc7ksIptY=", + "requires": { + "react-transition-group": "^1.2.0" + } + }, + "react-addons-test-utils": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz", + "integrity": "sha1-wStu/cIkfBDae4dw0YUICnsEcVY=" + }, + "react-beautiful-dnd": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz", + "integrity": "sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg==", + "requires": { + "@babel/runtime": "^7.8.4", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.1.1", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + } + }, + "react-copy-to-clipboard": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", + "integrity": "sha512-/2t5mLMMPuN5GmdXo6TebFa8IoFxZ+KTDDqYhcDm0PhkgEzSxVvIX26G20s1EB02A4h2UZgwtfymZ3lGJm0OLg==", + "requires": { + "copy-to-clipboard": "^3", + "prop-types": "^15.5.8" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "react-datepicker": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.2.2.tgz", + "integrity": "sha512-/3D6hfhXcCNCbO8LICuQeoNDItWFyitGo+aLcsi0tAyJLtCInamYRwPIXhsEF+N6/qWim1yNyr71mqjj4YEBmg==", + "requires": { + "classnames": "^2.2.6", + "date-fns": "^2.0.1", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.9.0", + "react-popper": "^1.3.4" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "react-dom": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", + "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "react-is": { + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", + "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==" + }, + "react-json-pretty": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-json-pretty/-/react-json-pretty-2.2.0.tgz", + "integrity": "sha512-3UMzlAXkJ4R8S4vmkRKtvJHTewG4/rn1Q18n0zqdu/ipZbUPLVZD+QwC7uVcD/IAY3s8iNVHlgR2dMzIUS0n1A==", + "requires": { + "prop-types": "^15.6.2" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-onclickoutside": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz", + "integrity": "sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A==" + }, + "react-paginate": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-6.5.0.tgz", + "integrity": "sha512-H7xSi9jyiJzgfaj+2nNhQcjZfwzJ/Mxb64V2RiyDctjZyCWojwsaGwMqhLBpQ58iAuMVtBMRQ7ECqMcUKG9QSQ==", + "requires": { + "prop-types": "^15.6.1" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "react-pdf": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-4.2.0.tgz", + "integrity": "sha512-Ao44mZszfPwtCUsrXHtXnhM+czTvPxfG5wqssbWgj2onL70TKSOKGzQfCH4csCnNDOKji/Pc/s0Og70/VOE+Rg==", + "requires": { + "@babel/runtime": "^7.0.0", + "make-cancellable-promise": "^1.0.0", + "make-event-props": "^1.1.0", + "merge-class-names": "^1.1.1", + "pdfjs-dist": "2.1.266", + "prop-types": "^15.6.2" + } + }, + "react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==", + "requires": { + "@babel/runtime": "^7.1.2", + "create-react-context": "^0.3.0", + "deep-equal": "^1.1.1", + "popper.js": "^1.14.4", + "prop-types": "^15.6.1", + "typed-styles": "^0.0.7", + "warning": "^4.0.2" + }, + "dependencies": { + "deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "requires": { + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.1", + "is-regex": "^1.0.4", + "object-is": "^1.0.1", + "object-keys": "^1.1.1", + "regexp.prototype.flags": "^1.2.0" + } + }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "react-redux": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz", + "integrity": "sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg==", + "requires": { + "@babel/runtime": "^7.5.5", + "hoist-non-react-statics": "^3.3.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.9.0" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, + "react-scroll-sync": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/react-scroll-sync/-/react-scroll-sync-0.8.0.tgz", + "integrity": "sha512-Ms9srm41UM+lWexuqdocXjqaqqt6ZRSFxcudgB0sYhC7Or+m12WemTwY8BaQCRf7hA8zHDk55FHvMkqsH7gF+w==" + }, + "react-textarea-autosize": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz", + "integrity": "sha512-grajUlVbkx6VdtSxCgzloUIphIZF5bKr21OYMceWPKkniy7H0mRAT/AXPrRtObAe+zUePnNlBwUc4ivVjUGIjw==", + "requires": { + "@babel/runtime": "^7.10.2", + "use-composed-ref": "^1.0.0", + "use-latest": "^1.0.0" + } + }, + "react-tools": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/react-tools/-/react-tools-0.13.3.tgz", + "integrity": "sha1-2mrH1Nd3elml6VHPRucv1La0Ciw=", + "requires": { + "commoner": "^0.10.0", + "jstransform": "^10.1.0" + } + }, + "react-tooltip": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.10.tgz", + "integrity": "sha512-D7ZLx6/QwpUl0SZRek3IZy/HWpsEEp0v3562tcT8IwZgu8IgV7hY5ZzniTkHyRcuL+IQnljpjj7A7zCgl2+T3w==", + "requires": { + "prop-types": "^15.7.2", + "uuid": "^7.0.3" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "uuid": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", + "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" + } + } + }, + "react-transition-group": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz", + "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==", + "requires": { + "chain-function": "^1.0.0", + "dom-helpers": "^3.2.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.5.6", + "warning": "^3.0.0" + }, + "dependencies": { + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, + "warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", + "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, + "react_ujs": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/react_ujs/-/react_ujs-2.6.1.tgz", + "integrity": "sha512-9M33/A8cubStkZ2cpJSimcTD0RlCWiqXF6e90IQmMw/Caf/W0dtAzOtHtiQE3JjLbt/nhRR7NLPxMfnlm141ig==", + "requires": { + "react_ujs": "^2.6.0" + } + }, + "reactify": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/reactify/-/reactify-1.1.1.tgz", + "integrity": "sha1-qPEZWWJzwNS/savqDBTCYB6gO7o=", + "requires": { + "react-tools": "~0.13.0", + "through": "~2.3.4" + } + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", + "requires": { + "pify": "^2.3.0" + } + }, + "read-only-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", + "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", + "requires": { + "readable-stream": "^2.0.2" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + }, + "recast": { + "version": "0.11.23", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", + "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", + "requires": { + "ast-types": "0.9.6", + "esprima": "~3.1.0", + "private": "~0.1.5", + "source-map": "~0.5.0" + } + }, + "redent": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + }, + "dependencies": { + "indent-string": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", + "requires": { + "repeating": "^2.0.0" + } + } + } + }, + "reduce-css-calc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", + "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", + "dev": true, + "requires": { + "balanced-match": "^0.4.2", + "math-expression-evaluator": "^1.2.14", + "reduce-function-call": "^1.0.1" + }, + "dependencies": { + "balanced-match": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + } + } + }, + "reduce-function-call": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", + "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "redux": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", + "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + }, + "regenerate": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", + "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==" + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, + "regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "regexpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", + "dev": true + }, + "regexpu-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" + }, + "regjsparser": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", + "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" + } + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "repeating": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "dependencies": { + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + } + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, + "rgb": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/rgb/-/rgb-0.1.0.tgz", + "integrity": "sha1-vieykej+/+rBvZlylyG/pA/AN7U=", + "dev": true + }, + "rgb-hex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/rgb-hex/-/rgb-hex-2.1.0.tgz", + "integrity": "sha1-x3PF/iJoolV42SU5qCp6XOU77aY=", + "dev": true + }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "requires": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "requires": { + "aproba": "^1.1.1" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sass-graph": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", + "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", + "requires": { + "glob": "^7.0.0", + "lodash": "^4.0.0", + "scss-tokenizer": "^0.2.3", + "yargs": "^13.3.2" + } + }, + "sass-loader": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", + "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", + "requires": { + "clone-deep": "^4.0.1", + "loader-utils": "^1.2.3", + "neo-async": "^2.6.1", + "schema-utils": "^2.6.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "requires": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "scss-tokenizer": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", + "requires": { + "js-base64": "^2.1.8", + "source-map": "^0.4.2" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "selfsigned": { + "version": "1.10.8", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", + "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", + "dev": true, + "requires": { + "node-forge": "^0.10.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dev": true, + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "dev": true + }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "requires": { + "kind-of": "^6.0.2" + } + }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "shasum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", + "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", + "requires": { + "json-stable-stringify": "~0.0.0", + "sha.js": "~2.4.4" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shell-quote": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.1.tgz", + "integrity": "sha512-2kUqeAGnMAu6YrTPX4E3LfxacH9gKljzVjlkUeSqY0soGwK4KLl7TURXCem712tkhBCeeaFP9QK4dKn88s3Icg==" + }, + "side-channel": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", + "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", + "dev": true, + "requires": { + "es-abstract": "^1.18.0-next.0", + "object-inspect": "^1.8.0" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", + "requires": { + "is-arrayish": "^0.3.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + } + } + }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sockjs": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", + "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", + "dev": true, + "requires": { + "faye-websocket": "^0.10.0", + "uuid": "^3.4.0", + "websocket-driver": "0.6.5" + }, + "dependencies": { + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + } + } + }, + "sockjs-client": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", + "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", + "dev": true, + "requires": { + "debug": "^3.2.5", + "eventsource": "^1.0.7", + "faye-websocket": "~0.11.1", + "inherits": "^2.0.3", + "json3": "^3.3.2", + "url-parse": "^1.4.3" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "faye-websocket": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", + "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", + "dev": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + } + } + }, + "sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==" + }, + "spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "requires": { + "minipass": "^3.1.1" + } + }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + }, + "stdout-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", + "requires": { + "readable-stream": "^2.0.1" + } + }, + "stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "requires": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + } + }, + "stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-http": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", + "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", + "requires": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "xtend": "^4.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, + "stream-splicer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", + "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string.prototype.matchall": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", + "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", + "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", + "requires": { + "get-stdin": "^4.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "strip-url-auth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz", + "integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=" + }, + "style-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", + "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.7.0" + }, + "dependencies": { + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "requires": { + "minimist": "^1.2.5" + } + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + } + } + }, + "stylehacks": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", + "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "postcss-selector-parser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", + "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", + "requires": { + "dot-prop": "^5.2.0", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, + "subarg": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", + "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", + "requires": { + "minimist": "^1.1.0" + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "svgo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", + "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.37", + "csso": "^4.0.2", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, + "syntax-error": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", + "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", + "requires": { + "acorn-node": "^1.2.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + } + }, + "tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" + }, + "tar": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", + "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, + "terser": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.4.tgz", + "integrity": "sha512-dxuB8KQo8Gt6OVOeLg/rxfcxdNZI/V1G6ze1czFUzPeCFWZRtvZMgSzlZZ5OYBZ4HoG607F6pFPNLekJyV+yVw==", + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + } + } + }, + "terser-webpack-plugin": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.2.tgz", + "integrity": "sha512-3qAQpykRTD5DReLu5/cwpsg7EZFzP3Q0Hp2XUWJUw2mpq2jfgOKTZr8IZKKnNieRVVo1UauROTdhbQJZveGKtQ==", + "requires": { + "cacache": "^15.0.5", + "find-cache-dir": "^3.3.1", + "jest-worker": "^26.3.0", + "p-limit": "^3.0.2", + "schema-utils": "^2.7.1", + "serialize-javascript": "^5.0.1", + "source-map": "^0.6.1", + "terser": "^5.3.2", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "requires": { + "p-locate": "^4.1.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "requires": { + "find-up": "^4.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "requires": { + "randombytes": "^2.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "timers-browserify": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", + "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", + "requires": { + "process": "~0.11.0" + } + }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" + }, + "tiny-invariant": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", + "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" + }, + "to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "trim-newlines": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", + "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" + }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, + "true-case-path": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", + "requires": { + "glob": "^7.1.2" + } + }, + "ts-essentials": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz", + "integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==" + }, + "ts-loader": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.4.tgz", + "integrity": "sha512-5u8KF1SW8eCUb/Ff7At81e3wznPmT/27fvaGRO9CziVy+6NlPVRvrzSox4OwU0/e6OflOUB32Err4VquysCSAQ==", + "requires": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.0.2", + "micromatch": "^4.0.0", + "semver": "^6.0.0" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "ts-pnp": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", + "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" + }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, + "tslib": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.0.tgz", + "integrity": "sha512-+Zw5lu0D9tvBMjGP8LpvMb0u2WW2QV3y+D8mO6J+cNzCYIN4sVy43Bf9vl92nqFahutN0I8zHa7cc4vihIshnw==" + }, + "tty-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typed-styles": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", + "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "typescript": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", + "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==" + }, + "umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==" + }, + "undeclared-identifiers": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", + "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", + "requires": { + "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "unfetch": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", + "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" + }, + "uniqs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", + "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" + }, + "unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "units-css": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/units-css/-/units-css-0.4.0.tgz", + "integrity": "sha1-1iKGU6UZg9fBb/KPi53Dsf/tOgc=", + "dev": true, + "requires": { + "isnumeric": "^0.2.0", + "viewport-dimensions": "^0.2.0" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true + }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "url-parse": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", + "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "use-composed-ref": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.0.0.tgz", + "integrity": "sha512-RVqY3NFNjZa0xrmK3bIMWNmQ01QjKPDc7DeWR3xa/N8aliVppuutOE5bZzPkQfvL+5NRWMMp0DJ99Trd974FIw==", + "requires": { + "ts-essentials": "^2.0.3" + } + }, + "use-isomorphic-layout-effect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.0.0.tgz", + "integrity": "sha512-JMwJ7Vd86NwAt1jH7q+OIozZSIxA4ND0fx6AsOe2q1H8ooBUp5aN6DvVCqZiIaYU6JaMRJGyR0FO7EBCIsb/Rg==" + }, + "use-latest": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.1.0.tgz", + "integrity": "sha512-gF04d0ZMV3AMB8Q7HtfkAWe+oq1tFXP6dZKwBHQF5nVXtGsh2oAYeeqma5ZzxtlpOcW8Ro/tLcfmEodjDeqtuw==", + "requires": { + "use-isomorphic-layout-effect": "^1.0.0" + } + }, + "use-memo-one": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz", + "integrity": "sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==" + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + }, + "dependencies": { + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "util.promisify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", + "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.2", + "has-symbols": "^1.0.1", + "object.getownpropertydescriptors": "^2.1.0" + }, + "dependencies": { + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "requires": { + "has-symbols": "^1.0.1" + } + } + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "v8-compile-cache": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", + "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "vendors": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", + "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "viewport-dimensions": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz", + "integrity": "sha1-3nQHR9tTh/0XJfUXXpG6x2r982w=", + "dev": true + }, + "vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" + }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, + "watchpack": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", + "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", + "requires": { + "chokidar": "^3.4.1", + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0", + "watchpack-chokidar2": "^2.0.0" + }, + "dependencies": { + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "optional": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "optional": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "optional": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", + "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", + "optional": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.4.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "optional": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "optional": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "optional": true + }, + "readdirp": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", + "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "optional": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "optional": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "watchpack-chokidar2": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", + "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", + "optional": true, + "requires": { + "chokidar": "^2.1.8" + } + }, + "wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "requires": { + "minimalistic-assert": "^1.0.0" + } + }, + "weakmap-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/weakmap-polyfill/-/weakmap-polyfill-2.0.1.tgz", + "integrity": "sha512-Jy177Lvb1LCrPQDWJsXyyqf4eOhcdvQHFGoCqSv921kVF5i42MVbr4e2WEwetuTLBn1NX0IhPzTmMu0N3cURqQ==" + }, + "webpack": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", + "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", + "requires": { + "@webassemblyjs/ast": "1.9.0", + "@webassemblyjs/helper-module-context": "1.9.0", + "@webassemblyjs/wasm-edit": "1.9.0", + "@webassemblyjs/wasm-parser": "1.9.0", + "acorn": "^6.4.1", + "ajv": "^6.10.2", + "ajv-keywords": "^3.4.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^4.3.0", + "eslint-scope": "^4.0.3", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.4.0", + "loader-utils": "^1.2.3", + "memory-fs": "^0.4.1", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.3", + "neo-async": "^2.6.1", + "node-libs-browser": "^2.2.1", + "schema-utils": "^1.0.0", + "tapable": "^1.1.3", + "terser-webpack-plugin": "^1.4.3", + "watchpack": "^1.7.4", + "webpack-sources": "^1.4.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" + }, + "cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "terser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", + "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, + "terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "requires": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } + }, + "webpack-assets-manifest": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/webpack-assets-manifest/-/webpack-assets-manifest-3.1.1.tgz", + "integrity": "sha512-JV9V2QKc5wEWQptdIjvXDUL1ucbPLH2f27toAY3SNdGZp+xSaStAgpoMcvMZmqtFrBc9a5pTS1058vxyMPOzRQ==", + "requires": { + "chalk": "^2.0", + "lodash.get": "^4.0", + "lodash.has": "^4.0", + "mkdirp": "^0.5", + "schema-utils": "^1.0.0", + "tapable": "^1.0.0", + "webpack-sources": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "webpack-cli": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", + "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", + "requires": { + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.1", + "findup-sync": "^3.0.0", + "global-modules": "^2.0.0", + "import-local": "^2.0.0", + "interpret": "^1.4.0", + "loader-utils": "^1.4.0", + "supports-color": "^6.1.0", + "v8-compile-cache": "^2.1.1", + "yargs": "^13.3.2" + } + }, + "webpack-dev-middleware": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", + "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", + "dev": true, + "requires": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "dependencies": { + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", + "dev": true + } + } + }, + "webpack-dev-server": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", + "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", + "dev": true, + "requires": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.1.8", + "compression": "^1.7.4", + "connect-history-api-fallback": "^1.6.0", + "debug": "^4.1.1", + "del": "^4.1.1", + "express": "^4.17.1", + "html-entities": "^1.3.1", + "http-proxy-middleware": "0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.3.0", + "ip": "^1.1.5", + "is-absolute-url": "^3.0.3", + "killable": "^1.0.1", + "loglevel": "^1.6.8", + "opn": "^5.5.0", + "p-retry": "^3.0.1", + "portfinder": "^1.0.26", + "schema-utils": "^1.0.0", + "selfsigned": "^1.10.7", + "semver": "^6.3.0", + "serve-index": "^1.9.1", + "sockjs": "0.3.20", + "sockjs-client": "1.4.0", + "spdy": "^4.0.2", + "strip-ansi": "^3.0.1", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.7.2", + "webpack-log": "^2.0.0", + "ws": "^6.2.1", + "yargs": "^13.3.2" + }, + "dependencies": { + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "requires": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + } + }, + "webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "requires": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "websocket-driver": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", + "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", + "dev": true, + "requires": { + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "requires": { + "errno": "~0.1.7" + } + }, + "worker-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz", + "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==", + "requires": { + "loader-utils": "^1.0.0", + "schema-utils": "^0.4.0" + }, + "dependencies": { + "schema-utils": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", + "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", + "requires": { + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yaml": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", + "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/package.json b/package.json index 7db9399..6b46e51 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "react-dom": "^16.13.1", "react-json-pretty": "^2.2.0", "react-paginate": "^6.5.0", - "react-pdf": "^5.0.0", + "react-pdf": "^4.2.0", "react-scroll-sync": "^0.8.0", "react-textarea-autosize": "^8.2.0", "react-tooltip": "^4.2.10", -- GitLab From dd16df144e1624bd823d1820eb5f2af78aa8798f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 7 Oct 2020 17:43:51 -1000 Subject: [PATCH 138/287] removing old files. removing package lock file. updating yarn lock file --- babelrc.sav | 18 - package-lock.json | 14172 -------------------------------------------- package.json.sav | 83 - yarn.lock | 65 +- 4 files changed, 40 insertions(+), 14298 deletions(-) delete mode 100644 babelrc.sav delete mode 100644 package-lock.json delete mode 100644 package.json.sav diff --git a/babelrc.sav b/babelrc.sav deleted file mode 100644 index ded31c0..0000000 --- a/babelrc.sav +++ /dev/null @@ -1,18 +0,0 @@ -{ - "presets": [ - ["env", { - "modules": false, - "targets": { - "browsers": "> 1%", - "uglify": true - }, - "useBuiltIns": true - }] - ], - - "plugins": [ - "syntax-dynamic-import", - "transform-object-rest-spread", - ["transform-class-properties", { "spec": true }] - ] -} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 382cf49..0000000 --- a/package-lock.json +++ /dev/null @@ -1,14172 +0,0 @@ -{ - "name": "forge", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/compat-data": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.11.0.tgz", - "integrity": "sha512-TPSvJfv73ng0pfnEOh17bYMPQbI95+nGWc71Ss4vZdRBHTDqmM9Z8ZV4rYz8Ks7sfzc95n30k6ODIq5UGnXcYQ==", - "requires": { - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } - }, - "@babel/core": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.11.6.tgz", - "integrity": "sha512-Wpcv03AGnmkgm6uS6k8iwhIwTrcP0m17TL1n1sy7qD0qelDu4XNeW0dN0mHfa+Gei211yDaLoEe/VlbXQzM4Bg==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.6", - "@babel/helper-module-transforms": "^7.11.0", - "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.11.5", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.11.5", - "@babel/types": "^7.11.5", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "requires": { - "ms": "2.1.2" - } - }, - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - } - } - }, - "@babel/generator": { - "version": "7.11.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", - "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", - "requires": { - "@babel/types": "^7.11.5", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", - "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.10.4.tgz", - "integrity": "sha512-a3rYhlsGV0UHNDvrtOXBg8/OpfV0OKTkxKPzIplS1zpx7CygDcWWxckxZeDd3gzPzC4kUT0A4nVFDK0wGMh4MQ==", - "requires": { - "@babel/compat-data": "^7.10.4", - "browserslist": "^4.12.0", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.5.tgz", - "integrity": "sha512-0nkdeijB7VlZoLT3r/mY3bUkw3T8WG/hNw+FATs/6+pG2039IJWjTYL0VTISqsNHMUTEnwbVnc89WIJX9Qed0A==", - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.10.5", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.10.4.tgz", - "integrity": "sha512-2/hu58IEPKeoLF45DBwx3XFqsbCXmkdAay4spVr2x0jYgRxrSNp+ePwvSsy9g6YSaNDcKIQVPXk1Ov8S2edk2g==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.0" - } - }, - "@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.11.4.tgz", - "integrity": "sha512-ux9hm3zR4WV1Y3xXxXkdG/0gxF9nvI0YVmKVhvK9AfMoaQkemL3sJpXw+Xbz65azo8qJiEz2XVDUpK3KYhH3ZQ==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.11.0.tgz", - "integrity": "sha512-JbFlKHFntRV5qKw3YC0CvQnDZ4XMwgzzBbld7Ly4Mj4cbFy3KywcR8NtNctRToMWJOVvLINJv525Gd6wwVEx/Q==", - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", - "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.11.0.tgz", - "integrity": "sha512-02EVu8COMuTRO1TAzdMtpBPbe6aQ1w/8fePD2YgQmxZU4gpNWaL9gK3Jp7dxlkUlUCJOTaSeA+Hrm1BRQwqIhg==", - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/template": "^7.10.4", - "@babel/types": "^7.11.0", - "lodash": "^4.17.19" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "requires": { - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==" - }, - "@babel/helper-regex": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", - "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", - "requires": { - "lodash": "^4.17.19" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.11.4", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.11.4.tgz", - "integrity": "sha512-tR5vJ/vBa9wFy3m5LLv2faapJLnDFxNWff2SAYkSE4rLUdbp7CdObYFgI7wK4T/Mj4UzpjPwzR8Pzmr5m7MHGA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-replace-supers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz", - "integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-simple-access": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.10.4.tgz", - "integrity": "sha512-0fMy72ej/VEvF8ULmX6yb5MtHG4uH4Dbd6I/aHDb/JVg0bbivwt9Wg+h3uMvX+QSFtwr5MeItvazbrc4jtRAXw==", - "requires": { - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz", - "integrity": "sha512-0XIdiQln4Elglgjbwo9wuJpL/K7AGCY26kmEt0+pRP0TAj4jjyNq1MjoRvikrTVqKcx4Gysxt4cXvVFXP/JO2Q==", - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "requires": { - "@babel/types": "^7.11.0" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" - }, - "@babel/helper-wrap-function": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz", - "integrity": "sha512-6py45WvEF0MhiLrdxtRjKjufwLL1/ob2qDJgg5JgNdojBAZSAKnAjkyOCNug6n+OBl4VW76XjvgSFTdaMcW0Ug==", - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/helpers": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.10.4.tgz", - "integrity": "sha512-L2gX/XeUONeEbI78dXSrJzGdz4GQ+ZTA/aazfUsFaWjSe95kiCuOZ5HsXvkiw3iwF+mFHSRUfJU8t6YavocdXA==", - "requires": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", - "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==" - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.10.5.tgz", - "integrity": "sha512-cNMCVezQbrRGvXJwm9fu/1sJj9bHdGAgKodZdLqOQIpfoH3raqmRPBM17+lh7CzhiKRRBrGtZL9WcjxSoGYUSg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz", - "integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.10.4.tgz", - "integrity": "sha512-up6oID1LeidOOASNXgv/CFbgBqTuKJ0cJjz6An5tWD+NVBNlp3VNSBxv2ZdU7SYl3NxJC7agAQDApZusV6uFwQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.10.4.tgz", - "integrity": "sha512-aNdf0LY6/3WXkhh0Fdb6Zk9j1NMD8ovj3F6r0+3j837Pn1S1PdNtcwJ5EG9WkVPNHPxyJDaxMaAOVq4eki0qbg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.10.4.tgz", - "integrity": "sha512-fCL7QF0Jo83uy1K0P2YXrfX11tj3lkpN7l4dMv9Y9VkowkhkQDwFHFd8IiwyK5MZjE8UpbgokkgtcReH88Abaw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.11.0.tgz", - "integrity": "sha512-/f8p4z+Auz0Uaf+i8Ekf1iM7wUNLcViFUGiPxKeXvxTSl63B875YPiVdUDdem7hREcI0E0kSpEhS8tF5RphK7Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.10.4.tgz", - "integrity": "sha512-wq5n1M3ZUlHl9sqT2ok1T2/MTt6AXE0e1Lz4WzWBr95LsAZ5qDXe4KnFuauYyEyLiohvXFMdbsOTMyLZs91Zlw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.10.4.tgz", - "integrity": "sha512-73/G7QoRoeNkLZFxsoCCvlg4ezE4eM+57PnOqgaPOozd5myfj7p0muD1mRVJvbUWbOzD+q3No2bWbaKy+DJ8DA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.11.0.tgz", - "integrity": "sha512-wzch41N4yztwoRw0ak+37wxwJM2oiIiy6huGCoqkvSTA9acYWcPfn9Y4aJqmFFJ70KTJUu29f3DQ43uJ9HXzEA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.10.4" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.10.4.tgz", - "integrity": "sha512-LflT6nPh+GK2MnFiKDyLiqSqVHkQnVf7hdoAvyTnnKj9xB3docGRsdPuxp6qqqW19ifK3xgc9U5/FwrSaCNX5g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.11.0.tgz", - "integrity": "sha512-v9fZIu3Y8562RRwhm1BbMRxtqZNFmFA2EG+pT2diuU8PT3H6T/KXoZ54KgYisfOFZHV6PfvAiBIZ9Rcz+/JCxA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.10.4.tgz", - "integrity": "sha512-wh5GJleuI8k3emgTg5KkJK6kHNsGEr0uBTDBuQUBJwckk9xs1ez79ioheEVVxMLyPscB0LfkbVHslQqIzWV6Bw==", - "requires": { - "@babel/helper-create-class-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.10.4.tgz", - "integrity": "sha512-H+3fOgPnEXFL9zGYtKQe4IDOPKYlZdF1kqFDQRRb8PK4B8af1vAGK04tF5iQAAsui+mHNBQSAtd2/ndEDe9wuA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.10.4.tgz", - "integrity": "sha512-GCSBF7iUle6rNugfURwNmCGG3Z/2+opxAMLs1nND4bhEG5PuxTIggDBoeYYSujAlLtsupzOHYJQgPS3pivwXIA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.10.4.tgz", - "integrity": "sha512-ni1brg4lXEmWyafKr0ccFWkJG0CeMt4WV1oyeBW6EFObF4oOHclbkj5cARxAPQyAQ2UTuplJyK4nfkXIMMFvsQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.10.4.tgz", - "integrity": "sha512-9J/oD1jV0ZCBcgnoFWFq1vJd4msoKb/TCpGNFyyLt0zABdcvgK3aYikZ8HjzB14c26bc7E3Q1yugpwGy2aTPNA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.10.4.tgz", - "integrity": "sha512-F6nREOan7J5UXTLsDsZG3DXmZSVofr2tGNwfdrVwkDWHfQckbQXnXSPfD7iO+c/2HGqycwyLST3DnZ16n+cBJQ==", - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.10.4" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.10.4.tgz", - "integrity": "sha512-WzXDarQXYYfjaV1szJvN3AD7rZgZzC1JtjJZ8dMHUyiK8mxPRahynp14zzNjU3VkPqPsO38CzxiWO1c9ARZ8JA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz", - "integrity": "sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.10.4.tgz", - "integrity": "sha512-2oZ9qLjt161dn1ZE0Ms66xBncQH4In8Sqw1YWgBUZuGVJJS5c0OFZXL6dP2MRHrkU/eKhWg8CzFJhRQl50rQxA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.10.4", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.10.4.tgz", - "integrity": "sha512-JFwVDXcP/hM/TbyzGq3l/XWGut7p46Z3QvqFMXTfk6/09m7xZHJUN9xHfsv7vqqD4YnfI5ueYdSJtXqqBLyjBw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.10.4.tgz", - "integrity": "sha512-+WmfvyfsyF603iPa6825mq6Qrb7uLjTOsa3XOFzlYcYDHSS4QmpOWOL0NNBY5qMbvrcf3tq0Cw+v4lxswOBpgA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.10.4.tgz", - "integrity": "sha512-ZEAVvUTCMlMFAbASYSVQoxIbHm2OkG2MseW6bV2JjIygOjdVv8tuxrCTzj1+Rynh7ODb8GivUy7dzEXzEhuPaA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.10.4.tgz", - "integrity": "sha512-GL0/fJnmgMclHiBTTWXNlYjYsA7rDrtsazHG6mglaGSTh0KsrW04qml+Bbz9FL0LcJIRwBWL5ZqlNHKTkU3xAA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.10.4.tgz", - "integrity": "sha512-S5HgLVgkBcRdyQAHbKj+7KyuWx8C6t5oETmUuwz1pt3WTWJhsUV0WIIXuVvfXMxl/QQyHKlSCNNtaIamG8fysw==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.10.4.tgz", - "integrity": "sha512-ItdQfAzu9AlEqmusA/65TqJ79eRcgGmpPPFvBnGILXZH975G0LNjP1yjHvGgfuCxqrPPueXOPe+FsvxmxKiHHQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.10.4.tgz", - "integrity": "sha512-OcDCq2y5+E0dVD5MagT5X+yTRbcvFjDI2ZVAottGH6tzqjx/LKpgkUepu3hp/u4tZBzxxpNGwLsAvGBvQ2mJzg==", - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.10.4.tgz", - "integrity": "sha512-Xd/dFSTEVuUWnyZiMu76/InZxLTYilOSr1UlHV+p115Z/Le2Fi1KXkJUYz0b42DfndostYlPub3m8ZTQlMaiqQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.10.4.tgz", - "integrity": "sha512-0bFOvPyAoTBhtcJLr9VcwZqKmSjFml1iVxvPL0ReomGU53CX53HsM4h2SzckNdkQcHox1bpAqzxBI1Y09LlBSw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.10.5.tgz", - "integrity": "sha512-elm5uruNio7CTLFItVC/rIzKLfQ17+fX7EVz5W0TMgIHFo1zY0Ozzx+lgwhL4plzl8OzVn6Qasx5DeEFyoNiRw==", - "requires": { - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.10.4.tgz", - "integrity": "sha512-Xj7Uq5o80HDLlW64rVfDBhao6OX89HKUmb+9vWYaLXBZOma4gA6tw4Ni1O5qVDoZWUV0fxMYA0aYzOawz0l+1w==", - "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.10.5.tgz", - "integrity": "sha512-f4RLO/OL14/FP1AEbcsWMzpbUz6tssRaeQg11RH1BP/XnPpRoVwgeYViMFacnkaw4k4wjRSjn3ip1Uw9TaXuMw==", - "requires": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.5", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.10.4.tgz", - "integrity": "sha512-mohW5q3uAEt8T45YT7Qc5ws6mWgJAaL/8BfWD9Dodo1A3RKWli8wTS+WiQ/knF+tXlPirW/1/MqzzGfCExKECA==", - "requires": { - "@babel/helper-module-transforms": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.10.4.tgz", - "integrity": "sha512-V6LuOnD31kTkxQPhKiVYzYC/Jgdq53irJC/xBSmqcNcqFGV+PER4l6rU5SH2Vl7bH9mLDHcc0+l9HUOe4RNGKA==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.10.4.tgz", - "integrity": "sha512-YXwWUDAH/J6dlfwqlWsztI2Puz1NtUAubXhOPLQ5gjR/qmQ5U96DY4FQO8At33JN4XPBhrjB8I4eMmLROjjLjw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.10.4.tgz", - "integrity": "sha512-5iTw0JkdRdJvr7sY0vHqTpnruUpTea32JHmq/atIWqsnNussbRzjEDyWep8UNztt1B5IusBYg8Irb0bLbiEBCQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.10.4" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.10.5.tgz", - "integrity": "sha512-xPHwUj5RdFV8l1wuYiu5S9fqWGM2DrYc24TMvUiRrPVm+SM3XeqU9BcokQX/kEUe+p2RBwy+yoiR1w/Blq6ubw==", - "requires": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.10.4.tgz", - "integrity": "sha512-ofsAcKiUxQ8TY4sScgsGeR2vJIsfrzqvFb9GvJ5UdXDzl+MyYCaBj/FGzXuv7qE0aJcjWMILny1epqelnFlz8g==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.10.4.tgz", - "integrity": "sha512-3thAHwtor39A7C04XucbMg17RcZ3Qppfxr22wYzZNcVIkPHfpM9J0SO8zuCV6SZa265kxBJSrfKTvDCYqBFXGw==", - "requires": { - "regenerator-transform": "^0.14.2" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.10.4.tgz", - "integrity": "sha512-hGsw1O6Rew1fkFbDImZIEqA8GoidwTAilwCyWqLBM9f+e/u/sQMQu7uX6dyokfOayRuuVfKOW4O7HvaBWM+JlQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.11.5.tgz", - "integrity": "sha512-9aIoee+EhjySZ6vY5hnLjigHzunBlscx9ANKutkeWTJTx6m5Rbq6Ic01tLvO54lSusR+BxV7u4UDdCmXv5aagg==", - "requires": { - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "resolve": "^1.8.1", - "semver": "^5.5.1" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.10.4.tgz", - "integrity": "sha512-AC2K/t7o07KeTIxMoHneyX90v3zkm5cjHJEokrPEAGEy3UCp8sLKfnfOIGdZ194fyN4wfX/zZUWT9trJZ0qc+Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.11.0.tgz", - "integrity": "sha512-UwQYGOqIdQJe4aWNyS7noqAnN2VbaczPLiEtln+zPowRNlD+79w3oi2TWfYe0eZgd+gjZCbsydN7lzWysDt+gw==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.11.0" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.10.4.tgz", - "integrity": "sha512-Ddy3QZfIbEV0VYcVtFDCjeE4xwVTJWTmUtorAJkn6u/92Z/nWJNV+mILyqHKrUxXYKA2EoCilgoPePymKL4DvQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-regex": "^7.10.4" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.10.5.tgz", - "integrity": "sha512-V/lnPGIb+KT12OQikDvgSuesRX14ck5FfJXt6+tXhdkJ+Vsd0lDCVtF6jcB4rNClYFzaB2jusZ+lNISDk2mMMw==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.10.4.tgz", - "integrity": "sha512-QqNgYwuuW0y0H+kUE/GWSR45t/ccRhe14Fs/4ZRouNNQsyd4o3PG4OtHiIrepbM2WKUBDAXKCAK/Lk4VhzTaGA==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.10.4.tgz", - "integrity": "sha512-y5XJ9waMti2J+e7ij20e+aH+fho7Wb7W8rNuu72aKRwCHFqQdhkdU2lo3uZ9tQuboEJcUFayXdARhcxLQ3+6Fg==", - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.10.4.tgz", - "integrity": "sha512-wNfsc4s8N2qnIwpO/WP2ZiSyjfpTamT2C9V9FDH/Ljub9zw6P3SjkXcFmc0RQUt96k2fmIvtla2MMjgTwIAC+A==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/preset-env": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz", - "integrity": "sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==", - "requires": { - "@babel/compat-data": "^7.11.0", - "@babel/helper-compilation-targets": "^7.10.4", - "@babel/helper-module-imports": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-proposal-async-generator-functions": "^7.10.4", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-proposal-dynamic-import": "^7.10.4", - "@babel/plugin-proposal-export-namespace-from": "^7.10.4", - "@babel/plugin-proposal-json-strings": "^7.10.4", - "@babel/plugin-proposal-logical-assignment-operators": "^7.11.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.10.4", - "@babel/plugin-proposal-numeric-separator": "^7.10.4", - "@babel/plugin-proposal-object-rest-spread": "^7.11.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.10.4", - "@babel/plugin-proposal-optional-chaining": "^7.11.0", - "@babel/plugin-proposal-private-methods": "^7.10.4", - "@babel/plugin-proposal-unicode-property-regex": "^7.10.4", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.10.4", - "@babel/plugin-transform-arrow-functions": "^7.10.4", - "@babel/plugin-transform-async-to-generator": "^7.10.4", - "@babel/plugin-transform-block-scoped-functions": "^7.10.4", - "@babel/plugin-transform-block-scoping": "^7.10.4", - "@babel/plugin-transform-classes": "^7.10.4", - "@babel/plugin-transform-computed-properties": "^7.10.4", - "@babel/plugin-transform-destructuring": "^7.10.4", - "@babel/plugin-transform-dotall-regex": "^7.10.4", - "@babel/plugin-transform-duplicate-keys": "^7.10.4", - "@babel/plugin-transform-exponentiation-operator": "^7.10.4", - "@babel/plugin-transform-for-of": "^7.10.4", - "@babel/plugin-transform-function-name": "^7.10.4", - "@babel/plugin-transform-literals": "^7.10.4", - "@babel/plugin-transform-member-expression-literals": "^7.10.4", - "@babel/plugin-transform-modules-amd": "^7.10.4", - "@babel/plugin-transform-modules-commonjs": "^7.10.4", - "@babel/plugin-transform-modules-systemjs": "^7.10.4", - "@babel/plugin-transform-modules-umd": "^7.10.4", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.10.4", - "@babel/plugin-transform-new-target": "^7.10.4", - "@babel/plugin-transform-object-super": "^7.10.4", - "@babel/plugin-transform-parameters": "^7.10.4", - "@babel/plugin-transform-property-literals": "^7.10.4", - "@babel/plugin-transform-regenerator": "^7.10.4", - "@babel/plugin-transform-reserved-words": "^7.10.4", - "@babel/plugin-transform-shorthand-properties": "^7.10.4", - "@babel/plugin-transform-spread": "^7.11.0", - "@babel/plugin-transform-sticky-regex": "^7.10.4", - "@babel/plugin-transform-template-literals": "^7.10.4", - "@babel/plugin-transform-typeof-symbol": "^7.10.4", - "@babel/plugin-transform-unicode-escapes": "^7.10.4", - "@babel/plugin-transform-unicode-regex": "^7.10.4", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.11.5", - "browserslist": "^4.12.0", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/runtime": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz", - "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/runtime-corejs3": { - "version": "7.11.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz", - "integrity": "sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A==", - "dev": true, - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "@babel/traverse": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", - "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.11.5", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.11.5", - "@babel/types": "^7.11.5", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "requires": { - "ms": "2.1.2" - } - } - } - }, - "@babel/types": { - "version": "7.11.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", - "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "@csstools/convert-colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@csstools/convert-colors/-/convert-colors-1.4.0.tgz", - "integrity": "sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw==" - }, - "@eslint/eslintrc": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", - "integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - } - } - }, - "@npmcli/move-file": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", - "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", - "requires": { - "mkdirp": "^1.0.4" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - } - } - }, - "@rails/webpacker": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@rails/webpacker/-/webpacker-5.2.1.tgz", - "integrity": "sha512-rO0kOv0o4ESB8ZnKX+b54ZKogNJGWSMULGmsJacREfm9SahKEQwXBeHNsqSGtS9NAPsU6YUFhGKRd4i/kbMNrQ==", - "requires": { - "@babel/core": "^7.11.1", - "@babel/plugin-proposal-class-properties": "^7.10.4", - "@babel/plugin-proposal-object-rest-spread": "^7.10.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.10.1", - "@babel/plugin-transform-regenerator": "^7.10.1", - "@babel/plugin-transform-runtime": "^7.11.0", - "@babel/preset-env": "^7.11.0", - "@babel/runtime": "^7.11.2", - "babel-loader": "^8.1.0", - "babel-plugin-dynamic-import-node": "^2.3.3", - "babel-plugin-macros": "^2.8.0", - "case-sensitive-paths-webpack-plugin": "^2.3.0", - "compression-webpack-plugin": "^4.0.0", - "core-js": "^3.6.5", - "css-loader": "^3.5.3", - "file-loader": "^6.0.0", - "flatted": "^3.0.4", - "glob": "^7.1.6", - "js-yaml": "^3.14.0", - "mini-css-extract-plugin": "^0.9.0", - "node-sass": "^4.14.1", - "optimize-css-assets-webpack-plugin": "^5.0.3", - "path-complete-extname": "^1.0.0", - "pnp-webpack-plugin": "^1.6.4", - "postcss-flexbugs-fixes": "^4.2.1", - "postcss-import": "^12.0.1", - "postcss-loader": "^3.0.0", - "postcss-preset-env": "^6.7.0", - "postcss-safe-parser": "^4.0.2", - "regenerator-runtime": "^0.13.7", - "sass-loader": "^8.0.2", - "style-loader": "^1.2.1", - "terser-webpack-plugin": "^4.0.0", - "webpack": "^4.44.1", - "webpack-assets-manifest": "^3.1.1", - "webpack-cli": "^3.3.12", - "webpack-sources": "^1.4.3" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - } - } - } - } - }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==" - }, - "@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", - "dev": true - }, - "@types/lodash": { - "version": "4.14.137", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.137.tgz", - "integrity": "sha512-g4rNK5SRKloO+sUGbuO7aPtwbwzMgjK+bm9BBhLD7jGUiGR7zhwYEhSln/ihgYQBeIJ5j7xjyaYzrWTcu3UotQ==", - "dev": true - }, - "@types/lodash-es": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.3.tgz", - "integrity": "sha512-iHI0i7ZAL1qepz1Y7f3EKg/zUMDwDfTzitx+AlHhJJvXwenP682ZyGbgPSc5Ej3eEAKVbNWKFuwOadCj5vBbYQ==", - "dev": true, - "requires": { - "@types/lodash": "*" - } - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, - "@types/node": { - "version": "12.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", - "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==" - }, - "@types/node-fetch": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.7.tgz", - "integrity": "sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "@types/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" - }, - "@types/pdfjs-dist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/pdfjs-dist/-/pdfjs-dist-2.1.2.tgz", - "integrity": "sha512-tUMIcX3z8M8EXA0SA4YIZcgZ6r/Rb2wbwvM9AKJhE/MPh5rEC90Sg8lMyVX6hzMaToR4jWEvN7IUA1bvoVpctA==" - }, - "@types/prop-types": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.1.tgz", - "integrity": "sha512-CFzn9idOEpHrgdw8JsoTkaDDyRWk1jrzIV8djzcgpq0y9tG4B4lFT+Nxh52DVpDXV+n4+NPNv7M1Dj5uMp6XFg==" - }, - "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" - }, - "@types/rc-slider": { - "version": "8.6.6", - "resolved": "https://registry.npmjs.org/@types/rc-slider/-/rc-slider-8.6.6.tgz", - "integrity": "sha512-2Q3vwKrSm3PbgiMNwzxMkOaMtcAGi0xQ8WPeVKoabk1vNYHiVR44DMC3mr9jC2lhbxCBgGBJWF9sBhmnSDQ8Bg==", - "dev": true, - "requires": { - "@types/rc-tooltip": "*", - "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", - "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", - "dev": true - } - } - }, - "@types/rc-tooltip": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/@types/rc-tooltip/-/rc-tooltip-3.7.1.tgz", - "integrity": "sha512-H+pW9+H42rlb3PBjcxuXhQre1ldr0gdDVbVfCj0Pf4Mdjd1plti9ekt1yJ+gGig3bRFLpCknWDmfOkAjKY+S+Q==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react": { - "version": "16.9.51", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.51.tgz", - "integrity": "sha512-lQa12IyO+DMlnSZ3+AGHRUiUcpK47aakMMoBG8f7HGxJT8Yfe+WE128HIXaHOHVPReAW0oDS3KAI0JI2DDe1PQ==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "csstype": "^3.0.2" - } - }, - "@types/react-addons-css-transition-group": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/@types/react-addons-css-transition-group/-/react-addons-css-transition-group-15.0.5.tgz", - "integrity": "sha512-UIJt5HQDOzRI7AOmnGnc2OZA0N3p7r6yMsxZ3T0+dyGPB3zWiKOPKrMkJr9tyuY3kHKPm26GyihcJKNJdMY8CQ==", - "dev": true, - "requires": { - "@types/react": "*", - "@types/react-addons-transition-group": "*" - }, - "dependencies": { - "@types/react": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", - "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", - "dev": true - } - } - }, - "@types/react-addons-transition-group": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/@types/react-addons-transition-group/-/react-addons-transition-group-15.0.4.tgz", - "integrity": "sha512-0S2cKn9OLYr6N36oRH4ybzidkgQ0UGhuvrFvU3tdktJfrx3muu7MgfIWG434wKg7rcysBEfpmQaNpGteEtx6vw==", - "dev": true, - "requires": { - "@types/react": "*" - } - }, - "@types/react-beautiful-dnd": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz", - "integrity": "sha512-by80tJ8aTTDXT256Gl+RfLRtFjYbUWOnZuEigJgNsJrSEGxvFe5eY6k3g4VIvf0M/6+xoLgfYWoWonlOo6Wqdg==", - "dev": true, - "requires": { - "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", - "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", - "dev": true - } - } - }, - "@types/react-copy-to-clipboard": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz", - "integrity": "sha512-iideNPRyroENqsOFh1i2Dv3zkviYS9r/9qD9Uh3Z9NNoAAqqa2x53i7iGndGNnJFIo20wIu7Hgh77tx1io8bgw==", - "dev": true, - "requires": { - "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", - "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", - "dev": true - } - } - }, - "@types/react-datepicker": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-3.1.1.tgz", - "integrity": "sha512-vwNrgaIMJThvvwmtnA8jSVVJZ0FNgljQrq1jDA4MtYJIDmVmd9NNrFaXf9u2JqR2nS+8Kvi8OVs/tnAbUqZhHw==", - "dev": true, - "requires": { - "@types/react": "*", - "date-fns": "^2.0.1", - "popper.js": "^1.14.1" - }, - "dependencies": { - "@types/react": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", - "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", - "dev": true - }, - "popper.js": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.15.0.tgz", - "integrity": "sha512-w010cY1oCUmI+9KwwlWki+r5jxKfTFDVoadl7MSrIujHU5MJ5OR6HTDj6Xo8aoR/QsA56x8jKjA59qGH4ELtrA==", - "dev": true - } - } - }, - "@types/react-dom": { - "version": "16.9.8", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", - "integrity": "sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA==", - "dev": true, - "requires": { - "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", - "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", - "dev": true - } - } - }, - "@types/react-paginate": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/@types/react-paginate/-/react-paginate-6.2.1.tgz", - "integrity": "sha512-+q8k1N0WzbMyOCsIEH/p5D6/KQD8dXYLzfvSvriYn//94icd2sqhAL2rWXkgwGvqHGCSTU9AoHtsWCJxPfquUQ==", - "requires": { - "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", - "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==" - } - } - }, - "@types/react-pdf": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/react-pdf/-/react-pdf-4.0.6.tgz", - "integrity": "sha512-ZmtUA31L5AaF9PilB8cJ3PuGOHIiyWcHnCir7KOux1cDjfVH6VxiN/j7CcyX98U9hwlyN81bkqxeNWNuEc0a4w==", - "requires": { - "@types/pdfjs-dist": "*", - "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", - "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==" - } - } - }, - "@types/react-textarea-autosize": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@types/react-textarea-autosize/-/react-textarea-autosize-4.3.5.tgz", - "integrity": "sha512-PiDL83kPMTolyZAWW3lyzO6ktooTb9tFTntVy7CA83/qFLWKLJ5bLeRboy6J6j3b1e8h2Eec6gBTEOOJRjV14A==", - "dev": true, - "requires": { - "@types/react": "*" - }, - "dependencies": { - "@types/react": { - "version": "16.9.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.2.tgz", - "integrity": "sha512-jYP2LWwlh+FTqGd9v7ynUKZzjj98T8x7Yclz479QdRhHfuW9yQ+0jjnD31eXSXutmBpppj5PYNLYLRfnZJvcfg==", - "dev": true, - "requires": { - "@types/prop-types": "*", - "csstype": "^2.2.0" - } - }, - "csstype": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.13.tgz", - "integrity": "sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==", - "dev": true - } - } - }, - "@types/vimeo__player": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@types/vimeo__player/-/vimeo__player-2.9.1.tgz", - "integrity": "sha512-L6XHWenPkN+WHFWmo/fhA70kTQnNUxOs3bQS3nF/FK3kv+UzEVRqPEkxJNUZWExwrhOKaTKzplZHmxYwmr0SJA==", - "dev": true - }, - "@vimeo/player": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/@vimeo/player/-/player-2.14.0.tgz", - "integrity": "sha512-hTeROVnkcyboRjetPi9tasryrjDPVsmr/73clwbYsBpSk8k5q1ygFyvkWXQShv9Rc+hvXM5RJXPd7i7Wh7dqcA==", - "requires": { - "native-promise-only": "0.8.1", - "weakmap-polyfill": "2.0.1" - } - }, - "@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "requires": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==" - }, - "@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==" - }, - "@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==" - }, - "@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "requires": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==" - }, - "@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "requires": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==" - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==" - }, - "@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==" - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "accepts": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", - "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, - "requires": { - "mime-types": "~2.1.24", - "negotiator": "0.6.2" - } - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" - }, - "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true - }, - "acorn-node": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.8.2.tgz", - "integrity": "sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==", - "requires": { - "acorn": "^7.0.0", - "acorn-walk": "^7.0.0", - "xtend": "^4.0.2" - } - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==" - }, - "add-dom-event-listener": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz", - "integrity": "sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw==", - "requires": { - "object-assign": "4.x" - } - }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.5", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", - "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" - }, - "alphanum-sort": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", - "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=" - }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" - }, - "ansi-colors": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", - "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", - "dev": true - }, - "ansi-html": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } - } - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" - }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" - }, - "array-flatten": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", - "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", - "dev": true - }, - "array-includes": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", - "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "is-string": "^1.0.5" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" - }, - "array.prototype.flat": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", - "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "array.prototype.flatmap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz", - "integrity": "sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "requires": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=" - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "requires": { - "inherits": "2.0.1" - } - } - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" - }, - "ast-types": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", - "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=" - }, - "ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - }, - "async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==" - }, - "async-foreach": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" - }, - "autoprefixer": { - "version": "7.2.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-7.2.6.tgz", - "integrity": "sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ==", - "dev": true, - "requires": { - "browserslist": "^2.11.3", - "caniuse-lite": "^1.0.30000805", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^6.0.17", - "postcss-value-parser": "^3.2.3" - }, - "dependencies": { - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" - }, - "aws4": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", - "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" - }, - "axe-core": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.5.tgz", - "integrity": "sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==", - "dev": true - }, - "axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", - "dev": true - }, - "babel-helper-builder-react-jsx": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", - "integrity": "sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA=", - "requires": { - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "esutils": "^2.0.2" - } - }, - "babel-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", - "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", - "requires": { - "find-cache-dir": "^2.1.0", - "loader-utils": "^1.4.0", - "mkdirp": "^0.5.3", - "pify": "^4.0.1", - "schema-utils": "^2.6.5" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - } - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "requires": { - "object.assign": "^4.1.0" - } - }, - "babel-plugin-macros": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", - "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", - "requires": { - "@babel/runtime": "^7.7.2", - "cosmiconfig": "^6.0.0", - "resolve": "^1.12.0" - } - }, - "babel-plugin-syntax-flow": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", - "integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=" - }, - "babel-plugin-syntax-jsx": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" - }, - "babel-plugin-transform-flow-strip-types": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", - "integrity": "sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988=", - "requires": { - "babel-plugin-syntax-flow": "^6.18.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-react-display-name": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", - "integrity": "sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE=", - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-react-jsx": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", - "integrity": "sha1-hAoCjn30YN/DotKfDA2R9jduZqM=", - "requires": { - "babel-helper-builder-react-jsx": "^6.24.1", - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-react-jsx-self": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", - "integrity": "sha1-322AqdomEqEh5t3XVYvL7PBuY24=", - "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-transform-react-jsx-source": { - "version": "6.22.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", - "integrity": "sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY=", - "requires": { - "babel-plugin-syntax-jsx": "^6.8.0", - "babel-runtime": "^6.22.0" - } - }, - "babel-preset-flow": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", - "integrity": "sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0=", - "requires": { - "babel-plugin-transform-flow-strip-types": "^6.22.0" - } - }, - "babel-preset-react": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", - "integrity": "sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A=", - "requires": { - "babel-plugin-syntax-jsx": "^6.3.13", - "babel-plugin-transform-react-display-name": "^6.23.0", - "babel-plugin-transform-react-jsx": "^6.24.1", - "babel-plugin-transform-react-jsx-self": "^6.22.0", - "babel-plugin-transform-react-jsx-source": "^6.22.0", - "babel-preset-flow": "^6.23.0" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - }, - "dependencies": { - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" - } - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "base62": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz", - "integrity": "sha1-e0F0wvlESXU7EcJlHAg9qEGnsIQ=" - }, - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" - }, - "batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" - }, - "binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==" - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "requires": { - "inherits": "~2.0.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==" - }, - "body-parser": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", - "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.7.0", - "raw-body": "2.4.0", - "type-is": "~1.6.17" - }, - "dependencies": { - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true - } - } - }, - "bonjour": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", - "dev": true, - "requires": { - "array-flatten": "^2.1.0", - "deep-equal": "^1.0.1", - "dns-equal": "^1.0.0", - "dns-txt": "^2.0.2", - "multicast-dns": "^6.0.1", - "multicast-dns-service-types": "^1.1.0" - } - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" - }, - "brace": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", - "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" - }, - "browser-pack": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.1.0.tgz", - "integrity": "sha512-erYug8XoqzU3IfcU8fUgyHqyOXqIE4tUTTQ+7mqUjQlvnXkOO6OlT9c/ZoJVHYoAaqGxr09CN53G7XIsO4KtWA==", - "requires": { - "JSONStream": "^1.0.3", - "combine-source-map": "~0.8.0", - "defined": "^1.0.0", - "safe-buffer": "^5.1.1", - "through2": "^2.0.0", - "umd": "^3.0.0" - } - }, - "browser-resolve": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-2.0.0.tgz", - "integrity": "sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==", - "requires": { - "resolve": "^1.17.0" - } - }, - "browserify": { - "version": "16.5.2", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.5.2.tgz", - "integrity": "sha512-TkOR1cQGdmXU9zW4YukWzWVSJwrxmNdADFbqbE3HFgQWe5wqZmOawqZ7J/8MPCwk/W8yY7Y0h+7mOtcZxLP23g==", - "requires": { - "JSONStream": "^1.0.3", - "assert": "^1.4.0", - "browser-pack": "^6.0.1", - "browser-resolve": "^2.0.0", - "browserify-zlib": "~0.2.0", - "buffer": "~5.2.1", - "cached-path-relative": "^1.0.0", - "concat-stream": "^1.6.0", - "console-browserify": "^1.1.0", - "constants-browserify": "~1.0.0", - "crypto-browserify": "^3.0.0", - "defined": "^1.0.0", - "deps-sort": "^2.0.0", - "domain-browser": "^1.2.0", - "duplexer2": "~0.1.2", - "events": "^2.0.0", - "glob": "^7.1.0", - "has": "^1.0.0", - "htmlescape": "^1.1.0", - "https-browserify": "^1.0.0", - "inherits": "~2.0.1", - "insert-module-globals": "^7.0.0", - "labeled-stream-splicer": "^2.0.0", - "mkdirp-classic": "^0.5.2", - "module-deps": "^6.2.3", - "os-browserify": "~0.3.0", - "parents": "^1.0.1", - "path-browserify": "~0.0.0", - "process": "~0.11.0", - "punycode": "^1.3.2", - "querystring-es3": "~0.2.0", - "read-only-stream": "^2.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.1.4", - "shasum": "^1.0.0", - "shell-quote": "^1.6.1", - "stream-browserify": "^2.0.0", - "stream-http": "^3.0.0", - "string_decoder": "^1.1.1", - "subarg": "^1.0.0", - "syntax-error": "^1.1.1", - "through2": "^2.0.0", - "timers-browserify": "^1.0.1", - "tty-browserify": "0.0.1", - "url": "~0.11.0", - "util": "~0.10.1", - "vm-browserify": "^1.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", - "requires": { - "path-parse": "^1.0.6" - } - } - } - }, - "browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "requires": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "browserify-cache-api": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/browserify-cache-api/-/browserify-cache-api-3.0.1.tgz", - "integrity": "sha1-liR+hT8Gj9bg1FzHPwuyzZd47wI=", - "requires": { - "async": "^1.5.2", - "through2": "^2.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" - } - } - }, - "browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "requires": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "requires": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "browserify-incremental": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/browserify-incremental/-/browserify-incremental-3.1.1.tgz", - "integrity": "sha1-BxPLdYckemMqnwjPG9FpuHi2Koo=", - "requires": { - "JSONStream": "^0.10.0", - "browserify-cache-api": "^3.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "JSONStream": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-0.10.0.tgz", - "integrity": "sha1-dDSdDYlSK3HzDwoD/5vSDKbxKsA=", - "requires": { - "jsonparse": "0.0.5", - "through": ">=2.2.7 <3" - } - }, - "jsonparse": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-0.0.5.tgz", - "integrity": "sha1-MwVCrT8KZUZlt3jz6y2an6UHrGQ=" - } - } - }, - "browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "requires": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "requires": { - "pako": "~1.0.5" - } - }, - "browserslist": { - "version": "4.14.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz", - "integrity": "sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==", - "requires": { - "caniuse-lite": "^1.0.30001135", - "electron-to-chromium": "^1.3.571", - "escalade": "^3.1.0", - "node-releases": "^1.1.61" - } - }, - "buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.2.1.tgz", - "integrity": "sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "buffer-indexof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", - "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", - "dev": true - }, - "buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=" - }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=" - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", - "dev": true - }, - "cacache": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", - "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", - "requires": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.0", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "cached-path-relative": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.2.tgz", - "integrity": "sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==" - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", - "requires": { - "callsites": "^2.0.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" - } - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" - } - } - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001144", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001144.tgz", - "integrity": "sha512-4GQTEWNMnVZVOFG3BK0xvGeaDAtiPAbG2N8yuMXuXzx/c2Vd4XoMPO8+E918zeXn5IF0FRVtGShBfkfQea2wHQ==" - }, - "case-sensitive-paths-webpack-plugin": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz", - "integrity": "sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ==" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "chain-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chain-function/-/chain-function-1.0.1.tgz", - "integrity": "sha512-SxltgMwL9uCko5/ZCLiyG2B7R9fY4pDZUw7hJ4MhirdjBLosoDqkWABi3XMucddHdLiFJMb7PD2MZifZriuMTg==" - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "dependencies": { - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - }, - "dependencies": { - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } - } - } - } - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" - }, - "chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "requires": { - "tslib": "^1.9.0" - } - }, - "cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "classnames": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", - "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" - }, - "cliui": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", - "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", - "requires": { - "string-width": "^3.1.0", - "strip-ansi": "^5.2.0", - "wrap-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "coa": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", - "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", - "requires": { - "@types/q": "^1.5.1", - "chalk": "^2.4.1", - "q": "^1.1.2" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", - "dev": true, - "requires": { - "clone": "^1.0.2", - "color-convert": "^1.3.0", - "color-string": "^0.3.0" - }, - "dependencies": { - "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", - "dev": true, - "requires": { - "color-name": "^1.0.0" - } - } - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - }, - "dependencies": { - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - } - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "color-string": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", - "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, - "colorette": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", - "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==" - }, - "combine-source-map": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=", - "requires": { - "convert-source-map": "~1.1.0", - "inline-source-map": "~0.6.0", - "lodash.memoize": "~3.0.3", - "source-map": "~0.5.3" - }, - "dependencies": { - "convert-source-map": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=" - }, - "lodash.memoize": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=" - } - } - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==" - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" - }, - "commoner": { - "version": "0.10.8", - "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.8.tgz", - "integrity": "sha1-NPw2cs0kOT6LtH5wyqApOBH08sU=", - "requires": { - "commander": "^2.5.0", - "detective": "^4.3.1", - "glob": "^5.0.15", - "graceful-fs": "^4.1.2", - "iconv-lite": "^0.4.5", - "mkdirp": "^0.5.0", - "private": "^0.1.6", - "q": "^1.1.2", - "recast": "^0.11.17" - }, - "dependencies": { - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==" - }, - "detective": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", - "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", - "requires": { - "acorn": "^5.2.1", - "defined": "^1.0.0" - } - }, - "glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } - }, - "component-classes": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/component-classes/-/component-classes-1.2.6.tgz", - "integrity": "sha1-xkI5TDYYpNiwuJGe/Mu9kw5c1pE=", - "requires": { - "component-indexof": "0.0.3" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" - }, - "component-indexof": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/component-indexof/-/component-indexof-0.0.3.tgz", - "integrity": "sha1-EdCRMSI5648yyPJa6csAL/6NPCQ=" - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - }, - "dependencies": { - "mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", - "dev": true - } - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "compression-webpack-plugin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-4.0.1.tgz", - "integrity": "sha512-0mg6PgwTsUe5LEcUrOu3ob32vraDx2VdbMGAT1PARcOV+UJWDYZFdkSo6RbHoGQ061mmmkC7XpRKOlvwm/gzJQ==", - "requires": { - "cacache": "^15.0.5", - "find-cache-dir": "^3.3.1", - "schema-utils": "^2.7.0", - "serialize-javascript": "^4.0.0", - "webpack-sources": "^1.4.3" - }, - "dependencies": { - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "requires": { - "find-up": "^4.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "confusing-browser-globals": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz", - "integrity": "sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw==", - "dev": true - }, - "connect-history-api-fallback": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", - "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", - "dev": true - }, - "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "requires": { - "date-now": "^0.1.4" - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=" - }, - "contains-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", - "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", - "dev": true - }, - "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" - }, - "copy-to-clipboard": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz", - "integrity": "sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w==", - "requires": { - "toggle-selection": "^1.0.6" - } - }, - "core-js": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" - }, - "core-js-compat": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", - "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", - "requires": { - "browserslist": "^4.8.5", - "semver": "7.0.0" - }, - "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } - } - }, - "core-js-pure": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", - "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cosmiconfig": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", - "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", - "requires": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.1.0", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.7.2" - }, - "dependencies": { - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" - } - } - }, - "create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "requires": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "requires": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "requires": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "create-react-context": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", - "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", - "requires": { - "gud": "^1.0.0", - "warning": "^4.0.3" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "requires": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - } - }, - "css-animation": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/css-animation/-/css-animation-1.6.1.tgz", - "integrity": "sha512-/48+/BaEaHRY6kNQ2OIPzKf9A6g8WjZYjhiNDNuIVbsm5tXCGIAsHDjB4Xu1C4vXJtUWZo26O68OQkDpNBaPog==", - "requires": { - "babel-runtime": "6.x", - "component-classes": "^1.2.5" - } - }, - "css-blank-pseudo": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", - "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", - "requires": { - "postcss": "^7.0.5" - } - }, - "css-box-model": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", - "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", - "requires": { - "tiny-invariant": "^1.0.6" - } - }, - "css-color-function": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/css-color-function/-/css-color-function-1.3.3.tgz", - "integrity": "sha1-jtJMLAIFBzM5+voAS8jBQfzLKC4=", - "dev": true, - "requires": { - "balanced-match": "0.1.0", - "color": "^0.11.0", - "debug": "^3.1.0", - "rgb": "~0.1.0" - }, - "dependencies": { - "balanced-match": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.1.0.tgz", - "integrity": "sha1-tQS9BYabOSWd0MXvw12EMXbczEo=", - "dev": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "css-color-names": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", - "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=" - }, - "css-declaration-sorter": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", - "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", - "requires": { - "postcss": "^7.0.1", - "timsort": "^0.3.0" - } - }, - "css-has-pseudo": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", - "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^5.0.0-rc.4" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "css-loader": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", - "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", - "requires": { - "camelcase": "^5.3.1", - "cssesc": "^3.0.0", - "icss-utils": "^4.1.1", - "loader-utils": "^1.2.3", - "normalize-path": "^3.0.0", - "postcss": "^7.0.32", - "postcss-modules-extract-imports": "^2.0.0", - "postcss-modules-local-by-default": "^3.0.2", - "postcss-modules-scope": "^2.2.0", - "postcss-modules-values": "^3.0.0", - "postcss-value-parser": "^4.1.0", - "schema-utils": "^2.7.0", - "semver": "^6.3.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-prefers-color-scheme": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", - "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", - "requires": { - "postcss": "^7.0.5" - } - }, - "css-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", - "integrity": "sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==", - "requires": { - "boolbase": "^1.0.0", - "css-what": "^3.2.1", - "domutils": "^1.7.0", - "nth-check": "^1.0.2" - } - }, - "css-select-base-adapter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", - "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" - }, - "css-tree": { - "version": "1.0.0-alpha.37", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", - "integrity": "sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==", - "requires": { - "mdn-data": "2.0.4", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "css-unit-converter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz", - "integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==", - "dev": true - }, - "css-what": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-3.4.1.tgz", - "integrity": "sha512-wHOppVDKl4vTAOWzJt5Ek37Sgd9qq1Bmj/T1OjvicWbU5W7ru7Pqbn0Jdqii3Drx/h+dixHKXNhZYx7blthL7g==" - }, - "cssdb": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.4.0.tgz", - "integrity": "sha512-LsTAR1JPEM9TpGhl/0p3nQecC2LJ0kD8X5YARu1hk/9I1gril5vDtMZyNxcEpxxDj34YNck/ucjuoUd66K03oQ==" - }, - "cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==" - }, - "cssnano": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz", - "integrity": "sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==", - "requires": { - "cosmiconfig": "^5.0.0", - "cssnano-preset-default": "^4.0.7", - "is-resolvable": "^1.0.0", - "postcss": "^7.0.0" - }, - "dependencies": { - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - } - } - }, - "cssnano-preset-default": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz", - "integrity": "sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==", - "requires": { - "css-declaration-sorter": "^4.0.1", - "cssnano-util-raw-cache": "^4.0.1", - "postcss": "^7.0.0", - "postcss-calc": "^7.0.1", - "postcss-colormin": "^4.0.3", - "postcss-convert-values": "^4.0.1", - "postcss-discard-comments": "^4.0.2", - "postcss-discard-duplicates": "^4.0.2", - "postcss-discard-empty": "^4.0.1", - "postcss-discard-overridden": "^4.0.1", - "postcss-merge-longhand": "^4.0.11", - "postcss-merge-rules": "^4.0.3", - "postcss-minify-font-values": "^4.0.2", - "postcss-minify-gradients": "^4.0.2", - "postcss-minify-params": "^4.0.2", - "postcss-minify-selectors": "^4.0.2", - "postcss-normalize-charset": "^4.0.1", - "postcss-normalize-display-values": "^4.0.2", - "postcss-normalize-positions": "^4.0.2", - "postcss-normalize-repeat-style": "^4.0.2", - "postcss-normalize-string": "^4.0.2", - "postcss-normalize-timing-functions": "^4.0.2", - "postcss-normalize-unicode": "^4.0.1", - "postcss-normalize-url": "^4.0.1", - "postcss-normalize-whitespace": "^4.0.2", - "postcss-ordered-values": "^4.1.2", - "postcss-reduce-initial": "^4.0.3", - "postcss-reduce-transforms": "^4.0.2", - "postcss-svgo": "^4.0.2", - "postcss-unique-selectors": "^4.0.1" - }, - "dependencies": { - "postcss-calc": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.5.tgz", - "integrity": "sha512-1tKHutbGtLtEZF6PT4JSihCHfIVldU72mZ8SdZHIYriIZ9fh9k9aWSppaT8rHsyI3dX+KSR+W+Ix9BMY3AODrg==", - "requires": { - "postcss": "^7.0.27", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.0.2" - }, - "dependencies": { - "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - } - } - }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "cssnano-util-get-arguments": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", - "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=" - }, - "cssnano-util-get-match": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", - "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=" - }, - "cssnano-util-raw-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", - "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", - "requires": { - "postcss": "^7.0.0" - } - }, - "cssnano-util-same-parent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", - "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==" - }, - "csso": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.0.3.tgz", - "integrity": "sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==", - "requires": { - "css-tree": "1.0.0-alpha.39" - }, - "dependencies": { - "css-tree": { - "version": "1.0.0-alpha.39", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.39.tgz", - "integrity": "sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==", - "requires": { - "mdn-data": "2.0.6", - "source-map": "^0.6.1" - } - }, - "mdn-data": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.6.tgz", - "integrity": "sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "csstype": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.3.tgz", - "integrity": "sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==", - "dev": true - }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "requires": { - "array-find-index": "^1.0.1" - } - }, - "cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=" - }, - "damerau-levenshtein": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", - "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", - "dev": true - }, - "dash-ast": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dash-ast/-/dash-ast-1.0.0.tgz", - "integrity": "sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "date-fns": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz", - "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ==" - }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-gateway": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", - "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "ip-regex": "^2.1.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" - }, - "del": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", - "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", - "dev": true, - "requires": { - "@types/glob": "^7.1.1", - "globby": "^6.1.0", - "is-path-cwd": "^2.0.0", - "is-path-in-cwd": "^2.0.0", - "p-map": "^2.0.0", - "pify": "^4.0.1", - "rimraf": "^2.6.3" - }, - "dependencies": { - "p-map": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", - "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", - "dev": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true - }, - "deps-sort": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", - "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=", - "requires": { - "JSONStream": "^1.0.3", - "shasum": "^1.0.0", - "subarg": "^1.0.0", - "through2": "^2.0.0" - } - }, - "des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "requires": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true - }, - "detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" - }, - "detect-node": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz", - "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", - "dev": true - }, - "detective": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-5.2.0.tgz", - "integrity": "sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==", - "requires": { - "acorn-node": "^1.6.1", - "defined": "^1.0.0", - "minimist": "^1.1.1" - } - }, - "diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "requires": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "dns-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", - "dev": true - }, - "dns-packet": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz", - "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==", - "dev": true, - "requires": { - "ip": "^1.1.0", - "safe-buffer": "^5.0.1" - } - }, - "dns-txt": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", - "dev": true, - "requires": { - "buffer-indexof": "^1.0.0" - } - }, - "doctrine": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", - "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", - "dev": true, - "requires": { - "esutils": "^2.0.2", - "isarray": "^1.0.0" - } - }, - "dom-align": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/dom-align/-/dom-align-1.12.0.tgz", - "integrity": "sha512-YkoezQuhp3SLFGdOlr5xkqZ640iXrnHAwVYcDg8ZKRUtO7mSzSC2BA5V0VuyAwPSJA4CLIc6EDDJh4bEsD2+zA==" - }, - "dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", - "requires": { - "@babel/runtime": "^7.1.2" - } - }, - "dom-serializer": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", - "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", - "requires": { - "domelementtype": "^2.0.1", - "entities": "^2.0.0" - }, - "dependencies": { - "domelementtype": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.2.tgz", - "integrity": "sha512-wFwTwCVebUrMgGeAwRL/NhZtHAUyT9n9yg4IMDwf10+6iCMxSkVq9MGCVEH+QZWo1nNidy8kNvwmv4zWHDTqvA==" - } - } - }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==" - }, - "domelementtype": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", - "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" - }, - "domutils": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", - "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", - "requires": { - "dom-serializer": "0", - "domelementtype": "1" - } - }, - "dot-prop": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", - "requires": { - "is-obj": "^2.0.0" - } - }, - "dropzone": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.7.2.tgz", - "integrity": "sha512-m217bJHtf0J1IiKn4Tv6mnu1h5QvQNBnKZ39gma7hzGQhIZMxYq1vYEHs4AVd4ThFwmALys+52NAOD4zdLTG4w==" - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=", - "requires": { - "readable-stream": "^2.0.2" - } - }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true - }, - "electron-to-chromium": { - "version": "1.3.578", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.578.tgz", - "integrity": "sha512-z4gU6dA1CbBJsAErW5swTGAaU2TBzc2mPAonJb00zqW1rOraDo2zfBMDRvaz9cVic+0JEZiYbHWPw/fTaZlG2Q==" - }, - "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", - "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "email-addresses": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", - "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==" - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "requires": { - "once": "^1.4.0" - } - }, - "enhanced-resolve": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", - "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - } - } - }, - "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - } - } - }, - "entities": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz", - "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==" - }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "requires": { - "prr": "~1.0.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - }, - "dependencies": { - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escalade": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", - "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==" - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "eslint": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.10.0.tgz", - "integrity": "sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.1.3", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^1.3.0", - "espree": "^7.3.0", - "esquery": "^1.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "eslint-config-airbnb": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz", - "integrity": "sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg==", - "dev": true, - "requires": { - "eslint-config-airbnb-base": "^14.2.0", - "object.assign": "^4.1.0", - "object.entries": "^1.1.2" - } - }, - "eslint-config-airbnb-base": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz", - "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==", - "dev": true, - "requires": { - "confusing-browser-globals": "^1.0.9", - "object.assign": "^4.1.0", - "object.entries": "^1.1.2" - } - }, - "eslint-import-resolver-node": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", - "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "resolve": "^1.13.1" - } - }, - "eslint-module-utils": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", - "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "pkg-dir": "^2.0.0" - }, - "dependencies": { - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - } - } - }, - "eslint-plugin-import": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz", - "integrity": "sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw==", - "dev": true, - "requires": { - "array-includes": "^3.1.1", - "array.prototype.flat": "^1.2.3", - "contains-path": "^0.1.0", - "debug": "^2.6.9", - "doctrine": "1.5.0", - "eslint-import-resolver-node": "^0.3.4", - "eslint-module-utils": "^2.6.0", - "has": "^1.0.3", - "minimatch": "^3.0.4", - "object.values": "^1.1.1", - "read-pkg-up": "^2.0.0", - "resolve": "^1.17.0", - "tsconfig-paths": "^3.9.0" - } - }, - "eslint-plugin-jsx-a11y": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz", - "integrity": "sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g==", - "dev": true, - "requires": { - "@babel/runtime": "^7.10.2", - "aria-query": "^4.2.2", - "array-includes": "^3.1.1", - "ast-types-flow": "^0.0.7", - "axe-core": "^3.5.4", - "axobject-query": "^2.1.2", - "damerau-levenshtein": "^1.0.6", - "emoji-regex": "^9.0.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.4.1", - "language-tags": "^1.0.5" - }, - "dependencies": { - "emoji-regex": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.0.0.tgz", - "integrity": "sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w==", - "dev": true - } - } - }, - "eslint-plugin-promise": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", - "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", - "dev": true - }, - "eslint-plugin-react": { - "version": "7.21.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.21.3.tgz", - "integrity": "sha512-OI4GwTCqyIb4ipaOEGLWdaOHCXZZydStAsBEPB2e1ZfNM37bojpgO1BoOQbFb0eLVz3QLDx7b+6kYcrxCuJfhw==", - "dev": true, - "requires": { - "array-includes": "^3.1.1", - "array.prototype.flatmap": "^1.2.3", - "doctrine": "^2.1.0", - "has": "^1.0.3", - "jsx-ast-utils": "^2.4.1", - "object.entries": "^1.1.2", - "object.fromentries": "^2.0.2", - "object.values": "^1.1.1", - "prop-types": "^15.7.2", - "resolve": "^1.17.0", - "string.prototype.matchall": "^4.0.2" - }, - "dependencies": { - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - } - } - }, - "eslint-plugin-standard": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", - "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", - "dev": true - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true - }, - "espree": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", - "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", - "dev": true, - "requires": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.3.0" - } - }, - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" - }, - "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==" - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "events": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/events/-/events-2.1.0.tgz", - "integrity": "sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg==" - }, - "eventsource": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz", - "integrity": "sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ==", - "dev": true, - "requires": { - "original": "^1.0.0" - } - }, - "evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "requires": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", - "requires": { - "homedir-polyfill": "^1.0.1" - } - }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", - "dev": true, - "requires": { - "accepts": "~1.3.7", - "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "faye-websocket": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", - "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==" - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "file-loader": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.1.0.tgz", - "integrity": "sha512-26qPdHyTsArQ6gU4P1HJbAbnFTyT2r0pG7czh1GFAd9TZbj0n94wWbupgixZH/ET/meqi2/5+F7DhW4OAXD+Lg==", - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^2.7.1" - }, - "dependencies": { - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - } - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, - "filename-reserved-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz", - "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=" - }, - "filenamify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz", - "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=", - "requires": { - "filename-reserved-regex": "^1.0.0", - "strip-outer": "^1.0.0", - "trim-repeated": "^1.0.0" - } - }, - "filenamify-url": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz", - "integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=", - "requires": { - "filenamify": "^1.0.0", - "humanize-url": "^1.0.0" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "findup-sync": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", - "integrity": "sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg==", - "requires": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "dependencies": { - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==" - }, - "flatten": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", - "integrity": "sha1-2uRqnXj74lKSJYzB54CkHZXAN4I=" - }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "follow-redirects": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", - "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", - "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", - "dev": true - }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "requires": { - "minipass": "^3.0.0" - } - }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "optional": true - }, - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "requires": { - "globule": "^1.0.0" - } - }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" - }, - "get-assigned-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-assigned-identifiers/-/get-assigned-identifiers-1.2.0.tgz", - "integrity": "sha512-mBBwmeGTrxEMO4pMaaf/uUEFHnYtwr8FTe8Y/mer4rcV/bye0qGm6pw1bGZFGStxC5O76c5ZAVBGnqHmOaJpdQ==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" - } - }, - "gh-pages": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-2.2.0.tgz", - "integrity": "sha512-c+yPkNOPMFGNisYg9r4qvsMIjVYikJv7ImFOhPIVPt0+AcRUamZ7zkGRLHz7FKB0xrlZ+ddSOJsZv9XAFVXLmA==", - "requires": { - "async": "^2.6.1", - "commander": "^2.18.0", - "email-addresses": "^3.0.1", - "filenamify-url": "^1.0.0", - "fs-extra": "^8.1.0", - "globby": "^6.1.0" - } - }, - "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "requires": { - "is-glob": "^4.0.1" - } - }, - "global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "requires": { - "global-prefix": "^3.0.0" - }, - "dependencies": { - "global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "requires": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - } - } - } - }, - "global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", - "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - } - }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "globule": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.2.tgz", - "integrity": "sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA==", - "requires": { - "glob": "~7.1.1", - "lodash": "~4.17.10", - "minimatch": "~3.0.2" - } - }, - "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==" - }, - "gud": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", - "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" - }, - "handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "hex-color-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", - "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" - }, - "highlightjs": { - "version": "9.16.2", - "resolved": "https://registry.npmjs.org/highlightjs/-/highlightjs-9.16.2.tgz", - "integrity": "sha512-FK1vmMj8BbEipEy8DLIvp71t5UsC7n2D6En/UfM/91PCwmOpj6f2iu0Y0coRC62KSRHHC+dquM2xMULV/X7NFg==" - }, - "hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "requires": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "requires": { - "react-is": "^16.7.0" - } - }, - "homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "requires": { - "parse-passwd": "^1.0.0" - } - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" - }, - "hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "hsl-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", - "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=" - }, - "hsla-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", - "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=" - }, - "html-comment-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", - "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==" - }, - "html-entities": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.3.1.tgz", - "integrity": "sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA==", - "dev": true - }, - "htmlescape": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=" - }, - "http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", - "dev": true - }, - "http-errors": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", - "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } - } - }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-middleware": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz", - "integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==", - "dev": true, - "requires": { - "http-proxy": "^1.17.0", - "is-glob": "^4.0.0", - "lodash": "^4.17.11", - "micromatch": "^3.1.10" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" - }, - "humanize-url": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz", - "integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=", - "requires": { - "normalize-url": "^1.0.0", - "strip-url-auth": "^1.0.0" - } - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "icss-utils": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", - "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", - "requires": { - "postcss": "^7.0.14" - } - }, - "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" - }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "immutability-helper": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-3.1.1.tgz", - "integrity": "sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ==" - }, - "import-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", - "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=", - "requires": { - "import-from": "^2.1.0" - } - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" - } - } - }, - "import-from": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", - "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=", - "requires": { - "resolve-from": "^3.0.0" - } - }, - "import-local": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", - "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", - "requires": { - "pkg-dir": "^3.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, - "in-publish": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz", - "integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ==" - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" - }, - "indexes-of": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" - }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "inline-source-map": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=", - "requires": { - "source-map": "~0.5.3" - } - }, - "insert-module-globals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.2.0.tgz", - "integrity": "sha512-VE6NlW+WGn2/AeOMd496AHFYmE7eLKkUY6Ty31k4og5vmA3Fjuwe9v6ifH6Xx/Hz27QvdoMoviw1/pqWRB09Sw==", - "requires": { - "JSONStream": "^1.0.3", - "acorn-node": "^1.5.2", - "combine-source-map": "^0.8.0", - "concat-stream": "^1.6.1", - "is-buffer": "^1.1.0", - "path-is-absolute": "^1.0.1", - "process": "~0.11.0", - "through2": "^2.0.0", - "undeclared-identifiers": "^1.1.2", - "xtend": "^4.0.0" - } - }, - "internal-ip": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", - "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", - "dev": true, - "requires": { - "default-gateway": "^4.2.0", - "ipaddr.js": "^1.9.0" - } - }, - "internal-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", - "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", - "dev": true, - "requires": { - "es-abstract": "^1.17.0-next.1", - "has": "^1.0.3", - "side-channel": "^1.0.2" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", - "dev": true - }, - "ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true - }, - "is-absolute-url": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz", - "integrity": "sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-arguments": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", - "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "requires": { - "binary-extensions": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" - }, - "is-color-stop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", - "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", - "requires": { - "css-color-names": "^0.0.4", - "hex-color-regex": "^1.1.0", - "hsl-regex": "^1.0.0", - "hsla-regex": "^1.0.0", - "rgb-regex": "^1.0.1", - "rgba-regex": "^1.0.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" - } - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-negative-zero": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.0.tgz", - "integrity": "sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE=" - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" - }, - "is-path-cwd": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", - "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", - "dev": true - }, - "is-path-in-cwd": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", - "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", - "dev": true, - "requires": { - "is-path-inside": "^2.1.0" - } - }, - "is-path-inside": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", - "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", - "dev": true, - "requires": { - "path-is-inside": "^1.0.2" - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "requires": { - "isobject": "^3.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-string": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", - "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", - "dev": true - }, - "is-svg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", - "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", - "requires": { - "html-comment-regex": "^1.1.0" - } - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isnumeric": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/isnumeric/-/isnumeric-0.2.0.tgz", - "integrity": "sha1-ojR7o2DeGeM9D/1ZD933dVy/LmQ=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "jest-worker": { - "version": "26.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", - "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-base64": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", - "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==" - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "dependencies": { - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - } - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" - }, - "json-stable-stringify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", - "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=", - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "json3": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", - "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", - "dev": true - }, - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "requires": { - "minimist": "^1.2.0" - } - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=" - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "jstransform": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz", - "integrity": "sha1-tMSb9j8WLBCLA0g5moc3xxOwqDo=", - "requires": { - "base62": "0.1.1", - "esprima-fb": "13001.1001.0-dev-harmony-fb", - "source-map": "0.1.31" - }, - "dependencies": { - "esprima-fb": { - "version": "13001.1001.0-dev-harmony-fb", - "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-13001.1001.0-dev-harmony-fb.tgz", - "integrity": "sha1-YzrNtA2b1NuKHB1owGqUKVn60rA=" - }, - "source-map": { - "version": "0.1.31", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz", - "integrity": "sha1-n3BNDWnZ4TioG63267T94z0VHGE=", - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, - "jsx-ast-utils": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", - "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", - "dev": true, - "requires": { - "array-includes": "^3.1.1", - "object.assign": "^4.1.0" - } - }, - "killable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", - "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" - }, - "labeled-stream-splicer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz", - "integrity": "sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==", - "requires": { - "inherits": "^2.0.1", - "stream-splicer": "^2.0.0" - } - }, - "language-subtag-registry": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz", - "integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==", - "dev": true - }, - "language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", - "dev": true, - "requires": { - "language-subtag-registry": "~0.3.2" - } - }, - "last-call-webpack-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz", - "integrity": "sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w==", - "requires": { - "lodash": "^4.17.5", - "webpack-sources": "^1.1.0" - } - }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" - }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "requires": { - "leven": "^3.1.0" - } - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==" - }, - "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "lodash-es": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz", - "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ==" - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" - }, - "lodash.has": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" - }, - "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" - } - }, - "lodash.templatesettings": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", - "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", - "requires": { - "lodash._reinterpolate": "^3.0.0" - } - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" - }, - "loglevel": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.7.0.tgz", - "integrity": "sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-cancellable-promise": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-1.0.0.tgz", - "integrity": "sha512-+YO6Grg2uy/z8Mv3uV90OP6yAUHIF43YGgEFbejmBrK9VWFsVO6DvzFMcopXr9wCNg3/QIltIKiSCROC7zFB2g==" - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - } - } - }, - "make-event-props": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.2.0.tgz", - "integrity": "sha512-BmWFkm/jZzVH9A0tEBdkjAARUz/eha+5IRyfOndeSMKRadkgR5DawoBHoRwLxkYmjJOI5bHkXKpaZocxj+dKgg==" - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "requires": { - "object-visit": "^1.0.0" - } - }, - "marked": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-1.2.0.tgz", - "integrity": "sha512-tiRxakgbNPBr301ihe/785NntvYyhxlqcL3YaC8CaxJQh7kiaEtrN9B/eK2I2943Yjkh5gw25chYFDQhOMCwMA==" - }, - "math-expression-evaluator": { - "version": "1.2.22", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.22.tgz", - "integrity": "sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ==", - "dev": true - }, - "md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "mdn-data": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", - "integrity": "sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==" - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true - }, - "memoize-one": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", - "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" - }, - "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - } - } - }, - "merge-class-names": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/merge-class-names/-/merge-class-names-1.3.0.tgz", - "integrity": "sha512-k0Qaj36VBpKgdc8c188LEZvo6v/zzry/FUufwopWbMSp6/knfVFU/KIB55/hJjeIpg18IH2WskXJCRnM/1BrdQ==" - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "requires": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true - }, - "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" - }, - "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", - "requires": { - "mime-db": "1.40.0" - } - }, - "mini-css-extract-plugin": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.9.0.tgz", - "integrity": "sha512-lp3GeY7ygcgAmVIcRPBVhIkf8Us7FZjA+ILpal44qLdSu11wmjKQ3d9k15lfD7pO4esu9eUIAW7qiYIBppv40A==", - "requires": { - "loader-utils": "^1.1.0", - "normalize-url": "1.9.1", - "schema-utils": "^1.0.0", - "webpack-sources": "^1.1.0" - }, - "dependencies": { - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" - }, - "minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "requires": { - "yallist": "^4.0.0" - } - }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "requires": { - "minipass": "^3.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" - }, - "module-deps": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.2.3.tgz", - "integrity": "sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==", - "requires": { - "JSONStream": "^1.0.3", - "browser-resolve": "^2.0.0", - "cached-path-relative": "^1.0.2", - "concat-stream": "~1.6.0", - "defined": "^1.0.0", - "detective": "^5.2.0", - "duplexer2": "^0.1.2", - "inherits": "^2.0.1", - "parents": "^1.0.0", - "readable-stream": "^2.0.2", - "resolve": "^1.4.0", - "stream-combiner2": "^1.1.1", - "subarg": "^1.0.0", - "through2": "^2.0.0", - "xtend": "^4.0.0" - } - }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" - }, - "moment-timezone": { - "version": "0.5.31", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.31.tgz", - "integrity": "sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA==", - "requires": { - "moment": ">= 2.9.0" - }, - "dependencies": { - "moment": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", - "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" - } - } - }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "multicast-dns": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", - "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", - "dev": true, - "requires": { - "dns-packet": "^1.3.1", - "thunky": "^1.0.2" - } - }, - "multicast-dns-service-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", - "dev": true - }, - "nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==" - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=" - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "negotiator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node-ensure": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", - "integrity": "sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=" - }, - "node-forge": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", - "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", - "dev": true - }, - "node-gyp": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", - "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", - "requires": { - "fstream": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "osenv": "0", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", - "tar": "^2.0.0", - "which": "1" - }, - "dependencies": { - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" - }, - "tar": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", - "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", - "requires": { - "block-stream": "*", - "fstream": "^1.0.12", - "inherits": "2" - } - } - } - }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - }, - "dependencies": { - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "events": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", - "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==" - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "timers-browserify": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.11.tgz", - "integrity": "sha512-60aV6sgJ5YEbzUdn9c8kYGIqOubPoUdqQCul3SBAsRCZ40s6Y5cMcrW4dt3/k/EsbLVJNl9n6Vz3fTc+k2GeKQ==", - "requires": { - "setimmediate": "^1.0.4" - } - }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=" - }, - "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - } - } - }, - "node-releases": { - "version": "1.1.61", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", - "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==" - }, - "node-sass": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.14.1.tgz", - "integrity": "sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g==", - "requires": { - "async-foreach": "^0.1.3", - "chalk": "^1.1.1", - "cross-spawn": "^3.0.0", - "gaze": "^1.0.0", - "get-stdin": "^4.0.1", - "glob": "^7.0.3", - "in-publish": "^2.0.0", - "lodash": "^4.17.15", - "meow": "^3.7.0", - "mkdirp": "^0.5.1", - "nan": "^2.13.2", - "node-gyp": "^3.8.0", - "npmlog": "^4.0.0", - "request": "^2.88.0", - "sass-graph": "2.2.5", - "stdout-stream": "^1.4.0", - "true-case-path": "^1.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cross-spawn": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", - "requires": { - "lru-cache": "^4.0.1", - "which": "^1.2.9" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - } - } - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" - }, - "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" - }, - "object-is": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.3.tgz", - "integrity": "sha512-teyqLvFWzLkq5B9ki8FVWA902UER2qkxmdA4nLf+wjOLAWgxzCWZNCxpDq9MvE8MmhWNr+I8w3BN49Vx36Y6Xg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.1.tgz", - "integrity": "sha512-VT/cxmx5yaoHSOTSyrCygIDFco+RsibY2NM0a4RdEeY/4KgqezwFtK1yr3U67xYhqJSlASm2pKhLVzPj2lr4bA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.0", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.entries": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", - "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5", - "has": "^1.0.3" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "object.fromentries": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", - "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", - "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", - "has": "^1.0.3" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "objectify-array": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/objectify-array/-/objectify-array-2.1.0.tgz", - "integrity": "sha512-tG8ndq75CyLdsVSB5e3Xp6ajVi0oC3LsR0lMiGx3imtYWrGNnpdPzP/Tv3UQsRO2OCvpqUedQyZAv20OrlK2WA==" - }, - "obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onecolor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/onecolor/-/onecolor-3.1.0.tgz", - "integrity": "sha512-YZSypViXzu3ul5LMu/m6XjJ9ol8qAy9S2VjHl5E6UlhUH1KGKWabyEJifn0Jjpw23bYDzC2ucKMPGiH5kfwSGQ==", - "dev": true - }, - "opn": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", - "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", - "dev": true, - "requires": { - "is-wsl": "^1.1.0" - } - }, - "optimize-css-assets-webpack-plugin": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz", - "integrity": "sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A==", - "requires": { - "cssnano": "^4.1.10", - "last-call-webpack-plugin": "^3.0.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "original": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", - "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==", - "dev": true, - "requires": { - "url-parse": "^1.4.3" - } - }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=" - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - }, - "dependencies": { - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - } - } - }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-retry": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-3.0.1.tgz", - "integrity": "sha512-XE6G4+YTTkT2a0UWb2kjZe8xNwf8bIbnqpc/IS/idOBVhyves0mK5OJgeocjx7q5pvX/6m23xuzVPYT1uGM73w==", - "dev": true, - "requires": { - "retry": "^0.12.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" - }, - "parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "requires": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "requires": { - "callsites": "^3.0.0" - } - }, - "parents": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=", - "requires": { - "path-platform": "~0.11.15" - } - }, - "parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "requires": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" - }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==" - }, - "path-complete-extname": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/path-complete-extname/-/path-complete-extname-1.0.0.tgz", - "integrity": "sha512-CVjiWcMRdGU8ubs08YQVzhutOR5DEfO97ipRIlOGMK5Bek5nQySknBpuxVAVJ36hseTNs+vdIcv57ZrWxH7zvg==" - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-platform": { - "version": "0.11.15", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } - }, - "pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", - "requires": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "pdfjs-dist": { - "version": "2.1.266", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.1.266.tgz", - "integrity": "sha512-Jy7o1wE3NezPxozexSbq4ltuLT0Z21ew/qrEiAEeUZzHxMHGk4DUV1D7RuCXg5vJDvHmjX1YssN+we9QfRRgXQ==", - "requires": { - "node-ensure": "^0.0.0", - "worker-loader": "^2.0.0" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "pixrem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pixrem/-/pixrem-4.0.1.tgz", - "integrity": "sha1-LaSh3m7EQjxfw3lOkwuB1EkOxoY=", - "dev": true, - "requires": { - "browserslist": "^2.0.0", - "postcss": "^6.0.0", - "reduce-css-calc": "^1.2.7" - }, - "dependencies": { - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "requires": { - "find-up": "^3.0.0" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - } - } - }, - "pleeease-filters": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pleeease-filters/-/pleeease-filters-4.0.0.tgz", - "integrity": "sha1-ZjKy+wVkjSdY2GU4T7zteeHMrsc=", - "dev": true, - "requires": { - "onecolor": "^3.0.4", - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "pnp-webpack-plugin": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz", - "integrity": "sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg==", - "requires": { - "ts-pnp": "^1.1.6" - } - }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" - }, - "portfinder": { - "version": "1.0.28", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", - "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", - "dev": true, - "requires": { - "async": "^2.6.2", - "debug": "^3.1.1", - "mkdirp": "^0.5.5" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" - }, - "postcss": { - "version": "7.0.17", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.17.tgz", - "integrity": "sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-apply": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/postcss-apply/-/postcss-apply-0.8.0.tgz", - "integrity": "sha1-FOVEu7XLbxweBIhXll15rgZrE0M=", - "dev": true, - "requires": { - "babel-runtime": "^6.23.0", - "balanced-match": "^0.4.2", - "postcss": "^6.0.0" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-attribute-case-insensitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-2.0.0.tgz", - "integrity": "sha1-lNxCLI+QmX8WvTOjZUu77AhJY7Q=", - "dev": true, - "requires": { - "postcss": "^6.0.0", - "postcss-selector-parser": "^2.2.3" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-calc": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-6.0.2.tgz", - "integrity": "sha512-fiznXjEN5T42Qm7qqMCVJXS3roaj9r4xsSi+meaBVe7CJBl8t/QLOXu02Z2E6oWAMWIvCuF6JrvzFekmVEbOKA==", - "dev": true, - "requires": { - "css-unit-converter": "^1.1.1", - "postcss": "^7.0.2", - "postcss-selector-parser": "^2.2.2", - "reduce-css-calc": "^2.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "reduce-css-calc": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz", - "integrity": "sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA==", - "dev": true, - "requires": { - "css-unit-converter": "^1.1.1", - "postcss-value-parser": "^3.3.0" - } - } - } - }, - "postcss-color-function": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-color-function/-/postcss-color-function-4.1.0.tgz", - "integrity": "sha512-2/fuv6mP5Lt03XbRpVfMdGC8lRP1sykme+H1bR4ARyOmSMB8LPSjcL6EAI1iX6dqUF+jNEvKIVVXhan1w/oFDQ==", - "dev": true, - "requires": { - "css-color-function": "~1.3.3", - "postcss": "^6.0.23", - "postcss-message-helpers": "^2.0.0", - "postcss-value-parser": "^3.3.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-color-functional-notation": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", - "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-color-gray": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-4.1.0.tgz", - "integrity": "sha512-L4iLKQLdqChz6ZOgGb6dRxkBNw78JFYcJmBz1orHpZoeLtuhDDGegRtX9gSyfoCIM7rWZ3VNOyiqqvk83BEN+w==", - "dev": true, - "requires": { - "color": "^2.0.1", - "postcss": "^6.0.14", - "postcss-message-helpers": "^2.0.0", - "reduce-function-call": "^1.0.2" - }, - "dependencies": { - "color": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color/-/color-2.0.1.tgz", - "integrity": "sha512-ubUCVVKfT7r2w2D3qtHakj8mbmKms+tThR8gI8zEYCbUBl8/voqFGt3kgBqGwXAopgXybnkuOq+qMYCRrp4cXw==", - "dev": true, - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-color-hex-alpha": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-3.0.0.tgz", - "integrity": "sha1-HlPmyKyyN5Vej9CLfs2xuLgwn5U=", - "dev": true, - "requires": { - "color": "^1.0.3", - "postcss": "^6.0.1", - "postcss-message-helpers": "^2.0.0" - }, - "dependencies": { - "color": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", - "integrity": "sha1-5I6DLYXxTvaU+0aIEcLVz+cptV0=", - "dev": true, - "requires": { - "color-convert": "^1.8.2", - "color-string": "^1.4.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-color-hsl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hsl/-/postcss-color-hsl-2.0.0.tgz", - "integrity": "sha1-EnA2ZvoxBDDj8wpFTawThjF9WEQ=", - "dev": true, - "requires": { - "postcss": "^6.0.1", - "postcss-value-parser": "^3.3.0", - "units-css": "^0.4.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-color-hwb": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hwb/-/postcss-color-hwb-3.0.0.tgz", - "integrity": "sha1-NAKxnvTYSXVAwftQcr6YY8qVVx4=", - "dev": true, - "requires": { - "color": "^1.0.3", - "postcss": "^6.0.1", - "postcss-message-helpers": "^2.0.0", - "reduce-function-call": "^1.0.2" - }, - "dependencies": { - "color": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", - "integrity": "sha1-5I6DLYXxTvaU+0aIEcLVz+cptV0=", - "dev": true, - "requires": { - "color-convert": "^1.8.2", - "color-string": "^1.4.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-color-mod-function": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", - "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", - "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-color-rebeccapurple": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-3.1.0.tgz", - "integrity": "sha512-212hJUk9uSsbwO5ECqVjmh/iLsmiVL1xy9ce9TVf+X3cK/ZlUIlaMdoxje/YpsL9cmUH3I7io+/G2LyWx5rg1g==", - "dev": true, - "requires": { - "postcss": "^6.0.22", - "postcss-values-parser": "^1.5.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "postcss-values-parser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-1.5.0.tgz", - "integrity": "sha512-3M3p+2gMp0AH3da530TlX8kiO1nxdTnc3C6vr8dMxRLIlh8UYkz0/wcwptSXjhtx2Fr0TySI7a+BHDQ8NL7LaQ==", - "dev": true, - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-color-rgb": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rgb/-/postcss-color-rgb-2.0.0.tgz", - "integrity": "sha1-FFOcinExSUtILg3RzCZf9lFLUmM=", - "dev": true, - "requires": { - "postcss": "^6.0.1", - "postcss-value-parser": "^3.3.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-color-rgba-fallback": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rgba-fallback/-/postcss-color-rgba-fallback-3.0.0.tgz", - "integrity": "sha1-N9XJNToHoJJwkSqCYGu0Kg1wLAQ=", - "dev": true, - "requires": { - "postcss": "^6.0.6", - "postcss-value-parser": "^3.3.0", - "rgb-hex": "^2.1.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-colormin": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz", - "integrity": "sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==", - "requires": { - "browserslist": "^4.0.0", - "color": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz", - "integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==", - "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" - } - } - } - }, - "postcss-convert-values": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", - "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-cssnext": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-cssnext/-/postcss-cssnext-3.1.0.tgz", - "integrity": "sha512-awPDhI4OKetcHCr560iVCoDuP6e/vn0r6EAqdWPpAavJMvkBSZ6kDpSN4b3mB3Ti57hQMunHHM8Wvx9PeuYXtA==", - "dev": true, - "requires": { - "autoprefixer": "^7.1.1", - "caniuse-api": "^2.0.0", - "chalk": "^2.0.1", - "pixrem": "^4.0.0", - "pleeease-filters": "^4.0.0", - "postcss": "^6.0.5", - "postcss-apply": "^0.8.0", - "postcss-attribute-case-insensitive": "^2.0.0", - "postcss-calc": "^6.0.0", - "postcss-color-function": "^4.0.0", - "postcss-color-gray": "^4.0.0", - "postcss-color-hex-alpha": "^3.0.0", - "postcss-color-hsl": "^2.0.0", - "postcss-color-hwb": "^3.0.0", - "postcss-color-rebeccapurple": "^3.0.0", - "postcss-color-rgb": "^2.0.0", - "postcss-color-rgba-fallback": "^3.0.0", - "postcss-custom-media": "^6.0.0", - "postcss-custom-properties": "^6.1.0", - "postcss-custom-selectors": "^4.0.1", - "postcss-font-family-system-ui": "^3.0.0", - "postcss-font-variant": "^3.0.0", - "postcss-image-set-polyfill": "^0.3.5", - "postcss-initial": "^2.0.0", - "postcss-media-minmax": "^3.0.0", - "postcss-nesting": "^4.0.1", - "postcss-pseudo-class-any-link": "^4.0.0", - "postcss-pseudoelements": "^5.0.0", - "postcss-replace-overflow-wrap": "^2.0.0", - "postcss-selector-matches": "^3.0.1", - "postcss-selector-not": "^3.0.1" - }, - "dependencies": { - "browserslist": { - "version": "2.11.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", - "integrity": "sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30000792", - "electron-to-chromium": "^1.3.30" - } - }, - "caniuse-api": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-2.0.0.tgz", - "integrity": "sha1-sd21pZZrFvSNxJmERNS7xsfZ2DQ=", - "dev": true, - "requires": { - "browserslist": "^2.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-custom-media": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-6.0.0.tgz", - "integrity": "sha1-vlMnhBEOyylQRPtTlaGABushpzc=", - "dev": true, - "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-custom-properties": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-6.3.1.tgz", - "integrity": "sha512-zoiwn4sCiUFbr4KcgcNZLFkR6gVQom647L+z1p/KBVHZ1OYwT87apnS42atJtx6XlX2yI7N5fjXbFixShQO2QQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "postcss": "^6.0.18" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-custom-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-4.0.1.tgz", - "integrity": "sha1-eBOC+UxS5yfvXKR3bqKt9JphE4I=", - "dev": true, - "requires": { - "postcss": "^6.0.1", - "postcss-selector-matches": "^3.0.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-dir-pseudo-class": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", - "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "dependencies": { - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-discard-comments": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz", - "integrity": "sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-duplicates": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", - "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-empty": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", - "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-discard-overridden": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", - "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-double-position-gradients": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", - "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", - "requires": { - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-env-function": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", - "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-flexbugs-fixes": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz", - "integrity": "sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ==", - "requires": { - "postcss": "^7.0.26" - }, - "dependencies": { - "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-focus-visible": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", - "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-focus-within": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", - "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-font-family-system-ui": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-family-system-ui/-/postcss-font-family-system-ui-3.0.0.tgz", - "integrity": "sha512-58G/hTxMSSKlIRpcPUjlyo6hV2MEzvcVO2m4L/T7Bb2fJTG4DYYfQjQeRvuimKQh1V1sOzCIz99g+H2aFNtlQw==", - "dev": true, - "requires": { - "postcss": "^6.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-font-variant": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-3.0.0.tgz", - "integrity": "sha1-CMzIj2BQuoLtjvLMdsDGprQfGD4=", - "dev": true, - "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-gap-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", - "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-image-set-function": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", - "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-image-set-polyfill": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/postcss-image-set-polyfill/-/postcss-image-set-polyfill-0.3.5.tgz", - "integrity": "sha1-Dxk0E3AM8fgr05Bm7wFtZaShgYE=", - "dev": true, - "requires": { - "postcss": "^6.0.1", - "postcss-media-query-parser": "^0.2.3" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-import": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", - "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", - "requires": { - "postcss": "^7.0.1", - "postcss-value-parser": "^3.2.3", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - } - }, - "postcss-initial": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-2.0.0.tgz", - "integrity": "sha1-cnFfczbgu3k1HZnuZcSiU6hEG6Q=", - "dev": true, - "requires": { - "lodash.template": "^4.2.4", - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-lab-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", - "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", - "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-load-config": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.2.tgz", - "integrity": "sha512-/rDeGV6vMUo3mwJZmeHfEDvwnTKKqQ0S7OHUi/kJvvtx3aWtyWG2/0ZWnzCt2keEclwN6Tf0DST2v9kITdOKYw==", - "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" - }, - "dependencies": { - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - } - }, - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - } - } - }, - "postcss-loader": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", - "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", - "requires": { - "loader-utils": "^1.1.0", - "postcss": "^7.0.0", - "postcss-load-config": "^2.0.0", - "schema-utils": "^1.0.0" - }, - "dependencies": { - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "postcss-logical": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", - "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-media-minmax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-3.0.0.tgz", - "integrity": "sha1-Z1JWA3pD70C8Twdgv9BtTcadSNI=", - "dev": true, - "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-media-query-parser": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", - "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", - "dev": true - }, - "postcss-merge-longhand": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", - "integrity": "sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==", - "requires": { - "css-color-names": "0.0.4", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "stylehacks": "^4.0.0" - } - }, - "postcss-merge-rules": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz", - "integrity": "sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "cssnano-util-same-parent": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0", - "vendors": "^1.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-message-helpers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", - "dev": true - }, - "postcss-minify-font-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", - "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-minify-gradients": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz", - "integrity": "sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "is-color-stop": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-minify-params": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz", - "integrity": "sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==", - "requires": { - "alphanum-sort": "^1.0.0", - "browserslist": "^4.0.0", - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "uniqs": "^2.0.0" - } - }, - "postcss-minify-selectors": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz", - "integrity": "sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==", - "requires": { - "alphanum-sort": "^1.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-modules-extract-imports": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", - "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", - "requires": { - "postcss": "^7.0.5" - } - }, - "postcss-modules-local-by-default": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", - "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", - "requires": { - "icss-utils": "^4.1.1", - "postcss": "^7.0.32", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-modules-scope": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", - "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", - "requires": { - "postcss": "^7.0.6", - "postcss-selector-parser": "^6.0.0" - } - }, - "postcss-modules-values": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", - "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", - "requires": { - "icss-utils": "^4.0.0", - "postcss": "^7.0.6" - } - }, - "postcss-nesting": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-4.2.1.tgz", - "integrity": "sha512-IkyWXICwagCnlaviRexi7qOdwPw3+xVVjgFfGsxmztvRVaNxAlrypOIKqDE5mxY+BVxnId1rnUKBRQoNE2VDaA==", - "dev": true, - "requires": { - "postcss": "^6.0.11" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-normalize-charset": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", - "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", - "requires": { - "postcss": "^7.0.0" - } - }, - "postcss-normalize-display-values": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz", - "integrity": "sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-positions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz", - "integrity": "sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz", - "integrity": "sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-string": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz", - "integrity": "sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==", - "requires": { - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz", - "integrity": "sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-unicode": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", - "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-normalize-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", - "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", - "requires": { - "is-absolute-url": "^2.0.0", - "normalize-url": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - }, - "dependencies": { - "is-absolute-url": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz", - "integrity": "sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=" - }, - "normalize-url": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", - "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==" - } - } - }, - "postcss-normalize-whitespace": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz", - "integrity": "sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==", - "requires": { - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-ordered-values": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz", - "integrity": "sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==", - "requires": { - "cssnano-util-get-arguments": "^4.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-overflow-shorthand": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", - "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-page-break": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", - "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-place": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", - "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-preset-env": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.7.0.tgz", - "integrity": "sha512-eU4/K5xzSFwUFJ8hTdTQzo2RBLbDVt83QZrAvI07TULOkmyQlnYlpwep+2yIK+K+0KlZO4BvFcleOCCcUtwchg==", - "requires": { - "autoprefixer": "^9.6.1", - "browserslist": "^4.6.4", - "caniuse-lite": "^1.0.30000981", - "css-blank-pseudo": "^0.1.4", - "css-has-pseudo": "^0.10.0", - "css-prefers-color-scheme": "^3.1.1", - "cssdb": "^4.4.0", - "postcss": "^7.0.17", - "postcss-attribute-case-insensitive": "^4.0.1", - "postcss-color-functional-notation": "^2.0.1", - "postcss-color-gray": "^5.0.0", - "postcss-color-hex-alpha": "^5.0.3", - "postcss-color-mod-function": "^3.0.3", - "postcss-color-rebeccapurple": "^4.0.1", - "postcss-custom-media": "^7.0.8", - "postcss-custom-properties": "^8.0.11", - "postcss-custom-selectors": "^5.1.2", - "postcss-dir-pseudo-class": "^5.0.0", - "postcss-double-position-gradients": "^1.0.0", - "postcss-env-function": "^2.0.2", - "postcss-focus-visible": "^4.0.0", - "postcss-focus-within": "^3.0.0", - "postcss-font-variant": "^4.0.0", - "postcss-gap-properties": "^2.0.0", - "postcss-image-set-function": "^3.0.1", - "postcss-initial": "^3.0.0", - "postcss-lab-function": "^2.0.1", - "postcss-logical": "^3.0.0", - "postcss-media-minmax": "^4.0.0", - "postcss-nesting": "^7.0.0", - "postcss-overflow-shorthand": "^2.0.0", - "postcss-page-break": "^2.0.0", - "postcss-place": "^4.0.1", - "postcss-pseudo-class-any-link": "^6.0.0", - "postcss-replace-overflow-wrap": "^3.0.0", - "postcss-selector-matches": "^4.0.0", - "postcss-selector-not": "^4.0.0" - }, - "dependencies": { - "autoprefixer": { - "version": "9.8.6", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.6.tgz", - "integrity": "sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg==", - "requires": { - "browserslist": "^4.12.0", - "caniuse-lite": "^1.0.30001109", - "colorette": "^1.2.1", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.32", - "postcss-value-parser": "^4.1.0" - }, - "dependencies": { - "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - } - } - }, - "cssesc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", - "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==" - }, - "postcss-attribute-case-insensitive": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz", - "integrity": "sha512-clkFxk/9pcdb4Vkn0hAHq3YnxBQ2p0CGD1dy24jN+reBck+EWxMbxSUqN4Yj7t0w8csl87K6p0gxBe1utkJsYA==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^6.0.2" - } - }, - "postcss-color-gray": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", - "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", - "requires": { - "@csstools/convert-colors": "^1.4.0", - "postcss": "^7.0.5", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-color-hex-alpha": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz", - "integrity": "sha512-PF4GDel8q3kkreVXKLAGNpHKilXsZ6xuu+mOQMHWHLPNyjiUBOr75sp5ZKJfmv1MCus5/DWUGcK9hm6qHEnXYw==", - "requires": { - "postcss": "^7.0.14", - "postcss-values-parser": "^2.0.1" - } - }, - "postcss-color-rebeccapurple": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", - "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", - "requires": { - "postcss": "^7.0.2", - "postcss-values-parser": "^2.0.0" - } - }, - "postcss-custom-media": { - "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz", - "integrity": "sha512-c9s5iX0Ge15o00HKbuRuTqNndsJUbaXdiNsksnVH8H4gdc+zbLzr/UasOwNG6CTDpLFekVY4672eWdiiWu2GUg==", - "requires": { - "postcss": "^7.0.14" - } - }, - "postcss-custom-properties": { - "version": "8.0.11", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz", - "integrity": "sha512-nm+o0eLdYqdnJ5abAJeXp4CEU1c1k+eB2yMCvhgzsds/e0umabFrN6HoTy/8Q4K5ilxERdl/JD1LO5ANoYBeMA==", - "requires": { - "postcss": "^7.0.17", - "postcss-values-parser": "^2.0.1" - } - }, - "postcss-custom-selectors": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", - "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-font-variant": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", - "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-initial": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.2.tgz", - "integrity": "sha512-ugA2wKonC0xeNHgirR4D3VWHs2JcU08WAi1KFLVcnb7IN89phID6Qtg2RIctWbnvp1TM2BOmDtX8GGLCKdR8YA==", - "requires": { - "lodash.template": "^4.5.0", - "postcss": "^7.0.2" - } - }, - "postcss-media-minmax": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", - "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-nesting": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.1.tgz", - "integrity": "sha512-FrorPb0H3nuVq0Sff7W2rnc3SmIcruVC6YwpcS+k687VxyxO33iE1amna7wHuRVzM8vfiYofXSBHNAZ3QhLvYg==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-pseudo-class-any-link": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", - "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", - "requires": { - "postcss": "^7.0.2", - "postcss-selector-parser": "^5.0.0-rc.3" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", - "integrity": "sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==", - "requires": { - "cssesc": "^2.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-replace-overflow-wrap": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", - "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", - "requires": { - "postcss": "^7.0.2" - } - }, - "postcss-selector-matches": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", - "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", - "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" - } - }, - "postcss-selector-not": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", - "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", - "requires": { - "balanced-match": "^1.0.0", - "postcss": "^7.0.2" - } - }, - "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==" - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-pseudo-class-any-link": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-4.0.0.tgz", - "integrity": "sha1-kVKgYT00UHIFE+iJKFS65C0O5o4=", - "dev": true, - "requires": { - "postcss": "^6.0.1", - "postcss-selector-parser": "^2.2.3" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", - "dev": true, - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-pseudoelements": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudoelements/-/postcss-pseudoelements-5.0.0.tgz", - "integrity": "sha1-7vGU6NUkZFylIKlJ6V5RjoEkAss=", - "dev": true, - "requires": { - "postcss": "^6.0.0" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-reduce-initial": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz", - "integrity": "sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==", - "requires": { - "browserslist": "^4.0.0", - "caniuse-api": "^3.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz", - "integrity": "sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==", - "requires": { - "cssnano-util-get-match": "^4.0.0", - "has": "^1.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0" - } - }, - "postcss-replace-overflow-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-2.0.0.tgz", - "integrity": "sha1-eU22+qVPjbEAhUOSqTr0V2i04ls=", - "dev": true, - "requires": { - "postcss": "^6.0.1" - }, - "dependencies": { - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-safe-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", - "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", - "requires": { - "postcss": "^7.0.26" - }, - "dependencies": { - "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "postcss-selector-matches": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz", - "integrity": "sha1-5WNAEeE5UIgYYbvdWMLQER/8lqs=", - "dev": true, - "requires": { - "balanced-match": "^0.4.2", - "postcss": "^6.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-selector-not": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz", - "integrity": "sha1-Lk2y8JZTNsAefOx9tsYN/3ZzNdk=", - "dev": true, - "requires": { - "balanced-match": "^0.4.2", - "postcss": "^6.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - }, - "postcss": { - "version": "6.0.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", - "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", - "dev": true, - "requires": { - "chalk": "^2.4.1", - "source-map": "^0.6.1", - "supports-color": "^5.4.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "postcss-selector-parser": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.4.tgz", - "integrity": "sha512-gjMeXBempyInaBqpp8gODmwZ52WaYsVOsfr4L4lDQ7n3ncD6mEyySiDtgzCT+NYC0mmeOLvtsF8iaEf0YT6dBw==", - "requires": { - "cssesc": "^3.0.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1", - "util-deprecate": "^1.0.2" - } - }, - "postcss-svgo": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.2.tgz", - "integrity": "sha512-C6wyjo3VwFm0QgBy+Fu7gCYOkCmgmClghO+pjcxvrcBKtiKt0uCF+hvbMO1fyv5BMImRK90SMb+dwUnfbGd+jw==", - "requires": { - "is-svg": "^3.0.0", - "postcss": "^7.0.0", - "postcss-value-parser": "^3.0.0", - "svgo": "^1.0.0" - } - }, - "postcss-unique-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", - "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", - "requires": { - "alphanum-sort": "^1.0.0", - "postcss": "^7.0.0", - "uniqs": "^2.0.0" - } - }, - "postcss-value-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", - "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==" - }, - "postcss-values-parser": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz", - "integrity": "sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg==", - "requires": { - "flatten": "^1.0.2", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "proxy-addr": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", - "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", - "dev": true, - "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.1" - } - }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" - }, - "public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "requires": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - }, - "dependencies": { - "bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" - } - } - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, - "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true - }, - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=" - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "requires": { - "performance-now": "^2.1.0" - } - }, - "raf-schd": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz", - "integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==" - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "requires": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true - }, - "raw-body": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", - "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", - "dev": true, - "requires": { - "bytes": "3.1.0", - "http-errors": "1.7.2", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "dependencies": { - "bytes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", - "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", - "dev": true - } - } - }, - "rc-align": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/rc-align/-/rc-align-2.4.5.tgz", - "integrity": "sha512-nv9wYUYdfyfK+qskThf4BQUSIadeI/dCsfaMZfNEoxm9HwOIioQ+LyqmMK6jWHAZQgOzMLaqawhuBXlF63vgjw==", - "requires": { - "babel-runtime": "^6.26.0", - "dom-align": "^1.7.0", - "prop-types": "^15.5.8", - "rc-util": "^4.0.4" - } - }, - "rc-animate": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/rc-animate/-/rc-animate-2.11.1.tgz", - "integrity": "sha512-1NyuCGFJG/0Y+9RKh5y/i/AalUCA51opyyS/jO2seELpgymZm2u9QV3xwODwEuzkmeQ1BDPxMLmYLcTJedPlkQ==", - "requires": { - "babel-runtime": "6.x", - "classnames": "^2.2.6", - "css-animation": "^1.3.2", - "prop-types": "15.x", - "raf": "^3.4.0", - "rc-util": "^4.15.3", - "react-lifecycles-compat": "^3.0.4" - } - }, - "rc-slider": { - "version": "github:Galvanize-IT/slider#c569ca3b11979aced8306e6c02f37466cb7cd365", - "from": "github:Galvanize-IT/slider#c569ca3b11979aced8306e6c02f37466cb7cd365", - "requires": { - "babel-runtime": "6.x", - "classnames": "^2.2.5", - "gh-pages": "^2.0.1", - "prop-types": "^15.5.4", - "rc-tooltip": "^3.7.0", - "rc-util": "^4.0.4", - "shallowequal": "^1.0.1", - "warning": "^4.0.3" - } - }, - "rc-tooltip": { - "version": "3.7.3", - "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-3.7.3.tgz", - "integrity": "sha512-dE2ibukxxkrde7wH9W8ozHKUO4aQnPZ6qBHtrTH9LoO836PjDdiaWO73fgPB05VfJs9FbZdmGPVEbXCeOP99Ww==", - "requires": { - "babel-runtime": "6.x", - "prop-types": "^15.5.8", - "rc-trigger": "^2.2.2" - } - }, - "rc-trigger": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/rc-trigger/-/rc-trigger-2.6.5.tgz", - "integrity": "sha512-m6Cts9hLeZWsTvWnuMm7oElhf+03GOjOLfTuU0QmdB9ZrW7jR2IpI5rpNM7i9MvAAlMAmTx5Zr7g3uu/aMvZAw==", - "requires": { - "babel-runtime": "6.x", - "classnames": "^2.2.6", - "prop-types": "15.x", - "rc-align": "^2.4.0", - "rc-animate": "2.x", - "rc-util": "^4.4.0", - "react-lifecycles-compat": "^3.0.4" - } - }, - "rc-util": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-4.21.1.tgz", - "integrity": "sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg==", - "requires": { - "add-dom-event-listener": "^1.1.0", - "prop-types": "^15.5.10", - "react-is": "^16.12.0", - "react-lifecycles-compat": "^3.0.4", - "shallowequal": "^1.1.0" - }, - "dependencies": { - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - } - } - }, - "react": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", - "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - } - } - }, - "react-addons-css-transition-group": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react-addons-css-transition-group/-/react-addons-css-transition-group-15.6.2.tgz", - "integrity": "sha1-nkN2vPQLUhfRTsaFUwgc7ksIptY=", - "requires": { - "react-transition-group": "^1.2.0" - } - }, - "react-addons-test-utils": { - "version": "15.6.2", - "resolved": "https://registry.npmjs.org/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz", - "integrity": "sha1-wStu/cIkfBDae4dw0YUICnsEcVY=" - }, - "react-beautiful-dnd": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz", - "integrity": "sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg==", - "requires": { - "@babel/runtime": "^7.8.4", - "css-box-model": "^1.2.0", - "memoize-one": "^5.1.1", - "raf-schd": "^4.0.2", - "react-redux": "^7.1.1", - "redux": "^4.0.4", - "use-memo-one": "^1.1.1" - } - }, - "react-copy-to-clipboard": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz", - "integrity": "sha512-/2t5mLMMPuN5GmdXo6TebFa8IoFxZ+KTDDqYhcDm0PhkgEzSxVvIX26G20s1EB02A4h2UZgwtfymZ3lGJm0OLg==", - "requires": { - "copy-to-clipboard": "^3", - "prop-types": "^15.5.8" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - } - } - }, - "react-datepicker": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.2.2.tgz", - "integrity": "sha512-/3D6hfhXcCNCbO8LICuQeoNDItWFyitGo+aLcsi0tAyJLtCInamYRwPIXhsEF+N6/qWim1yNyr71mqjj4YEBmg==", - "requires": { - "classnames": "^2.2.6", - "date-fns": "^2.0.1", - "prop-types": "^15.7.2", - "react-onclickoutside": "^6.9.0", - "react-popper": "^1.3.4" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - } - } - }, - "react-dom": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", - "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - } - } - }, - "react-is": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", - "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==" - }, - "react-json-pretty": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/react-json-pretty/-/react-json-pretty-2.2.0.tgz", - "integrity": "sha512-3UMzlAXkJ4R8S4vmkRKtvJHTewG4/rn1Q18n0zqdu/ipZbUPLVZD+QwC7uVcD/IAY3s8iNVHlgR2dMzIUS0n1A==", - "requires": { - "prop-types": "^15.6.2" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - } - } - }, - "react-lifecycles-compat": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" - }, - "react-onclickoutside": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz", - "integrity": "sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A==" - }, - "react-paginate": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/react-paginate/-/react-paginate-6.5.0.tgz", - "integrity": "sha512-H7xSi9jyiJzgfaj+2nNhQcjZfwzJ/Mxb64V2RiyDctjZyCWojwsaGwMqhLBpQ58iAuMVtBMRQ7ECqMcUKG9QSQ==", - "requires": { - "prop-types": "^15.6.1" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - } - } - }, - "react-pdf": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-4.2.0.tgz", - "integrity": "sha512-Ao44mZszfPwtCUsrXHtXnhM+czTvPxfG5wqssbWgj2onL70TKSOKGzQfCH4csCnNDOKji/Pc/s0Og70/VOE+Rg==", - "requires": { - "@babel/runtime": "^7.0.0", - "make-cancellable-promise": "^1.0.0", - "make-event-props": "^1.1.0", - "merge-class-names": "^1.1.1", - "pdfjs-dist": "2.1.266", - "prop-types": "^15.6.2" - } - }, - "react-popper": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz", - "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==", - "requires": { - "@babel/runtime": "^7.1.2", - "create-react-context": "^0.3.0", - "deep-equal": "^1.1.1", - "popper.js": "^1.14.4", - "prop-types": "^15.6.1", - "typed-styles": "^0.0.7", - "warning": "^4.0.2" - }, - "dependencies": { - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" - }, - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - } - } - }, - "react-redux": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.1.tgz", - "integrity": "sha512-T+VfD/bvgGTUA74iW9d2i5THrDQWbweXP0AVNI8tNd1Rk5ch1rnMiJkDD67ejw7YBKM4+REvcvqRuWJb7BLuEg==", - "requires": { - "@babel/runtime": "^7.5.5", - "hoist-non-react-statics": "^3.3.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.7.2", - "react-is": "^16.9.0" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - } - } - }, - "react-scroll-sync": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/react-scroll-sync/-/react-scroll-sync-0.8.0.tgz", - "integrity": "sha512-Ms9srm41UM+lWexuqdocXjqaqqt6ZRSFxcudgB0sYhC7Or+m12WemTwY8BaQCRf7hA8zHDk55FHvMkqsH7gF+w==" - }, - "react-textarea-autosize": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz", - "integrity": "sha512-grajUlVbkx6VdtSxCgzloUIphIZF5bKr21OYMceWPKkniy7H0mRAT/AXPrRtObAe+zUePnNlBwUc4ivVjUGIjw==", - "requires": { - "@babel/runtime": "^7.10.2", - "use-composed-ref": "^1.0.0", - "use-latest": "^1.0.0" - } - }, - "react-tools": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/react-tools/-/react-tools-0.13.3.tgz", - "integrity": "sha1-2mrH1Nd3elml6VHPRucv1La0Ciw=", - "requires": { - "commoner": "^0.10.0", - "jstransform": "^10.1.0" - } - }, - "react-tooltip": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-4.2.10.tgz", - "integrity": "sha512-D7ZLx6/QwpUl0SZRek3IZy/HWpsEEp0v3562tcT8IwZgu8IgV7hY5ZzniTkHyRcuL+IQnljpjj7A7zCgl2+T3w==", - "requires": { - "prop-types": "^15.7.2", - "uuid": "^7.0.3" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "uuid": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz", - "integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==" - } - } - }, - "react-transition-group": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-1.2.1.tgz", - "integrity": "sha512-CWaL3laCmgAFdxdKbhhps+c0HRGF4c+hdM4H23+FI1QBNUyx/AMeIJGWorehPNSaKnQNOAxL7PQmqMu78CDj3Q==", - "requires": { - "chain-function": "^1.0.0", - "dom-helpers": "^3.2.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.5.6", - "warning": "^3.0.0" - }, - "dependencies": { - "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.8.1" - } - }, - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, - "react_ujs": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/react_ujs/-/react_ujs-2.6.1.tgz", - "integrity": "sha512-9M33/A8cubStkZ2cpJSimcTD0RlCWiqXF6e90IQmMw/Caf/W0dtAzOtHtiQE3JjLbt/nhRR7NLPxMfnlm141ig==", - "requires": { - "react_ujs": "^2.6.0" - } - }, - "reactify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/reactify/-/reactify-1.1.1.tgz", - "integrity": "sha1-qPEZWWJzwNS/savqDBTCYB6gO7o=", - "requires": { - "react-tools": "~0.13.0", - "through": "~2.3.4" - } - }, - "read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=", - "requires": { - "pify": "^2.3.0" - } - }, - "read-only-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=", - "requires": { - "readable-stream": "^2.0.2" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - }, - "recast": { - "version": "0.11.23", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", - "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", - "requires": { - "ast-types": "0.9.6", - "esprima": "~3.1.0", - "private": "~0.1.5", - "source-map": "~0.5.0" - } - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - }, - "dependencies": { - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "requires": { - "repeating": "^2.0.0" - } - } - } - }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "^0.4.2", - "math-expression-evaluator": "^1.2.14", - "reduce-function-call": "^1.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "reduce-function-call": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.3.tgz", - "integrity": "sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "redux": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", - "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", - "requires": { - "loose-envify": "^1.4.0", - "symbol-observable": "^1.2.0" - } - }, - "regenerate": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.1.tgz", - "integrity": "sha512-j2+C8+NtXQgEKWk49MMP5P/u2GhnahTtVkRIHr5R5lVRlbKvmQ+oS+A5aLKWp2ma5VkT8sh6v+v4hbH0YHR66A==" - }, - "regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "requires": { - "regenerate": "^1.4.0" - } - }, - "regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" - }, - "regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexp.prototype.flags": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", - "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - } - }, - "regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" - }, - "regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" - } - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" - }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "requires": { - "resolve-from": "^3.0.0" - } - }, - "resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", - "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "dependencies": { - "global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - } - } - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true - }, - "rgb": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/rgb/-/rgb-0.1.0.tgz", - "integrity": "sha1-vieykej+/+rBvZlylyG/pA/AN7U=", - "dev": true - }, - "rgb-hex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/rgb-hex/-/rgb-hex-2.1.0.tgz", - "integrity": "sha1-x3PF/iJoolV42SU5qCp6XOU77aY=", - "dev": true - }, - "rgb-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", - "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=" - }, - "rgba-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", - "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=" - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "requires": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "requires": { - "aproba": "^1.1.1" - } - }, - "safe-buffer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "sass-graph": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz", - "integrity": "sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag==", - "requires": { - "glob": "^7.0.0", - "lodash": "^4.0.0", - "scss-tokenizer": "^0.2.3", - "yargs": "^13.3.2" - } - }, - "sass-loader": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz", - "integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==", - "requires": { - "clone-deep": "^4.0.1", - "loader-utils": "^1.2.3", - "neo-async": "^2.6.1", - "schema-utils": "^2.6.1", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" - }, - "scheduler": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", - "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", - "requires": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" - } - }, - "schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "requires": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - } - }, - "scss-tokenizer": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", - "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", - "requires": { - "js-base64": "^2.1.8", - "source-map": "^0.4.2" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "requires": { - "amdefine": ">=0.0.4" - } - } - } - }, - "select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", - "dev": true - }, - "selfsigned": { - "version": "1.10.8", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz", - "integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==", - "dev": true, - "requires": { - "node-forge": "^0.10.0" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - } - } - }, - "serve-static": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", - "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" - }, - "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", - "dev": true - }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "requires": { - "kind-of": "^6.0.2" - } - }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, - "shasum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", - "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=", - "requires": { - "json-stable-stringify": "~0.0.0", - "sha.js": "~2.4.4" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shell-quote": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.1.tgz", - "integrity": "sha512-2kUqeAGnMAu6YrTPX4E3LfxacH9gKljzVjlkUeSqY0soGwK4KLl7TURXCem712tkhBCeeaFP9QK4dKn88s3Icg==" - }, - "side-channel": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.3.tgz", - "integrity": "sha512-A6+ByhlLkksFoUepsGxfj5x1gTSrs+OydsRptUxeNCabQpCFUvcwIczgOigI8vhY/OJCnPnyE9rGiwgvr9cS1g==", - "dev": true, - "requires": { - "es-abstract": "^1.18.0-next.0", - "object-inspect": "^1.8.0" - } - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "simple-concat": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", - "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==" - }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } - } - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "sockjs": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.20.tgz", - "integrity": "sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA==", - "dev": true, - "requires": { - "faye-websocket": "^0.10.0", - "uuid": "^3.4.0", - "websocket-driver": "0.6.5" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "sockjs-client": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.4.0.tgz", - "integrity": "sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g==", - "dev": true, - "requires": { - "debug": "^3.2.5", - "eventsource": "^1.0.7", - "faye-websocket": "~0.11.1", - "inherits": "^2.0.3", - "json3": "^3.3.2", - "url-parse": "^1.4.3" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "faye-websocket": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.3.tgz", - "integrity": "sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA==", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - } - } - }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "requires": { - "is-plain-obj": "^1.0.0" - } - }, - "source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==" - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", - "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==" - }, - "spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - } - } - }, - "spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", - "requires": { - "minipass": "^3.1.1" - } - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true - }, - "stdout-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", - "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", - "requires": { - "readable-stream": "^2.0.1" - } - }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "stream-combiner2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=", - "requires": { - "duplexer2": "~0.1.0", - "readable-stream": "^2.0.2" - } - }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-http": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.1.1.tgz", - "integrity": "sha512-S7OqaYu0EkFpgeGFb/NPOoPLxFko7TPqtEeFg5DXPB4v/KETHG0Ln6fRFrNezoelpaDKmycEmmZ81cC9DAwgYg==", - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" - }, - "stream-splicer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.1.tgz", - "integrity": "sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.2" - } - }, - "strict-uri-encode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", - "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "string.prototype.matchall": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", - "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0", - "has-symbols": "^1.0.1", - "internal-slot": "^1.0.2", - "regexp.prototype.flags": "^1.3.0", - "side-channel": "^1.0.2" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "requires": { - "get-stdin": "^4.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "strip-outer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", - "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "strip-url-auth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz", - "integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=" - }, - "style-loader": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", - "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", - "requires": { - "loader-utils": "^2.0.0", - "schema-utils": "^2.7.0" - }, - "dependencies": { - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "requires": { - "minimist": "^1.2.5" - } - }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - } - } - }, - "stylehacks": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", - "integrity": "sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==", - "requires": { - "browserslist": "^4.0.0", - "postcss": "^7.0.0", - "postcss-selector-parser": "^3.0.0" - }, - "dependencies": { - "postcss-selector-parser": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz", - "integrity": "sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==", - "requires": { - "dot-prop": "^5.2.0", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "subarg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=", - "requires": { - "minimist": "^1.1.0" - } - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "svgo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", - "integrity": "sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==", - "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.37", - "csso": "^4.0.2", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - } - }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" - }, - "syntax-error": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==", - "requires": { - "acorn-node": "^1.2.0" - } - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - } - }, - "tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==" - }, - "tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", - "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - } - } - }, - "terser": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.4.tgz", - "integrity": "sha512-dxuB8KQo8Gt6OVOeLg/rxfcxdNZI/V1G6ze1czFUzPeCFWZRtvZMgSzlZZ5OYBZ4HoG607F6pFPNLekJyV+yVw==", - "requires": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" - } - } - }, - "terser-webpack-plugin": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.2.tgz", - "integrity": "sha512-3qAQpykRTD5DReLu5/cwpsg7EZFzP3Q0Hp2XUWJUw2mpq2jfgOKTZr8IZKKnNieRVVo1UauROTdhbQJZveGKtQ==", - "requires": { - "cacache": "^15.0.5", - "find-cache-dir": "^3.3.1", - "jest-worker": "^26.3.0", - "p-limit": "^3.0.2", - "schema-utils": "^2.7.1", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.3.2", - "webpack-sources": "^1.4.3" - }, - "dependencies": { - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - } - }, - "p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "requires": { - "find-up": "^4.0.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "requires": { - "randombytes": "^2.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true - }, - "timers-browserify": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=", - "requires": { - "process": "~0.11.0" - } - }, - "timsort": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", - "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" - }, - "tiny-invariant": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz", - "integrity": "sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==" - }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=" - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "toggle-selection": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", - "integrity": "sha1-bkWxJj8gF/oKzH2J14sVuL932jI=" - }, - "toidentifier": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", - "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", - "dev": true - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } - } - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" - }, - "trim-repeated": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", - "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", - "requires": { - "escape-string-regexp": "^1.0.2" - } - }, - "true-case-path": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", - "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", - "requires": { - "glob": "^7.1.2" - } - }, - "ts-essentials": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz", - "integrity": "sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w==" - }, - "ts-loader": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.4.tgz", - "integrity": "sha512-5u8KF1SW8eCUb/Ff7At81e3wznPmT/27fvaGRO9CziVy+6NlPVRvrzSox4OwU0/e6OflOUB32Err4VquysCSAQ==", - "requires": { - "chalk": "^2.3.0", - "enhanced-resolve": "^4.0.0", - "loader-utils": "^1.0.2", - "micromatch": "^4.0.0", - "semver": "^6.0.0" - }, - "dependencies": { - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "requires": { - "fill-range": "^7.0.1" - } - }, - "emojis-list": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", - "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" - }, - "loader-utils": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", - "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^2.0.0", - "json5": "^1.0.1" - } - }, - "micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "ts-pnp": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ts-pnp/-/ts-pnp-1.2.0.tgz", - "integrity": "sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==" - }, - "tsconfig-paths": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", - "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", - "dev": true, - "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" - } - }, - "tslib": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.0.tgz", - "integrity": "sha512-+Zw5lu0D9tvBMjGP8LpvMb0u2WW2QV3y+D8mO6J+cNzCYIN4sVy43Bf9vl92nqFahutN0I8zHa7cc4vihIshnw==" - }, - "tty-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", - "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typed-styles": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", - "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "typescript": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", - "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==" - }, - "umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==" - }, - "undeclared-identifiers": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", - "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", - "requires": { - "acorn-node": "^1.3.0", - "dash-ast": "^1.0.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" - } - }, - "unfetch": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", - "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" - }, - "unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "uniq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" - }, - "uniqs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz", - "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=" - }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "units-css": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/units-css/-/units-css-0.4.0.tgz", - "integrity": "sha1-1iKGU6UZg9fBb/KPi53Dsf/tOgc=", - "dev": true, - "requires": { - "isnumeric": "^0.2.0", - "viewport-dimensions": "^0.2.0" - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true - }, - "unquote": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", - "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - } - } - }, - "upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==" - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" - } - } - }, - "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" - }, - "use-composed-ref": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.0.0.tgz", - "integrity": "sha512-RVqY3NFNjZa0xrmK3bIMWNmQ01QjKPDc7DeWR3xa/N8aliVppuutOE5bZzPkQfvL+5NRWMMp0DJ99Trd974FIw==", - "requires": { - "ts-essentials": "^2.0.3" - } - }, - "use-isomorphic-layout-effect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.0.0.tgz", - "integrity": "sha512-JMwJ7Vd86NwAt1jH7q+OIozZSIxA4ND0fx6AsOe2q1H8ooBUp5aN6DvVCqZiIaYU6JaMRJGyR0FO7EBCIsb/Rg==" - }, - "use-latest": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.1.0.tgz", - "integrity": "sha512-gF04d0ZMV3AMB8Q7HtfkAWe+oq1tFXP6dZKwBHQF5nVXtGsh2oAYeeqma5ZzxtlpOcW8Ro/tLcfmEodjDeqtuw==", - "requires": { - "use-isomorphic-layout-effect": "^1.0.0" - } - }, - "use-memo-one": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.1.tgz", - "integrity": "sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==" - }, - "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - }, - "dependencies": { - "es-abstract": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", - "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "requires": { - "has-symbols": "^1.0.1" - } - } - } - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", - "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" - }, - "v8-compile-cache": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", - "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true - }, - "vendors": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz", - "integrity": "sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "viewport-dimensions": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz", - "integrity": "sha1-3nQHR9tTh/0XJfUXXpG6x2r982w=", - "dev": true - }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" - }, - "warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "requires": { - "loose-envify": "^1.0.0" - } - }, - "watchpack": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", - "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", - "requires": { - "chokidar": "^3.4.1", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.0" - }, - "dependencies": { - "anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "optional": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "optional": true - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "optional": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chokidar": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", - "integrity": "sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==", - "optional": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.4.0" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "optional": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "optional": true - }, - "readdirp": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", - "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", - "optional": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "optional": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "watchpack-chokidar2": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", - "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", - "optional": true, - "requires": { - "chokidar": "^2.1.8" - } - }, - "wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "weakmap-polyfill": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/weakmap-polyfill/-/weakmap-polyfill-2.0.1.tgz", - "integrity": "sha512-Jy177Lvb1LCrPQDWJsXyyqf4eOhcdvQHFGoCqSv921kVF5i42MVbr4e2WEwetuTLBn1NX0IhPzTmMu0N3cURqQ==" - }, - "webpack": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", - "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.3.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==" - }, - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "requires": { - "yallist": "^3.0.2" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "requires": { - "minimist": "^1.2.5" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - } - }, - "terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" - } - } - }, - "webpack-assets-manifest": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/webpack-assets-manifest/-/webpack-assets-manifest-3.1.1.tgz", - "integrity": "sha512-JV9V2QKc5wEWQptdIjvXDUL1ucbPLH2f27toAY3SNdGZp+xSaStAgpoMcvMZmqtFrBc9a5pTS1058vxyMPOzRQ==", - "requires": { - "chalk": "^2.0", - "lodash.get": "^4.0", - "lodash.has": "^4.0", - "mkdirp": "^0.5", - "schema-utils": "^1.0.0", - "tapable": "^1.0.0", - "webpack-sources": "^1.0.0" - }, - "dependencies": { - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "webpack-cli": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.12.tgz", - "integrity": "sha512-NVWBaz9k839ZH/sinurM+HcDvJOTXwSjYp1ku+5XKeOC03z8v5QitnK/x+lAxGXFyhdayoIf/GOpv85z3/xPag==", - "requires": { - "chalk": "^2.4.2", - "cross-spawn": "^6.0.5", - "enhanced-resolve": "^4.1.1", - "findup-sync": "^3.0.0", - "global-modules": "^2.0.0", - "import-local": "^2.0.0", - "interpret": "^1.4.0", - "loader-utils": "^1.4.0", - "supports-color": "^6.1.0", - "v8-compile-cache": "^2.1.1", - "yargs": "^13.3.2" - } - }, - "webpack-dev-middleware": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz", - "integrity": "sha512-1xC42LxbYoqLNAhV6YzTYacicgMZQTqRd27Sim9wn5hJrX3I5nxYy1SxSd4+gjUFsz1dQFj+yEe6zEVmSkeJjw==", - "dev": true, - "requires": { - "memory-fs": "^0.4.1", - "mime": "^2.4.4", - "mkdirp": "^0.5.1", - "range-parser": "^1.2.1", - "webpack-log": "^2.0.0" - }, - "dependencies": { - "mime": { - "version": "2.4.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", - "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==", - "dev": true - } - } - }, - "webpack-dev-server": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz", - "integrity": "sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg==", - "dev": true, - "requires": { - "ansi-html": "0.0.7", - "bonjour": "^3.5.0", - "chokidar": "^2.1.8", - "compression": "^1.7.4", - "connect-history-api-fallback": "^1.6.0", - "debug": "^4.1.1", - "del": "^4.1.1", - "express": "^4.17.1", - "html-entities": "^1.3.1", - "http-proxy-middleware": "0.19.1", - "import-local": "^2.0.0", - "internal-ip": "^4.3.0", - "ip": "^1.1.5", - "is-absolute-url": "^3.0.3", - "killable": "^1.0.1", - "loglevel": "^1.6.8", - "opn": "^5.5.0", - "p-retry": "^3.0.1", - "portfinder": "^1.0.26", - "schema-utils": "^1.0.0", - "selfsigned": "^1.10.7", - "semver": "^6.3.0", - "serve-index": "^1.9.1", - "sockjs": "0.3.20", - "sockjs-client": "1.4.0", - "spdy": "^4.0.2", - "strip-ansi": "^3.0.1", - "supports-color": "^6.1.0", - "url": "^0.11.0", - "webpack-dev-middleware": "^3.7.2", - "webpack-log": "^2.0.0", - "ws": "^6.2.1", - "yargs": "^13.3.2" - }, - "dependencies": { - "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "webpack-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", - "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", - "dev": true, - "requires": { - "ansi-colors": "^3.0.0", - "uuid": "^3.3.2" - } - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - } - } - }, - "websocket-driver": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz", - "integrity": "sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY=", - "dev": true, - "requires": { - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "requires": { - "errno": "~0.1.7" - } - }, - "worker-loader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-2.0.0.tgz", - "integrity": "sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==", - "requires": { - "loader-utils": "^1.0.0", - "schema-utils": "^0.4.0" - }, - "dependencies": { - "schema-utils": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz", - "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==", - "requires": { - "ajv": "^6.1.0", - "ajv-keywords": "^3.1.0" - } - } - } - }, - "wrap-ansi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", - "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", - "requires": { - "ansi-styles": "^3.2.0", - "string-width": "^3.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yaml": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz", - "integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==" - }, - "yargs": { - "version": "13.3.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", - "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", - "requires": { - "cliui": "^5.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.1.2" - }, - "dependencies": { - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - } - } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } -} diff --git a/package.json.sav b/package.json.sav deleted file mode 100644 index af9f46c..0000000 --- a/package.json.sav +++ /dev/null @@ -1,83 +0,0 @@ -{ - "name": "forge", - "version": "1.0.0", - "description": "Forge [![Build](http://circleci-badges-max.herokuapp.com/img/Galvanize-IT/forge/master?token=d736af2111a26f129d732b6d9696386619328cd5)](https://circleci.com/gh/Galvanize-IT/forge/tree/master) =====", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/Galvanize-IT/forge.git" - }, - "author": "", - "license": "MIT", - "bugs": { - "url": "https://github.com/Galvanize-IT/forge/issues" - }, - "homepage": "https://github.com/Galvanize-IT/forge#readme", - "dependencies": { - "@rails/webpacker": "3.5", - "@types/react-paginate": "^6.2.1", - "@types/react-pdf": "^4.0.3", - "@vimeo/player": "^2.2.1", - "babel-preset-react": "^6.24.1", - "brace": "^0.9.1", - "browserify": "^13.1.0", - "browserify-incremental": "^3.1.1", - "dropzone": "^4.3.0", - "gsap": "^2.0.1", - "highlightjs": "^9.16.2", - "immutability-helper": "^2.7.1", - "lodash-es": "^4.17.10", - "marked": "^0.5.0", - "moment": "2.19.2", - "moment-timezone": "0.5.14", - "objectify-array": "^2.1.0", - "popper.js": "^1.12.6", - "prop-types": "^15.7.2", - "rc-slider": "Galvanize-IT/slider#c569ca3b11979aced8306e6c02f37466cb7cd365", - "react": "^16.4.2", - "react-addons-css-transition-group": "^15.6.2", - "react-addons-test-utils": "^15.6.0", - "react-beautiful-dnd": "^10.0.0", - "react-copy-to-clipboard": "^5.0.1", - "react-datepicker": "^2.0.0", - "react-dom": "^16.4.2", - "react-json-pretty": "^2.2.0", - "react-paginate": "^6.3.2", - "react-pdf": "^4.1.0", - "react-scroll-sync": "^0.5.0", - "react-textarea-autosize": "^5.1.0", - "react-tooltip": "^3.4.0", - "react_ujs": "^2.4.4", - "reactify": "^1.1.1", - "ts-loader": "3.5.0", - "typescript": "^3.4.5", - "unfetch": "^3.1.1" - }, - "devDependencies": { - "@types/lodash-es": "^4.17.1", - "@types/node-fetch": "^2.1.2", - "@types/rc-slider": "^8.6.5", - "@types/react": "^16.8.24", - "@types/react-addons-css-transition-group": "^15.0.5", - "@types/react-beautiful-dnd": "^10.1.1", - "@types/react-copy-to-clipboard": "^4.2.6", - "@types/react-datepicker": "^2.0.2", - "@types/react-dom": "^16.0.7", - "@types/react-textarea-autosize": "^4.3.3", - "@types/vimeo__player": "^2.6.2", - "eslint": "3.19.0", - "eslint-config-airbnb": "14.1.0", - "eslint-plugin-import": "2.2.0", - "eslint-plugin-jsx-a11y": "4.0.0", - "eslint-plugin-promise": "3.5.0", - "eslint-plugin-react": "6.10.3", - "eslint-plugin-standard": "3.0.1", - "webpack-dev-server": "2.11.2" - }, - "engines": { - "node": "10.15.1" - } -} diff --git a/yarn.lock b/yarn.lock index 5e31736..fc3bc6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5227,15 +5227,7 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" - dependencies: - big.js "^5.2.2" - emojis-list "^2.0.0" - json5 "^1.0.1" - -loader-utils@^1.2.3, loader-utils@^1.4.0: +loader-utils@^1.0.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -5244,6 +5236,14 @@ loader-utils@^1.2.3, loader-utils@^1.4.0: emojis-list "^3.0.0" json5 "^1.0.1" +loader-utils@^1.0.2, loader-utils@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" + dependencies: + big.js "^5.2.2" + emojis-list "^2.0.0" + json5 "^1.0.1" + loader-utils@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" @@ -5819,6 +5819,11 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-ensure@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" + integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc= + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -6422,10 +6427,13 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pdfjs-dist@2.4.456: - version "2.4.456" - resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.4.456.tgz#0eaad2906cda866bbb393e79a0e5b4e68bd75520" - integrity sha512-yckJEHq3F48hcp6wStEpbN9McOj328Ib09UrBlGAKxvN2k+qYPN5iq6TH6jD1C0pso7zTep+g/CKsYgdrQd5QA== +pdfjs-dist@2.1.266: + version "2.1.266" + resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.1.266.tgz#cded02268b389559e807f410d2a729db62160026" + integrity sha512-Jy7o1wE3NezPxozexSbq4ltuLT0Z21ew/qrEiAEeUZzHxMHGk4DUV1D7RuCXg5vJDvHmjX1YssN+we9QfRRgXQ== + dependencies: + node-ensure "^0.0.0" + worker-loader "^2.0.0" performance-now@^2.1.0: version "2.1.0" @@ -7752,18 +7760,17 @@ react-paginate@^6.5.0: dependencies: prop-types "^15.6.1" -react-pdf@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/react-pdf/-/react-pdf-5.0.0.tgz#baddeecd5c5ef92ae57aed1ee141203ad14d4b5d" - integrity sha512-VpqZjpZGEevmotLYl6acU6GYQeJ0dxn9+5sth5QjWLFhKu0xy3zSZgt3U3m97zW6UWzQ/scvw5drfPyun5l4eA== +react-pdf@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/react-pdf/-/react-pdf-4.2.0.tgz#b83a01eb070912522075b7a51aee7d63b2c912ed" + integrity sha512-Ao44mZszfPwtCUsrXHtXnhM+czTvPxfG5wqssbWgj2onL70TKSOKGzQfCH4csCnNDOKji/Pc/s0Og70/VOE+Rg== dependencies: "@babel/runtime" "^7.0.0" make-cancellable-promise "^1.0.0" make-event-props "^1.1.0" merge-class-names "^1.1.1" - pdfjs-dist "2.4.456" + pdfjs-dist "2.1.266" prop-types "^15.6.2" - worker-loader "^3.0.0" react-popper@^1.3.4: version "1.3.7" @@ -8266,6 +8273,14 @@ scheduler@^0.19.1: loose-envify "^1.1.0" object-assign "^4.1.1" +schema-utils@^0.4.0: + version "0.4.7" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" + integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ== + dependencies: + ajv "^6.1.0" + ajv-keywords "^3.1.0" + schema-utils@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" @@ -9717,13 +9732,13 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" -worker-loader@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-3.0.3.tgz#a9e3a1840589bd2d279da74c9e0e4acdadecbeec" - integrity sha512-yLUJqzloOnoh2/9OisTrUbUHd2a3Tfx8o8ilXHEQJ9Z/x/O/Ll+yZZOoVLT8G33IT2oCrjsIZ6jNB3OVIYCllA== +worker-loader@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac" + integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw== dependencies: - loader-utils "^2.0.0" - schema-utils "^2.7.0" + loader-utils "^1.0.0" + schema-utils "^0.4.0" wrap-ansi@^5.1.0: version "5.1.0" -- GitLab From 91e22a9328c0305278642d5abeacbe6f90f7978f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 8 Oct 2020 10:13:59 -1000 Subject: [PATCH 139/287] fixing pdf rendering --- app/javascript/components/content_files/PDFRenderer.tsx | 3 +-- app/javascript/components/content_files/SubmissionRenderer.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/javascript/components/content_files/PDFRenderer.tsx b/app/javascript/components/content_files/PDFRenderer.tsx index b9ff2c6..9f9ebe8 100644 --- a/app/javascript/components/content_files/PDFRenderer.tsx +++ b/app/javascript/components/content_files/PDFRenderer.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { Document, Page, pdfjs } from 'react-pdf/dist/entry.webpack'; - pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; type Props = { @@ -32,7 +31,7 @@ export default class PDFRenderer extends React.Component { View on Github )} -
    +
    Date: Thu, 8 Oct 2020 10:21:06 -1000 Subject: [PATCH 140/287] updating docs --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fc701f1..b7fc7d5 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Install rbenv and get the right version of ruby (the version here may change, so version. ) - `brew install rbenv` -- `rbenv install 2.6.0` +- `rbenv install 2.6.6` Install our data stores: @@ -32,7 +32,7 @@ You will need a [Github access token](https://github.com/settings/tokens). Repla The token will need **Full control of private repositories** - `git clone git@github.com:Galvanize-IT/forge.git && cd forge` -- `rbenv local 2.6.0` +- `rbenv local 2.6.6` - `gem install bundler` - `bundle config --local GITHUB__COM YOUR_GENERATED_PERSONAL_ACCESS_TOKEN:x-oauth-basic` - `bundle` -- GitLab From 024e6ec42697668595c46a36e6dcace2dbcfcf90 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 8 Oct 2020 10:54:21 -1000 Subject: [PATCH 141/287] fixing build error. removing old module. adding new module dependencies --- .postcssrc.yml | 6 +- package.json | 3 +- yarn.lock | 465 ++----------------------------------------------- 3 files changed, 15 insertions(+), 459 deletions(-) diff --git a/.postcssrc.yml b/.postcssrc.yml index 150dac3..6574125 100644 --- a/.postcssrc.yml +++ b/.postcssrc.yml @@ -1,3 +1,3 @@ -plugins: - postcss-import: {} - postcss-cssnext: {} +#plugins: +# postcss-import: {} +# postcss-cssnext: {} diff --git a/package.json b/package.json index 6b46e51..cc79cf0 100644 --- a/package.json +++ b/package.json @@ -74,8 +74,9 @@ "eslint-plugin-promise": "4.2.1", "eslint-plugin-react": "7.21.3", "eslint-plugin-standard": "4.0.1", - "postcss-cssnext": "^3.1.0", + "postcss-flexbugs-fixes": "^4.2.1", "postcss-import": "^12.0.1", + "postcss-preset-env": "^6.7.0", "webpack-dev-server": "^3.11.0" }, "engines": { diff --git a/yarn.lock b/yarn.lock index fc3bc6f..36061c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1597,18 +1597,6 @@ atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" -autoprefixer@^7.1.1: - version "7.2.6" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.2.6.tgz#256672f86f7c735da849c4f07d008abb056067dc" - integrity sha512-Iq8TRIB+/9eQ8rbGhcP7ct5cYb/3qjNYAR2SnzLCEcwF6rvVOax8+9+fccgXk4bEhQGjOZd5TLhsksmAdsbGqQ== - dependencies: - browserslist "^2.11.3" - caniuse-lite "^1.0.30000805" - normalize-range "^0.1.2" - num2fraction "^1.2.2" - postcss "^6.0.17" - postcss-value-parser "^3.2.3" - autoprefixer@^9.6.1: version "9.8.6" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" @@ -1735,7 +1723,7 @@ babel-preset-react@^6.24.1: babel-plugin-transform-react-jsx-source "^6.22.0" babel-preset-flow "^6.23.0" -babel-runtime@6.x, babel-runtime@^6.22.0, babel-runtime@^6.23.0, babel-runtime@^6.26.0: +babel-runtime@6.x, babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1751,16 +1739,6 @@ babel-types@^6.26.0: lodash "^4.17.4" to-fast-properties "^1.0.3" -balanced-match@0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.1.0.tgz#b504bd05869b39259dd0c5efc35d843176dccc4a" - integrity sha1-tQS9BYabOSWd0MXvw12EMXbczEo= - -balanced-match@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" - integrity sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg= - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -2033,14 +2011,6 @@ browserify@^16.5.2: vm-browserify "^1.0.0" xtend "^4.0.0" -browserslist@^2.0.0, browserslist@^2.11.3: - version "2.11.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2" - integrity sha512-yWu5cXT7Av6mVwzWc8lMsJMHWn4xyjSuGYi4IozbVTLUOEYPSagUB8kiMDUHA1fS3zjr8nkxkn9jdvug4BBRmA== - dependencies: - caniuse-lite "^1.0.30000792" - electron-to-chromium "^1.3.30" - browserslist@^4.0.0: version "4.6.6" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453" @@ -2198,16 +2168,6 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-api@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-2.0.0.tgz#b1ddb5a5966b16f48dc4998444d4bbc6c7d9d834" - integrity sha1-sd21pZZrFvSNxJmERNS7xsfZ2DQ= - dependencies: - browserslist "^2.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -2221,7 +2181,7 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000984: version "1.0.30000989" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000989.tgz#b9193e293ccf7e4426c5245134b8f2a56c0ac4b9" -caniuse-lite@^1.0.30000792, caniuse-lite@^1.0.30000805, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001135: +caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001135: version "1.0.30001144" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001144.tgz#bca0fffde12f97e1127a351fec3bfc1971aa3b3d" integrity sha512-4GQTEWNMnVZVOFG3BK0xvGeaDAtiPAbG2N8yuMXuXzx/c2Vd4XoMPO8+E918zeXn5IF0FRVtGShBfkfQea2wHQ== @@ -2249,7 +2209,7 @@ chalk@^1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" dependencies: @@ -2358,11 +2318,6 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= - coa@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" @@ -2382,7 +2337,7 @@ collection-visit@^1.0.0: map-visit "^1.0.0" object-visit "^1.0.0" -color-convert@^1.3.0, color-convert@^1.8.2, color-convert@^1.9.0, color-convert@^1.9.1: +color-convert@^1.9.0, color-convert@^1.9.1: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" dependencies: @@ -2403,45 +2358,13 @@ color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" -color-string@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" - integrity sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE= - dependencies: - color-name "^1.0.0" - -color-string@^1.4.0, color-string@^1.5.2: +color-string@^1.5.2: version "1.5.3" resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" -color@^0.11.0: - version "0.11.4" - resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" - integrity sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q= - dependencies: - clone "^1.0.2" - color-convert "^1.3.0" - color-string "^0.3.0" - -color@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/color/-/color-1.0.3.tgz#e48e832d85f14ef694fb468811c2d5cfe729b55d" - integrity sha1-5I6DLYXxTvaU+0aIEcLVz+cptV0= - dependencies: - color-convert "^1.8.2" - color-string "^1.4.0" - -color@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color/-/color-2.0.1.tgz#e4ed78a3c4603d0891eba5430b04b86314f4c839" - integrity sha512-ubUCVVKfT7r2w2D3qtHakj8mbmKms+tThR8gI8zEYCbUBl8/voqFGt3kgBqGwXAopgXybnkuOq+qMYCRrp4cXw== - dependencies: - color-convert "^1.9.1" - color-string "^1.5.2" - color@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/color/-/color-3.1.2.tgz#68148e7f85d41ad7649c5fa8c8106f098d229e10" @@ -2776,16 +2699,6 @@ css-box-model@^1.2.0: dependencies: tiny-invariant "^1.0.6" -css-color-function@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/css-color-function/-/css-color-function-1.3.3.tgz#8ed24c2c0205073339fafa004bc8c141fccb282e" - integrity sha1-jtJMLAIFBzM5+voAS8jBQfzLKC4= - dependencies: - balanced-match "0.1.0" - color "^0.11.0" - debug "^3.1.0" - rgb "~0.1.0" - css-color-names@0.0.4, css-color-names@^0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" @@ -2997,7 +2910,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: +debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" dependencies: @@ -3278,7 +3191,7 @@ electron-to-chromium@^1.3.191: version "1.3.241" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.241.tgz#859dc49ab7f90773ed698767372d384190f60cb1" -electron-to-chromium@^1.3.30, electron-to-chromium@^1.3.571: +electron-to-chromium@^1.3.571: version "1.3.578" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.578.tgz#e6671936f4571a874eb26e2e833aa0b2c0b776e0" integrity sha512-z4gU6dA1CbBJsAErW5swTGAaU2TBzc2mPAonJb00zqW1rOraDo2zfBMDRvaz9cVic+0JEZiYbHWPw/fTaZlG2Q== @@ -4970,11 +4883,6 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" -isnumeric@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64" - integrity sha1-ojR7o2DeGeM9D/1ZD933dVy/LmQ= - isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" @@ -5322,7 +5230,7 @@ lodash.memoize@~3.0.3: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" -lodash.template@^4.2.4, lodash.template@^4.5.0: +lodash.template@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== @@ -5431,11 +5339,6 @@ marked@^1.2.0: resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.0.tgz#7221ce2395fa6cf6d722e6f2871a32d3513c85ca" integrity sha512-tiRxakgbNPBr301ihe/785NntvYyhxlqcL3YaC8CaxJQh7kiaEtrN9B/eK2I2943Yjkh5gw25chYFDQhOMCwMA== -math-expression-evaluator@^1.2.14: - version "1.2.22" - resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.22.tgz#c14dcb3d8b4d150e5dcea9c68c8dad80309b0d5e" - integrity sha512-L0j0tFVZBQQLeEjmWOvDLoRciIY8gQGWahvkztXUal8jH8R5Rlqo9GCvgqvXcy9LQhEWdQCVvzqAbxgYNt4blQ== - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -6138,11 +6041,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onecolor@^3.0.4: - version "3.1.0" - resolved "https://registry.yarnpkg.com/onecolor/-/onecolor-3.1.0.tgz#b72522270a49569ac20d244b3cd40fe157fda4d2" - integrity sha512-YZSypViXzu3ul5LMu/m6XjJ9ol8qAy9S2VjHl5E6UlhUH1KGKWabyEJifn0Jjpw23bYDzC2ucKMPGiH5kfwSGQ== - opn@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.5.0.tgz#fc7164fab56d235904c51c3b27da6758ca3b9bfc" @@ -6463,15 +6361,6 @@ pinkie@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" -pixrem@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pixrem/-/pixrem-4.0.1.tgz#2da4a1de6ec4423c5fc3794e930b81d4490ec686" - integrity sha1-LaSh3m7EQjxfw3lOkwuB1EkOxoY= - dependencies: - browserslist "^2.0.0" - postcss "^6.0.0" - reduce-css-calc "^1.2.7" - pkg-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" @@ -6492,14 +6381,6 @@ pkg-dir@^4.1.0: dependencies: find-up "^4.0.0" -pleeease-filters@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/pleeease-filters/-/pleeease-filters-4.0.0.tgz#6632b2fb05648d2758d865384fbced79e1ccaec7" - integrity sha1-ZjKy+wVkjSdY2GU4T7zteeHMrsc= - dependencies: - onecolor "^3.0.4" - postcss "^6.0.1" - pnp-webpack-plugin@^1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" @@ -6529,23 +6410,6 @@ posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" -postcss-apply@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/postcss-apply/-/postcss-apply-0.8.0.tgz#14e544bbb5cb6f1c1e048857965d79ae066b1343" - integrity sha1-FOVEu7XLbxweBIhXll15rgZrE0M= - dependencies: - babel-runtime "^6.23.0" - balanced-match "^0.4.2" - postcss "^6.0.0" - -postcss-attribute-case-insensitive@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-2.0.0.tgz#94dc422c8f90997f16bd33a3654bbbec084963b4" - integrity sha1-lNxCLI+QmX8WvTOjZUu77AhJY7Q= - dependencies: - postcss "^6.0.0" - postcss-selector-parser "^2.2.3" - postcss-attribute-case-insensitive@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.2.tgz#d93e46b504589e94ac7277b0463226c68041a880" @@ -6554,16 +6418,6 @@ postcss-attribute-case-insensitive@^4.0.1: postcss "^7.0.2" postcss-selector-parser "^6.0.2" -postcss-calc@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-6.0.2.tgz#4d9a43e27dbbf27d095fecb021ac6896e2318337" - integrity sha512-fiznXjEN5T42Qm7qqMCVJXS3roaj9r4xsSi+meaBVe7CJBl8t/QLOXu02Z2E6oWAMWIvCuF6JrvzFekmVEbOKA== - dependencies: - css-unit-converter "^1.1.1" - postcss "^7.0.2" - postcss-selector-parser "^2.2.2" - reduce-css-calc "^2.0.0" - postcss-calc@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.1.tgz#36d77bab023b0ecbb9789d84dcb23c4941145436" @@ -6573,16 +6427,6 @@ postcss-calc@^7.0.1: postcss-selector-parser "^5.0.0-rc.4" postcss-value-parser "^3.3.1" -postcss-color-function@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-color-function/-/postcss-color-function-4.1.0.tgz#b6f9355e07b12fcc5c34dab957834769b03d8f57" - integrity sha512-2/fuv6mP5Lt03XbRpVfMdGC8lRP1sykme+H1bR4ARyOmSMB8LPSjcL6EAI1iX6dqUF+jNEvKIVVXhan1w/oFDQ== - dependencies: - css-color-function "~1.3.3" - postcss "^6.0.23" - postcss-message-helpers "^2.0.0" - postcss-value-parser "^3.3.1" - postcss-color-functional-notation@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz#5efd37a88fbabeb00a2966d1e53d98ced93f74e0" @@ -6591,16 +6435,6 @@ postcss-color-functional-notation@^2.0.1: postcss "^7.0.2" postcss-values-parser "^2.0.0" -postcss-color-gray@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-4.1.0.tgz#e5581ed57eaa826fb652ca11b1e2b7b136a9f9df" - integrity sha512-L4iLKQLdqChz6ZOgGb6dRxkBNw78JFYcJmBz1orHpZoeLtuhDDGegRtX9gSyfoCIM7rWZ3VNOyiqqvk83BEN+w== - dependencies: - color "^2.0.1" - postcss "^6.0.14" - postcss-message-helpers "^2.0.0" - reduce-function-call "^1.0.2" - postcss-color-gray@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz#532a31eb909f8da898ceffe296fdc1f864be8547" @@ -6610,15 +6444,6 @@ postcss-color-gray@^5.0.0: postcss "^7.0.5" postcss-values-parser "^2.0.0" -postcss-color-hex-alpha@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-3.0.0.tgz#1e53e6c8acb237955e8fd08b7ecdb1b8b8309f95" - integrity sha1-HlPmyKyyN5Vej9CLfs2xuLgwn5U= - dependencies: - color "^1.0.3" - postcss "^6.0.1" - postcss-message-helpers "^2.0.0" - postcss-color-hex-alpha@^5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.3.tgz#a8d9ca4c39d497c9661e374b9c51899ef0f87388" @@ -6627,25 +6452,6 @@ postcss-color-hex-alpha@^5.0.3: postcss "^7.0.14" postcss-values-parser "^2.0.1" -postcss-color-hsl@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-color-hsl/-/postcss-color-hsl-2.0.0.tgz#12703666fa310430e3f30a454dac1386317d5844" - integrity sha1-EnA2ZvoxBDDj8wpFTawThjF9WEQ= - dependencies: - postcss "^6.0.1" - postcss-value-parser "^3.3.0" - units-css "^0.4.0" - -postcss-color-hwb@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-color-hwb/-/postcss-color-hwb-3.0.0.tgz#3402b19ef4d8497540c1fb5072be9863ca95571e" - integrity sha1-NAKxnvTYSXVAwftQcr6YY8qVVx4= - dependencies: - color "^1.0.3" - postcss "^6.0.1" - postcss-message-helpers "^2.0.0" - reduce-function-call "^1.0.2" - postcss-color-mod-function@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz#816ba145ac11cc3cb6baa905a75a49f903e4d31d" @@ -6655,14 +6461,6 @@ postcss-color-mod-function@^3.0.3: postcss "^7.0.2" postcss-values-parser "^2.0.0" -postcss-color-rebeccapurple@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-3.1.0.tgz#ce1269ecc2d0d8bf92aab44bd884e633124c33ec" - integrity sha512-212hJUk9uSsbwO5ECqVjmh/iLsmiVL1xy9ce9TVf+X3cK/ZlUIlaMdoxje/YpsL9cmUH3I7io+/G2LyWx5rg1g== - dependencies: - postcss "^6.0.22" - postcss-values-parser "^1.5.0" - postcss-color-rebeccapurple@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz#c7a89be872bb74e45b1e3022bfe5748823e6de77" @@ -6671,23 +6469,6 @@ postcss-color-rebeccapurple@^4.0.1: postcss "^7.0.2" postcss-values-parser "^2.0.0" -postcss-color-rgb@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-color-rgb/-/postcss-color-rgb-2.0.0.tgz#14539c8a7131494b482e0dd1cc265ff6514b5263" - integrity sha1-FFOcinExSUtILg3RzCZf9lFLUmM= - dependencies: - postcss "^6.0.1" - postcss-value-parser "^3.3.0" - -postcss-color-rgba-fallback@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-color-rgba-fallback/-/postcss-color-rgba-fallback-3.0.0.tgz#37d5c9353a07a09270912a82606bb42a0d702c04" - integrity sha1-N9XJNToHoJJwkSqCYGu0Kg1wLAQ= - dependencies: - postcss "^6.0.6" - postcss-value-parser "^3.3.0" - rgb-hex "^2.1.0" - postcss-colormin@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381" @@ -6705,50 +6486,6 @@ postcss-convert-values@^4.0.1: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-cssnext@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/postcss-cssnext/-/postcss-cssnext-3.1.0.tgz#927dc29341a938254cde38ea60a923b9dfedead9" - integrity sha512-awPDhI4OKetcHCr560iVCoDuP6e/vn0r6EAqdWPpAavJMvkBSZ6kDpSN4b3mB3Ti57hQMunHHM8Wvx9PeuYXtA== - dependencies: - autoprefixer "^7.1.1" - caniuse-api "^2.0.0" - chalk "^2.0.1" - pixrem "^4.0.0" - pleeease-filters "^4.0.0" - postcss "^6.0.5" - postcss-apply "^0.8.0" - postcss-attribute-case-insensitive "^2.0.0" - postcss-calc "^6.0.0" - postcss-color-function "^4.0.0" - postcss-color-gray "^4.0.0" - postcss-color-hex-alpha "^3.0.0" - postcss-color-hsl "^2.0.0" - postcss-color-hwb "^3.0.0" - postcss-color-rebeccapurple "^3.0.0" - postcss-color-rgb "^2.0.0" - postcss-color-rgba-fallback "^3.0.0" - postcss-custom-media "^6.0.0" - postcss-custom-properties "^6.1.0" - postcss-custom-selectors "^4.0.1" - postcss-font-family-system-ui "^3.0.0" - postcss-font-variant "^3.0.0" - postcss-image-set-polyfill "^0.3.5" - postcss-initial "^2.0.0" - postcss-media-minmax "^3.0.0" - postcss-nesting "^4.0.1" - postcss-pseudo-class-any-link "^4.0.0" - postcss-pseudoelements "^5.0.0" - postcss-replace-overflow-wrap "^2.0.0" - postcss-selector-matches "^3.0.1" - postcss-selector-not "^3.0.1" - -postcss-custom-media@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-6.0.0.tgz#be532784110ecb295044fb5395a18006eb21a737" - integrity sha1-vlMnhBEOyylQRPtTlaGABushpzc= - dependencies: - postcss "^6.0.1" - postcss-custom-media@^7.0.8: version "7.0.8" resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-7.0.8.tgz#fffd13ffeffad73621be5f387076a28b00294e0c" @@ -6756,14 +6493,6 @@ postcss-custom-media@^7.0.8: dependencies: postcss "^7.0.14" -postcss-custom-properties@^6.1.0: - version "6.3.1" - resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-6.3.1.tgz#5c52abde313d7ec9368c4abf67d27a656cba8b39" - integrity sha512-zoiwn4sCiUFbr4KcgcNZLFkR6gVQom647L+z1p/KBVHZ1OYwT87apnS42atJtx6XlX2yI7N5fjXbFixShQO2QQ== - dependencies: - balanced-match "^1.0.0" - postcss "^6.0.18" - postcss-custom-properties@^8.0.11: version "8.0.11" resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-8.0.11.tgz#2d61772d6e92f22f5e0d52602df8fae46fa30d97" @@ -6772,14 +6501,6 @@ postcss-custom-properties@^8.0.11: postcss "^7.0.17" postcss-values-parser "^2.0.1" -postcss-custom-selectors@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-4.0.1.tgz#781382f94c52e727ef5ca4776ea2adf49a611382" - integrity sha1-eBOC+UxS5yfvXKR3bqKt9JphE4I= - dependencies: - postcss "^6.0.1" - postcss-selector-matches "^3.0.0" - postcss-custom-selectors@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz#64858c6eb2ecff2fb41d0b28c9dd7b3db4de7fba" @@ -6857,20 +6578,6 @@ postcss-focus-within@^3.0.0: dependencies: postcss "^7.0.2" -postcss-font-family-system-ui@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-font-family-system-ui/-/postcss-font-family-system-ui-3.0.0.tgz#675fe7a9e029669f05f8dba2e44c2225ede80623" - integrity sha512-58G/hTxMSSKlIRpcPUjlyo6hV2MEzvcVO2m4L/T7Bb2fJTG4DYYfQjQeRvuimKQh1V1sOzCIz99g+H2aFNtlQw== - dependencies: - postcss "^6.0" - -postcss-font-variant@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-3.0.0.tgz#08ccc88f6050ba82ed8ef2cc76c0c6a6b41f183e" - integrity sha1-CMzIj2BQuoLtjvLMdsDGprQfGD4= - dependencies: - postcss "^6.0.1" - postcss-font-variant@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz#71dd3c6c10a0d846c5eda07803439617bbbabacc" @@ -6893,14 +6600,6 @@ postcss-image-set-function@^3.0.1: postcss "^7.0.2" postcss-values-parser "^2.0.0" -postcss-image-set-polyfill@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/postcss-image-set-polyfill/-/postcss-image-set-polyfill-0.3.5.tgz#0f193413700cf1f82bd39066ef016d65a4a18181" - integrity sha1-Dxk0E3AM8fgr05Bm7wFtZaShgYE= - dependencies: - postcss "^6.0.1" - postcss-media-query-parser "^0.2.3" - postcss-import@^12.0.1: version "12.0.1" resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-12.0.1.tgz#cf8c7ab0b5ccab5649024536e565f841928b7153" @@ -6911,14 +6610,6 @@ postcss-import@^12.0.1: read-cache "^1.0.0" resolve "^1.1.7" -postcss-initial@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-2.0.0.tgz#72715f7336e0bb79351d99ee65c4a253a8441ba4" - integrity sha1-cnFfczbgu3k1HZnuZcSiU6hEG6Q= - dependencies: - lodash.template "^4.2.4" - postcss "^6.0.1" - postcss-initial@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" @@ -6960,13 +6651,6 @@ postcss-logical@^3.0.0: dependencies: postcss "^7.0.2" -postcss-media-minmax@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-3.0.0.tgz#675256037a43ef40bc4f0760bfd06d4dc69d48d2" - integrity sha1-Z1JWA3pD70C8Twdgv9BtTcadSNI= - dependencies: - postcss "^6.0.1" - postcss-media-minmax@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz#b75bb6cbc217c8ac49433e12f22048814a4f5ed5" @@ -6974,11 +6658,6 @@ postcss-media-minmax@^4.0.0: dependencies: postcss "^7.0.2" -postcss-media-query-parser@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz#27b39c6f4d94f81b1a73b8f76351c609e5cef244" - integrity sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ= - postcss-merge-longhand@^4.0.11: version "4.0.11" resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24" @@ -6999,11 +6678,6 @@ postcss-merge-rules@^4.0.3: postcss-selector-parser "^3.0.0" vendors "^1.0.0" -postcss-message-helpers@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" - integrity sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4= - postcss-minify-font-values@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6" @@ -7073,13 +6747,6 @@ postcss-modules-values@^3.0.0: icss-utils "^4.0.0" postcss "^7.0.6" -postcss-nesting@^4.0.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-4.2.1.tgz#0483bce338b3f0828ced90ff530b29b98b00300d" - integrity sha512-IkyWXICwagCnlaviRexi7qOdwPw3+xVVjgFfGsxmztvRVaNxAlrypOIKqDE5mxY+BVxnId1rnUKBRQoNE2VDaA== - dependencies: - postcss "^6.0.11" - postcss-nesting@^7.0.0: version "7.0.1" resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-7.0.1.tgz#b50ad7b7f0173e5b5e3880c3501344703e04c052" @@ -7232,14 +6899,6 @@ postcss-preset-env@^6.7.0: postcss-selector-matches "^4.0.0" postcss-selector-not "^4.0.0" -postcss-pseudo-class-any-link@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-4.0.0.tgz#9152a0613d3450720513e8892854bae42d0ee68e" - integrity sha1-kVKgYT00UHIFE+iJKFS65C0O5o4= - dependencies: - postcss "^6.0.1" - postcss-selector-parser "^2.2.3" - postcss-pseudo-class-any-link@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz#2ed3eed393b3702879dec4a87032b210daeb04d1" @@ -7248,13 +6907,6 @@ postcss-pseudo-class-any-link@^6.0.0: postcss "^7.0.2" postcss-selector-parser "^5.0.0-rc.3" -postcss-pseudoelements@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/postcss-pseudoelements/-/postcss-pseudoelements-5.0.0.tgz#eef194e8d524645ca520a949e95e518e812402cb" - integrity sha1-7vGU6NUkZFylIKlJ6V5RjoEkAss= - dependencies: - postcss "^6.0.0" - postcss-reduce-initial@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df" @@ -7273,13 +6925,6 @@ postcss-reduce-transforms@^4.0.2: postcss "^7.0.0" postcss-value-parser "^3.0.0" -postcss-replace-overflow-wrap@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-2.0.0.tgz#794db6faa54f8db100854392a93af45768b4e25b" - integrity sha1-eU22+qVPjbEAhUOSqTr0V2i04ls= - dependencies: - postcss "^6.0.1" - postcss-replace-overflow-wrap@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz#61b360ffdaedca84c7c918d2b0f0d0ea559ab01c" @@ -7294,14 +6939,6 @@ postcss-safe-parser@^4.0.2: dependencies: postcss "^7.0.26" -postcss-selector-matches@^3.0.0, postcss-selector-matches@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz#e5634011e13950881861bbdd58c2d0111ffc96ab" - integrity sha1-5WNAEeE5UIgYYbvdWMLQER/8lqs= - dependencies: - balanced-match "^0.4.2" - postcss "^6.0.1" - postcss-selector-matches@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz#71c8248f917ba2cc93037c9637ee09c64436fcff" @@ -7310,14 +6947,6 @@ postcss-selector-matches@^4.0.0: balanced-match "^1.0.0" postcss "^7.0.2" -postcss-selector-not@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz#2e4db2f0965336c01e7cec7db6c60dff767335d9" - integrity sha1-Lk2y8JZTNsAefOx9tsYN/3ZzNdk= - dependencies: - balanced-match "^0.4.2" - postcss "^6.0.1" - postcss-selector-not@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz#c68ff7ba96527499e832724a2674d65603b645c0" @@ -7326,15 +6955,6 @@ postcss-selector-not@^4.0.0: balanced-match "^1.0.0" postcss "^7.0.2" -postcss-selector-parser@^2.2.2, postcss-selector-parser@^2.2.3: - version "2.2.3" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" - integrity sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A= - dependencies: - flatten "^1.0.2" - indexes-of "^1.0.1" - uniq "^1.0.1" - postcss-selector-parser@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz#4f875f4afb0c96573d5cf4d74011aee250a7e865" @@ -7378,7 +6998,7 @@ postcss-unique-selectors@^4.0.1: postcss "^7.0.0" uniqs "^2.0.0" -postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1: +postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" @@ -7387,15 +7007,6 @@ postcss-value-parser@^4.1.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -postcss-values-parser@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-1.5.0.tgz#5d9fa63e2bcb0179ce48f3235303765eb89f3047" - integrity sha512-3M3p+2gMp0AH3da530TlX8kiO1nxdTnc3C6vr8dMxRLIlh8UYkz0/wcwptSXjhtx2Fr0TySI7a+BHDQ8NL7LaQ== - dependencies: - flatten "^1.0.2" - indexes-of "^1.0.1" - uniq "^1.0.1" - postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" @@ -7405,15 +7016,6 @@ postcss-values-parser@^2.0.0, postcss-values-parser@^2.0.1: indexes-of "^1.0.1" uniq "^1.0.1" -postcss@^6.0, postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.11, postcss@^6.0.14, postcss@^6.0.17, postcss@^6.0.18, postcss@^6.0.22, postcss@^6.0.23, postcss@^6.0.5, postcss@^6.0.6: - version "6.0.23" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" - integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== - dependencies: - chalk "^2.4.1" - source-map "^0.6.1" - supports-color "^5.4.0" - postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.2, postcss@^7.0.5: version "7.0.17" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f" @@ -7958,30 +7560,6 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" -reduce-css-calc@^1.2.7: - version "1.3.0" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" - integrity sha1-dHyRTgSWFKTJz7umKYca0dKSdxY= - dependencies: - balanced-match "^0.4.2" - math-expression-evaluator "^1.2.14" - reduce-function-call "^1.0.1" - -reduce-css-calc@^2.0.0: - version "2.1.7" - resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-2.1.7.tgz#1ace2e02c286d78abcd01fd92bfe8097ab0602c2" - integrity sha512-fDnlZ+AybAS3C7Q9xDq5y8A2z+lT63zLbynew/lur/IR24OQF5x98tfNwf79mzEdfywZ0a2wpM860FhFfMxZlA== - dependencies: - css-unit-converter "^1.1.1" - postcss-value-parser "^3.3.0" - -reduce-function-call@^1.0.1, reduce-function-call@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.3.tgz#60350f7fb252c0a67eb10fd4694d16909971300f" - integrity sha512-Hl/tuV2VDgWgCSEeWMLwxLZqX7OK59eU1guxXsRKTAyeYimivsKdtcV4fu3r710tpG5GmDKDhQ0HSZLExnNmyQ== - dependencies: - balanced-match "^1.0.0" - redux@^4.0.4: version "4.0.5" resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f" @@ -8171,20 +7749,10 @@ retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= -rgb-hex@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/rgb-hex/-/rgb-hex-2.1.0.tgz#c773c5fe2268a25578d92539a82a7a5ce53beda6" - integrity sha1-x3PF/iJoolV42SU5qCp6XOU77aY= - rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" -rgb@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/rgb/-/rgb-0.1.0.tgz#be27b291e8feffeac1bd99729721bfa40fc037b5" - integrity sha1-vieykej+/+rBvZlylyG/pA/AN7U= - rgba-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3" @@ -8927,7 +8495,7 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^5.3.0, supports-color@^5.4.0: +supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" dependencies: @@ -9341,14 +8909,6 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -units-css@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07" - integrity sha1-1iKGU6UZg9fBb/KPi53Dsf/tOgc= - dependencies: - isnumeric "^0.2.0" - viewport-dimensions "^0.2.0" - universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -9499,11 +9059,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -viewport-dimensions@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c" - integrity sha1-3nQHR9tTh/0XJfUXXpG6x2r982w= - vm-browserify@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" -- GitLab From a12e20bbe2343f0fe83b93791babe84269489f6e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 8 Oct 2020 13:09:29 -1000 Subject: [PATCH 143/287] adding module to auto size the pdfs --- .../components/content_files/PDFRenderer.tsx | 44 +++++++++++-------- package.json | 1 + yarn.lock | 29 +++++++++++- 3 files changed, 55 insertions(+), 19 deletions(-) diff --git a/app/javascript/components/content_files/PDFRenderer.tsx b/app/javascript/components/content_files/PDFRenderer.tsx index 9f9ebe8..803de67 100644 --- a/app/javascript/components/content_files/PDFRenderer.tsx +++ b/app/javascript/components/content_files/PDFRenderer.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { SizeMe } from 'react-sizeme' import { Document, Page, pdfjs } from 'react-pdf/dist/entry.webpack'; pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; @@ -31,24 +32,31 @@ export default class PDFRenderer extends React.Component { View on Github )} -
    - - { - Array.from( - new Array(this.state.numPages), - (_el, index) => ( - - ), - ) - } - -
    + + {({ size }) => ( +
    + + { + Array.from( + new Array(this.state.numPages), + (_el, index) => ( + + ), + ) + } + +
    + )} +
    ) } diff --git a/package.json b/package.json index cc79cf0..099c822 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "react-paginate": "^6.5.0", "react-pdf": "^4.2.0", "react-scroll-sync": "^0.8.0", + "react-sizeme": "^2.6.12", "react-textarea-autosize": "^8.2.0", "react-tooltip": "^4.2.10", "react_ujs": "^2.6.1", diff --git a/yarn.lock b/yarn.lock index 36061c0..f8fc261 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1763,6 +1763,11 @@ base@^0.11.1: mixin-deep "^1.2.0" pascalcase "^0.1.1" +batch-processor@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/batch-processor/-/batch-processor-1.0.0.tgz#75c95c32b748e0850d10c2b168f6bdbe9891ace8" + integrity sha1-dclcMrdI4IUNEMKxaPa9vpiRrOg= + batch@0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" @@ -3196,6 +3201,13 @@ electron-to-chromium@^1.3.571: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.578.tgz#e6671936f4571a874eb26e2e833aa0b2c0b776e0" integrity sha512-z4gU6dA1CbBJsAErW5swTGAaU2TBzc2mPAonJb00zqW1rOraDo2zfBMDRvaz9cVic+0JEZiYbHWPw/fTaZlG2Q== +element-resize-detector@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/element-resize-detector/-/element-resize-detector-1.2.1.tgz#b0305194447a4863155e58f13323a0aef30851d1" + integrity sha512-BdFsPepnQr9fznNPF9nF4vQ457U/ZJXQDSNF1zBe7yaga8v9AdZf3/NElYxFdUh7SitSGt040QygiTo6dtatIw== + dependencies: + batch-processor "1.0.0" + elliptic@^6.0.0: version "6.5.0" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.0.tgz#2b8ed4c891b7de3200e14412a5b8248c7af505ca" @@ -7403,6 +7415,16 @@ react-scroll-sync@^0.8.0: resolved "https://registry.yarnpkg.com/react-scroll-sync/-/react-scroll-sync-0.8.0.tgz#61fabed2afc47d41e6938819d620799da3610548" integrity sha512-Ms9srm41UM+lWexuqdocXjqaqqt6ZRSFxcudgB0sYhC7Or+m12WemTwY8BaQCRf7hA8zHDk55FHvMkqsH7gF+w== +react-sizeme@^2.6.12: + version "2.6.12" + resolved "https://registry.yarnpkg.com/react-sizeme/-/react-sizeme-2.6.12.tgz#ed207be5476f4a85bf364e92042520499455453e" + integrity sha512-tL4sCgfmvapYRZ1FO2VmBmjPVzzqgHA7kI8lSJ6JS6L78jXFNRdOZFpXyK6P1NBZvKPPCZxReNgzZNUajAerZw== + dependencies: + element-resize-detector "^1.2.1" + invariant "^2.2.4" + shallowequal "^1.1.0" + throttle-debounce "^2.1.0" + react-textarea-autosize@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz#fae38653f5ec172a855fd5fffb39e466d56aebdb" @@ -8007,7 +8029,7 @@ shallowequal@^0.2.2: dependencies: lodash.keys "^3.1.2" -shallowequal@^1.0.1: +shallowequal@^1.0.1, shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" @@ -8642,6 +8664,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +throttle-debounce@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz#fd31865e66502071e411817e241465b3e9c372e2" + integrity sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ== + through2@^2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" -- GitLab From 2428c0141444cc329bdb7dee306fd552df3d965a Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 8 Oct 2020 14:44:57 -1000 Subject: [PATCH 144/287] Removed unused files --- .../webhooks/auth/cohorts_controller.rb | 34 -------- .../webhooks/auth/users_controller.rb | 24 ------ app/services/auth_resolver_service.rb | 85 ------------------- 3 files changed, 143 deletions(-) delete mode 100644 app/controllers/webhooks/auth/cohorts_controller.rb delete mode 100644 app/controllers/webhooks/auth/users_controller.rb delete mode 100644 app/services/auth_resolver_service.rb diff --git a/app/controllers/webhooks/auth/cohorts_controller.rb b/app/controllers/webhooks/auth/cohorts_controller.rb deleted file mode 100644 index c51cc92..0000000 --- a/app/controllers/webhooks/auth/cohorts_controller.rb +++ /dev/null @@ -1,34 +0,0 @@ -module Webhooks - module Auth - class CohortsController < ActionController::Base - def update - cohort = AuthResolverService.resolve(request.env[:resolved_resource]) - - if cohort.nil? - error_message = "Provided cohort is not compatible with Learn V2" - results = { cohort: cohort_params, errors: [error_message] } - - Honeybadger.notify( - error_message, - context: { auth_resource: request.env[:resolved_resource] } - ) - - render json: { errors: results }, status: 400 - elsif cohort.errors.empty? - render json: {}, status: 200 - else - results = { cohort: cohort_params, errors: cohort.errors.full_messages } - - Honeybadger.notify("Invalid cohort payload", context: results) - render json: { errors: results }, status: 400 - end - end - - private - - def cohort_params - params.require(:data).permit! - end - end - end -end diff --git a/app/controllers/webhooks/auth/users_controller.rb b/app/controllers/webhooks/auth/users_controller.rb deleted file mode 100644 index 866f006..0000000 --- a/app/controllers/webhooks/auth/users_controller.rb +++ /dev/null @@ -1,24 +0,0 @@ -module Webhooks - module Auth - class UsersController < ActionController::Base - def update - user = AuthResolverService.resolve(request.env[:resolved_resource]) - - if user.errors.empty? - render json: {}, status: 200 - else - results = { user: user_params, errors: user.errors.full_messages } - - Honeybadger.notify("Invalid user payload", context: results) - render json: { errors: results }, status: 400 - end - end - - private - - def user_params - params.require(:data).permit! - end - end - end -end diff --git a/app/services/auth_resolver_service.rb b/app/services/auth_resolver_service.rb deleted file mode 100644 index 3601e48..0000000 --- a/app/services/auth_resolver_service.rb +++ /dev/null @@ -1,85 +0,0 @@ -class AuthResolverService - attr_accessor :auth_user - - def self.resolve(auth_resource) - # TODO: AuthApi fix - # case auth_resource - # when AuthApi::User - # new.resolve_user(auth_resource) - # when AuthApi::Product - # new.resolve_cohort(auth_resource) - # end - puts "Removed resolver" - end - - def resolve_user(auth_user) - @auth_user = auth_user - user = find_user_from(auth_user) - - update_user(user) - rescue ActiveRecord::ActiveRecordError => e - user.errors.add(:base, e.message) - user - end - - def resolve_cohort(auth_product) - return unless product_type_matches_forge?(auth_product) - - cohort = find_cohort_from(auth_product) - attrs = auth_product.attribute_intersection(cohort) - attrs[:deleted_at] = DateTime.current if auth_product.webhook_event == "product.destroy" - cohort.update(attrs) - cohort - end - - private - - def update_user(user) - attrs = auth_user.attribute_intersection(user) - - User.transaction do - user.update(attrs) - - if auth_user.attributes.dig(:is_deleted) - CohortUser.where(user: user).destroy_all - else - old_cohort_ids = CohortUser.where(user: user).pluck(:cohort_id) - - course_registrations.each do |registration| - cohort = resolve_cohort(registration.product) - next if cohort.blank? - - old_cohort_ids.delete(cohort.id) - - cohort_user = CohortUser.find_or_initialize_by(user: user, cohort: cohort) - cohort_user.update!(roles: registration.roles) - end - CohortUser.where(user: user, cohort_id: old_cohort_ids).destroy_all - end - end - - user - end - - def find_user_from(auth_user) - User.find_by(uid: auth_user.uid) || User.new - end - - def find_cohort_from(auth_product) - Cohort.find_by(uid: auth_product.uid) || Cohort.new - end - - def product_type_matches_forge?(auth_product) - auth_product.product_type.present? - end - - def course_registrations - auth_user.registrations.select { |r| product_type_matches_forge?(r.product) } - end - - def registration_unchanged?(old_registration_role, auth_product_role) - return false if old_registration_role.nil? - - old_registration_role == auth_product_role - end -end -- GitLab From d45b8a92d1085763180a6c459c8c4f3411435ed5 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Fri, 9 Oct 2020 19:41:58 -1000 Subject: [PATCH 146/287] Initial pass fixing bundle update --- Gemfile.lock | 339 ++++++------ Gemfile.lock.new | 488 ++++++++++++++++++ Gemfile.lock.old | 476 +++++++++++++++++ Gemfile.outdated | 142 +++++ .../mobile/_submissions-dashboard-table.scss | 4 +- 5 files changed, 1278 insertions(+), 171 deletions(-) create mode 100644 Gemfile.lock.new create mode 100644 Gemfile.lock.old create mode 100644 Gemfile.outdated diff --git a/Gemfile.lock b/Gemfile.lock index 34ea2ba..3d86107 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,14 +16,14 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (5.2.1) - actionpack (= 5.2.1) + actioncable (5.2.4.4) + actionpack (= 5.2.4.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.1) - actionpack (= 5.2.1) - actionview (= 5.2.1) - activejob (= 5.2.1) + actionmailer (5.2.4.4) + actionpack (= 5.2.4.4) + actionview (= 5.2.4.4) + activejob (= 5.2.4.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) actionpack (5.2.1) @@ -33,26 +33,26 @@ GEM rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.1) - activesupport (= 5.2.1) + actionview (5.2.4.4) + activesupport (= 5.2.4.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.1) - activesupport (= 5.2.1) + activejob (5.2.4.4) + activesupport (= 5.2.4.4) globalid (>= 0.3.6) - activemodel (5.2.1) - activesupport (= 5.2.1) - activerecord (5.2.1) - activemodel (= 5.2.1) - activesupport (= 5.2.1) + activemodel (5.2.4.4) + activesupport (= 5.2.4.4) + activerecord (5.2.4.4) + activemodel (= 5.2.4.4) + activesupport (= 5.2.4.4) arel (>= 9.0) - activestorage (5.2.1) - actionpack (= 5.2.1) - activerecord (= 5.2.1) + activestorage (5.2.4.4) + actionpack (= 5.2.4.4) + activerecord (= 5.2.4.4) marcel (~> 0.3.1) - activesupport (5.2.1) + activesupport (5.2.4.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -63,9 +63,10 @@ GEM kramdown railties arel (9.0.0) - ast (2.4.0) - autoprefixer-rails (7.2.5) + ast (2.4.1) + autoprefixer-rails (10.0.1.0) execjs + aws-eventstream (1.1.0) aws-sdk (2.6.50) aws-sdk-resources (= 2.6.50) aws-sdk-core (2.6.50) @@ -73,29 +74,30 @@ GEM jmespath (~> 1.0) aws-sdk-resources (2.6.50) aws-sdk-core (= 2.6.50) - aws-sigv4 (1.0.2) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) babel-source (5.8.35) babel-transpiler (0.7.0) babel-source (>= 4.0, < 6) execjs (~> 2.0) - barnes (0.0.7) + barnes (0.0.8) multi_json (~> 1) statsd-ruby (~> 1.1) - better_errors (2.5.0) + better_errors (2.8.3) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) binding_of_caller (0.8.0) debug_inspector (>= 0.0.1) - bootsnap (1.3.1) + bootsnap (1.4.8) msgpack (~> 1.0) bootstrap (4.0.0) autoprefixer-rails (>= 6.0.3) popper_js (>= 1.12.9, < 2) sass (>= 3.5.2) - browser (2.5.2) - builder (3.2.3) - byebug (9.1.0) + browser (5.1.0) + builder (3.2.4) + byebug (11.1.3) capybara (2.16.1) addressable mini_mime (>= 0.1.3) @@ -103,51 +105,51 @@ GEM rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - childprocess (0.8.0) - ffi (~> 1.0, >= 1.0.11) - coderay (1.1.2) - concurrent-ruby (1.1.5) - connection_pool (2.2.1) - crack (0.4.3) - safe_yaml (~> 1.0.0) - crass (1.0.4) - database_cleaner (1.6.2) + childprocess (3.0.0) + coderay (1.1.3) + concurrent-ruby (1.1.7) + connection_pool (2.2.3) + crack (0.4.4) + crass (1.0.6) + database_cleaner (1.8.5) debug_inspector (0.0.3) deterministic (0.6.0) - diff-lcs (1.3) + diff-lcs (1.4.4) docile (1.3.2) - dotenv (2.5.0) - dotenv-rails (2.5.0) - dotenv (= 2.5.0) - railties (>= 3.2, < 6.0) - dry-configurable (0.7.0) + dotenv (2.7.6) + dotenv-rails (2.7.6) + dotenv (= 2.7.6) + railties (>= 3.2) + dry-configurable (0.11.6) concurrent-ruby (~> 1.0) - dry-container (0.6.0) + dry-core (~> 0.4, >= 0.4.7) + dry-equalizer (~> 0.2) + dry-container (0.7.2) concurrent-ruby (~> 1.0) dry-configurable (~> 0.1, >= 0.1.3) - dry-core (0.4.7) + dry-core (0.4.9) concurrent-ruby (~> 1.0) - dry-equalizer (0.2.1) - dry-inflector (0.1.2) + dry-equalizer (0.3.0) + dry-inflector (0.2.0) dry-logic (0.4.2) dry-container (~> 0.2, >= 0.2.6) dry-core (~> 0.2) dry-equalizer (~> 0.2) - dry-types (0.13.2) + dry-types (0.13.4) concurrent-ruby (~> 1.0) dry-container (~> 0.3) dry-core (~> 0.4, >= 0.4.4) dry-equalizer (~> 0.2) dry-inflector (~> 0.1, >= 0.1.2) dry-logic (~> 0.4, >= 0.4.2) - dry-validation (0.12.2) + dry-validation (0.12.3) concurrent-ruby (~> 1.0) dry-configurable (~> 0.1, >= 0.1.3) dry-core (~> 0.2, >= 0.2.1) dry-equalizer (~> 0.2) - dry-logic (~> 0.4, >= 0.4.0) + dry-logic (~> 0.4.2) dry-types (~> 0.13.1) - erubi (1.7.1) + erubi (1.9.0) execjs (2.7.0) factory_bot (4.8.2) activesupport (>= 3.0.0) @@ -156,38 +158,37 @@ GEM railties (>= 3.0.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) - ffi (1.9.18) + ffi (1.13.1) flamegraph (0.9.5) - font-awesome-rails (4.7.0.4) - railties (>= 3.2, < 6.0) - foreman (0.84.0) - thor (~> 0.19.1) + font-awesome-rails (4.7.0.5) + railties (>= 3.2, < 6.1) + foreman (0.87.2) github-markdown (0.6.9) github-markup (1.6.1) github_url (0.2.1) gitlab (4.14.1) httparty (~> 0.14, >= 0.14.0) terminal-table (~> 1.5, >= 1.5.1) - globalid (0.4.1) + globalid (0.4.2) activesupport (>= 4.2.0) - haml (5.0.4) + haml (5.2.0) temple (>= 0.8.0) tilt - hashdiff (0.4.0) - honeybadger (3.2.0) + hashdiff (1.0.1) + honeybadger (3.3.1) httparty (0.15.6) multi_xml (>= 0.5.2) - i18n (1.6.0) + i18n (1.8.5) concurrent-ruby (~> 1.0) - iniparse (1.4.4) + iniparse (1.5.0) jaro_winkler (1.5.3) - jmespath (1.3.1) - jquery-rails (4.3.1) + jmespath (1.4.0) + jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - js-routes (1.4.2) - railties (>= 3.2) + js-routes (1.4.9) + railties (>= 4) sprockets-rails json_spec (1.1.5) multi_json (~> 1.0) @@ -198,88 +199,88 @@ GEM addressable (~> 2.3) letter_opener (1.7.0) launchy (~> 2.2) - loofah (2.2.2) + loofah (2.7.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.0) + mail (2.7.1) mini_mime (>= 0.1.1) - marcel (0.3.2) + marcel (0.3.3) mimemagic (~> 0.3.2) mathjax-rails (2.6.1) railties (>= 3.0) - memory_profiler (0.9.12) - method_source (0.9.0) - mimemagic (0.3.2) - mini_mime (1.0.0) + memory_profiler (0.9.14) + method_source (1.0.0) + mimemagic (0.3.5) + mini_mime (1.0.2) mini_portile2 (2.2.0) - minitest (5.11.3) - msgpack (1.2.4) - multi_json (1.13.1) + minitest (5.14.2) + msgpack (1.3.3) + multi_json (1.15.0) multi_xml (0.6.0) - multipart-post (2.0.0) - mustache (1.1.0) - nio4r (2.3.1) + multipart-post (2.1.1) + mustache (1.1.1) + nio4r (2.5.4) nokogiri (1.8.0) mini_portile2 (~> 2.2.0) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) - overcommit (0.41.0) - childprocess (~> 0.6, >= 0.6.3) + overcommit (0.57.0) + childprocess (>= 0.6.3, < 5) iniparse (~> 1.4) - pagy (3.7.1) - parallel (1.17.0) - parser (2.6.3.0) - ast (~> 2.4.0) + pagy (3.8.3) + parallel (1.19.2) + parser (2.7.2.0) + ast (~> 2.4.1) pg (0.21.0) - popper_js (1.12.9) + popper_js (1.16.0) psych (2.2.4) - puma (3.12.1) - pundit (1.1.0) + puma (3.12.6) + pundit (2.1.0) activesupport (>= 3.0.0) - rack (2.0.3) - rack-attack (6.2.1) + rack (2.2.3) + rack-attack (6.3.1) rack (>= 1.0, < 3) - rack-mini-profiler (1.0.0) + rack-mini-profiler (2.1.0) rack (>= 1.2.0) - rack-protection (2.0.0) + rack-protection (2.1.0) rack rack-proxy (0.6.5) rack - rack-test (0.8.2) + rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.1) - actioncable (= 5.2.1) - actionmailer (= 5.2.1) - actionpack (= 5.2.1) - actionview (= 5.2.1) - activejob (= 5.2.1) - activemodel (= 5.2.1) - activerecord (= 5.2.1) - activestorage (= 5.2.1) - activesupport (= 5.2.1) + rails (5.2.4.4) + actioncable (= 5.2.4.4) + actionmailer (= 5.2.4.4) + actionpack (= 5.2.4.4) + actionview (= 5.2.4.4) + activejob (= 5.2.4.4) + activemodel (= 5.2.4.4) + activerecord (= 5.2.4.4) + activestorage (= 5.2.4.4) + activesupport (= 5.2.4.4) bundler (>= 1.3.0) - railties (= 5.2.1) + railties (= 5.2.4.4) sprockets-rails (>= 2.0.0) - rails-controller-testing (1.0.2) - actionpack (~> 5.x, >= 5.0.1) - actionview (~> 5.x, >= 5.0.1) - activesupport (~> 5.x) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (5.2.1) - actionpack (= 5.2.1) - activesupport (= 5.2.1) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (5.2.4.4) + actionpack (= 5.2.4.4) + activesupport (= 5.2.4.4) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) rainbow (3.0.0) - rake (12.3.1) - rb-fsevent (0.10.2) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) + rake (13.0.1) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) react-rails (2.4.5) babel-transpiler (>= 0.7.0) connection_pool @@ -288,32 +289,32 @@ GEM tilt redcarpet (3.3.4) redis (3.3.5) - rspec (3.7.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-core (3.7.0) - rspec-support (~> 3.7.0) - rspec-expectations (3.7.0) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.3) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-mocks (3.7.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-rails (3.7.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-support (~> 3.7.0) - rspec-support (3.7.0) + rspec-support (~> 3.9.0) + rspec-rails (4.0.1) + actionpack (>= 4.2) + activesupport (>= 4.2) + railties (>= 4.2) + rspec-core (~> 3.9) + rspec-expectations (~> 3.9) + rspec-mocks (~> 3.9) + rspec-support (~> 3.9) + rspec-support (3.9.3) rspec_api_documentation (6.1.0) activesupport (>= 3.0.0) mustache (~> 1.0, >= 0.99.4) rspec (~> 3.0) - rspec_junit_formatter (0.3.0) + rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) rubocop (0.72.0) jaro_winkler (~> 1.5.1) @@ -323,15 +324,14 @@ GEM ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 1.7) ruby-progressbar (1.10.1) - rubyzip (1.2.1) - safe_yaml (1.0.5) - sass (3.5.3) + rubyzip (2.3.0) + sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) + sass-rails (5.1.0) + railties (>= 5.2.0) sass (~> 3.1) sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) @@ -339,50 +339,51 @@ GEM sawyer (0.7.0) addressable (>= 2.3.5, < 2.5) faraday (~> 0.8, < 0.10) - scout_apm (2.4.19) - selenium-webdriver (3.8.0) - childprocess (~> 0.5) - rubyzip (~> 1.0) + scout_apm (2.6.9) + parser + selenium-webdriver (3.142.7) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) semantic_range (2.3.0) - shoulda-matchers (3.1.2) - activesupport (>= 4.0.0) - sidekiq (5.0.5) - concurrent-ruby (~> 1.0) - connection_pool (~> 2.2, >= 2.2.0) + shoulda-matchers (4.4.1) + activesupport (>= 4.2.0) + sidekiq (5.2.9) + connection_pool (~> 2.2, >= 2.2.2) + rack (~> 2.0) rack-protection (>= 1.5.0) - redis (>= 3.3.4, < 5) + redis (>= 3.3.5, < 4.2) simplecov (0.19.0) docile (~> 1.1) simplecov-html (~> 0.11) simplecov-html (0.12.3) solid_use_case (2.2.0) deterministic (~> 0.6.0) - sprockets (3.7.1) + sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.1) + sprockets-rails (3.2.2) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - stackprof (0.2.12) + stackprof (0.2.15) statsd-ruby (1.4.0) - temple (0.8.0) + temple (0.8.2) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thor (0.19.4) + thor (1.0.1) thread_safe (0.3.6) - tilt (2.0.8) + tilt (2.0.10) timecop (0.9.1) - ts_routes (1.0.1) - railties (>= 5.0) - tzinfo (1.2.5) + ts_routes (1.0.3) + railties (>= 4.0) + tzinfo (1.2.7) thread_safe (~> 0.1) - uglifier (4.0.2) + uglifier (4.2.0) execjs (>= 0.3.0, < 3) underscore-rails (1.8.3) - unicode-display_width (1.6.0) - vcr (4.0.0) - webmock (3.6.0) + unicode-display_width (1.7.0) + vcr (6.0.0) + webmock (3.9.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -391,11 +392,11 @@ GEM rack-proxy (>= 0.6.1) railties (>= 5.2) semantic_range (>= 2.3.0) - websocket-driver (0.7.0) + websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) - xpath (2.1.0) - nokogiri (~> 1.3) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) zip-zip (0.3) rubyzip (>= 1.0.0) diff --git a/Gemfile.lock.new b/Gemfile.lock.new new file mode 100644 index 0000000..0a1bc01 --- /dev/null +++ b/Gemfile.lock.new @@ -0,0 +1,488 @@ +PATH + remote: gems/block-parser + specs: + block_parser (0.1.0) + activemodel (> 4.2) + github-markdown (= 0.6.9) + github-markup (= 1.6.1) + github_url (= 0.2.1) + gitlab (= 4.14.1) + nokogiri (= 1.8.0) + octokit (= 4.3.0) + psych (= 2.2.4) + redcarpet (= 3.3.4) + rspec_junit_formatter (> 0.2.3) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.4.4) + actionpack (= 5.2.4.4) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.4.4) + actionpack (= 5.2.4.4) + actionview (= 5.2.4.4) + activejob (= 5.2.4.4) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.4.4) + actionview (= 5.2.4.4) + activesupport (= 5.2.4.4) + rack (~> 2.0, >= 2.0.8) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.4.4) + activesupport (= 5.2.4.4) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.4.4) + activesupport (= 5.2.4.4) + globalid (>= 0.3.6) + activemodel (5.2.4.4) + activesupport (= 5.2.4.4) + activerecord (5.2.4.4) + activemodel (= 5.2.4.4) + activesupport (= 5.2.4.4) + arel (>= 9.0) + activestorage (5.2.4.4) + actionpack (= 5.2.4.4) + activerecord (= 5.2.4.4) + marcel (~> 0.3.1) + activesupport (5.2.4.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.4.0) + analytics-ruby (2.0.13) + apitome (0.3.0) + kramdown + railties + arel (9.0.0) + ast (2.4.1) + autoprefixer-rails (10.0.1.0) + execjs + aws-eventstream (1.1.0) + aws-sdk (2.6.50) + aws-sdk-resources (= 2.6.50) + aws-sdk-core (2.6.50) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-resources (2.6.50) + aws-sdk-core (= 2.6.50) + aws-sigv4 (1.2.2) + aws-eventstream (~> 1, >= 1.0.2) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) + barnes (0.0.8) + multi_json (~> 1) + statsd-ruby (~> 1.1) + better_errors (2.8.3) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.4.8) + msgpack (~> 1.0) + bootstrap (4.0.0) + autoprefixer-rails (>= 6.0.3) + popper_js (>= 1.12.9, < 2) + sass (>= 3.5.2) + browser (5.1.0) + builder (3.2.4) + byebug (11.1.3) + capybara (3.33.0) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (~> 1.5) + xpath (~> 3.2) + childprocess (3.0.0) + coderay (1.1.3) + concurrent-ruby (1.1.7) + connection_pool (2.2.3) + crack (0.4.4) + crass (1.0.6) + database_cleaner (1.8.5) + debug_inspector (0.0.3) + deterministic (0.6.0) + diff-lcs (1.4.4) + docile (1.3.2) + dotenv (2.7.6) + dotenv-rails (2.7.6) + dotenv (= 2.7.6) + railties (>= 3.2) + dry-configurable (0.11.6) + concurrent-ruby (~> 1.0) + dry-core (~> 0.4, >= 0.4.7) + dry-equalizer (~> 0.2) + dry-container (0.7.2) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (0.4.9) + concurrent-ruby (~> 1.0) + dry-equalizer (0.3.0) + dry-inflector (0.2.0) + dry-logic (0.4.2) + dry-container (~> 0.2, >= 0.2.6) + dry-core (~> 0.2) + dry-equalizer (~> 0.2) + dry-types (0.13.4) + concurrent-ruby (~> 1.0) + dry-container (~> 0.3) + dry-core (~> 0.4, >= 0.4.4) + dry-equalizer (~> 0.2) + dry-inflector (~> 0.1, >= 0.1.2) + dry-logic (~> 0.4, >= 0.4.2) + dry-validation (0.12.3) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (~> 0.2, >= 0.2.1) + dry-equalizer (~> 0.2) + dry-logic (~> 0.4.2) + dry-types (~> 0.13.1) + erubi (1.9.0) + execjs (2.7.0) + factory_bot (6.1.0) + activesupport (>= 5.0.0) + factory_bot_rails (6.1.0) + factory_bot (~> 6.1.0) + railties (>= 5.0.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + ffi (1.13.1) + flamegraph (0.9.5) + font-awesome-rails (4.7.0.5) + railties (>= 3.2, < 6.1) + foreman (0.87.2) + github-markdown (0.6.9) + github-markup (1.6.1) + github_url (0.2.1) + gitlab (4.14.1) + httparty (~> 0.14, >= 0.14.0) + terminal-table (~> 1.5, >= 1.5.1) + globalid (0.4.2) + activesupport (>= 4.2.0) + haml (5.2.0) + temple (>= 0.8.0) + tilt + hashdiff (1.0.1) + honeybadger (3.3.1) + httparty (0.18.1) + mime-types (~> 3.0) + multi_xml (>= 0.5.2) + i18n (1.8.5) + concurrent-ruby (~> 1.0) + iniparse (1.5.0) + jmespath (1.4.0) + jquery-rails (4.4.0) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + js-routes (1.4.9) + railties (>= 4) + sprockets-rails + json_spec (1.1.5) + multi_json (~> 1.0) + rspec (>= 2.0, < 4.0) + jwt (2.2.2) + kramdown (2.3.0) + rexml + launchy (2.4.3) + addressable (~> 2.3) + letter_opener (1.7.0) + launchy (~> 2.2) + loofah (2.7.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + mathjax-rails (2.6.1) + railties (>= 3.0) + memory_profiler (0.9.14) + method_source (1.0.0) + mime-types (3.3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2020.0512) + mimemagic (0.3.5) + mini_mime (1.0.2) + mini_portile2 (2.2.0) + minitest (5.14.2) + msgpack (1.3.3) + multi_json (1.15.0) + multi_xml (0.6.0) + multipart-post (2.1.1) + mustache (1.1.1) + nio4r (2.5.4) + nokogiri (1.8.0) + mini_portile2 (~> 2.2.0) + octokit (4.3.0) + sawyer (~> 0.7.0, >= 0.5.3) + overcommit (0.57.0) + childprocess (>= 0.6.3, < 5) + iniparse (~> 1.4) + pagy (3.8.3) + parallel (1.19.2) + parser (2.7.2.0) + ast (~> 2.4.1) + pg (0.21.0) + popper_js (1.16.0) + psych (2.2.4) + puma (3.12.6) + pundit (2.1.0) + activesupport (>= 3.0.0) + rack (2.2.3) + rack-attack (6.3.1) + rack (>= 1.0, < 3) + rack-mini-profiler (2.1.0) + rack (>= 1.2.0) + rack-protection (2.1.0) + rack + rack-proxy (0.6.5) + rack + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.4.4) + actioncable (= 5.2.4.4) + actionmailer (= 5.2.4.4) + actionpack (= 5.2.4.4) + actionview (= 5.2.4.4) + activejob (= 5.2.4.4) + activemodel (= 5.2.4.4) + activerecord (= 5.2.4.4) + activestorage (= 5.2.4.4) + activesupport (= 5.2.4.4) + bundler (>= 1.3.0) + railties (= 5.2.4.4) + sprockets-rails (>= 2.0.0) + rails-controller-testing (1.0.5) + actionpack (>= 5.0.1.rc1) + actionview (>= 5.0.1.rc1) + activesupport (>= 5.0.1.rc1) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (5.2.4.4) + actionpack (= 5.2.4.4) + activesupport (= 5.2.4.4) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rainbow (3.0.0) + rake (13.0.1) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + react-rails (2.4.5) + babel-transpiler (>= 0.7.0) + connection_pool + execjs + railties (>= 3.2) + tilt + redcarpet (3.3.4) + redis (3.3.5) + regexp_parser (1.8.1) + rexml (3.2.4) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.3) + rspec-support (~> 3.9.3) + rspec-expectations (3.9.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-rails (4.0.1) + actionpack (>= 4.2) + activesupport (>= 4.2) + railties (>= 4.2) + rspec-core (~> 3.9) + rspec-expectations (~> 3.9) + rspec-mocks (~> 3.9) + rspec-support (~> 3.9) + rspec-support (3.9.3) + rspec_api_documentation (6.1.0) + activesupport (>= 3.0.0) + mustache (~> 1.0, >= 0.99.4) + rspec (~> 3.0) + rspec_junit_formatter (0.4.1) + rspec-core (>= 2, < 4, != 2.12.0) + rubocop (0.93.0) + parallel (~> 1.10) + parser (>= 2.7.1.5) + rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8) + rexml + rubocop-ast (>= 0.6.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 2.0) + rubocop-ast (0.7.1) + parser (>= 2.7.1.5) + ruby-progressbar (1.10.1) + rubyzip (2.3.0) + sass (3.7.4) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.1.0) + railties (>= 5.2.0) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sawyer (0.7.0) + addressable (>= 2.3.5, < 2.5) + faraday (~> 0.8, < 0.10) + scout_apm (2.6.9) + parser + selenium-webdriver (3.142.7) + childprocess (>= 0.5, < 4.0) + rubyzip (>= 1.2.2) + semantic_range (2.3.0) + shoulda-matchers (4.4.1) + activesupport (>= 4.2.0) + sidekiq (5.2.9) + connection_pool (~> 2.2, >= 2.2.2) + rack (~> 2.0) + rack-protection (>= 1.5.0) + redis (>= 3.3.5, < 4.2) + simplecov (0.19.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov-html (0.12.3) + solid_use_case (2.2.0) + deterministic (~> 0.6.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.2) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + stackprof (0.2.15) + statsd-ruby (1.4.0) + temple (0.8.2) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thor (1.0.1) + thread_safe (0.3.6) + tilt (2.0.10) + timecop (0.9.1) + ts_routes (1.0.3) + railties (>= 4.0) + tzinfo (1.2.7) + thread_safe (~> 0.1) + uglifier (4.2.0) + execjs (>= 0.3.0, < 3) + underscore-rails (1.8.3) + unicode-display_width (1.7.0) + vcr (6.0.0) + webmock (3.9.1) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + webpacker (5.2.1) + activesupport (>= 5.2) + rack-proxy (>= 0.6.1) + railties (>= 5.2) + semantic_range (>= 2.3.0) + websocket-driver (0.7.3) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + zip-zip (0.3) + rubyzip (>= 1.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + analytics-ruby (~> 2.0.0) + apitome + aws-sdk (~> 2.6.5) + barnes + better_errors + binding_of_caller + block_parser! + bootsnap (>= 1.1.0) + bootstrap (= 4.0.0) + browser + byebug + capybara + database_cleaner + dotenv-rails + dry-validation (~> 0.12.2) + factory_bot_rails + flamegraph + font-awesome-rails + foreman + github_url (= 0.2.1) + gitlab + haml + honeybadger (~> 3.1) + httparty + jquery-rails + js-routes + json_spec + jwt + letter_opener + mathjax-rails + memory_profiler + octokit + overcommit + pagy + pg (~> 0.18) + puma (~> 3.12.0) + pundit + rack-attack + rack-mini-profiler + rails (~> 5.2.1) + rails-controller-testing + react-rails (= 2.4.5) + redis (~> 3.0) + rspec-rails + rspec_api_documentation + rspec_junit_formatter + rubocop + rubyzip (>= 1.0.0) + sass-rails (~> 5.0) + scout_apm + selenium-webdriver + shoulda-matchers + sidekiq + simplecov + solid_use_case (~> 2.2.0) + stackprof + timecop + ts_routes + tzinfo-data + uglifier (>= 1.3.0) + underscore-rails (= 1.8.3) + vcr + webmock + webpacker (~> 5.2.1) + zip-zip + +RUBY VERSION + ruby 2.6.6p146 + +BUNDLED WITH + 2.1.4 diff --git a/Gemfile.lock.old b/Gemfile.lock.old new file mode 100644 index 0000000..34ea2ba --- /dev/null +++ b/Gemfile.lock.old @@ -0,0 +1,476 @@ +PATH + remote: gems/block-parser + specs: + block_parser (0.1.0) + activemodel (> 4.2) + github-markdown (= 0.6.9) + github-markup (= 1.6.1) + github_url (= 0.2.1) + gitlab (= 4.14.1) + nokogiri (= 1.8.0) + octokit (= 4.3.0) + psych (= 2.2.4) + redcarpet (= 3.3.4) + rspec_junit_formatter (> 0.2.3) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.1) + actionpack (= 5.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.1) + actionview (= 5.2.1) + activesupport (= 5.2.1) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.1) + activesupport (= 5.2.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.1) + activesupport (= 5.2.1) + globalid (>= 0.3.6) + activemodel (5.2.1) + activesupport (= 5.2.1) + activerecord (5.2.1) + activemodel (= 5.2.1) + activesupport (= 5.2.1) + arel (>= 9.0) + activestorage (5.2.1) + actionpack (= 5.2.1) + activerecord (= 5.2.1) + marcel (~> 0.3.1) + activesupport (5.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.4.0) + analytics-ruby (2.0.13) + apitome (0.3.0) + kramdown + railties + arel (9.0.0) + ast (2.4.0) + autoprefixer-rails (7.2.5) + execjs + aws-sdk (2.6.50) + aws-sdk-resources (= 2.6.50) + aws-sdk-core (2.6.50) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-resources (2.6.50) + aws-sdk-core (= 2.6.50) + aws-sigv4 (1.0.2) + babel-source (5.8.35) + babel-transpiler (0.7.0) + babel-source (>= 4.0, < 6) + execjs (~> 2.0) + barnes (0.0.7) + multi_json (~> 1) + statsd-ruby (~> 1.1) + better_errors (2.5.0) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.3.1) + msgpack (~> 1.0) + bootstrap (4.0.0) + autoprefixer-rails (>= 6.0.3) + popper_js (>= 1.12.9, < 2) + sass (>= 3.5.2) + browser (2.5.2) + builder (3.2.3) + byebug (9.1.0) + capybara (2.16.1) + addressable + mini_mime (>= 0.1.3) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.8.0) + ffi (~> 1.0, >= 1.0.11) + coderay (1.1.2) + concurrent-ruby (1.1.5) + connection_pool (2.2.1) + crack (0.4.3) + safe_yaml (~> 1.0.0) + crass (1.0.4) + database_cleaner (1.6.2) + debug_inspector (0.0.3) + deterministic (0.6.0) + diff-lcs (1.3) + docile (1.3.2) + dotenv (2.5.0) + dotenv-rails (2.5.0) + dotenv (= 2.5.0) + railties (>= 3.2, < 6.0) + dry-configurable (0.7.0) + concurrent-ruby (~> 1.0) + dry-container (0.6.0) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (0.4.7) + concurrent-ruby (~> 1.0) + dry-equalizer (0.2.1) + dry-inflector (0.1.2) + dry-logic (0.4.2) + dry-container (~> 0.2, >= 0.2.6) + dry-core (~> 0.2) + dry-equalizer (~> 0.2) + dry-types (0.13.2) + concurrent-ruby (~> 1.0) + dry-container (~> 0.3) + dry-core (~> 0.4, >= 0.4.4) + dry-equalizer (~> 0.2) + dry-inflector (~> 0.1, >= 0.1.2) + dry-logic (~> 0.4, >= 0.4.2) + dry-validation (0.12.2) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (~> 0.2, >= 0.2.1) + dry-equalizer (~> 0.2) + dry-logic (~> 0.4, >= 0.4.0) + dry-types (~> 0.13.1) + erubi (1.7.1) + execjs (2.7.0) + factory_bot (4.8.2) + activesupport (>= 3.0.0) + factory_bot_rails (4.8.2) + factory_bot (~> 4.8.2) + railties (>= 3.0.0) + faraday (0.9.2) + multipart-post (>= 1.2, < 3) + ffi (1.9.18) + flamegraph (0.9.5) + font-awesome-rails (4.7.0.4) + railties (>= 3.2, < 6.0) + foreman (0.84.0) + thor (~> 0.19.1) + github-markdown (0.6.9) + github-markup (1.6.1) + github_url (0.2.1) + gitlab (4.14.1) + httparty (~> 0.14, >= 0.14.0) + terminal-table (~> 1.5, >= 1.5.1) + globalid (0.4.1) + activesupport (>= 4.2.0) + haml (5.0.4) + temple (>= 0.8.0) + tilt + hashdiff (0.4.0) + honeybadger (3.2.0) + httparty (0.15.6) + multi_xml (>= 0.5.2) + i18n (1.6.0) + concurrent-ruby (~> 1.0) + iniparse (1.4.4) + jaro_winkler (1.5.3) + jmespath (1.3.1) + jquery-rails (4.3.1) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + js-routes (1.4.2) + railties (>= 3.2) + sprockets-rails + json_spec (1.1.5) + multi_json (~> 1.0) + rspec (>= 2.0, < 4.0) + jwt (2.2.2) + kramdown (2.1.0) + launchy (2.4.3) + addressable (~> 2.3) + letter_opener (1.7.0) + launchy (~> 2.2) + loofah (2.2.2) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.0) + mini_mime (>= 0.1.1) + marcel (0.3.2) + mimemagic (~> 0.3.2) + mathjax-rails (2.6.1) + railties (>= 3.0) + memory_profiler (0.9.12) + method_source (0.9.0) + mimemagic (0.3.2) + mini_mime (1.0.0) + mini_portile2 (2.2.0) + minitest (5.11.3) + msgpack (1.2.4) + multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + mustache (1.1.0) + nio4r (2.3.1) + nokogiri (1.8.0) + mini_portile2 (~> 2.2.0) + octokit (4.3.0) + sawyer (~> 0.7.0, >= 0.5.3) + overcommit (0.41.0) + childprocess (~> 0.6, >= 0.6.3) + iniparse (~> 1.4) + pagy (3.7.1) + parallel (1.17.0) + parser (2.6.3.0) + ast (~> 2.4.0) + pg (0.21.0) + popper_js (1.12.9) + psych (2.2.4) + puma (3.12.1) + pundit (1.1.0) + activesupport (>= 3.0.0) + rack (2.0.3) + rack-attack (6.2.1) + rack (>= 1.0, < 3) + rack-mini-profiler (1.0.0) + rack (>= 1.2.0) + rack-protection (2.0.0) + rack + rack-proxy (0.6.5) + rack + rack-test (0.8.2) + rack (>= 1.0, < 3) + rails (5.2.1) + actioncable (= 5.2.1) + actionmailer (= 5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + activemodel (= 5.2.1) + activerecord (= 5.2.1) + activestorage (= 5.2.1) + activesupport (= 5.2.1) + bundler (>= 1.3.0) + railties (= 5.2.1) + sprockets-rails (>= 2.0.0) + rails-controller-testing (1.0.2) + actionpack (~> 5.x, >= 5.0.1) + actionview (~> 5.x, >= 5.0.1) + activesupport (~> 5.x) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.1) + actionpack (= 5.2.1) + activesupport (= 5.2.1) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rainbow (3.0.0) + rake (12.3.1) + rb-fsevent (0.10.2) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + react-rails (2.4.5) + babel-transpiler (>= 0.7.0) + connection_pool + execjs + railties (>= 3.2) + tilt + redcarpet (3.3.4) + redis (3.3.5) + rspec (3.7.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-core (3.7.0) + rspec-support (~> 3.7.0) + rspec-expectations (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-mocks (3.7.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.7.0) + rspec-rails (3.7.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.7.0) + rspec-expectations (~> 3.7.0) + rspec-mocks (~> 3.7.0) + rspec-support (~> 3.7.0) + rspec-support (3.7.0) + rspec_api_documentation (6.1.0) + activesupport (>= 3.0.0) + mustache (~> 1.0, >= 0.99.4) + rspec (~> 3.0) + rspec_junit_formatter (0.3.0) + rspec-core (>= 2, < 4, != 2.12.0) + rubocop (0.72.0) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.6) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 1.7) + ruby-progressbar (1.10.1) + rubyzip (1.2.1) + safe_yaml (1.0.5) + sass (3.5.3) + sass-listen (~> 4.0.0) + sass-listen (4.0.0) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + sass-rails (5.0.7) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sawyer (0.7.0) + addressable (>= 2.3.5, < 2.5) + faraday (~> 0.8, < 0.10) + scout_apm (2.4.19) + selenium-webdriver (3.8.0) + childprocess (~> 0.5) + rubyzip (~> 1.0) + semantic_range (2.3.0) + shoulda-matchers (3.1.2) + activesupport (>= 4.0.0) + sidekiq (5.0.5) + concurrent-ruby (~> 1.0) + connection_pool (~> 2.2, >= 2.2.0) + rack-protection (>= 1.5.0) + redis (>= 3.3.4, < 5) + simplecov (0.19.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov-html (0.12.3) + solid_use_case (2.2.0) + deterministic (~> 0.6.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + stackprof (0.2.12) + statsd-ruby (1.4.0) + temple (0.8.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + thor (0.19.4) + thread_safe (0.3.6) + tilt (2.0.8) + timecop (0.9.1) + ts_routes (1.0.1) + railties (>= 5.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uglifier (4.0.2) + execjs (>= 0.3.0, < 3) + underscore-rails (1.8.3) + unicode-display_width (1.6.0) + vcr (4.0.0) + webmock (3.6.0) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff (>= 0.4.0, < 2.0.0) + webpacker (5.2.1) + activesupport (>= 5.2) + rack-proxy (>= 0.6.1) + railties (>= 5.2) + semantic_range (>= 2.3.0) + websocket-driver (0.7.0) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + xpath (2.1.0) + nokogiri (~> 1.3) + zip-zip (0.3) + rubyzip (>= 1.0.0) + +PLATFORMS + ruby + +DEPENDENCIES + analytics-ruby (~> 2.0.0) + apitome + aws-sdk (~> 2.6.5) + barnes + better_errors + binding_of_caller + block_parser! + bootsnap (>= 1.1.0) + bootstrap (= 4.0.0) + browser + byebug + capybara + database_cleaner + dotenv-rails + dry-validation (~> 0.12.2) + factory_bot_rails + flamegraph + font-awesome-rails + foreman + github_url (= 0.2.1) + gitlab + haml + honeybadger (~> 3.1) + httparty + jquery-rails + js-routes + json_spec + jwt + letter_opener + mathjax-rails + memory_profiler + octokit + overcommit + pagy + pg (~> 0.18) + puma (~> 3.12.0) + pundit + rack-attack + rack-mini-profiler + rails (~> 5.2.1) + rails-controller-testing + react-rails (= 2.4.5) + redis (~> 3.0) + rspec-rails + rspec_api_documentation + rspec_junit_formatter + rubocop + rubyzip (>= 1.0.0) + sass-rails (~> 5.0) + scout_apm + selenium-webdriver + shoulda-matchers + sidekiq + simplecov + solid_use_case (~> 2.2.0) + stackprof + timecop + ts_routes + tzinfo-data + uglifier (>= 1.3.0) + underscore-rails (= 1.8.3) + vcr + webmock + webpacker (~> 5.2.1) + zip-zip + +RUBY VERSION + ruby 2.6.6p146 + +BUNDLED WITH + 2.1.4 diff --git a/Gemfile.outdated b/Gemfile.outdated new file mode 100644 index 0000000..5a1c8bd --- /dev/null +++ b/Gemfile.outdated @@ -0,0 +1,142 @@ +Fetching gem metadata from https://rubygems.org/......... +Fetching gem metadata from https://rubygems.org/. +Resolving dependencies.......... + +Outdated gems included in the bundle: + * actioncable (newest 6.0.3.4, installed 5.2.1) + * actionmailer (newest 6.0.3.4, installed 5.2.1) + * actionpack (newest 6.0.3.4, installed 5.2.1) + * actionview (newest 6.0.3.4, installed 5.2.1) + * activejob (newest 6.0.3.4, installed 5.2.1) + * activemodel (newest 6.0.3.4, installed 5.2.1) + * activerecord (newest 6.0.3.4, installed 5.2.1) + * activestorage (newest 6.0.3.4, installed 5.2.1) + * activesupport (newest 6.0.3.4, installed 5.2.1) + * addressable (newest 2.7.0, installed 2.4.0) + * analytics-ruby (newest 2.2.8, installed 2.0.13, requested ~> 2.0.0) in group "default" + * ast (newest 2.4.1, installed 2.4.0) + * autoprefixer-rails (newest 10.0.1.0, installed 7.2.5) + * aws-sdk (newest 3.0.1, installed 2.6.50, requested ~> 2.6.5) in group "default" + * aws-sdk-core (newest 3.109.1, installed 2.6.50) + * aws-sdk-resources (newest 3.84.0, installed 2.6.50) + * aws-sigv4 (newest 1.2.2, installed 1.0.2) + * barnes (newest 0.0.8, installed 0.0.7) in group "default" + * better_errors (newest 2.8.3, installed 2.5.0) in group "development" + * bootsnap (newest 1.4.8, installed 1.3.1) in group "default" + * bootstrap (newest 4.5.2, installed 4.0.0, requested = 4.0.0) in group "default" + * browser (newest 5.1.0, installed 2.5.2) in group "default" + * builder (newest 3.2.4, installed 3.2.3) + * byebug (newest 11.1.3, installed 9.1.0) in groups "development, test" + * capybara (newest 3.33.0, installed 2.16.1) in group "test" + * childprocess (newest 4.0.0, installed 0.8.0) + * coderay (newest 1.1.3, installed 1.1.2) + * concurrent-ruby (newest 1.1.7, installed 1.1.5) + * connection_pool (newest 2.2.3, installed 2.2.1) + * crack (newest 0.4.4, installed 0.4.3) + * crass (newest 1.0.6, installed 1.0.4) + * database_cleaner (newest 1.8.5, installed 1.6.2) in group "test" + * deterministic (newest 0.16.0, installed 0.6.0) + * diff-lcs (newest 1.4.4, installed 1.3) + * dotenv (newest 2.7.6, installed 2.5.0) + * dotenv-rails (newest 2.7.6, installed 2.5.0) in group "default" + * dry-configurable (newest 0.11.6, installed 0.7.0) + * dry-container (newest 0.7.2, installed 0.6.0) + * dry-core (newest 0.4.9, installed 0.4.7) + * dry-equalizer (newest 0.3.0, installed 0.2.1) + * dry-inflector (newest 0.2.0, installed 0.1.2) + * dry-logic (newest 1.0.8, installed 0.4.2) + * dry-types (newest 1.4.0, installed 0.13.2) + * dry-validation (newest 1.5.6, installed 0.12.2, requested ~> 0.12.2) in group "default" + * erubi (newest 1.9.0, installed 1.7.1) + * factory_bot (newest 6.1.0, installed 4.8.2) + * factory_bot_rails (newest 6.1.0, installed 4.8.2) in groups "development, test" + * faraday (newest 1.0.1, installed 0.9.2) + * ffi (newest 1.13.1, installed 1.9.18) + * font-awesome-rails (newest 4.7.0.5, installed 4.7.0.4) in group "default" + * foreman (newest 0.87.2, installed 0.84.0) in group "development" + * github-markup (newest 3.0.4, installed 1.6.1) + * gitlab (newest 4.16.1, installed 4.14.1) in group "default" + * globalid (newest 0.4.2, installed 0.4.1) + * haml (newest 5.2.0, installed 5.0.4) in group "default" + * hashdiff (newest 1.0.1, installed 0.4.0) + * honeybadger (newest 4.7.2, installed 3.2.0, requested ~> 3.1) in group "default" + * httparty (newest 0.18.1, installed 0.15.6) in group "default" + * i18n (newest 1.8.5, installed 1.6.0) + * iniparse (newest 1.5.0, installed 1.4.4) + * jaro_winkler (newest 1.5.4, installed 1.5.3) + * jmespath (newest 1.4.0, installed 1.3.1) + * jquery-rails (newest 4.4.0, installed 4.3.1) in group "default" + * js-routes (newest 1.4.9, installed 1.4.2) in group "default" + * kramdown (newest 2.3.0, installed 2.1.0) + * launchy (newest 2.5.0, installed 2.4.3) + * loofah (newest 2.7.0, installed 2.2.2) + * mail (newest 2.7.1, installed 2.7.0) + * marcel (newest 0.3.3, installed 0.3.2) + * memory_profiler (newest 0.9.14, installed 0.9.12) in group "default" + * method_source (newest 1.0.0, installed 0.9.0) + * mimemagic (newest 0.3.5, installed 0.3.2) + * mini_mime (newest 1.0.2, installed 1.0.0) + * mini_portile2 (newest 2.5.0, installed 2.2.0) + * minitest (newest 5.14.2, installed 5.11.3) + * msgpack (newest 1.3.3, installed 1.2.4) + * multi_json (newest 1.15.0, installed 1.13.1) + * multipart-post (newest 2.1.1, installed 2.0.0) + * mustache (newest 1.1.1, installed 1.1.0) + * nio4r (newest 2.5.4, installed 2.3.1) + * nokogiri (newest 1.10.10, installed 1.8.0) + * octokit (newest 4.18.0, installed 4.3.0) in group "default" + * overcommit (newest 0.57.0, installed 0.41.0) in group "development" + * pagy (newest 3.8.3, installed 3.7.1) in group "default" + * parallel (newest 1.19.2, installed 1.17.0) + * parser (newest 2.7.2.0, installed 2.6.3.0) + * pg (newest 1.2.3, installed 0.21.0, requested ~> 0.18) in group "default" + * popper_js (newest 1.16.0, installed 1.12.9) + * psych (newest 3.2.0, installed 2.2.4) + * puma (newest 5.0.2, installed 3.12.1, requested ~> 3.12.0) in group "default" + * pundit (newest 2.1.0, installed 1.1.0) in group "default" + * rack (newest 2.2.3, installed 2.0.3) + * rack-attack (newest 6.3.1, installed 6.2.1) in group "default" + * rack-mini-profiler (newest 2.1.0, installed 1.0.0) in group "default" + * rack-protection (newest 2.1.0, installed 2.0.0) + * rack-test (newest 1.1.0, installed 0.8.2) + * rails (newest 6.0.3.4, installed 5.2.1, requested ~> 5.2.1) in group "default" + * rails-controller-testing (newest 1.0.5, installed 1.0.2) in group "test" + * rails-html-sanitizer (newest 1.3.0, installed 1.0.4) + * railties (newest 6.0.3.4, installed 5.2.1) + * rake (newest 13.0.1, installed 12.3.1) + * rb-fsevent (newest 0.10.4, installed 0.10.2) + * rb-inotify (newest 0.10.1, installed 0.9.10) + * react-rails (newest 2.6.1, installed 2.4.5, requested = 2.4.5) in group "default" + * redcarpet (newest 3.5.0, installed 3.3.4) + * redis (newest 4.2.2, installed 3.3.5, requested ~> 3.0) in group "default" + * rspec (newest 3.9.0, installed 3.7.0) + * rspec-core (newest 3.9.3, installed 3.7.0) + * rspec-expectations (newest 3.9.2, installed 3.7.0) + * rspec-mocks (newest 3.9.1, installed 3.7.0) + * rspec-rails (newest 4.0.1, installed 3.7.2) in groups "development, test" + * rspec-support (newest 3.9.3, installed 3.7.0) + * rspec_junit_formatter (newest 0.4.1, installed 0.3.0) in group "test" + * rubocop (newest 0.93.0, installed 0.72.0) in group "development" + * rubyzip (newest 2.3.0, installed 1.2.1) in group "default" + * sass (newest 3.7.4, installed 3.5.3) + * sass-rails (newest 6.0.0, installed 5.0.7, requested ~> 5.0) in group "default" + * sawyer (newest 0.8.2, installed 0.7.0) + * scout_apm (newest 2.6.9, installed 2.4.19) in group "default" + * selenium-webdriver (newest 3.142.7, installed 3.8.0) in group "test" + * shoulda-matchers (newest 4.4.1, installed 3.1.2) in group "test" + * sidekiq (newest 6.1.2, installed 5.0.5) in group "default" + * sprockets (newest 4.0.2, installed 3.7.1) + * sprockets-rails (newest 3.2.2, installed 3.2.1) + * stackprof (newest 0.2.15, installed 0.2.12) in group "default" + * temple (newest 0.8.2, installed 0.8.0) + * thor (newest 1.0.1, installed 0.19.4) + * tilt (newest 2.0.10, installed 2.0.8) + * ts_routes (newest 1.0.3, installed 1.0.1) in group "default" + * tzinfo (newest 2.0.2, installed 1.2.5) + * uglifier (newest 4.2.0, installed 4.0.2) in group "default" + * unicode-display_width (newest 1.7.0, installed 1.6.0) + * vcr (newest 6.0.0, installed 4.0.0) in group "test" + * webmock (newest 3.9.1, installed 3.6.0) in group "test" + * websocket-driver (newest 0.7.3, installed 0.7.0) + * websocket-extensions (newest 0.1.5, installed 0.1.3) + * xpath (newest 3.2.0, installed 2.1.0) diff --git a/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss b/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss index ac9e98f..c93e515 100644 --- a/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss +++ b/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss @@ -5,7 +5,7 @@ .challengeindex-table > .studentspanel > .studentcolumn > .standard > .emptyrow { height: 80px; - background: linear-gradient(top, #d4d4d4 50%,#d4d4d4 50%,#ebebeb 50%) + background: linear-gradient(to bottom, #d4d4d4 50%,#d4d4d4 50%,#ebebeb 50%) } .challengeindex-table > .curriculumpanel > .standard > .standardtitle { @@ -48,4 +48,4 @@ padding: .5rem 1rem; } } -} \ No newline at end of file +} -- GitLab From 9438bf4f052fc3bb093951c5aa9bb4e395fdb713 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Sat, 10 Oct 2020 00:34:56 -1000 Subject: [PATCH 147/287] Changes to get to bundle update state --- Gemfile.lock | 53 +- Gemfile.lock.new | 488 ------------------ Gemfile.lock.old | 476 ----------------- Gemfile.outdated | 142 ----- app/assets/stylesheets/components/_modal.scss | 4 +- 5 files changed, 34 insertions(+), 1129 deletions(-) delete mode 100644 Gemfile.lock.new delete mode 100644 Gemfile.lock.old delete mode 100644 Gemfile.outdated diff --git a/Gemfile.lock b/Gemfile.lock index 3d86107..0a1bc01 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -26,10 +26,10 @@ GEM activejob (= 5.2.4.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.1) - actionview (= 5.2.1) - activesupport (= 5.2.1) - rack (~> 2.0) + actionpack (5.2.4.4) + actionview (= 5.2.4.4) + activesupport (= 5.2.4.4) + rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) @@ -98,13 +98,14 @@ GEM browser (5.1.0) builder (3.2.4) byebug (11.1.3) - capybara (2.16.1) + capybara (3.33.0) addressable mini_mime (>= 0.1.3) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - xpath (~> 2.0) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (~> 1.5) + xpath (~> 3.2) childprocess (3.0.0) coderay (1.1.3) concurrent-ruby (1.1.7) @@ -151,11 +152,11 @@ GEM dry-types (~> 0.13.1) erubi (1.9.0) execjs (2.7.0) - factory_bot (4.8.2) - activesupport (>= 3.0.0) - factory_bot_rails (4.8.2) - factory_bot (~> 4.8.2) - railties (>= 3.0.0) + factory_bot (6.1.0) + activesupport (>= 5.0.0) + factory_bot_rails (6.1.0) + factory_bot (~> 6.1.0) + railties (>= 5.0.0) faraday (0.9.2) multipart-post (>= 1.2, < 3) ffi (1.13.1) @@ -176,12 +177,12 @@ GEM tilt hashdiff (1.0.1) honeybadger (3.3.1) - httparty (0.15.6) + httparty (0.18.1) + mime-types (~> 3.0) multi_xml (>= 0.5.2) i18n (1.8.5) concurrent-ruby (~> 1.0) iniparse (1.5.0) - jaro_winkler (1.5.3) jmespath (1.4.0) jquery-rails (4.4.0) rails-dom-testing (>= 1, < 3) @@ -194,7 +195,8 @@ GEM multi_json (~> 1.0) rspec (>= 2.0, < 4.0) jwt (2.2.2) - kramdown (2.1.0) + kramdown (2.3.0) + rexml launchy (2.4.3) addressable (~> 2.3) letter_opener (1.7.0) @@ -210,6 +212,9 @@ GEM railties (>= 3.0) memory_profiler (0.9.14) method_source (1.0.0) + mime-types (3.3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2020.0512) mimemagic (0.3.5) mini_mime (1.0.2) mini_portile2 (2.2.0) @@ -289,6 +294,8 @@ GEM tilt redcarpet (3.3.4) redis (3.3.5) + regexp_parser (1.8.1) + rexml (3.2.4) rspec (3.9.0) rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) @@ -316,13 +323,17 @@ GEM rspec (~> 3.0) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.72.0) - jaro_winkler (~> 1.5.1) + rubocop (0.93.0) parallel (~> 1.10) - parser (>= 2.6) + parser (>= 2.7.1.5) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8) + rexml + rubocop-ast (>= 0.6.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.7) + unicode-display_width (>= 1.4.0, < 2.0) + rubocop-ast (0.7.1) + parser (>= 2.7.1.5) ruby-progressbar (1.10.1) rubyzip (2.3.0) sass (3.7.4) diff --git a/Gemfile.lock.new b/Gemfile.lock.new deleted file mode 100644 index 0a1bc01..0000000 --- a/Gemfile.lock.new +++ /dev/null @@ -1,488 +0,0 @@ -PATH - remote: gems/block-parser - specs: - block_parser (0.1.0) - activemodel (> 4.2) - github-markdown (= 0.6.9) - github-markup (= 1.6.1) - github_url (= 0.2.1) - gitlab (= 4.14.1) - nokogiri (= 1.8.0) - octokit (= 4.3.0) - psych (= 2.2.4) - redcarpet (= 3.3.4) - rspec_junit_formatter (> 0.2.3) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (5.2.4.4) - actionpack (= 5.2.4.4) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailer (5.2.4.4) - actionpack (= 5.2.4.4) - actionview (= 5.2.4.4) - activejob (= 5.2.4.4) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (5.2.4.4) - actionview (= 5.2.4.4) - activesupport (= 5.2.4.4) - rack (~> 2.0, >= 2.0.8) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.4.4) - activesupport (= 5.2.4.4) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.4.4) - activesupport (= 5.2.4.4) - globalid (>= 0.3.6) - activemodel (5.2.4.4) - activesupport (= 5.2.4.4) - activerecord (5.2.4.4) - activemodel (= 5.2.4.4) - activesupport (= 5.2.4.4) - arel (>= 9.0) - activestorage (5.2.4.4) - actionpack (= 5.2.4.4) - activerecord (= 5.2.4.4) - marcel (~> 0.3.1) - activesupport (5.2.4.4) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.4.0) - analytics-ruby (2.0.13) - apitome (0.3.0) - kramdown - railties - arel (9.0.0) - ast (2.4.1) - autoprefixer-rails (10.0.1.0) - execjs - aws-eventstream (1.1.0) - aws-sdk (2.6.50) - aws-sdk-resources (= 2.6.50) - aws-sdk-core (2.6.50) - aws-sigv4 (~> 1.0) - jmespath (~> 1.0) - aws-sdk-resources (2.6.50) - aws-sdk-core (= 2.6.50) - aws-sigv4 (1.2.2) - aws-eventstream (~> 1, >= 1.0.2) - babel-source (5.8.35) - babel-transpiler (0.7.0) - babel-source (>= 4.0, < 6) - execjs (~> 2.0) - barnes (0.0.8) - multi_json (~> 1) - statsd-ruby (~> 1.1) - better_errors (2.8.3) - coderay (>= 1.0.0) - erubi (>= 1.0.0) - rack (>= 0.9.0) - binding_of_caller (0.8.0) - debug_inspector (>= 0.0.1) - bootsnap (1.4.8) - msgpack (~> 1.0) - bootstrap (4.0.0) - autoprefixer-rails (>= 6.0.3) - popper_js (>= 1.12.9, < 2) - sass (>= 3.5.2) - browser (5.1.0) - builder (3.2.4) - byebug (11.1.3) - capybara (3.33.0) - addressable - mini_mime (>= 0.1.3) - nokogiri (~> 1.8) - rack (>= 1.6.0) - rack-test (>= 0.6.3) - regexp_parser (~> 1.5) - xpath (~> 3.2) - childprocess (3.0.0) - coderay (1.1.3) - concurrent-ruby (1.1.7) - connection_pool (2.2.3) - crack (0.4.4) - crass (1.0.6) - database_cleaner (1.8.5) - debug_inspector (0.0.3) - deterministic (0.6.0) - diff-lcs (1.4.4) - docile (1.3.2) - dotenv (2.7.6) - dotenv-rails (2.7.6) - dotenv (= 2.7.6) - railties (>= 3.2) - dry-configurable (0.11.6) - concurrent-ruby (~> 1.0) - dry-core (~> 0.4, >= 0.4.7) - dry-equalizer (~> 0.2) - dry-container (0.7.2) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (0.4.9) - concurrent-ruby (~> 1.0) - dry-equalizer (0.3.0) - dry-inflector (0.2.0) - dry-logic (0.4.2) - dry-container (~> 0.2, >= 0.2.6) - dry-core (~> 0.2) - dry-equalizer (~> 0.2) - dry-types (0.13.4) - concurrent-ruby (~> 1.0) - dry-container (~> 0.3) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.2) - dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 0.4, >= 0.4.2) - dry-validation (0.12.3) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (~> 0.2, >= 0.2.1) - dry-equalizer (~> 0.2) - dry-logic (~> 0.4.2) - dry-types (~> 0.13.1) - erubi (1.9.0) - execjs (2.7.0) - factory_bot (6.1.0) - activesupport (>= 5.0.0) - factory_bot_rails (6.1.0) - factory_bot (~> 6.1.0) - railties (>= 5.0.0) - faraday (0.9.2) - multipart-post (>= 1.2, < 3) - ffi (1.13.1) - flamegraph (0.9.5) - font-awesome-rails (4.7.0.5) - railties (>= 3.2, < 6.1) - foreman (0.87.2) - github-markdown (0.6.9) - github-markup (1.6.1) - github_url (0.2.1) - gitlab (4.14.1) - httparty (~> 0.14, >= 0.14.0) - terminal-table (~> 1.5, >= 1.5.1) - globalid (0.4.2) - activesupport (>= 4.2.0) - haml (5.2.0) - temple (>= 0.8.0) - tilt - hashdiff (1.0.1) - honeybadger (3.3.1) - httparty (0.18.1) - mime-types (~> 3.0) - multi_xml (>= 0.5.2) - i18n (1.8.5) - concurrent-ruby (~> 1.0) - iniparse (1.5.0) - jmespath (1.4.0) - jquery-rails (4.4.0) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) - js-routes (1.4.9) - railties (>= 4) - sprockets-rails - json_spec (1.1.5) - multi_json (~> 1.0) - rspec (>= 2.0, < 4.0) - jwt (2.2.2) - kramdown (2.3.0) - rexml - launchy (2.4.3) - addressable (~> 2.3) - letter_opener (1.7.0) - launchy (~> 2.2) - loofah (2.7.0) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - mathjax-rails (2.6.1) - railties (>= 3.0) - memory_profiler (0.9.14) - method_source (1.0.0) - mime-types (3.3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) - mimemagic (0.3.5) - mini_mime (1.0.2) - mini_portile2 (2.2.0) - minitest (5.14.2) - msgpack (1.3.3) - multi_json (1.15.0) - multi_xml (0.6.0) - multipart-post (2.1.1) - mustache (1.1.1) - nio4r (2.5.4) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) - octokit (4.3.0) - sawyer (~> 0.7.0, >= 0.5.3) - overcommit (0.57.0) - childprocess (>= 0.6.3, < 5) - iniparse (~> 1.4) - pagy (3.8.3) - parallel (1.19.2) - parser (2.7.2.0) - ast (~> 2.4.1) - pg (0.21.0) - popper_js (1.16.0) - psych (2.2.4) - puma (3.12.6) - pundit (2.1.0) - activesupport (>= 3.0.0) - rack (2.2.3) - rack-attack (6.3.1) - rack (>= 1.0, < 3) - rack-mini-profiler (2.1.0) - rack (>= 1.2.0) - rack-protection (2.1.0) - rack - rack-proxy (0.6.5) - rack - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (5.2.4.4) - actioncable (= 5.2.4.4) - actionmailer (= 5.2.4.4) - actionpack (= 5.2.4.4) - actionview (= 5.2.4.4) - activejob (= 5.2.4.4) - activemodel (= 5.2.4.4) - activerecord (= 5.2.4.4) - activestorage (= 5.2.4.4) - activesupport (= 5.2.4.4) - bundler (>= 1.3.0) - railties (= 5.2.4.4) - sprockets-rails (>= 2.0.0) - rails-controller-testing (1.0.5) - actionpack (>= 5.0.1.rc1) - actionview (>= 5.0.1.rc1) - activesupport (>= 5.0.1.rc1) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (5.2.4.4) - actionpack (= 5.2.4.4) - activesupport (= 5.2.4.4) - method_source - rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rainbow (3.0.0) - rake (13.0.1) - rb-fsevent (0.10.4) - rb-inotify (0.10.1) - ffi (~> 1.0) - react-rails (2.4.5) - babel-transpiler (>= 0.7.0) - connection_pool - execjs - railties (>= 3.2) - tilt - redcarpet (3.3.4) - redis (3.3.5) - regexp_parser (1.8.1) - rexml (3.2.4) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.3) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-rails (4.0.1) - actionpack (>= 4.2) - activesupport (>= 4.2) - railties (>= 4.2) - rspec-core (~> 3.9) - rspec-expectations (~> 3.9) - rspec-mocks (~> 3.9) - rspec-support (~> 3.9) - rspec-support (3.9.3) - rspec_api_documentation (6.1.0) - activesupport (>= 3.0.0) - mustache (~> 1.0, >= 0.99.4) - rspec (~> 3.0) - rspec_junit_formatter (0.4.1) - rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.93.0) - parallel (~> 1.10) - parser (>= 2.7.1.5) - rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8) - rexml - rubocop-ast (>= 0.6.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.7.1) - parser (>= 2.7.1.5) - ruby-progressbar (1.10.1) - rubyzip (2.3.0) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.1.0) - railties (>= 5.2.0) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) - sawyer (0.7.0) - addressable (>= 2.3.5, < 2.5) - faraday (~> 0.8, < 0.10) - scout_apm (2.6.9) - parser - selenium-webdriver (3.142.7) - childprocess (>= 0.5, < 4.0) - rubyzip (>= 1.2.2) - semantic_range (2.3.0) - shoulda-matchers (4.4.1) - activesupport (>= 4.2.0) - sidekiq (5.2.9) - connection_pool (~> 2.2, >= 2.2.2) - rack (~> 2.0) - rack-protection (>= 1.5.0) - redis (>= 3.3.5, < 4.2) - simplecov (0.19.0) - docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov-html (0.12.3) - solid_use_case (2.2.0) - deterministic (~> 0.6.0) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.2) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - stackprof (0.2.15) - statsd-ruby (1.4.0) - temple (0.8.2) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thor (1.0.1) - thread_safe (0.3.6) - tilt (2.0.10) - timecop (0.9.1) - ts_routes (1.0.3) - railties (>= 4.0) - tzinfo (1.2.7) - thread_safe (~> 0.1) - uglifier (4.2.0) - execjs (>= 0.3.0, < 3) - underscore-rails (1.8.3) - unicode-display_width (1.7.0) - vcr (6.0.0) - webmock (3.9.1) - addressable (>= 2.3.6) - crack (>= 0.3.2) - hashdiff (>= 0.4.0, < 2.0.0) - webpacker (5.2.1) - activesupport (>= 5.2) - rack-proxy (>= 0.6.1) - railties (>= 5.2) - semantic_range (>= 2.3.0) - websocket-driver (0.7.3) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.5) - xpath (3.2.0) - nokogiri (~> 1.8) - zip-zip (0.3) - rubyzip (>= 1.0.0) - -PLATFORMS - ruby - -DEPENDENCIES - analytics-ruby (~> 2.0.0) - apitome - aws-sdk (~> 2.6.5) - barnes - better_errors - binding_of_caller - block_parser! - bootsnap (>= 1.1.0) - bootstrap (= 4.0.0) - browser - byebug - capybara - database_cleaner - dotenv-rails - dry-validation (~> 0.12.2) - factory_bot_rails - flamegraph - font-awesome-rails - foreman - github_url (= 0.2.1) - gitlab - haml - honeybadger (~> 3.1) - httparty - jquery-rails - js-routes - json_spec - jwt - letter_opener - mathjax-rails - memory_profiler - octokit - overcommit - pagy - pg (~> 0.18) - puma (~> 3.12.0) - pundit - rack-attack - rack-mini-profiler - rails (~> 5.2.1) - rails-controller-testing - react-rails (= 2.4.5) - redis (~> 3.0) - rspec-rails - rspec_api_documentation - rspec_junit_formatter - rubocop - rubyzip (>= 1.0.0) - sass-rails (~> 5.0) - scout_apm - selenium-webdriver - shoulda-matchers - sidekiq - simplecov - solid_use_case (~> 2.2.0) - stackprof - timecop - ts_routes - tzinfo-data - uglifier (>= 1.3.0) - underscore-rails (= 1.8.3) - vcr - webmock - webpacker (~> 5.2.1) - zip-zip - -RUBY VERSION - ruby 2.6.6p146 - -BUNDLED WITH - 2.1.4 diff --git a/Gemfile.lock.old b/Gemfile.lock.old deleted file mode 100644 index 34ea2ba..0000000 --- a/Gemfile.lock.old +++ /dev/null @@ -1,476 +0,0 @@ -PATH - remote: gems/block-parser - specs: - block_parser (0.1.0) - activemodel (> 4.2) - github-markdown (= 0.6.9) - github-markup (= 1.6.1) - github_url (= 0.2.1) - gitlab (= 4.14.1) - nokogiri (= 1.8.0) - octokit (= 4.3.0) - psych (= 2.2.4) - redcarpet (= 3.3.4) - rspec_junit_formatter (> 0.2.3) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (5.2.1) - actionpack (= 5.2.1) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailer (5.2.1) - actionpack (= 5.2.1) - actionview (= 5.2.1) - activejob (= 5.2.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (5.2.1) - actionview (= 5.2.1) - activesupport (= 5.2.1) - rack (~> 2.0) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.1) - activesupport (= 5.2.1) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.1) - activesupport (= 5.2.1) - globalid (>= 0.3.6) - activemodel (5.2.1) - activesupport (= 5.2.1) - activerecord (5.2.1) - activemodel (= 5.2.1) - activesupport (= 5.2.1) - arel (>= 9.0) - activestorage (5.2.1) - actionpack (= 5.2.1) - activerecord (= 5.2.1) - marcel (~> 0.3.1) - activesupport (5.2.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.4.0) - analytics-ruby (2.0.13) - apitome (0.3.0) - kramdown - railties - arel (9.0.0) - ast (2.4.0) - autoprefixer-rails (7.2.5) - execjs - aws-sdk (2.6.50) - aws-sdk-resources (= 2.6.50) - aws-sdk-core (2.6.50) - aws-sigv4 (~> 1.0) - jmespath (~> 1.0) - aws-sdk-resources (2.6.50) - aws-sdk-core (= 2.6.50) - aws-sigv4 (1.0.2) - babel-source (5.8.35) - babel-transpiler (0.7.0) - babel-source (>= 4.0, < 6) - execjs (~> 2.0) - barnes (0.0.7) - multi_json (~> 1) - statsd-ruby (~> 1.1) - better_errors (2.5.0) - coderay (>= 1.0.0) - erubi (>= 1.0.0) - rack (>= 0.9.0) - binding_of_caller (0.8.0) - debug_inspector (>= 0.0.1) - bootsnap (1.3.1) - msgpack (~> 1.0) - bootstrap (4.0.0) - autoprefixer-rails (>= 6.0.3) - popper_js (>= 1.12.9, < 2) - sass (>= 3.5.2) - browser (2.5.2) - builder (3.2.3) - byebug (9.1.0) - capybara (2.16.1) - addressable - mini_mime (>= 0.1.3) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - xpath (~> 2.0) - childprocess (0.8.0) - ffi (~> 1.0, >= 1.0.11) - coderay (1.1.2) - concurrent-ruby (1.1.5) - connection_pool (2.2.1) - crack (0.4.3) - safe_yaml (~> 1.0.0) - crass (1.0.4) - database_cleaner (1.6.2) - debug_inspector (0.0.3) - deterministic (0.6.0) - diff-lcs (1.3) - docile (1.3.2) - dotenv (2.5.0) - dotenv-rails (2.5.0) - dotenv (= 2.5.0) - railties (>= 3.2, < 6.0) - dry-configurable (0.7.0) - concurrent-ruby (~> 1.0) - dry-container (0.6.0) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (0.4.7) - concurrent-ruby (~> 1.0) - dry-equalizer (0.2.1) - dry-inflector (0.1.2) - dry-logic (0.4.2) - dry-container (~> 0.2, >= 0.2.6) - dry-core (~> 0.2) - dry-equalizer (~> 0.2) - dry-types (0.13.2) - concurrent-ruby (~> 1.0) - dry-container (~> 0.3) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.2) - dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 0.4, >= 0.4.2) - dry-validation (0.12.2) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (~> 0.2, >= 0.2.1) - dry-equalizer (~> 0.2) - dry-logic (~> 0.4, >= 0.4.0) - dry-types (~> 0.13.1) - erubi (1.7.1) - execjs (2.7.0) - factory_bot (4.8.2) - activesupport (>= 3.0.0) - factory_bot_rails (4.8.2) - factory_bot (~> 4.8.2) - railties (>= 3.0.0) - faraday (0.9.2) - multipart-post (>= 1.2, < 3) - ffi (1.9.18) - flamegraph (0.9.5) - font-awesome-rails (4.7.0.4) - railties (>= 3.2, < 6.0) - foreman (0.84.0) - thor (~> 0.19.1) - github-markdown (0.6.9) - github-markup (1.6.1) - github_url (0.2.1) - gitlab (4.14.1) - httparty (~> 0.14, >= 0.14.0) - terminal-table (~> 1.5, >= 1.5.1) - globalid (0.4.1) - activesupport (>= 4.2.0) - haml (5.0.4) - temple (>= 0.8.0) - tilt - hashdiff (0.4.0) - honeybadger (3.2.0) - httparty (0.15.6) - multi_xml (>= 0.5.2) - i18n (1.6.0) - concurrent-ruby (~> 1.0) - iniparse (1.4.4) - jaro_winkler (1.5.3) - jmespath (1.3.1) - jquery-rails (4.3.1) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) - js-routes (1.4.2) - railties (>= 3.2) - sprockets-rails - json_spec (1.1.5) - multi_json (~> 1.0) - rspec (>= 2.0, < 4.0) - jwt (2.2.2) - kramdown (2.1.0) - launchy (2.4.3) - addressable (~> 2.3) - letter_opener (1.7.0) - launchy (~> 2.2) - loofah (2.2.2) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.0) - mini_mime (>= 0.1.1) - marcel (0.3.2) - mimemagic (~> 0.3.2) - mathjax-rails (2.6.1) - railties (>= 3.0) - memory_profiler (0.9.12) - method_source (0.9.0) - mimemagic (0.3.2) - mini_mime (1.0.0) - mini_portile2 (2.2.0) - minitest (5.11.3) - msgpack (1.2.4) - multi_json (1.13.1) - multi_xml (0.6.0) - multipart-post (2.0.0) - mustache (1.1.0) - nio4r (2.3.1) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) - octokit (4.3.0) - sawyer (~> 0.7.0, >= 0.5.3) - overcommit (0.41.0) - childprocess (~> 0.6, >= 0.6.3) - iniparse (~> 1.4) - pagy (3.7.1) - parallel (1.17.0) - parser (2.6.3.0) - ast (~> 2.4.0) - pg (0.21.0) - popper_js (1.12.9) - psych (2.2.4) - puma (3.12.1) - pundit (1.1.0) - activesupport (>= 3.0.0) - rack (2.0.3) - rack-attack (6.2.1) - rack (>= 1.0, < 3) - rack-mini-profiler (1.0.0) - rack (>= 1.2.0) - rack-protection (2.0.0) - rack - rack-proxy (0.6.5) - rack - rack-test (0.8.2) - rack (>= 1.0, < 3) - rails (5.2.1) - actioncable (= 5.2.1) - actionmailer (= 5.2.1) - actionpack (= 5.2.1) - actionview (= 5.2.1) - activejob (= 5.2.1) - activemodel (= 5.2.1) - activerecord (= 5.2.1) - activestorage (= 5.2.1) - activesupport (= 5.2.1) - bundler (>= 1.3.0) - railties (= 5.2.1) - sprockets-rails (>= 2.0.0) - rails-controller-testing (1.0.2) - actionpack (~> 5.x, >= 5.0.1) - actionview (~> 5.x, >= 5.0.1) - activesupport (~> 5.x) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (5.2.1) - actionpack (= 5.2.1) - activesupport (= 5.2.1) - method_source - rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rainbow (3.0.0) - rake (12.3.1) - rb-fsevent (0.10.2) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - react-rails (2.4.5) - babel-transpiler (>= 0.7.0) - connection_pool - execjs - railties (>= 3.2) - tilt - redcarpet (3.3.4) - redis (3.3.5) - rspec (3.7.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-core (3.7.0) - rspec-support (~> 3.7.0) - rspec-expectations (3.7.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-mocks (3.7.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-rails (3.7.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-support (~> 3.7.0) - rspec-support (3.7.0) - rspec_api_documentation (6.1.0) - activesupport (>= 3.0.0) - mustache (~> 1.0, >= 0.99.4) - rspec (~> 3.0) - rspec_junit_formatter (0.3.0) - rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.72.0) - jaro_winkler (~> 1.5.1) - parallel (~> 1.10) - parser (>= 2.6) - rainbow (>= 2.2.2, < 4.0) - ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 1.7) - ruby-progressbar (1.10.1) - rubyzip (1.2.1) - safe_yaml (1.0.5) - sass (3.5.3) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.0.7) - railties (>= 4.0.0, < 6) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) - sawyer (0.7.0) - addressable (>= 2.3.5, < 2.5) - faraday (~> 0.8, < 0.10) - scout_apm (2.4.19) - selenium-webdriver (3.8.0) - childprocess (~> 0.5) - rubyzip (~> 1.0) - semantic_range (2.3.0) - shoulda-matchers (3.1.2) - activesupport (>= 4.0.0) - sidekiq (5.0.5) - concurrent-ruby (~> 1.0) - connection_pool (~> 2.2, >= 2.2.0) - rack-protection (>= 1.5.0) - redis (>= 3.3.4, < 5) - simplecov (0.19.0) - docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov-html (0.12.3) - solid_use_case (2.2.0) - deterministic (~> 0.6.0) - sprockets (3.7.1) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - stackprof (0.2.12) - statsd-ruby (1.4.0) - temple (0.8.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thor (0.19.4) - thread_safe (0.3.6) - tilt (2.0.8) - timecop (0.9.1) - ts_routes (1.0.1) - railties (>= 5.0) - tzinfo (1.2.5) - thread_safe (~> 0.1) - uglifier (4.0.2) - execjs (>= 0.3.0, < 3) - underscore-rails (1.8.3) - unicode-display_width (1.6.0) - vcr (4.0.0) - webmock (3.6.0) - addressable (>= 2.3.6) - crack (>= 0.3.2) - hashdiff (>= 0.4.0, < 2.0.0) - webpacker (5.2.1) - activesupport (>= 5.2) - rack-proxy (>= 0.6.1) - railties (>= 5.2) - semantic_range (>= 2.3.0) - websocket-driver (0.7.0) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) - xpath (2.1.0) - nokogiri (~> 1.3) - zip-zip (0.3) - rubyzip (>= 1.0.0) - -PLATFORMS - ruby - -DEPENDENCIES - analytics-ruby (~> 2.0.0) - apitome - aws-sdk (~> 2.6.5) - barnes - better_errors - binding_of_caller - block_parser! - bootsnap (>= 1.1.0) - bootstrap (= 4.0.0) - browser - byebug - capybara - database_cleaner - dotenv-rails - dry-validation (~> 0.12.2) - factory_bot_rails - flamegraph - font-awesome-rails - foreman - github_url (= 0.2.1) - gitlab - haml - honeybadger (~> 3.1) - httparty - jquery-rails - js-routes - json_spec - jwt - letter_opener - mathjax-rails - memory_profiler - octokit - overcommit - pagy - pg (~> 0.18) - puma (~> 3.12.0) - pundit - rack-attack - rack-mini-profiler - rails (~> 5.2.1) - rails-controller-testing - react-rails (= 2.4.5) - redis (~> 3.0) - rspec-rails - rspec_api_documentation - rspec_junit_formatter - rubocop - rubyzip (>= 1.0.0) - sass-rails (~> 5.0) - scout_apm - selenium-webdriver - shoulda-matchers - sidekiq - simplecov - solid_use_case (~> 2.2.0) - stackprof - timecop - ts_routes - tzinfo-data - uglifier (>= 1.3.0) - underscore-rails (= 1.8.3) - vcr - webmock - webpacker (~> 5.2.1) - zip-zip - -RUBY VERSION - ruby 2.6.6p146 - -BUNDLED WITH - 2.1.4 diff --git a/Gemfile.outdated b/Gemfile.outdated deleted file mode 100644 index 5a1c8bd..0000000 --- a/Gemfile.outdated +++ /dev/null @@ -1,142 +0,0 @@ -Fetching gem metadata from https://rubygems.org/......... -Fetching gem metadata from https://rubygems.org/. -Resolving dependencies.......... - -Outdated gems included in the bundle: - * actioncable (newest 6.0.3.4, installed 5.2.1) - * actionmailer (newest 6.0.3.4, installed 5.2.1) - * actionpack (newest 6.0.3.4, installed 5.2.1) - * actionview (newest 6.0.3.4, installed 5.2.1) - * activejob (newest 6.0.3.4, installed 5.2.1) - * activemodel (newest 6.0.3.4, installed 5.2.1) - * activerecord (newest 6.0.3.4, installed 5.2.1) - * activestorage (newest 6.0.3.4, installed 5.2.1) - * activesupport (newest 6.0.3.4, installed 5.2.1) - * addressable (newest 2.7.0, installed 2.4.0) - * analytics-ruby (newest 2.2.8, installed 2.0.13, requested ~> 2.0.0) in group "default" - * ast (newest 2.4.1, installed 2.4.0) - * autoprefixer-rails (newest 10.0.1.0, installed 7.2.5) - * aws-sdk (newest 3.0.1, installed 2.6.50, requested ~> 2.6.5) in group "default" - * aws-sdk-core (newest 3.109.1, installed 2.6.50) - * aws-sdk-resources (newest 3.84.0, installed 2.6.50) - * aws-sigv4 (newest 1.2.2, installed 1.0.2) - * barnes (newest 0.0.8, installed 0.0.7) in group "default" - * better_errors (newest 2.8.3, installed 2.5.0) in group "development" - * bootsnap (newest 1.4.8, installed 1.3.1) in group "default" - * bootstrap (newest 4.5.2, installed 4.0.0, requested = 4.0.0) in group "default" - * browser (newest 5.1.0, installed 2.5.2) in group "default" - * builder (newest 3.2.4, installed 3.2.3) - * byebug (newest 11.1.3, installed 9.1.0) in groups "development, test" - * capybara (newest 3.33.0, installed 2.16.1) in group "test" - * childprocess (newest 4.0.0, installed 0.8.0) - * coderay (newest 1.1.3, installed 1.1.2) - * concurrent-ruby (newest 1.1.7, installed 1.1.5) - * connection_pool (newest 2.2.3, installed 2.2.1) - * crack (newest 0.4.4, installed 0.4.3) - * crass (newest 1.0.6, installed 1.0.4) - * database_cleaner (newest 1.8.5, installed 1.6.2) in group "test" - * deterministic (newest 0.16.0, installed 0.6.0) - * diff-lcs (newest 1.4.4, installed 1.3) - * dotenv (newest 2.7.6, installed 2.5.0) - * dotenv-rails (newest 2.7.6, installed 2.5.0) in group "default" - * dry-configurable (newest 0.11.6, installed 0.7.0) - * dry-container (newest 0.7.2, installed 0.6.0) - * dry-core (newest 0.4.9, installed 0.4.7) - * dry-equalizer (newest 0.3.0, installed 0.2.1) - * dry-inflector (newest 0.2.0, installed 0.1.2) - * dry-logic (newest 1.0.8, installed 0.4.2) - * dry-types (newest 1.4.0, installed 0.13.2) - * dry-validation (newest 1.5.6, installed 0.12.2, requested ~> 0.12.2) in group "default" - * erubi (newest 1.9.0, installed 1.7.1) - * factory_bot (newest 6.1.0, installed 4.8.2) - * factory_bot_rails (newest 6.1.0, installed 4.8.2) in groups "development, test" - * faraday (newest 1.0.1, installed 0.9.2) - * ffi (newest 1.13.1, installed 1.9.18) - * font-awesome-rails (newest 4.7.0.5, installed 4.7.0.4) in group "default" - * foreman (newest 0.87.2, installed 0.84.0) in group "development" - * github-markup (newest 3.0.4, installed 1.6.1) - * gitlab (newest 4.16.1, installed 4.14.1) in group "default" - * globalid (newest 0.4.2, installed 0.4.1) - * haml (newest 5.2.0, installed 5.0.4) in group "default" - * hashdiff (newest 1.0.1, installed 0.4.0) - * honeybadger (newest 4.7.2, installed 3.2.0, requested ~> 3.1) in group "default" - * httparty (newest 0.18.1, installed 0.15.6) in group "default" - * i18n (newest 1.8.5, installed 1.6.0) - * iniparse (newest 1.5.0, installed 1.4.4) - * jaro_winkler (newest 1.5.4, installed 1.5.3) - * jmespath (newest 1.4.0, installed 1.3.1) - * jquery-rails (newest 4.4.0, installed 4.3.1) in group "default" - * js-routes (newest 1.4.9, installed 1.4.2) in group "default" - * kramdown (newest 2.3.0, installed 2.1.0) - * launchy (newest 2.5.0, installed 2.4.3) - * loofah (newest 2.7.0, installed 2.2.2) - * mail (newest 2.7.1, installed 2.7.0) - * marcel (newest 0.3.3, installed 0.3.2) - * memory_profiler (newest 0.9.14, installed 0.9.12) in group "default" - * method_source (newest 1.0.0, installed 0.9.0) - * mimemagic (newest 0.3.5, installed 0.3.2) - * mini_mime (newest 1.0.2, installed 1.0.0) - * mini_portile2 (newest 2.5.0, installed 2.2.0) - * minitest (newest 5.14.2, installed 5.11.3) - * msgpack (newest 1.3.3, installed 1.2.4) - * multi_json (newest 1.15.0, installed 1.13.1) - * multipart-post (newest 2.1.1, installed 2.0.0) - * mustache (newest 1.1.1, installed 1.1.0) - * nio4r (newest 2.5.4, installed 2.3.1) - * nokogiri (newest 1.10.10, installed 1.8.0) - * octokit (newest 4.18.0, installed 4.3.0) in group "default" - * overcommit (newest 0.57.0, installed 0.41.0) in group "development" - * pagy (newest 3.8.3, installed 3.7.1) in group "default" - * parallel (newest 1.19.2, installed 1.17.0) - * parser (newest 2.7.2.0, installed 2.6.3.0) - * pg (newest 1.2.3, installed 0.21.0, requested ~> 0.18) in group "default" - * popper_js (newest 1.16.0, installed 1.12.9) - * psych (newest 3.2.0, installed 2.2.4) - * puma (newest 5.0.2, installed 3.12.1, requested ~> 3.12.0) in group "default" - * pundit (newest 2.1.0, installed 1.1.0) in group "default" - * rack (newest 2.2.3, installed 2.0.3) - * rack-attack (newest 6.3.1, installed 6.2.1) in group "default" - * rack-mini-profiler (newest 2.1.0, installed 1.0.0) in group "default" - * rack-protection (newest 2.1.0, installed 2.0.0) - * rack-test (newest 1.1.0, installed 0.8.2) - * rails (newest 6.0.3.4, installed 5.2.1, requested ~> 5.2.1) in group "default" - * rails-controller-testing (newest 1.0.5, installed 1.0.2) in group "test" - * rails-html-sanitizer (newest 1.3.0, installed 1.0.4) - * railties (newest 6.0.3.4, installed 5.2.1) - * rake (newest 13.0.1, installed 12.3.1) - * rb-fsevent (newest 0.10.4, installed 0.10.2) - * rb-inotify (newest 0.10.1, installed 0.9.10) - * react-rails (newest 2.6.1, installed 2.4.5, requested = 2.4.5) in group "default" - * redcarpet (newest 3.5.0, installed 3.3.4) - * redis (newest 4.2.2, installed 3.3.5, requested ~> 3.0) in group "default" - * rspec (newest 3.9.0, installed 3.7.0) - * rspec-core (newest 3.9.3, installed 3.7.0) - * rspec-expectations (newest 3.9.2, installed 3.7.0) - * rspec-mocks (newest 3.9.1, installed 3.7.0) - * rspec-rails (newest 4.0.1, installed 3.7.2) in groups "development, test" - * rspec-support (newest 3.9.3, installed 3.7.0) - * rspec_junit_formatter (newest 0.4.1, installed 0.3.0) in group "test" - * rubocop (newest 0.93.0, installed 0.72.0) in group "development" - * rubyzip (newest 2.3.0, installed 1.2.1) in group "default" - * sass (newest 3.7.4, installed 3.5.3) - * sass-rails (newest 6.0.0, installed 5.0.7, requested ~> 5.0) in group "default" - * sawyer (newest 0.8.2, installed 0.7.0) - * scout_apm (newest 2.6.9, installed 2.4.19) in group "default" - * selenium-webdriver (newest 3.142.7, installed 3.8.0) in group "test" - * shoulda-matchers (newest 4.4.1, installed 3.1.2) in group "test" - * sidekiq (newest 6.1.2, installed 5.0.5) in group "default" - * sprockets (newest 4.0.2, installed 3.7.1) - * sprockets-rails (newest 3.2.2, installed 3.2.1) - * stackprof (newest 0.2.15, installed 0.2.12) in group "default" - * temple (newest 0.8.2, installed 0.8.0) - * thor (newest 1.0.1, installed 0.19.4) - * tilt (newest 2.0.10, installed 2.0.8) - * ts_routes (newest 1.0.3, installed 1.0.1) in group "default" - * tzinfo (newest 2.0.2, installed 1.2.5) - * uglifier (newest 4.2.0, installed 4.0.2) in group "default" - * unicode-display_width (newest 1.7.0, installed 1.6.0) - * vcr (newest 6.0.0, installed 4.0.0) in group "test" - * webmock (newest 3.9.1, installed 3.6.0) in group "test" - * websocket-driver (newest 0.7.3, installed 0.7.0) - * websocket-extensions (newest 0.1.5, installed 0.1.3) - * xpath (newest 3.2.0, installed 2.1.0) diff --git a/app/assets/stylesheets/components/_modal.scss b/app/assets/stylesheets/components/_modal.scss index 56d47f4..91a7560 100644 --- a/app/assets/stylesheets/components/_modal.scss +++ b/app/assets/stylesheets/components/_modal.scss @@ -124,7 +124,7 @@ textarea { resize: none; - @extend input[type="text"]; + @extend input, [type="text"]; padding: 8px 16px; &.modal__input--large { @@ -192,4 +192,4 @@ .display-none { display: none; -} \ No newline at end of file +} -- GitLab From a3f24de213e883c0abd270ffdab96720fc138d7a Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Sat, 10 Oct 2020 10:55:32 -1000 Subject: [PATCH 148/287] Update gems for block parser --- Gemfile.lock | 4 +- gems/block-parser/Gemfile.lock | 57 ++++++++++++++------------ gems/block-parser/block_parser.gemspec | 14 +++---- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0a1bc01..20cbf32 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,7 +2,7 @@ PATH remote: gems/block-parser specs: block_parser (0.1.0) - activemodel (> 4.2) + activemodel (> 5.2) github-markdown (= 0.6.9) github-markup (= 1.6.1) github_url (= 0.2.1) @@ -11,7 +11,7 @@ PATH octokit (= 4.3.0) psych (= 2.2.4) redcarpet (= 3.3.4) - rspec_junit_formatter (> 0.2.3) + rspec_junit_formatter (>= 0.4.1) GEM remote: https://rubygems.org/ diff --git a/gems/block-parser/Gemfile.lock b/gems/block-parser/Gemfile.lock index f077409..d0bc97f 100644 --- a/gems/block-parser/Gemfile.lock +++ b/gems/block-parser/Gemfile.lock @@ -2,7 +2,7 @@ PATH remote: . specs: block_parser (0.1.0) - activemodel (> 4.2) + activemodel (> 5.2) github-markdown (= 0.6.9) github-markup (= 1.6.1) github_url (= 0.2.1) @@ -11,24 +11,24 @@ PATH octokit (= 4.3.0) psych (= 2.2.4) redcarpet (= 3.3.4) - rspec_junit_formatter (> 0.2.3) + rspec_junit_formatter (>= 0.4.1) GEM remote: https://rubygems.org/ specs: - activemodel (6.0.3.1) - activesupport (= 6.0.3.1) - activesupport (6.0.3.1) + activemodel (6.0.3.4) + activesupport (= 6.0.3.4) + activesupport (6.0.3.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.2, >= 2.2.2) addressable (2.4.0) - ast (2.4.0) - byebug (9.1.0) - concurrent-ruby (1.1.6) - diff-lcs (1.3) + ast (2.4.1) + byebug (11.1.3) + concurrent-ruby (1.1.7) + diff-lcs (1.4.4) faraday (0.9.2) multipart-post (>= 1.2, < 3) github-markdown (0.6.9) @@ -37,35 +37,36 @@ GEM gitlab (4.14.1) httparty (~> 0.14, >= 0.14.0) terminal-table (~> 1.5, >= 1.5.1) - httparty (0.18.0) + httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.8.2) + i18n (1.8.5) concurrent-ruby (~> 1.0) mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2020.0512) mini_portile2 (2.2.0) - minitest (5.14.1) + minitest (5.14.2) multi_xml (0.6.0) multipart-post (2.1.1) nokogiri (1.8.0) mini_portile2 (~> 2.2.0) octokit (4.3.0) sawyer (~> 0.7.0, >= 0.5.3) - parallel (1.19.1) - parser (2.7.1.2) - ast (~> 2.4.0) + parallel (1.19.2) + parser (2.7.2.0) + ast (~> 2.4.1) psych (2.2.4) rainbow (3.0.0) - rake (10.5.0) + rake (13.0.1) redcarpet (3.3.4) + regexp_parser (1.8.1) rexml (3.2.4) rspec (3.9.0) rspec-core (~> 3.9.0) rspec-expectations (~> 3.9.0) rspec-mocks (~> 3.9.0) - rspec-core (3.9.2) + rspec-core (3.9.3) rspec-support (~> 3.9.3) rspec-expectations (3.9.2) diff-lcs (>= 1.2.0, < 2.0) @@ -76,13 +77,17 @@ GEM rspec-support (3.9.3) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.83.0) + rubocop (0.93.0) parallel (~> 1.10) - parser (>= 2.7.0.1) + parser (>= 2.7.1.5) rainbow (>= 2.2.2, < 4.0) + regexp_parser (>= 1.8) rexml + rubocop-ast (>= 0.6.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) + rubocop-ast (0.7.1) + parser (>= 2.7.1.5) ruby-progressbar (1.10.1) sawyer (0.7.0) addressable (>= 2.3.5, < 2.5) @@ -93,18 +98,18 @@ GEM tzinfo (1.2.7) thread_safe (~> 0.1) unicode-display_width (1.7.0) - zeitwerk (2.3.0) + zeitwerk (2.4.0) PLATFORMS ruby DEPENDENCIES block_parser! - bundler (~> 1.15) - byebug (= 9.1.0) - rake (~> 10.0) - rspec (~> 3.0) - rubocop (> 0.72) + bundler (~> 2.0) + byebug (= 11.1.3) + rake (~> 13.0) + rspec (~> 3.8) + rubocop (>= 0.93) BUNDLED WITH - 1.17.2 + 2.1.4 diff --git a/gems/block-parser/block_parser.gemspec b/gems/block-parser/block_parser.gemspec index 5c84156..481d6f1 100644 --- a/gems/block-parser/block_parser.gemspec +++ b/gems/block-parser/block_parser.gemspec @@ -26,13 +26,13 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^bin/}) { |f| f[3..-1] } spec.require_paths = ["lib"] - spec.add_development_dependency "bundler", "~> 1.15" - spec.add_development_dependency "byebug", "9.1.0" - spec.add_development_dependency "rake", "~> 10.0" - spec.add_development_dependency "rspec", "~> 3.0" - spec.add_development_dependency "rubocop", "> 0.72" + spec.add_development_dependency "bundler", "~> 2.0" + spec.add_development_dependency "byebug", "11.1.3" + spec.add_development_dependency "rake", "~> 13.0" + spec.add_development_dependency "rspec", "~> 3.8" + spec.add_development_dependency "rubocop", ">= 0.93" - spec.add_dependency "activemodel", "> 4.2" + spec.add_dependency "activemodel", "> 5.2" spec.add_dependency "github-markdown", "0.6.9" spec.add_dependency "github-markup", "1.6.1" spec.add_dependency "github_url", "0.2.1" @@ -41,5 +41,5 @@ Gem::Specification.new do |spec| spec.add_dependency "octokit", "4.3.0" spec.add_dependency "psych", "2.2.4" spec.add_dependency "redcarpet", "3.3.4" - spec.add_dependency "rspec_junit_formatter", "> 0.2.3" + spec.add_dependency "rspec_junit_formatter", ">= 0.4.1" end -- GitLab From 3721c34c92c1899415727c1370ea48cbc83cb7b6 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Sat, 10 Oct 2020 21:36:57 -1000 Subject: [PATCH 149/287] Updated several gems --- Gemfile | 28 +- Gemfile.lock | 1014 ++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 992 insertions(+), 50 deletions(-) diff --git a/Gemfile b/Gemfile index d744d89..9c67f99 100644 --- a/Gemfile +++ b/Gemfile @@ -7,17 +7,17 @@ git_source(:github) do |repo_name| end # architecture -gem "aws-sdk", "~> 2.6.5" -gem "pg", "~> 0.18" -gem "redis", "~> 3.0" -gem "puma", "~> 3.12.0" +gem "aws-sdk" +gem "pg" +gem "redis" +gem "puma" gem "pundit" gem "httparty" gem "pagy" gem "rails", "~> 5.2.1" gem "sidekiq" gem "dotenv-rails" -gem "rubyzip", ">= 1.0.0" +gem "rubyzip" gem "zip-zip" gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby] gem "rack-attack" @@ -25,31 +25,31 @@ gem "rack-attack" # assets gem "ts_routes" gem "webpacker", "~> 5.2.1" -gem "bootstrap", "4.0.0" +gem "bootstrap" gem "font-awesome-rails" gem "sass-rails", "~> 5.0" -gem "uglifier", ">= 1.3.0" +gem "uglifier" # views gem "haml" gem "jquery-rails" gem "mathjax-rails" -gem "react-rails", "2.4.5" -gem "underscore-rails", "1.8.3" +gem "react-rails" +gem "underscore-rails" gem "js-routes" gem "browser" gem "apitome" # services gem 'jwt' -gem "honeybadger", "~> 3.1" -gem "github_url", "0.2.1" +gem "honeybadger" +gem "github_url" gem "gitlab" -gem "analytics-ruby", "~> 2.0.0", require: "segment/analytics" +gem "analytics-ruby", require: "segment/analytics" gem "octokit" gem "barnes" gem "scout_apm" -gem "solid_use_case", "~> 2.2.0" +gem "solid_use_case" gem "dry-validation", "~> 0.12.2" # file processing @@ -58,7 +58,7 @@ gem "dry-validation", "~> 0.12.2" gem "block_parser", path: "./gems/block-parser" # Reduces boot times through caching; required in config/boot.rb -gem "bootsnap", ">= 1.1.0" +gem "bootsnap" # performance gem "rack-mini-profiler" diff --git a/Gemfile.lock b/Gemfile.lock index 20cbf32..e5e1f23 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -58,7 +58,7 @@ GEM minitest (~> 5.1) tzinfo (~> 1.1) addressable (2.4.0) - analytics-ruby (2.0.13) + analytics-ruby (2.2.8) apitome (0.3.0) kramdown railties @@ -67,13 +67,949 @@ GEM autoprefixer-rails (10.0.1.0) execjs aws-eventstream (1.1.0) - aws-sdk (2.6.50) - aws-sdk-resources (= 2.6.50) - aws-sdk-core (2.6.50) - aws-sigv4 (~> 1.0) + aws-partitions (1.381.0) + aws-sdk (3.0.1) + aws-sdk-resources (~> 3) + aws-sdk-accessanalyzer (1.12.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-acm (1.38.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-acmpca (1.30.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-alexaforbusiness (1.43.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-amplify (1.26.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-apigateway (1.55.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-apigatewaymanagementapi (1.19.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-apigatewayv2 (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-appconfig (1.12.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-appflow (1.2.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-applicationautoscaling (1.48.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-applicationdiscoveryservice (1.33.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-applicationinsights (1.15.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-appmesh (1.31.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-appstream (1.48.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-appsync (1.36.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-athena (1.33.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-augmentedairuntime (1.10.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-autoscaling (1.47.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-autoscalingplans (1.28.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-backup (1.23.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-batch (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-braket (1.4.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-budgets (1.35.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-chime (1.37.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloud9 (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-clouddirectory (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudformation (1.44.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudfront (1.43.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudhsm (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudhsmv2 (1.30.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudsearch (1.26.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudsearchdomain (1.22.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudtrail (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudwatch (1.45.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudwatchevents (1.38.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cloudwatchlogs (1.38.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codeartifact (1.4.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codebuild (1.63.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codecommit (1.40.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codedeploy (1.37.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codeguruprofiler (1.12.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codegurureviewer (1.13.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codepipeline (1.37.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codestar (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codestarconnections (1.11.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-codestarnotifications (1.8.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cognitoidentity (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cognitoidentityprovider (1.47.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-cognitosync (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-comprehend (1.41.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-comprehendmedical (1.23.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-computeoptimizer (1.9.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-configservice (1.53.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-connect (1.34.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-connectparticipant (1.8.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-core (3.109.1) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-resources (2.6.50) - aws-sdk-core (= 2.6.50) + aws-sdk-costandusagereportservice (1.28.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-costexplorer (1.51.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-databasemigrationservice (1.44.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-dataexchange (1.10.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-datapipeline (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-datasync (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-dax (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-detective (1.11.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-devicefarm (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-directconnect (1.37.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-directoryservice (1.34.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-dlm (1.35.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-docdb (1.25.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-dynamodb (1.55.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-dynamodbstreams (1.26.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ebs (1.11.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ec2 (1.200.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ec2instanceconnect (1.11.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ecr (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ecs (1.70.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-efs (1.36.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-eks (1.45.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-elasticache (1.44.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-elasticbeanstalk (1.38.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-elasticinference (1.10.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-elasticloadbalancing (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-elasticloadbalancingv2 (1.53.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-elasticsearchservice (1.43.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-elastictranscoder (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-emr (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-eventbridge (1.16.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-firehose (1.35.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-fms (1.32.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-forecastqueryservice (1.10.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-forecastservice (1.11.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-frauddetector (1.13.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-fsx (1.31.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-gamelift (1.38.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-glacier (1.35.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-globalaccelerator (1.23.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-glue (1.74.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-greengrass (1.37.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-groundstation (1.14.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-guardduty (1.42.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-health (1.31.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-honeycode (1.3.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iam (1.46.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-identitystore (1.3.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-imagebuilder (1.15.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-importexport (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv2 (~> 1.0) + aws-sdk-inspector (1.32.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iot (1.58.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iot1clickdevicesservice (1.26.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iot1clickprojects (1.26.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iotanalytics (1.34.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iotdataplane (1.26.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iotevents (1.20.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ioteventsdata (1.13.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iotjobsdataplane (1.25.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iotsecuretunneling (1.8.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iotsitewise (1.12.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-iotthingsgraph (1.12.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ivs (1.5.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kafka (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kendra (1.14.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kinesis (1.30.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kinesisanalytics (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kinesisanalyticsv2 (1.23.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kinesisvideo (1.30.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kinesisvideoarchivedmedia (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kinesisvideomedia (1.26.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kinesisvideosignalingchannels (1.8.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-kms (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-lakeformation (1.11.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-lambda (1.51.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-lambdapreview (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-lex (1.32.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-lexmodelbuildingservice (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-licensemanager (1.20.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-lightsail (1.39.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-machinelearning (1.25.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-macie (1.25.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-macie2 (1.12.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-managedblockchain (1.17.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-marketplacecatalog (1.9.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-marketplacecommerceanalytics (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-marketplaceentitlementservice (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-marketplacemetering (1.31.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mediaconnect (1.28.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mediaconvert (1.58.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-medialive (1.56.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mediapackage (1.36.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mediapackagevod (1.19.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mediastore (1.30.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mediastoredata (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mediatailor (1.32.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-migrationhub (1.29.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-migrationhubconfig (1.9.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mobile (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mq (1.33.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-mturk (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-neptune (1.30.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-networkmanager (1.8.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-opsworks (1.30.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-opsworkscm (1.40.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-organizations (1.52.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-outposts (1.10.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-personalize (1.19.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-personalizeevents (1.14.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-personalizeruntime (1.18.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-pi (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-pinpoint (1.47.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-pinpointemail (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-pinpointsmsvoice (1.21.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-polly (1.37.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-pricing (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-qldb (1.11.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-qldbsession (1.9.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-quicksight (1.33.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ram (1.22.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-rds (1.103.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-rdsdataservice (1.23.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-redshift (1.50.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-redshiftdataapiservice (1.2.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-rekognition (1.46.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-resourcegroups (1.32.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-resourcegroupstaggingapi (1.34.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-resources (3.84.0) + aws-sdk-accessanalyzer (~> 1) + aws-sdk-acm (~> 1) + aws-sdk-acmpca (~> 1) + aws-sdk-alexaforbusiness (~> 1) + aws-sdk-amplify (~> 1) + aws-sdk-apigateway (~> 1) + aws-sdk-apigatewaymanagementapi (~> 1) + aws-sdk-apigatewayv2 (~> 1) + aws-sdk-appconfig (~> 1) + aws-sdk-appflow (~> 1) + aws-sdk-applicationautoscaling (~> 1) + aws-sdk-applicationdiscoveryservice (~> 1) + aws-sdk-applicationinsights (~> 1) + aws-sdk-appmesh (~> 1) + aws-sdk-appstream (~> 1) + aws-sdk-appsync (~> 1) + aws-sdk-athena (~> 1) + aws-sdk-augmentedairuntime (~> 1) + aws-sdk-autoscaling (~> 1) + aws-sdk-autoscalingplans (~> 1) + aws-sdk-backup (~> 1) + aws-sdk-batch (~> 1) + aws-sdk-braket (~> 1) + aws-sdk-budgets (~> 1) + aws-sdk-chime (~> 1) + aws-sdk-cloud9 (~> 1) + aws-sdk-clouddirectory (~> 1) + aws-sdk-cloudformation (~> 1) + aws-sdk-cloudfront (~> 1) + aws-sdk-cloudhsm (~> 1) + aws-sdk-cloudhsmv2 (~> 1) + aws-sdk-cloudsearch (~> 1) + aws-sdk-cloudsearchdomain (~> 1) + aws-sdk-cloudtrail (~> 1) + aws-sdk-cloudwatch (~> 1) + aws-sdk-cloudwatchevents (~> 1) + aws-sdk-cloudwatchlogs (~> 1) + aws-sdk-codeartifact (~> 1) + aws-sdk-codebuild (~> 1) + aws-sdk-codecommit (~> 1) + aws-sdk-codedeploy (~> 1) + aws-sdk-codeguruprofiler (~> 1) + aws-sdk-codegurureviewer (~> 1) + aws-sdk-codepipeline (~> 1) + aws-sdk-codestar (~> 1) + aws-sdk-codestarconnections (~> 1) + aws-sdk-codestarnotifications (~> 1) + aws-sdk-cognitoidentity (~> 1) + aws-sdk-cognitoidentityprovider (~> 1) + aws-sdk-cognitosync (~> 1) + aws-sdk-comprehend (~> 1) + aws-sdk-comprehendmedical (~> 1) + aws-sdk-computeoptimizer (~> 1) + aws-sdk-configservice (~> 1) + aws-sdk-connect (~> 1) + aws-sdk-connectparticipant (~> 1) + aws-sdk-costandusagereportservice (~> 1) + aws-sdk-costexplorer (~> 1) + aws-sdk-databasemigrationservice (~> 1) + aws-sdk-dataexchange (~> 1) + aws-sdk-datapipeline (~> 1) + aws-sdk-datasync (~> 1) + aws-sdk-dax (~> 1) + aws-sdk-detective (~> 1) + aws-sdk-devicefarm (~> 1) + aws-sdk-directconnect (~> 1) + aws-sdk-directoryservice (~> 1) + aws-sdk-dlm (~> 1) + aws-sdk-docdb (~> 1) + aws-sdk-dynamodb (~> 1) + aws-sdk-dynamodbstreams (~> 1) + aws-sdk-ebs (~> 1) + aws-sdk-ec2 (~> 1) + aws-sdk-ec2instanceconnect (~> 1) + aws-sdk-ecr (~> 1) + aws-sdk-ecs (~> 1) + aws-sdk-efs (~> 1) + aws-sdk-eks (~> 1) + aws-sdk-elasticache (~> 1) + aws-sdk-elasticbeanstalk (~> 1) + aws-sdk-elasticinference (~> 1) + aws-sdk-elasticloadbalancing (~> 1) + aws-sdk-elasticloadbalancingv2 (~> 1) + aws-sdk-elasticsearchservice (~> 1) + aws-sdk-elastictranscoder (~> 1) + aws-sdk-emr (~> 1) + aws-sdk-eventbridge (~> 1) + aws-sdk-firehose (~> 1) + aws-sdk-fms (~> 1) + aws-sdk-forecastqueryservice (~> 1) + aws-sdk-forecastservice (~> 1) + aws-sdk-frauddetector (~> 1) + aws-sdk-fsx (~> 1) + aws-sdk-gamelift (~> 1) + aws-sdk-glacier (~> 1) + aws-sdk-globalaccelerator (~> 1) + aws-sdk-glue (~> 1) + aws-sdk-greengrass (~> 1) + aws-sdk-groundstation (~> 1) + aws-sdk-guardduty (~> 1) + aws-sdk-health (~> 1) + aws-sdk-honeycode (~> 1) + aws-sdk-iam (~> 1) + aws-sdk-identitystore (~> 1) + aws-sdk-imagebuilder (~> 1) + aws-sdk-importexport (~> 1) + aws-sdk-inspector (~> 1) + aws-sdk-iot (~> 1) + aws-sdk-iot1clickdevicesservice (~> 1) + aws-sdk-iot1clickprojects (~> 1) + aws-sdk-iotanalytics (~> 1) + aws-sdk-iotdataplane (~> 1) + aws-sdk-iotevents (~> 1) + aws-sdk-ioteventsdata (~> 1) + aws-sdk-iotjobsdataplane (~> 1) + aws-sdk-iotsecuretunneling (~> 1) + aws-sdk-iotsitewise (~> 1) + aws-sdk-iotthingsgraph (~> 1) + aws-sdk-ivs (~> 1) + aws-sdk-kafka (~> 1) + aws-sdk-kendra (~> 1) + aws-sdk-kinesis (~> 1) + aws-sdk-kinesisanalytics (~> 1) + aws-sdk-kinesisanalyticsv2 (~> 1) + aws-sdk-kinesisvideo (~> 1) + aws-sdk-kinesisvideoarchivedmedia (~> 1) + aws-sdk-kinesisvideomedia (~> 1) + aws-sdk-kinesisvideosignalingchannels (~> 1) + aws-sdk-kms (~> 1) + aws-sdk-lakeformation (~> 1) + aws-sdk-lambda (~> 1) + aws-sdk-lambdapreview (~> 1) + aws-sdk-lex (~> 1) + aws-sdk-lexmodelbuildingservice (~> 1) + aws-sdk-licensemanager (~> 1) + aws-sdk-lightsail (~> 1) + aws-sdk-machinelearning (~> 1) + aws-sdk-macie (~> 1) + aws-sdk-macie2 (~> 1) + aws-sdk-managedblockchain (~> 1) + aws-sdk-marketplacecatalog (~> 1) + aws-sdk-marketplacecommerceanalytics (~> 1) + aws-sdk-marketplaceentitlementservice (~> 1) + aws-sdk-marketplacemetering (~> 1) + aws-sdk-mediaconnect (~> 1) + aws-sdk-mediaconvert (~> 1) + aws-sdk-medialive (~> 1) + aws-sdk-mediapackage (~> 1) + aws-sdk-mediapackagevod (~> 1) + aws-sdk-mediastore (~> 1) + aws-sdk-mediastoredata (~> 1) + aws-sdk-mediatailor (~> 1) + aws-sdk-migrationhub (~> 1) + aws-sdk-migrationhubconfig (~> 1) + aws-sdk-mobile (~> 1) + aws-sdk-mq (~> 1) + aws-sdk-mturk (~> 1) + aws-sdk-neptune (~> 1) + aws-sdk-networkmanager (~> 1) + aws-sdk-opsworks (~> 1) + aws-sdk-opsworkscm (~> 1) + aws-sdk-organizations (~> 1) + aws-sdk-outposts (~> 1) + aws-sdk-personalize (~> 1) + aws-sdk-personalizeevents (~> 1) + aws-sdk-personalizeruntime (~> 1) + aws-sdk-pi (~> 1) + aws-sdk-pinpoint (~> 1) + aws-sdk-pinpointemail (~> 1) + aws-sdk-pinpointsmsvoice (~> 1) + aws-sdk-polly (~> 1) + aws-sdk-pricing (~> 1) + aws-sdk-qldb (~> 1) + aws-sdk-qldbsession (~> 1) + aws-sdk-quicksight (~> 1) + aws-sdk-ram (~> 1) + aws-sdk-rds (~> 1) + aws-sdk-rdsdataservice (~> 1) + aws-sdk-redshift (~> 1) + aws-sdk-redshiftdataapiservice (~> 1) + aws-sdk-rekognition (~> 1) + aws-sdk-resourcegroups (~> 1) + aws-sdk-resourcegroupstaggingapi (~> 1) + aws-sdk-robomaker (~> 1) + aws-sdk-route53 (~> 1) + aws-sdk-route53domains (~> 1) + aws-sdk-route53resolver (~> 1) + aws-sdk-s3 (~> 1) + aws-sdk-s3control (~> 1) + aws-sdk-s3outposts (~> 1) + aws-sdk-sagemaker (~> 1) + aws-sdk-sagemakerruntime (~> 1) + aws-sdk-savingsplans (~> 1) + aws-sdk-schemas (~> 1) + aws-sdk-secretsmanager (~> 1) + aws-sdk-securityhub (~> 1) + aws-sdk-serverlessapplicationrepository (~> 1) + aws-sdk-servicecatalog (~> 1) + aws-sdk-servicediscovery (~> 1) + aws-sdk-servicequotas (~> 1) + aws-sdk-ses (~> 1) + aws-sdk-sesv2 (~> 1) + aws-sdk-shield (~> 1) + aws-sdk-signer (~> 1) + aws-sdk-simpledb (~> 1) + aws-sdk-sms (~> 1) + aws-sdk-snowball (~> 1) + aws-sdk-sns (~> 1) + aws-sdk-sqs (~> 1) + aws-sdk-ssm (~> 1) + aws-sdk-ssoadmin (~> 1) + aws-sdk-ssooidc (~> 1) + aws-sdk-states (~> 1) + aws-sdk-storagegateway (~> 1) + aws-sdk-support (~> 1) + aws-sdk-swf (~> 1) + aws-sdk-synthetics (~> 1) + aws-sdk-textract (~> 1) + aws-sdk-timestreamquery (~> 1) + aws-sdk-timestreamwrite (~> 1) + aws-sdk-transcribeservice (~> 1) + aws-sdk-transcribestreamingservice (~> 1) + aws-sdk-transfer (~> 1) + aws-sdk-translate (~> 1) + aws-sdk-waf (~> 1) + aws-sdk-wafregional (~> 1) + aws-sdk-wafv2 (~> 1) + aws-sdk-workdocs (~> 1) + aws-sdk-worklink (~> 1) + aws-sdk-workmail (~> 1) + aws-sdk-workmailmessageflow (~> 1) + aws-sdk-workspaces (~> 1) + aws-sdk-xray (~> 1) + aws-sdk-robomaker (1.30.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-route53 (1.44.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-route53domains (1.28.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-route53resolver (1.21.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.83.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sdk-s3control (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3outposts (1.0.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-sagemaker (1.70.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-sagemakerruntime (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-savingsplans (1.12.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-schemas (1.10.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-secretsmanager (1.43.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-securityhub (1.35.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-serverlessapplicationrepository (1.32.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-servicecatalog (1.50.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-servicediscovery (1.31.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-servicequotas (1.11.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ses (1.36.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-sesv2 (1.13.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-shield (1.32.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-signer (1.26.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-simpledb (1.24.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv2 (~> 1.0) + aws-sdk-sms (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-snowball (1.35.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-sns (1.33.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-sqs (1.34.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ssm (1.93.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ssoadmin (1.3.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ssooidc (1.8.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-states (1.36.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-storagegateway (1.50.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-support (1.28.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-swf (1.25.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-synthetics (1.9.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-textract (1.21.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-timestreamquery (1.1.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-timestreamwrite (1.1.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-transcribeservice (1.50.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-transcribestreamingservice (1.22.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-transfer (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-translate (1.28.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-waf (1.36.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-wafregional (1.37.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-wafv2 (1.14.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-workdocs (1.28.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-worklink (1.21.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-workmail (1.31.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-workmailmessageflow (1.9.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-workspaces (1.47.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-xray (1.33.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sigv2 (1.0.1) aws-sigv4 (1.2.2) aws-eventstream (~> 1, >= 1.0.2) babel-source (5.8.35) @@ -91,10 +1027,10 @@ GEM debug_inspector (>= 0.0.1) bootsnap (1.4.8) msgpack (~> 1.0) - bootstrap (4.0.0) - autoprefixer-rails (>= 6.0.3) - popper_js (>= 1.12.9, < 2) - sass (>= 3.5.2) + bootstrap (4.5.2) + autoprefixer-rails (>= 9.1.0) + popper_js (>= 1.14.3, < 2) + sassc-rails (>= 2.0.0) browser (5.1.0) builder (3.2.4) byebug (11.1.3) @@ -176,7 +1112,7 @@ GEM temple (>= 0.8.0) tilt hashdiff (1.0.1) - honeybadger (3.3.1) + honeybadger (4.7.2) httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) @@ -236,10 +1172,11 @@ GEM parallel (1.19.2) parser (2.7.2.0) ast (~> 2.4.1) - pg (0.21.0) + pg (1.2.3) popper_js (1.16.0) psych (2.2.4) - puma (3.12.6) + puma (5.0.2) + nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) rack (2.2.3) @@ -247,8 +1184,6 @@ GEM rack (>= 1.0, < 3) rack-mini-profiler (2.1.0) rack (>= 1.2.0) - rack-protection (2.1.0) - rack rack-proxy (0.6.5) rack rack-test (1.1.0) @@ -286,14 +1221,14 @@ GEM rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) - react-rails (2.4.5) + react-rails (2.6.1) babel-transpiler (>= 0.7.0) connection_pool execjs railties (>= 3.2) tilt redcarpet (3.3.4) - redis (3.3.5) + redis (4.2.2) regexp_parser (1.8.1) rexml (3.2.4) rspec (3.9.0) @@ -347,6 +1282,14 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt sawyer (0.7.0) addressable (>= 2.3.5, < 2.5) faraday (~> 0.8, < 0.10) @@ -358,11 +1301,10 @@ GEM semantic_range (2.3.0) shoulda-matchers (4.4.1) activesupport (>= 4.2.0) - sidekiq (5.2.9) - connection_pool (~> 2.2, >= 2.2.2) + sidekiq (6.1.2) + connection_pool (>= 2.2.2) rack (~> 2.0) - rack-protection (>= 1.5.0) - redis (>= 3.3.5, < 4.2) + redis (>= 4.2.0) simplecov (0.19.0) docile (~> 1.1) simplecov-html (~> 0.11) @@ -415,15 +1357,15 @@ PLATFORMS ruby DEPENDENCIES - analytics-ruby (~> 2.0.0) + analytics-ruby apitome - aws-sdk (~> 2.6.5) + aws-sdk barnes better_errors binding_of_caller block_parser! - bootsnap (>= 1.1.0) - bootstrap (= 4.0.0) + bootsnap + bootstrap browser byebug capybara @@ -434,10 +1376,10 @@ DEPENDENCIES flamegraph font-awesome-rails foreman - github_url (= 0.2.1) + github_url gitlab haml - honeybadger (~> 3.1) + honeybadger httparty jquery-rails js-routes @@ -449,33 +1391,33 @@ DEPENDENCIES octokit overcommit pagy - pg (~> 0.18) - puma (~> 3.12.0) + pg + puma pundit rack-attack rack-mini-profiler rails (~> 5.2.1) rails-controller-testing - react-rails (= 2.4.5) - redis (~> 3.0) + react-rails + redis rspec-rails rspec_api_documentation rspec_junit_formatter rubocop - rubyzip (>= 1.0.0) + rubyzip sass-rails (~> 5.0) scout_apm selenium-webdriver shoulda-matchers sidekiq simplecov - solid_use_case (~> 2.2.0) + solid_use_case stackprof timecop ts_routes tzinfo-data - uglifier (>= 1.3.0) - underscore-rails (= 1.8.3) + uglifier + underscore-rails vcr webmock webpacker (~> 5.2.1) -- GitLab From 2530da42f4250ebc226724f07719c3cd72138e4d Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Sun, 11 Oct 2020 11:42:30 -1000 Subject: [PATCH 150/287] Update webpacker --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 9c67f99..f91c403 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem "rack-attack" # assets gem "ts_routes" -gem "webpacker", "~> 5.2.1" +gem "webpacker" gem "bootstrap" gem "font-awesome-rails" gem "sass-rails", "~> 5.0" diff --git a/Gemfile.lock b/Gemfile.lock index e5e1f23..4b888a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1420,7 +1420,7 @@ DEPENDENCIES underscore-rails vcr webmock - webpacker (~> 5.2.1) + webpacker zip-zip RUBY VERSION -- GitLab From 12f044348a77fc22affcd253a0b5eaf8926d32a4 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Mon, 12 Oct 2020 15:45:24 -1000 Subject: [PATCH 151/287] Rewrite course validator not to use dry-validation --- Gemfile | 2 +- Gemfile.lock | 33 ++++++++----- app/services/course_validator.rb | 82 +++++++++++++++++--------------- 3 files changed, 66 insertions(+), 51 deletions(-) diff --git a/Gemfile b/Gemfile index f91c403..b582b85 100644 --- a/Gemfile +++ b/Gemfile @@ -50,7 +50,7 @@ gem "octokit" gem "barnes" gem "scout_apm" gem "solid_use_case" -gem "dry-validation", "~> 0.12.2" +gem "dry-validation" # file processing ### We need to pass git credentials into the docker build command so we can clone the block-parser diff --git a/Gemfile.lock b/Gemfile.lock index 4b888a2..a29d286 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1068,24 +1068,33 @@ GEM concurrent-ruby (~> 1.0) dry-equalizer (0.3.0) dry-inflector (0.2.0) - dry-logic (0.4.2) - dry-container (~> 0.2, >= 0.2.6) + dry-initializer (3.0.4) + dry-logic (1.0.8) + concurrent-ruby (~> 1.0) dry-core (~> 0.2) dry-equalizer (~> 0.2) - dry-types (0.13.4) + dry-schema (1.5.5) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.8, >= 0.8.3) + dry-core (~> 0.4) + dry-equalizer (~> 0.2) + dry-initializer (~> 3.0) + dry-logic (~> 1.0) + dry-types (~> 1.4) + dry-types (1.4.0) concurrent-ruby (~> 1.0) dry-container (~> 0.3) dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.2) + dry-equalizer (~> 0.3) dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 0.4, >= 0.4.2) - dry-validation (0.12.3) + dry-logic (~> 1.0, >= 1.0.2) + dry-validation (1.5.6) concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (~> 0.2, >= 0.2.1) + dry-container (~> 0.7, >= 0.7.1) + dry-core (~> 0.4) dry-equalizer (~> 0.2) - dry-logic (~> 0.4.2) - dry-types (~> 0.13.1) + dry-initializer (~> 3.0) + dry-schema (~> 1.5, >= 1.5.2) erubi (1.9.0) execjs (2.7.0) factory_bot (6.1.0) @@ -1229,7 +1238,7 @@ GEM tilt redcarpet (3.3.4) redis (4.2.2) - regexp_parser (1.8.1) + regexp_parser (1.8.2) rexml (3.2.4) rspec (3.9.0) rspec-core (~> 3.9.0) @@ -1371,7 +1380,7 @@ DEPENDENCIES capybara database_cleaner dotenv-rails - dry-validation (~> 0.12.2) + dry-validation factory_bot_rails flamegraph font-awesome-rails diff --git a/app/services/course_validator.rb b/app/services/course_validator.rb index 08a4d1a..67d2737 100644 --- a/app/services/course_validator.rb +++ b/app/services/course_validator.rb @@ -1,5 +1,3 @@ -require "dry-validation" - class CourseValidator include SolidUseCase @@ -69,7 +67,7 @@ class CourseValidator end end - errors = @@schema.call(contents).messages + errors = validate_course_format(contents) if errors.any? fail :course_validation_error, errors: massage_validation_errors(contents, errors) @@ -168,54 +166,62 @@ class CourseValidator errors end - @@schema = Dry::Validation.Schema do - configure do - def github_url?(value) - GitUrlService.new(url: value) - true - rescue StandardError - false - end - - def self.messages - super.merge(en: { - errors: { - no_duplicate_repos: "Duplicate github repos are not allowed", - github_url?: "is invalid" - } - }) - end + def validate_course_format(contents) + def github_url?(myurl) + GitUrlService.new(url: myurl) + true + rescue StandardError + false end - required("course").each do - # Sections - schema do - required("repos").each do - # Repos - schema do - required("url").filled(:str?, :github_url?) - end - end - required("section").filled(:str?) - end - end - - validate(no_duplicate_repos: "course") do |course| + def no_duplicate_repos?(course) seen = {} dupe = nil course.each do |section| break if dupe - section["repos"].each do |repo| - if seen[repo["url"]] - dupe = repo["url"] + section['repos'].each do |repo| + if seen[repo['url']] + dupe = repo['url'] break end - seen[repo["url"]] = true + seen[repo['url']] = true end end dupe.nil? end + + errors = {} + courses = contents['course'] + unless courses.kind_of?(Array) + errors['course'] = ["must be an array"] + return errors + end + + courses.each_with_index do |course, course_index| + unless course['section'] + errors['course'] = ['section is missing'] + return errors + end + repos = course['repos'] + unless repos.kind_of?(Array) + errors[:section_missing_repos] = { index: course_index, title: "No repos" } + return errors + end + repos.each_with_index do |repo, repo_index| + unless github_url?(repo['url']) + errors['course'] = { course_index => { "repos" => { + repo_index => { "url" => ['is invalid'] } + } } } + return errors + end + end + end + + errors[:no_duplicate_repos] = ['Duplicate github repos are not allowed'] unless no_duplicate_repos?(courses) + + errors end + end -- GitLab From 767e59f0097dff2c9765da0f8828cfddbd927105 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Mon, 12 Oct 2020 15:59:20 -1000 Subject: [PATCH 152/287] Remove dry-validation from Gemfile --- Gemfile | 1 - Gemfile.lock | 45 +++------------------------------------------ 2 files changed, 3 insertions(+), 43 deletions(-) diff --git a/Gemfile b/Gemfile index b582b85..6e94854 100644 --- a/Gemfile +++ b/Gemfile @@ -50,7 +50,6 @@ gem "octokit" gem "barnes" gem "scout_apm" gem "solid_use_case" -gem "dry-validation" # file processing ### We need to pass git credentials into the docker build command so we can clone the block-parser diff --git a/Gemfile.lock b/Gemfile.lock index a29d286..c572acf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1057,44 +1057,6 @@ GEM dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - dry-configurable (0.11.6) - concurrent-ruby (~> 1.0) - dry-core (~> 0.4, >= 0.4.7) - dry-equalizer (~> 0.2) - dry-container (0.7.2) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.1, >= 0.1.3) - dry-core (0.4.9) - concurrent-ruby (~> 1.0) - dry-equalizer (0.3.0) - dry-inflector (0.2.0) - dry-initializer (3.0.4) - dry-logic (1.0.8) - concurrent-ruby (~> 1.0) - dry-core (~> 0.2) - dry-equalizer (~> 0.2) - dry-schema (1.5.5) - concurrent-ruby (~> 1.0) - dry-configurable (~> 0.8, >= 0.8.3) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) - dry-initializer (~> 3.0) - dry-logic (~> 1.0) - dry-types (~> 1.4) - dry-types (1.4.0) - concurrent-ruby (~> 1.0) - dry-container (~> 0.3) - dry-core (~> 0.4, >= 0.4.4) - dry-equalizer (~> 0.3) - dry-inflector (~> 0.1, >= 0.1.2) - dry-logic (~> 1.0, >= 1.0.2) - dry-validation (1.5.6) - concurrent-ruby (~> 1.0) - dry-container (~> 0.7, >= 0.7.1) - dry-core (~> 0.4) - dry-equalizer (~> 0.2) - dry-initializer (~> 3.0) - dry-schema (~> 1.5, >= 1.5.2) erubi (1.9.0) execjs (2.7.0) factory_bot (6.1.0) @@ -1267,7 +1229,7 @@ GEM rspec (~> 3.0) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.93.0) + rubocop (0.93.1) parallel (~> 1.10) parser (>= 2.7.1.5) rainbow (>= 2.2.2, < 4.0) @@ -1276,7 +1238,7 @@ GEM rubocop-ast (>= 0.6.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.7.1) + rubocop-ast (0.8.0) parser (>= 2.7.1.5) ruby-progressbar (1.10.1) rubyzip (2.3.0) @@ -1345,7 +1307,7 @@ GEM underscore-rails (1.8.3) unicode-display_width (1.7.0) vcr (6.0.0) - webmock (3.9.1) + webmock (3.9.2) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -1380,7 +1342,6 @@ DEPENDENCIES capybara database_cleaner dotenv-rails - dry-validation factory_bot_rails flamegraph font-awesome-rails -- GitLab From b77e648f0aba19fff004a14aa34f2da65e33cbe1 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Mon, 12 Oct 2020 17:37:28 -1000 Subject: [PATCH 153/287] Refactor course_validator --- app/services/course_validator.rb | 61 ++++---------------------------- gems/block-parser/Gemfile.lock | 6 ++-- 2 files changed, 10 insertions(+), 57 deletions(-) diff --git a/app/services/course_validator.rb b/app/services/course_validator.rb index 67d2737..981dcaf 100644 --- a/app/services/course_validator.rb +++ b/app/services/course_validator.rb @@ -70,7 +70,7 @@ class CourseValidator errors = validate_course_format(contents) if errors.any? - fail :course_validation_error, errors: massage_validation_errors(contents, errors) + fail :course_validation_error, errors: errors else default_unit_visibility = contents["defaultunitvisibility"].nil? ? false : contents["defaultunitvisibility"] continue(params.merge(course: { @@ -152,20 +152,6 @@ class CourseValidator "User-Agent" => "Galvanize Forge").read end - def massage_validation_errors(contents, errors) - course = errors["course"] - if course == ["must be an array"] - return { empty_course: true } - elsif course&.is_a?(Hash) && course.keys.count == 1 - index = course.keys.first - if course[index]["repos"] == ["is missing"] - return { section_missing_repos: { title: contents["course"][index]["section"], index: index } } - end - end - - errors - end - def validate_course_format(contents) def github_url?(myurl) GitUrlService.new(url: myurl) @@ -174,53 +160,20 @@ class CourseValidator false end - def no_duplicate_repos?(course) - seen = {} - dupe = nil - course.each do |section| - break if dupe - - section['repos'].each do |repo| - if seen[repo['url']] - dupe = repo['url'] - break - end - seen[repo['url']] = true - end - end - - dupe.nil? - end - errors = {} + seen = {} courses = contents['course'] - unless courses.kind_of?(Array) - errors['course'] = ["must be an array"] - return errors - end - + errors = { empty_course: true } and return errors unless courses.kind_of?(Array) courses.each_with_index do |course, course_index| - unless course['section'] - errors['course'] = ['section is missing'] - return errors - end repos = course['repos'] - unless repos.kind_of?(Array) - errors[:section_missing_repos] = { index: course_index, title: "No repos" } - return errors - end + errors[:section_missing_repos] = { index: course_index, title: "No repos" } and return errors unless repos.kind_of?(Array) repos.each_with_index do |repo, repo_index| - unless github_url?(repo['url']) - errors['course'] = { course_index => { "repos" => { - repo_index => { "url" => ['is invalid'] } - } } } - return errors - end + errors[:no_duplicate_repos] = ['Duplicate github repos are not allowed'] and return errors if seen[repo['url']] + seen[repo['url']] = true + errors['course'] = { course_index => { "repos" => { repo_index => { "url" => ['is invalid'] } } } } and return errors unless github_url?(repo['url']) end end - errors[:no_duplicate_repos] = ['Duplicate github repos are not allowed'] unless no_duplicate_repos?(courses) - errors end diff --git a/gems/block-parser/Gemfile.lock b/gems/block-parser/Gemfile.lock index d0bc97f..29aa5e3 100644 --- a/gems/block-parser/Gemfile.lock +++ b/gems/block-parser/Gemfile.lock @@ -60,7 +60,7 @@ GEM rainbow (3.0.0) rake (13.0.1) redcarpet (3.3.4) - regexp_parser (1.8.1) + regexp_parser (1.8.2) rexml (3.2.4) rspec (3.9.0) rspec-core (~> 3.9.0) @@ -77,7 +77,7 @@ GEM rspec-support (3.9.3) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.93.0) + rubocop (0.93.1) parallel (~> 1.10) parser (>= 2.7.1.5) rainbow (>= 2.2.2, < 4.0) @@ -86,7 +86,7 @@ GEM rubocop-ast (>= 0.6.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.7.1) + rubocop-ast (0.8.0) parser (>= 2.7.1.5) ruby-progressbar (1.10.1) sawyer (0.7.0) -- GitLab From 2eb53654cbea01b40679d9c4496a0da7233ae95c Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 12 Oct 2020 20:36:36 -1000 Subject: [PATCH 154/287] got ruby image working. moving package installs to centos image builder layer --- Dockerfile | 124 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 97 insertions(+), 27 deletions(-) diff --git a/Dockerfile b/Dockerfile index d35d8b7..88f5677 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,97 @@ -FROM bitnami/ruby:2.6.6 - -# Install what we can through OS package management -RUN apt-get -y update -RUN apt-get -y install --no-install-recommends \ - redis-tools \ - curl \ - software-properties-common \ - postgresql-client\ - libpq-dev \ - libxml2-dev \ - libxslt1-dev \ - qt5-default \ - libqt5webkit5-dev \ - xvfb - -# Clear off space. -RUN apt-get -q clean - -# Install Node.js. +# Stage 1: Install libararies +FROM centos:8 as builder +USER 0 + +# Install the redis-cli tools +RUN dnf update -y && \ + dnf install -y \ + redis \ + make \ + patch \ + git +# libxml2-devel \ +# libxslt-devel \ +# libpq-devel \ +# gcc \ +# zlib-devel + +# Stage 2: Setup the Image. +FROM registry.il2.dsop.io/platform-one/devops/pipeline-templates/ironbank/ruby26:2.6.6.212 +USER 0 + +RUN dnf update -y && \ + dnf install -y gcc \ + zlib-devel \ + libpq-devel \ + libxml2-devel \ + libxslt-devel + +# Copy over the libraries. +COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli +COPY --from=builder /usr/bin/patch /usr/bin/patch +COPY --from=builder /usr/bin/make /usr/bin/make +COPY --from=builder /usr/bin/git* /usr/bin/ +#COPY --from=builder /usr/lib64/libxml* /usr/lib64/ +#COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libpq* /usr/lib64/ +#COPY --from=builder /usr/bin/gcc* /usr/bin/ +#COPY --from=builder /usr/lib/gcc /usr/lib/gcc +#COPY --from=builder /usr/bin/as /usr/bin/as +#COPY --from=builder /usr/bin/ld /usr/bin/ld +#COPY --from=builder /usr/bin/pkgconf /usr/bin/pkgconf +#COPY --from=builder /usr/libexec/gcc /usr/libexec/gcc +#COPY --from=builder /usr/lib64/libmpc* /usr/lib64/ +#COPY --from=builder /usr/lib64/libopcodes* /usr/lib64/ +#COPY --from=builder /usr/lib64/libbfd* /usr/lib64/ +#COPY --from=builder /usr/lib64/libc.so* /usr/lib64/ +#COPY --from=builder /usr/lib64/libc_nonshared* /usr/lib64/ +#COPY --from=builder /usr/lib64/libcrypt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libgomp* /usr/lib64/ +#COPY --from=builder /usr/lib64/libgpg* /usr/lib64/ +#COPY --from=builder /usr/lib64/libgcrypt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libgcc* /usr/lib64/ +#COPY --from=builder /usr/lib64/libpkgconf* /usr/lib64/ +#COPY --from=builder /usr/lib64/crt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ +#COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ +#COPY --from=builder /usr/lib64/libmcheck* /usr/lib64/ +#COPY --from=builder /usr/lib64/Mcrt1* /usr/lib64/ +#COPY --from=builder /usr/lib64/Scrt1* /usr/lib64/ +#COPY --from=builder /usr/lib64/gcrt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libanl* /usr/lib64/ +#COPY --from=builder /usr/lib64/libdl* /usr/lib64/ +#COPY --from=builder /usr/lib64/libexslt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libg* /usr/lib64/ +#COPY --from=builder /usr/lib64/libisl* /usr/lib64/ +#COPY --from=builder /usr/lib64/liblzma* /usr/lib64/ +#COPY --from=builder /usr/lib64/libmvec* /usr/lib64/ +#COPY --from=builder /usr/lib64/libpthread* /usr/lib64/ +#COPY --from=builder /usr/lib64/libresolv* /usr/lib64/ +#COPY --from=builder /usr/lib64/librt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libthread_db* /usr/lib64/ +#COPY --from=builder /usr/lib64/libutil* /usr/lib64/ +#COPY --from=builder /usr/lib64/libz* /usr/lib64/ +#COPY --from=builder /usr/lib64/rcrt* /usr/lib64/ +#COPY --from=builder /usr/lib64/xml2Conf.sh /usr/lib64/xml2Conf.sh +#COPY --from=builder /usr/lib64/xsltConf.sh /usr/lib64/xsltConf.sh +#COPY --from=builder /usr/include /usr/include + +# Install Node ADD .nvmrc . RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n RUN bash n auto -RUN npm install -g yarn # Setup our environment WORKDIR /usr/src/app ADD . . +# Add write permissions. +RUN chown -R 1001 . +RUN chmod -R u+w . + +# Become the ruby user +USER 1001 + # Set the Rails environment variable. ENV RAILS_ENV production @@ -36,18 +101,23 @@ RUN gem install rake:13.0.1 --source 'https://rubygems.org/' # Run the bundle install RUN bundle install +#RUN cat /usr/local/bundle/extensions/x86_64-linux/2.6.0/nokogiri-1.8.0/mkmf.log -# Precompile assets -RUN bundle exec rake assets:precompile +# Install Yarn. +RUN npm install yarn +RUN rm package-lock.json + +# Add the node_modules to the path. +ENV PATH /usr/src/app/node_modules/.bin:$PATH # Run Yarn Install RUN yarn install +# Precompile assets +RUN bundle exec rake assets:precompile + # Asset Clean RUN bundle exec rake assets:clean -# Add the node_modules to the path. -ENV PATH /usr/src/app/node_modules/.bin:$PATH - # Set the entry point. -CMD ["./entrypoint-server.sh"] +ENTRYPOINT ["./entrypoint-server.sh"] \ No newline at end of file -- GitLab From 1aaf4abec8db2322888f4d04bd64d8df9ce5dab1 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 12 Oct 2020 21:27:56 -1000 Subject: [PATCH 155/287] moving libz install --- Dockerfile | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 88f5677..e05ff3a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,12 +8,12 @@ RUN dnf update -y && \ redis \ make \ patch \ - git + git \ + zlib-devel # libxml2-devel \ # libxslt-devel \ # libpq-devel \ # gcc \ -# zlib-devel # Stage 2: Setup the Image. FROM registry.il2.dsop.io/platform-one/devops/pipeline-templates/ironbank/ruby26:2.6.6.212 @@ -31,6 +31,7 @@ COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli COPY --from=builder /usr/bin/patch /usr/bin/patch COPY --from=builder /usr/bin/make /usr/bin/make COPY --from=builder /usr/bin/git* /usr/bin/ +COPY --from=builder /usr/lib64/libz* /usr/lib64/ #COPY --from=builder /usr/lib64/libxml* /usr/lib64/ #COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ #COPY --from=builder /usr/lib64/libpq* /usr/lib64/ @@ -97,27 +98,27 @@ ENV RAILS_ENV production # Install Rails Dependecies. RUN gem install bundler:2.1.4 -RUN gem install rake:13.0.1 --source 'https://rubygems.org/' +#RUN gem install rake:13.0.1 --source 'https://rubygems.org/' # Run the bundle install RUN bundle install #RUN cat /usr/local/bundle/extensions/x86_64-linux/2.6.0/nokogiri-1.8.0/mkmf.log -# Install Yarn. -RUN npm install yarn -RUN rm package-lock.json - -# Add the node_modules to the path. -ENV PATH /usr/src/app/node_modules/.bin:$PATH - -# Run Yarn Install -RUN yarn install - -# Precompile assets -RUN bundle exec rake assets:precompile - -# Asset Clean -RUN bundle exec rake assets:clean - -# Set the entry point. -ENTRYPOINT ["./entrypoint-server.sh"] \ No newline at end of file +## Install Yarn. +#RUN npm install yarn +#RUN rm package-lock.json +# +## Add the node_modules to the path. +#ENV PATH /usr/src/app/node_modules/.bin:$PATH +# +## Run Yarn Install +#RUN yarn install +# +## Precompile assets +#RUN bundle exec rake assets:precompile +# +## Asset Clean +#RUN bundle exec rake assets:clean +# +## Set the entry point. +#ENTRYPOINT ["./entrypoint-server.sh"] \ No newline at end of file -- GitLab From 46d2455d9669271fd27aedc4faa398749f4566c5 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 12 Oct 2020 21:38:22 -1000 Subject: [PATCH 156/287] moving libz install --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e05ff3a..97e6a9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,6 @@ USER 0 RUN dnf update -y && \ dnf install -y gcc \ - zlib-devel \ libpq-devel \ libxml2-devel \ libxslt-devel -- GitLab From f55699ead8af09ea922e1a9bfb146e43950cc080 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 12 Oct 2020 23:07:11 -1000 Subject: [PATCH 157/287] moving postgres install --- Dockerfile | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 97e6a9a..968e8bd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,10 +9,10 @@ RUN dnf update -y && \ make \ patch \ git \ - zlib-devel + zlib-devel \ + libpq-devel # libxml2-devel \ # libxslt-devel \ -# libpq-devel \ # gcc \ # Stage 2: Setup the Image. @@ -21,19 +21,37 @@ USER 0 RUN dnf update -y && \ dnf install -y gcc \ - libpq-devel \ libxml2-devel \ libxslt-devel -# Copy over the libraries. +# Redis CLI Binary. COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli + +# Patch Binary. COPY --from=builder /usr/bin/patch /usr/bin/patch + +# Make Binary. COPY --from=builder /usr/bin/make /usr/bin/make + +# Git Binaries. COPY --from=builder /usr/bin/git* /usr/bin/ + +# libz Dependencies. COPY --from=builder /usr/lib64/libz* /usr/lib64/ + +# Postgres Dependencies +COPY --from=builder /usr/bin/pkgconf /usr/bin/pkgconf +COPY --from=builder /usr/bin/pg_config /usr/bin/pg_config +COPY --from=builder /usr/lib64/libpq* /usr/lib64/ +COPY --from=builder /usr/lib64/libpkgconf* /usr/lib64/ +COPY --from=builder /usr/include/libpq /usr/include/libpq +COPY --from=builder /usr/include/libpq* /usr/include/ +COPY --from=builder /usr/include/pg* /usr/include/ +COPY --from=builder /usr/include/pgsql /usr/include/pgsql +COPY --from=builder /usr/include/postgres* /usr/include/ + #COPY --from=builder /usr/lib64/libxml* /usr/lib64/ #COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libpq* /usr/lib64/ #COPY --from=builder /usr/bin/gcc* /usr/bin/ #COPY --from=builder /usr/lib/gcc /usr/lib/gcc #COPY --from=builder /usr/bin/as /usr/bin/as -- GitLab From a643fade205e41497b22a12236f2eb00f49bc5b1 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 00:19:08 -1000 Subject: [PATCH 158/287] moving libxml and libxslt --- Dockerfile | 68 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/Dockerfile b/Dockerfile index 968e8bd..7dd4d98 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,19 +10,17 @@ RUN dnf update -y && \ patch \ git \ zlib-devel \ - libpq-devel -# libxml2-devel \ -# libxslt-devel \ -# gcc \ + libpq-devel \ + libxml2-devel \ + libxslt-devel \ + gcc # Stage 2: Setup the Image. FROM registry.il2.dsop.io/platform-one/devops/pipeline-templates/ironbank/ruby26:2.6.6.212 USER 0 RUN dnf update -y && \ - dnf install -y gcc \ - libxml2-devel \ - libxslt-devel + dnf install -y gcc # Redis CLI Binary. COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli @@ -50,13 +48,28 @@ COPY --from=builder /usr/include/pg* /usr/include/ COPY --from=builder /usr/include/pgsql /usr/include/pgsql COPY --from=builder /usr/include/postgres* /usr/include/ -#COPY --from=builder /usr/lib64/libxml* /usr/lib64/ -#COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ +# libxml2 Dependencies. +COPY --from=builder /usr/lib64/libxml* /usr/lib64/ +COPY --from=builder /usr/lib64/liblz* /usr/lib64/ +COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ +COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ +COPY --from=builder /usr/include/lzma /usr/include/lzma +COPY --from=builder /usr/include/zlib* /usr/include/ +COPY --from=builder /usr/include/zconf* /usr/include/ +COPY --from=builder /usr/include/libxml2 /usr/include/libxml2 + +# libxslt Dependencies. +COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ +COPY --from=builder /usr/lib64/libexslt* /usr/lib64/ +COPY --from=builder /usr/include/libxslt /usr/include/libxslt +COPY --from=builder /usr/include/libexslt /usr/include/libexslt +COPY --from=builder /usr/include/gcrypt* /usr/include/ +COPY --from=builder /usr/include/gpg* /usr/include/ + #COPY --from=builder /usr/bin/gcc* /usr/bin/ #COPY --from=builder /usr/lib/gcc /usr/lib/gcc #COPY --from=builder /usr/bin/as /usr/bin/as #COPY --from=builder /usr/bin/ld /usr/bin/ld -#COPY --from=builder /usr/bin/pkgconf /usr/bin/pkgconf #COPY --from=builder /usr/libexec/gcc /usr/libexec/gcc #COPY --from=builder /usr/lib64/libmpc* /usr/lib64/ #COPY --from=builder /usr/lib64/libopcodes* /usr/lib64/ @@ -68,7 +81,6 @@ COPY --from=builder /usr/include/postgres* /usr/include/ #COPY --from=builder /usr/lib64/libgpg* /usr/lib64/ #COPY --from=builder /usr/lib64/libgcrypt* /usr/lib64/ #COPY --from=builder /usr/lib64/libgcc* /usr/lib64/ -#COPY --from=builder /usr/lib64/libpkgconf* /usr/lib64/ #COPY --from=builder /usr/lib64/crt* /usr/lib64/ #COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ #COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ @@ -121,21 +133,21 @@ RUN gem install bundler:2.1.4 RUN bundle install #RUN cat /usr/local/bundle/extensions/x86_64-linux/2.6.0/nokogiri-1.8.0/mkmf.log -## Install Yarn. -#RUN npm install yarn -#RUN rm package-lock.json -# -## Add the node_modules to the path. -#ENV PATH /usr/src/app/node_modules/.bin:$PATH -# +# Install Yarn. +RUN npm install yarn +RUN rm package-lock.json + +# Add the node_modules to the path. +ENV PATH /usr/src/app/node_modules/.bin:$PATH + ## Run Yarn Install -#RUN yarn install -# -## Precompile assets -#RUN bundle exec rake assets:precompile -# -## Asset Clean -#RUN bundle exec rake assets:clean -# -## Set the entry point. -#ENTRYPOINT ["./entrypoint-server.sh"] \ No newline at end of file +RUN yarn install + +# Precompile assets +RUN bundle exec rake assets:precompile + +# Asset Clean +RUN bundle exec rake assets:clean + +# Set the entry point. +ENTRYPOINT ["./entrypoint-server.sh"] \ No newline at end of file -- GitLab From b25811fbe73f88470f996b8c9a24fa08365106f3 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 01:01:30 -1000 Subject: [PATCH 159/287] moving yarn install to the builder --- Dockerfile | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7dd4d98..ed4f881 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,18 @@ FROM centos:8 as builder USER 0 -# Install the redis-cli tools +# Setup the yarn repo. +RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo +RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg + +# Install the required packages. RUN dnf update -y && \ dnf install -y \ redis \ make \ patch \ git \ + yarn \ zlib-devel \ libpq-devel \ libxml2-devel \ @@ -34,6 +39,11 @@ COPY --from=builder /usr/bin/make /usr/bin/make # Git Binaries. COPY --from=builder /usr/bin/git* /usr/bin/ +# Yarn Binaries. +COPY --from=builder /usr/share/yarn /usr/share/yarn +RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarn +RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarnpkg + # libz Dependencies. COPY --from=builder /usr/lib64/libz* /usr/lib64/ @@ -90,7 +100,6 @@ COPY --from=builder /usr/include/gpg* /usr/include/ #COPY --from=builder /usr/lib64/gcrt* /usr/lib64/ #COPY --from=builder /usr/lib64/libanl* /usr/lib64/ #COPY --from=builder /usr/lib64/libdl* /usr/lib64/ -#COPY --from=builder /usr/lib64/libexslt* /usr/lib64/ #COPY --from=builder /usr/lib64/libg* /usr/lib64/ #COPY --from=builder /usr/lib64/libisl* /usr/lib64/ #COPY --from=builder /usr/lib64/liblzma* /usr/lib64/ @@ -111,6 +120,8 @@ ADD .nvmrc . RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n RUN bash n auto +RUN yarn -v + # Setup our environment WORKDIR /usr/src/app ADD . . @@ -127,18 +138,9 @@ ENV RAILS_ENV production # Install Rails Dependecies. RUN gem install bundler:2.1.4 -#RUN gem install rake:13.0.1 --source 'https://rubygems.org/' # Run the bundle install RUN bundle install -#RUN cat /usr/local/bundle/extensions/x86_64-linux/2.6.0/nokogiri-1.8.0/mkmf.log - -# Install Yarn. -RUN npm install yarn -RUN rm package-lock.json - -# Add the node_modules to the path. -ENV PATH /usr/src/app/node_modules/.bin:$PATH ## Run Yarn Install RUN yarn install @@ -149,5 +151,8 @@ RUN bundle exec rake assets:precompile # Asset Clean RUN bundle exec rake assets:clean +# Add the node_modules to the path. +ENV PATH /usr/src/app/node_modules/.bin:$PATH + # Set the entry point. ENTRYPOINT ["./entrypoint-server.sh"] \ No newline at end of file -- GitLab From 45b8cba0fd9bcde0fe47023f1ea9e288a48834f9 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 01:02:35 -1000 Subject: [PATCH 160/287] moving yarn install to the builder --- Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ed4f881..dd60aaa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -120,8 +120,6 @@ ADD .nvmrc . RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n RUN bash n auto -RUN yarn -v - # Setup our environment WORKDIR /usr/src/app ADD . . -- GitLab From ee99b4e9b7c834039bb86286c843fd84bd0cfb92 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 10:17:36 -1000 Subject: [PATCH 161/287] switching to install in the ruby container --- Dockerfile | 129 +++++++++++++++++++++++++++++------------------------ 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/Dockerfile b/Dockerfile index dd60aaa..cc7161c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,14 +2,33 @@ FROM centos:8 as builder USER 0 -# Setup the yarn repo. +## Setup the yarn repo. +#RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo +#RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg + +## Install the required packages. +RUN dnf update -y && \ + dnf install -y \ + redis +# make \ +# patch \ +# git \ +# yarn \ +# zlib-devel \ +# libpq-devel \ +# libxml2-devel \ +# libxslt-devel \ +# gcc + +# Stage 2: Setup the Image. +FROM registry.il2.dsop.io/platform-one/devops/pipeline-templates/ironbank/ruby26:2.6.6.212 +USER 0 + RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg -# Install the required packages. RUN dnf update -y && \ dnf install -y \ - redis \ make \ patch \ git \ @@ -20,62 +39,58 @@ RUN dnf update -y && \ libxslt-devel \ gcc -# Stage 2: Setup the Image. -FROM registry.il2.dsop.io/platform-one/devops/pipeline-templates/ironbank/ruby26:2.6.6.212 -USER 0 - -RUN dnf update -y && \ - dnf install -y gcc - # Redis CLI Binary. COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli -# Patch Binary. -COPY --from=builder /usr/bin/patch /usr/bin/patch - -# Make Binary. -COPY --from=builder /usr/bin/make /usr/bin/make - -# Git Binaries. -COPY --from=builder /usr/bin/git* /usr/bin/ - -# Yarn Binaries. -COPY --from=builder /usr/share/yarn /usr/share/yarn -RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarn -RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarnpkg - -# libz Dependencies. -COPY --from=builder /usr/lib64/libz* /usr/lib64/ - -# Postgres Dependencies -COPY --from=builder /usr/bin/pkgconf /usr/bin/pkgconf -COPY --from=builder /usr/bin/pg_config /usr/bin/pg_config -COPY --from=builder /usr/lib64/libpq* /usr/lib64/ -COPY --from=builder /usr/lib64/libpkgconf* /usr/lib64/ -COPY --from=builder /usr/include/libpq /usr/include/libpq -COPY --from=builder /usr/include/libpq* /usr/include/ -COPY --from=builder /usr/include/pg* /usr/include/ -COPY --from=builder /usr/include/pgsql /usr/include/pgsql -COPY --from=builder /usr/include/postgres* /usr/include/ - -# libxml2 Dependencies. -COPY --from=builder /usr/lib64/libxml* /usr/lib64/ -COPY --from=builder /usr/lib64/liblz* /usr/lib64/ -COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ -COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ -COPY --from=builder /usr/include/lzma /usr/include/lzma -COPY --from=builder /usr/include/zlib* /usr/include/ -COPY --from=builder /usr/include/zconf* /usr/include/ -COPY --from=builder /usr/include/libxml2 /usr/include/libxml2 - -# libxslt Dependencies. -COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ -COPY --from=builder /usr/lib64/libexslt* /usr/lib64/ -COPY --from=builder /usr/include/libxslt /usr/include/libxslt -COPY --from=builder /usr/include/libexslt /usr/include/libexslt -COPY --from=builder /usr/include/gcrypt* /usr/include/ -COPY --from=builder /usr/include/gpg* /usr/include/ +## Patch Binary. +#COPY --from=builder /usr/bin/patch /usr/bin/patch +# +## Make Binary. +#COPY --from=builder /usr/bin/make /usr/bin/make +# +## Git Binaries. +#COPY --from=builder /usr/bin/git* /usr/bin/ +# +## Yarn Binaries. +#COPY --from=builder /usr/share/yarn /usr/share/yarn +#RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarn +#RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarnpkg +# +## libz Dependencies. +#COPY --from=builder /usr/lib64/libz* /usr/lib64/ +# +## Postgres Dependencies +#COPY --from=builder /usr/bin/pkgconf /usr/bin/pkgconf +#COPY --from=builder /usr/bin/pg_config /usr/bin/pg_config +#COPY --from=builder /usr/lib64/libpq* /usr/lib64/ +#COPY --from=builder /usr/lib64/libpkgconf* /usr/lib64/ +#COPY --from=builder /usr/include/libpq /usr/include/libpq +#COPY --from=builder /usr/include/libpq* /usr/include/ +#COPY --from=builder /usr/include/pg* /usr/include/ +#COPY --from=builder /usr/include/pgsql /usr/include/pgsql +#COPY --from=builder /usr/include/postgres* /usr/include/ +# +## libxml2 Dependencies. +#COPY --from=builder /usr/lib64/libxml* /usr/lib64/ +#COPY --from=builder /usr/lib64/liblz* /usr/lib64/ +#COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ +#COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ +#COPY --from=builder /usr/include/lzma /usr/include/lzma +#COPY --from=builder /usr/include/zlib* /usr/include/ +#COPY --from=builder /usr/include/zconf* /usr/include/ +#COPY --from=builder /usr/include/libxml2 /usr/include/libxml2 +#COPY --from=builder /usr/lib64/xml2Conf.sh /usr/lib64/xml2Conf.sh +# +## libxslt Dependencies. +#COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libexslt* /usr/lib64/ +#COPY --from=builder /usr/include/libxslt /usr/include/libxslt +#COPY --from=builder /usr/include/libexslt /usr/include/libexslt +#COPY --from=builder /usr/include/gcrypt* /usr/include/ +#COPY --from=builder /usr/include/gpg* /usr/include/ +#COPY --from=builder /usr/lib64/xsltConf.sh /usr/lib64/xsltConf.sh +## GCC Dependencies. #COPY --from=builder /usr/bin/gcc* /usr/bin/ #COPY --from=builder /usr/lib/gcc /usr/lib/gcc #COPY --from=builder /usr/bin/as /usr/bin/as @@ -111,8 +126,6 @@ COPY --from=builder /usr/include/gpg* /usr/include/ #COPY --from=builder /usr/lib64/libutil* /usr/lib64/ #COPY --from=builder /usr/lib64/libz* /usr/lib64/ #COPY --from=builder /usr/lib64/rcrt* /usr/lib64/ -#COPY --from=builder /usr/lib64/xml2Conf.sh /usr/lib64/xml2Conf.sh -#COPY --from=builder /usr/lib64/xsltConf.sh /usr/lib64/xsltConf.sh #COPY --from=builder /usr/include /usr/include # Install Node @@ -126,7 +139,7 @@ ADD . . # Add write permissions. RUN chown -R 1001 . -RUN chmod -R u+w . +#RUN chmod -R u+w . # Become the ruby user USER 1001 -- GitLab From 60c25840356b5f9745c627a31f5382bb7d7337ea Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 11:27:19 -1000 Subject: [PATCH 162/287] adding c++ compiler --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cc7161c..d747128 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,7 +37,8 @@ RUN dnf update -y && \ libpq-devel \ libxml2-devel \ libxslt-devel \ - gcc + gcc \ + gcc-c++ # Redis CLI Binary. COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli -- GitLab From 3b80e6a95a0ca43b40c102ce827cc5977fe427b3 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 12:23:42 -1000 Subject: [PATCH 163/287] updating initializer for aws sdk. --- config/initializers/aws.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/aws.rb b/config/initializers/aws.rb index bf80004..4002801 100644 --- a/config/initializers/aws.rb +++ b/config/initializers/aws.rb @@ -46,5 +46,6 @@ else options[:force_path_style] = true end - Rails.application.config.aws = Aws::S3::Resource.new(options).bucket(Rails.application.secrets.glearn_bucket_name) + client = Aws::S3::Client.new(options) + Rails.application.config.aws = Aws::S3::Resource.new(client: client).bucket(Rails.application.secrets.glearn_bucket_name) end \ No newline at end of file -- GitLab From c228b96af87eb367571403058d756374ff6dc402 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 12:39:27 -1000 Subject: [PATCH 164/287] defaulting s3 bucket names to temp name for build --- config/initializers/aws.rb | 3 +-- config/secrets.yml | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/config/initializers/aws.rb b/config/initializers/aws.rb index 4002801..bf80004 100644 --- a/config/initializers/aws.rb +++ b/config/initializers/aws.rb @@ -46,6 +46,5 @@ else options[:force_path_style] = true end - client = Aws::S3::Client.new(options) - Rails.application.config.aws = Aws::S3::Resource.new(client: client).bucket(Rails.application.secrets.glearn_bucket_name) + Rails.application.config.aws = Aws::S3::Resource.new(options).bucket(Rails.application.secrets.glearn_bucket_name) end \ No newline at end of file diff --git a/config/secrets.yml b/config/secrets.yml index f66ca92..bd0e92d 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -11,7 +11,7 @@ shared: github_token: <%= ENV["GITHUB_COM_TOKEN"] %> mixpanel_project_token: <%= ENV["MIXPANEL_PROJECT_TOKEN"] %> protocol: <%= ENV['PROTOCOL'] || "http://" %> - s3_bucket_name: <%= ENV["APP_S3_BUCKET"] %> + s3_bucket_name: <%= ENV["APP_S3_BUCKET"] || "temp" %> secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> sendgrid_password: <%= ENV['SENDGRID_PASSWORD'] %> sendgrid_username: <%= ENV['SENDGRID_USERNAME'] %> @@ -24,7 +24,7 @@ shared: glearn_access_key_id: <%= ENV['accesskey'] %> glearn_secret_access_key: <%= ENV['secretkey'] %> glearn_key_prefix: <%= ENV['GLEARN_KEY_PREFIX'] || "forge" %> - glearn_bucket_name: <%= ENV["APP_S3_BUCKET"] %> + glearn_bucket_name: <%= ENV["APP_S3_BUCKET"] || "temp" %> s3_region: <%= ENV["S3_REGION"] || "us-gov-west-1" %> dev_notify_slack_url: <%= ENV['DEV_NOTIFY_SLACK_URL'] %> minio_endpoint_url: <%= ENV["MINIO_ENDPOINT_URL"] %> -- GitLab From 6c9701d4889b78fc5099e092804e2db0e72fc2ee Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Tue, 13 Oct 2020 16:36:27 -1000 Subject: [PATCH 165/287] fix for sass_rails gem --- Gemfile | 2 +- Gemfile.lock | 20 ++++--------------- .../settings/CohortBlockReleaseRow.tsx | 2 +- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/Gemfile b/Gemfile index 6e94854..d9fca00 100644 --- a/Gemfile +++ b/Gemfile @@ -27,7 +27,7 @@ gem "ts_routes" gem "webpacker" gem "bootstrap" gem "font-awesome-rails" -gem "sass-rails", "~> 5.0" +gem "sass-rails" gem "uglifier" # views diff --git a/Gemfile.lock b/Gemfile.lock index c572acf..fafb5ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1189,9 +1189,6 @@ GEM thor (>= 0.19.0, < 2.0) rainbow (3.0.0) rake (13.0.1) - rb-fsevent (0.10.4) - rb-inotify (0.10.1) - ffi (~> 1.0) react-rails (2.6.1) babel-transpiler (>= 0.7.0) connection_pool @@ -1242,17 +1239,8 @@ GEM parser (>= 2.7.1.5) ruby-progressbar (1.10.1) rubyzip (2.3.0) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sass-rails (5.1.0) - railties (>= 5.2.0) - sass (~> 3.1) - sprockets (>= 2.8, < 4.0) - sprockets-rails (>= 2.0, < 4.0) - tilt (>= 1.1, < 3) + sass-rails (6.0.0) + sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) ffi (~> 1.9) sassc-rails (2.1.2) @@ -1282,7 +1270,7 @@ GEM simplecov-html (0.12.3) solid_use_case (2.2.0) deterministic (~> 0.6.0) - sprockets (3.7.2) + sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) sprockets-rails (3.2.2) @@ -1375,7 +1363,7 @@ DEPENDENCIES rspec_junit_formatter rubocop rubyzip - sass-rails (~> 5.0) + sass-rails scout_apm selenium-webdriver shoulda-matchers diff --git a/app/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx b/app/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx index 943214f..fe0693d 100644 --- a/app/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx +++ b/app/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx @@ -64,7 +64,7 @@ export default class CohortBlockReleaseRow extends React.Component }; // so dom element exists before init - setTimeout(() => { window.hopscotch.startTour(tour); }, 50); + setTimeout(() => { window.hopscotch.startTour(tour); }, 500); } } -- GitLab From fbcce7057f73d9cf55dd61410395428e07554c49 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 16:53:17 -1000 Subject: [PATCH 166/287] removing line --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index d747128..613216e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -140,7 +140,6 @@ ADD . . # Add write permissions. RUN chown -R 1001 . -#RUN chmod -R u+w . # Become the ruby user USER 1001 -- GitLab From 67e69865aa8424b5d02ab9c0c2fbe1fdb1d8b847 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 18:53:24 -1000 Subject: [PATCH 167/287] adding variable imports to scss files --- app/assets/stylesheets/base.scss | 2 ++ app/assets/stylesheets/cohorts.scss | 2 ++ app/assets/stylesheets/hopscotch-overrides.scss | 2 ++ app/assets/stylesheets/mixins.scss | 1 + 4 files changed, 7 insertions(+) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index 1e9d53d..e6d4540 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -1,3 +1,5 @@ +@import "variables"; + html, body { overflow-x: hidden; diff --git a/app/assets/stylesheets/cohorts.scss b/app/assets/stylesheets/cohorts.scss index 18fbca1..a65763f 100644 --- a/app/assets/stylesheets/cohorts.scss +++ b/app/assets/stylesheets/cohorts.scss @@ -1,3 +1,5 @@ +@import "variables"; + .center { text-align: center; } diff --git a/app/assets/stylesheets/hopscotch-overrides.scss b/app/assets/stylesheets/hopscotch-overrides.scss index d9bc5c3..0a3f058 100644 --- a/app/assets/stylesheets/hopscotch-overrides.scss +++ b/app/assets/stylesheets/hopscotch-overrides.scss @@ -1,3 +1,5 @@ +@import "variables"; + .hopscotch-bubble-number { content: ""; background-image: url(/assets/svg/mobile-logo-d01e8af51b97d9f642d52a826ffe07193b8290e4d1767f6f2b2475bac759e9bc.svg)!important; diff --git a/app/assets/stylesheets/mixins.scss b/app/assets/stylesheets/mixins.scss index d06598c..a7a5914 100644 --- a/app/assets/stylesheets/mixins.scss +++ b/app/assets/stylesheets/mixins.scss @@ -1,5 +1,6 @@ // Fonts // ===== +@import "variables"; @mixin font-base () { color: $color-tundora; -- GitLab From 9e9420544e837a1531c92bebb59be4c614e4049a Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Tue, 13 Oct 2020 18:46:40 -1000 Subject: [PATCH 168/287] Change to manifest.js --- app/assets/config/manifest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js index b16e53d..1983ee4 100644 --- a/app/assets/config/manifest.js +++ b/app/assets/config/manifest.js @@ -1,3 +1,3 @@ //= link_tree ../images -//= link_directory ../javascripts .js -//= link_directory ../stylesheets .css +//= link application.css +//= link application.js -- GitLab From 925e23b853c40b2eb81f474ce6e62ef25fe43e6a Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 13 Oct 2020 19:23:27 -1000 Subject: [PATCH 169/287] removing imports --- app/assets/stylesheets/base.scss | 2 -- app/assets/stylesheets/cohorts.scss | 2 -- app/assets/stylesheets/hopscotch-overrides.scss | 2 -- app/assets/stylesheets/mixins.scss | 1 - 4 files changed, 7 deletions(-) diff --git a/app/assets/stylesheets/base.scss b/app/assets/stylesheets/base.scss index e6d4540..1e9d53d 100644 --- a/app/assets/stylesheets/base.scss +++ b/app/assets/stylesheets/base.scss @@ -1,5 +1,3 @@ -@import "variables"; - html, body { overflow-x: hidden; diff --git a/app/assets/stylesheets/cohorts.scss b/app/assets/stylesheets/cohorts.scss index a65763f..18fbca1 100644 --- a/app/assets/stylesheets/cohorts.scss +++ b/app/assets/stylesheets/cohorts.scss @@ -1,5 +1,3 @@ -@import "variables"; - .center { text-align: center; } diff --git a/app/assets/stylesheets/hopscotch-overrides.scss b/app/assets/stylesheets/hopscotch-overrides.scss index 0a3f058..d9bc5c3 100644 --- a/app/assets/stylesheets/hopscotch-overrides.scss +++ b/app/assets/stylesheets/hopscotch-overrides.scss @@ -1,5 +1,3 @@ -@import "variables"; - .hopscotch-bubble-number { content: ""; background-image: url(/assets/svg/mobile-logo-d01e8af51b97d9f642d52a826ffe07193b8290e4d1767f6f2b2475bac759e9bc.svg)!important; diff --git a/app/assets/stylesheets/mixins.scss b/app/assets/stylesheets/mixins.scss index a7a5914..d06598c 100644 --- a/app/assets/stylesheets/mixins.scss +++ b/app/assets/stylesheets/mixins.scss @@ -1,6 +1,5 @@ // Fonts // ===== -@import "variables"; @mixin font-base () { color: $color-tundora; -- GitLab From 05546b02237740112fc5a361373392f52e38e30b Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Wed, 14 Oct 2020 08:36:59 -1000 Subject: [PATCH 170/287] Disable sidekiq --- entrypoint-server.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/entrypoint-server.sh b/entrypoint-server.sh index 95515d9..3df1bbc 100755 --- a/entrypoint-server.sh +++ b/entrypoint-server.sh @@ -2,5 +2,4 @@ rm -f tmp/pids/server.pid bundle exec rails db:migrate -bundle exec sidekiq -e production -L sidekiq.log & -RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 \ No newline at end of file +RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 -- GitLab From 9b30a79d9a39acdffa2cd5754db230647621bbf8 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Wed, 14 Oct 2020 10:38:21 -1000 Subject: [PATCH 171/287] Update gemfile for rails --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index d9fca00..8cc46b1 100644 --- a/Gemfile +++ b/Gemfile @@ -14,7 +14,7 @@ gem "puma" gem "pundit" gem "httparty" gem "pagy" -gem "rails", "~> 5.2.1" +gem "rails", "~> 5.2.4" gem "sidekiq" gem "dotenv-rails" gem "rubyzip" diff --git a/Gemfile.lock b/Gemfile.lock index fafb5ed..d456bf8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1285,7 +1285,7 @@ GEM thor (1.0.1) thread_safe (0.3.6) tilt (2.0.10) - timecop (0.9.1) + timecop (0.9.2) ts_routes (1.0.3) railties (>= 4.0) tzinfo (1.2.7) @@ -1354,7 +1354,7 @@ DEPENDENCIES pundit rack-attack rack-mini-profiler - rails (~> 5.2.1) + rails (~> 5.2.4) rails-controller-testing react-rails redis -- GitLab From cb1fc8a6ebe0790e6441d07e52e099a88e846897 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Wed, 14 Oct 2020 17:56:02 -1000 Subject: [PATCH 172/287] replace github-markdown with commonmarker --- Gemfile.lock | 7 +++++-- gems/block-parser/Gemfile.lock | 7 +++++-- gems/block-parser/block_parser.gemspec | 2 +- gems/block-parser/spec/build_html_from_md_json_spec.rb | 5 ++--- gems/block-parser/spec/parse_directory_spec.rb | 2 +- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d456bf8..433980f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,7 +3,7 @@ PATH specs: block_parser (0.1.0) activemodel (> 5.2) - github-markdown (= 0.6.9) + commonmarker (= 0.21.0) github-markup (= 1.6.1) github_url (= 0.2.1) gitlab (= 4.14.1) @@ -1044,6 +1044,8 @@ GEM xpath (~> 3.2) childprocess (3.0.0) coderay (1.1.3) + commonmarker (0.21.0) + ruby-enum (~> 0.5) concurrent-ruby (1.1.7) connection_pool (2.2.3) crack (0.4.4) @@ -1071,7 +1073,6 @@ GEM font-awesome-rails (4.7.0.5) railties (>= 3.2, < 6.1) foreman (0.87.2) - github-markdown (0.6.9) github-markup (1.6.1) github_url (0.2.1) gitlab (4.14.1) @@ -1237,6 +1238,8 @@ GEM unicode-display_width (>= 1.4.0, < 2.0) rubocop-ast (0.8.0) parser (>= 2.7.1.5) + ruby-enum (0.8.0) + i18n ruby-progressbar (1.10.1) rubyzip (2.3.0) sass-rails (6.0.0) diff --git a/gems/block-parser/Gemfile.lock b/gems/block-parser/Gemfile.lock index 29aa5e3..24076a7 100644 --- a/gems/block-parser/Gemfile.lock +++ b/gems/block-parser/Gemfile.lock @@ -3,7 +3,7 @@ PATH specs: block_parser (0.1.0) activemodel (> 5.2) - github-markdown (= 0.6.9) + commonmarker (= 0.21.0) github-markup (= 1.6.1) github_url (= 0.2.1) gitlab (= 4.14.1) @@ -27,11 +27,12 @@ GEM addressable (2.4.0) ast (2.4.1) byebug (11.1.3) + commonmarker (0.21.0) + ruby-enum (~> 0.5) concurrent-ruby (1.1.7) diff-lcs (1.4.4) faraday (0.9.2) multipart-post (>= 1.2, < 3) - github-markdown (0.6.9) github-markup (1.6.1) github_url (0.2.1) gitlab (4.14.1) @@ -88,6 +89,8 @@ GEM unicode-display_width (>= 1.4.0, < 2.0) rubocop-ast (0.8.0) parser (>= 2.7.1.5) + ruby-enum (0.8.0) + i18n ruby-progressbar (1.10.1) sawyer (0.7.0) addressable (>= 2.3.5, < 2.5) diff --git a/gems/block-parser/block_parser.gemspec b/gems/block-parser/block_parser.gemspec index 481d6f1..88f0fe1 100644 --- a/gems/block-parser/block_parser.gemspec +++ b/gems/block-parser/block_parser.gemspec @@ -33,7 +33,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency "rubocop", ">= 0.93" spec.add_dependency "activemodel", "> 5.2" - spec.add_dependency "github-markdown", "0.6.9" + spec.add_dependency "commonmarker", "0.21.0" spec.add_dependency "github-markup", "1.6.1" spec.add_dependency "github_url", "0.2.1" spec.add_dependency "gitlab", "4.14.1" diff --git a/gems/block-parser/spec/build_html_from_md_json_spec.rb b/gems/block-parser/spec/build_html_from_md_json_spec.rb index 3f4216c..f73e244 100644 --- a/gems/block-parser/spec/build_html_from_md_json_spec.rb +++ b/gems/block-parser/spec/build_html_from_md_json_spec.rb @@ -82,8 +82,7 @@ describe BlockParser::BuildHtmlFromMdJson do end it "returns the html wrapped in a callout container div" do - expected_html = "

    test

    \n\n

    test

    \n
    " - + expected_html = "

    Danger

    #test\ntest

    \n
    " expect(described_class.process_json( json_hash: json_hash, asset_uploader: Freeloader.new, @@ -191,7 +190,7 @@ describe BlockParser::BuildHtmlFromMdJson do ] } - expected_html = "
    \n" + expected_html = "\n" expect(described_class.process_json( json_hash: json_hash, diff --git a/gems/block-parser/spec/parse_directory_spec.rb b/gems/block-parser/spec/parse_directory_spec.rb index 9a32ded..b123eb1 100644 --- a/gems/block-parser/spec/parse_directory_spec.rb +++ b/gems/block-parser/spec/parse_directory_spec.rb @@ -171,7 +171,7 @@ describe BlockParser::ParseDirectory do content_file_attribute_hashes: [ { title: "Ipynb Test.Ipynb", - html: "

    Introduction

    \n\n

    To open this notebook so you can run this code, navigate to this file's directory at the comandline and then type jupyter notebook

    \n\n

    This notebook here is meant to get you oriented with one of the tools that we will be using for our pre-class material. The notes and exercises will be built into IPython notebooks (like this one). Before coming to each class, you should work through the materials in the pre-class folder and play around/follow along with the code in those notebooks. You should run the code provided, and additionally modify it to learn what happens when you change certain/different pieces.

    \n\n

    Effectively learning to program means having your hands-on the keyboard often. It means playing with code, breaking code, reading errors, and reading google. It means banging your head against the keyboard, but getting to revel in that time spent banging your head when you're program finally works. To that end, this notebook here will hopefully give you a taste of that.

    \n\n

    You might not know exactly what all the lines of code in this notebook do. But, don't worry, because you will know all about these ideas in a week. For now, it's going to be good to get used to your environment and reading and writing some simple Python code. Besides, there's a good chance you'll know what's going on in most of these exercises.

    \n\n

    You'll start off by type some working code, and playing around with it. Then, we'll walk you through an example of working with some broken code (don't worry, it won't be too bad). Finally, you'll finish up by learning how to move the working code into a script, and you'll run you're very first Python program!

    \n\n

    For all of the exercises below, we've included the exact number of cells for you to complete the assignment in. However, if you want more to experiment with, at any time you can click the + button in the upper-left hand corner of the notebook, and a new cell will be created below the one that is currently selected. If you would like to delete a cell you've created for experimentation, simply select the cell and click the edit tab at the top of the notebook. Then, select "Delete Cells".

    \n\n

    Part 1 - Working with Working Code

    \n\n

    For the first part, type in the following code. Type it one line at a time into a cell, and then run that cell (click the button at the top to do this, or try Shift + Enter). Don't copy and paste it, but instead type it - fingers on the keyboard style. Building up your programming chops will come through muscle memory, and tonight will get you started with that. You will be writing your own programs soon enough!

    \n\n

    Type this code out and run it one line at at time:

    \n\n
    my_first_var = 'Wohoo!'\nprint(my_first_var)\n\nmy_second_var = "I'm typing code."\nprint(my_second_var)\n\nmy_third_var = "Even though this doesn't feel like code, I've been told it is code."\nprint(my_third_var) \n\nmy_combined_var = my_first_var + ' ' + my_second_var + ' ' + my_third_var \nprint(my_combined_var)\n
    \n\n

    We've included a cell below for each one of the lines above.

    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n

    Above, we just worked with text. Let's try working with some numbers now.

    \n\n

    Type this code out line by line:

    \n\n
    my_first_num = 3\nprint(my_first_num)\n\nmy_second_num = 13\nprint(my_second_num)\n\nquotient = my_second_num // my_first_num\nremainder = my_second_num % my_first_num\n\nprint(quotient, remainder)\n
    \n\n

    We've included a cell below for each one of the lines above.

    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n

    Part 2 - Working with Error Messages

    \n\n

    Now, let's work with some code that will break (sorry). This can and will happen all the time when writing code. Half the battle of being a good programmer is learning how to read through error messages and fix your code. Google will be your best friend through this. If you ask any software engineer or programmer you may know, she/he will be likely to tell you the same.

    \n\n

    Type out the code below, again line by line. When you encounter the error, we'll walk you through it.

    \n\n
    my_str_var = 'Hello. My favorite number is '\nprint(my_str_var)\n\nmy_fav_num = 3\nprint(my_fav_num)\n\nfull_str = my_str_var + my_fav_num \nprint(full_str)\n
    \n\n

    There is a cell below for each one of the lines above.

    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n
    \n
    \n\n

    By now, you'll have encountered the following error:

    \n\n
    TypeError: cannot concatenate 'str' and 'int' objects\n
    \n\n

    We've said Google will be your best friend. Let's put that to the test. Take that error (the entire thing) and paste it into Google. The first link should be this page, a stackoverflow post explaining what this error means and how to fix it. Reading through the first five posts there, we see that we can fix it if we simply throw a str around the variable my_fav_num above. This means the line

    \n\n

    full_str = my_str_var + my_fav_num

    \n\n

    will become:

    \n\n

    full_str = my_str_var + str(my_fav_num).

    \n\n

    Make this change above, and then re-run all your code.

    \n\n
    \n
    \n\n

    Notice, too, that you could put that line in the cell below and it would still work. This is because the notebook's state is the same no matter what cell you're in. This is true throughout the life of a notebook session. So you could actually insert a cell at the top of the notebook and run the same line! However, this is a very dangerous thing to do! If you inserted that line in a cell at the top of the notebook, and then came back to it later after it was shutdown, it would not run. This is because the variables my_str_var and my_fav_num wouldn't have been defined yet. You can try this if you want. Insert the that line at the top of the notebook, then click the Kernel tab at the top of the notebook and select "Restart". This put's the notebook in a state as though you just opened it. Try running that top line now. It won't work. But, if you run the cells that define my_str_var and my_fav_num it will!

    \n\n

    Part 3 - Moving to a Script

    \n\n

    Now that you have some functioning code here in a notebook, it's time for you to turn it into a Python script!

    \n\n

    Aside: Python is known as a scripting language, which means that you technically write scripts, not programs. This distinction is normally not important, and as a result you will frequently hear people say they wrote a Python program (and that's accepted language).

    \n\n

    Python scripts live in .py files. So, what you're going to need to do is re-write all of the code from the exercise above in a file. This will be done in your text editor (Atom if you followed the advice from the introduction).

    \n\n

    Start a new file and name it my_first_program.py. Inside of the file, re-type all of the lines of code from the working version of the example from above. Once you're done, save it in today's folder.

    \n\n

    Now you have a python script you can run! This can be done in one of two ways. The first is by navigating to the directory that contains my_first_program.py and then typing python my_first_program.py at the command line. You should see the statements that were printed above appear in the console.

    \n\n

    The other way to run a python script is from IPython. To do this from the directory that contains my_first_program.py, type ipython. This will start the IPython console, which allows you to interact with Python code in much the same way as we have done in this notebook. Now, to run your script. Simply type run my_first_program.py into IPython and watch the same output appear on the screen!

    \n\n
    \n
    \n", + html: "

    Introduction

    \n

    To open this notebook so you can run this code, navigate to this file's directory at the comandline and then type jupyter notebook

    \n

    This notebook here is meant to get you oriented with one of the tools that we will be using for our pre-class material. The notes and exercises will be built into IPython notebooks (like this one). Before coming to each class, you should work through the materials in the pre-class folder and play around/follow along with the code in those notebooks. You should run the code provided, and additionally modify it to learn what happens when you change certain/different pieces.

    \n

    Effectively learning to program means having your hands-on the keyboard often. It means playing with code, breaking code, reading errors, and reading google. It means banging your head against the keyboard, but getting to revel in that time spent banging your head when you're program finally works. To that end, this notebook here will hopefully give you a taste of that.

    \n

    You might not know exactly what all the lines of code in this notebook do. But, don't worry, because you will know all about these ideas in a week. For now, it's going to be good to get used to your environment and reading and writing some simple Python code. Besides, there's a good chance you'll know what's going on in most of these exercises.

    \n

    You'll start off by type some working code, and playing around with it. Then, we'll walk you through an example of working with some broken code (don't worry, it won't be too bad). Finally, you'll finish up by learning how to move the working code into a script, and you'll run you're very first Python program!

    \n

    For all of the exercises below, we've included the exact number of cells for you to complete the assignment in. However, if you want more to experiment with, at any time you can click the + button in the upper-left hand corner of the notebook, and a new cell will be created below the one that is currently selected. If you would like to delete a cell you've created for experimentation, simply select the cell and click the edit tab at the top of the notebook. Then, select "Delete Cells".

    \n

    Part 1 - Working with Working Code

    \n

    For the first part, type in the following code. Type it one line at a time into a cell, and then run that cell (click the button at the top to do this, or try Shift + Enter). Don't copy and paste it, but instead type it - fingers on the keyboard style. Building up your programming chops will come through muscle memory, and tonight will get you started with that. You will be writing your own programs soon enough!

    \n

    Type this code out and run it one line at at time:

    \n
    my_first_var = 'Wohoo!'\nprint(my_first_var)\n\nmy_second_var = "I'm typing code."\nprint(my_second_var)\n\nmy_third_var = "Even though this doesn't feel like code, I've been told it is code."\nprint(my_third_var) \n\nmy_combined_var = my_first_var + ' ' + my_second_var + ' ' + my_third_var \nprint(my_combined_var)\n
    \n

    We've included a cell below for each one of the lines above.

    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n

    Above, we just worked with text. Let's try working with some numbers now.

    \n

    Type this code out line by line:

    \n
    my_first_num = 3\nprint(my_first_num)\n\nmy_second_num = 13\nprint(my_second_num)\n\nquotient = my_second_num // my_first_num\nremainder = my_second_num % my_first_num\n\nprint(quotient, remainder)\n
    \n

    We've included a cell below for each one of the lines above.

    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n

    Part 2 - Working with Error Messages

    \n

    Now, let's work with some code that will break (sorry). This can and will happen all the time when writing code. Half the battle of being a good programmer is learning how to read through error messages and fix your code. Google will be your best friend through this. If you ask any software engineer or programmer you may know, she/he will be likely to tell you the same.

    \n

    Type out the code below, again line by line. When you encounter the error, we'll walk you through it.

    \n
    my_str_var = 'Hello. My favorite number is '\nprint(my_str_var)\n\nmy_fav_num = 3\nprint(my_fav_num)\n\nfull_str = my_str_var + my_fav_num \nprint(full_str)\n
    \n

    There is a cell below for each one of the lines above.

    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n

    By now, you'll have encountered the following error:

    \n
    TypeError: cannot concatenate 'str' and 'int' objects\n
    \n

    We've said Google will be your best friend. Let's put that to the test. Take that error (the entire thing) and paste it into Google. The first link should be this page, a stackoverflow post explaining what this error means and how to fix it. Reading through the first five posts there, we see that we can fix it if we simply throw a str around the variable my_fav_num above. This means the line

    \n

    full_str = my_str_var + my_fav_num

    \n

    will become:

    \n

    full_str = my_str_var + str(my_fav_num).

    \n

    Make this change above, and then re-run all your code.

    \n
    \n
    \n

    Notice, too, that you could put that line in the cell below and it would still work. This is because the notebook's state is the same no matter what cell you're in. This is true throughout the life of a notebook session. So you could actually insert a cell at the top of the notebook and run the same line! However, this is a very dangerous thing to do! If you inserted that line in a cell at the top of the notebook, and then came back to it later after it was shutdown, it would not run. This is because the variables my_str_var and my_fav_num wouldn't have been defined yet. You can try this if you want. Insert the that line at the top of the notebook, then click the Kernel tab at the top of the notebook and select "Restart". This put's the notebook in a state as though you just opened it. Try running that top line now. It won't work. But, if you run the cells that define my_str_var and my_fav_num it will!

    \n

    Part 3 - Moving to a Script

    \n

    Now that you have some functioning code here in a notebook, it's time for you to turn it into a Python script!

    \n

    Aside: Python is known as a scripting language, which means that you technically write scripts, not programs. This distinction is normally not important, and as a result you will frequently hear people say they wrote a Python program (and that's accepted language).

    \n

    Python scripts live in .py files. So, what you're going to need to do is re-write all of the code from the exercise above in a file. This will be done in your text editor (Atom if you followed the advice from the introduction).

    \n

    Start a new file and name it my_first_program.py. Inside of the file, re-type all of the lines of code from the working version of the example from above. Once you're done, save it in today's folder.

    \n

    Now you have a python script you can run! This can be done in one of two ways. The first is by navigating to the directory that contains my_first_program.py and then typing python my_first_program.py at the command line. You should see the statements that were printed above appear in the console.

    \n

    The other way to run a python script is from IPython. To do this from the directory that contains my_first_program.py, type ipython. This will start the IPython console, which allows you to interact with Python code in much the same way as we have done in this notebook. Now, to run your script. Simply type run my_first_program.py into IPython and watch the same output appear on the screen!

    \n
    \n
    \n", challenges: [], path: "/ipynb-test.ipynb", content_file_type: BlockParser::ParseStandards::CONTENT_FILE_TYPES[:lesson], -- GitLab From 4d35b69ef8a7e163f9517a62e324de20ca4a1fa6 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 15 Oct 2020 11:06:56 -1000 Subject: [PATCH 173/287] Upgrade to Rails 6 --- Gemfile | 3 +- Gemfile.lock | 194 ++++++++++-------- bin/bundle | 2 +- bin/setup | 4 +- bin/update | 6 +- bin/yarn | 6 +- config/application.rb | 2 + config/boot.rb | 5 +- config/environment.rb | 2 +- config/environments/development.rb | 20 +- config/environments/production.rb | 38 +--- config/environments/test.rb | 8 +- config/initializers/assets.rb | 6 +- .../initializers/content_security_policy.rb | 25 +++ .../new_framework_defaults_5_2.rb | 38 ++++ config/puma.rb | 30 +-- config/storage.yml | 34 +++ gems/block-parser/Gemfile.lock | 61 +++--- gems/block-parser/block_parser.gemspec | 30 +-- 19 files changed, 307 insertions(+), 207 deletions(-) create mode 100644 config/initializers/content_security_policy.rb create mode 100644 config/initializers/new_framework_defaults_5_2.rb create mode 100644 config/storage.yml diff --git a/Gemfile b/Gemfile index 8cc46b1..d937d75 100644 --- a/Gemfile +++ b/Gemfile @@ -14,7 +14,7 @@ gem "puma" gem "pundit" gem "httparty" gem "pagy" -gem "rails", "~> 5.2.4" +gem "rails" gem "sidekiq" gem "dotenv-rails" gem "rubyzip" @@ -79,6 +79,7 @@ group :development do gem "better_errors" gem "binding_of_caller" gem "letter_opener" + gem "listen" end group :test do diff --git a/Gemfile.lock b/Gemfile.lock index 433980f..12b55c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,75 +2,89 @@ PATH remote: gems/block-parser specs: block_parser (0.1.0) - activemodel (> 5.2) - commonmarker (= 0.21.0) - github-markup (= 1.6.1) - github_url (= 0.2.1) - gitlab (= 4.14.1) - nokogiri (= 1.8.0) - octokit (= 4.3.0) - psych (= 2.2.4) - redcarpet (= 3.3.4) - rspec_junit_formatter (>= 0.4.1) + activemodel + commonmarker + github-markup + github_url + gitlab + nokogiri + octokit + psych + redcarpet + rspec_junit_formatter GEM remote: https://rubygems.org/ specs: - actioncable (5.2.4.4) - actionpack (= 5.2.4.4) + actioncable (6.0.3.4) + actionpack (= 6.0.3.4) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.4.4) - actionpack (= 5.2.4.4) - actionview (= 5.2.4.4) - activejob (= 5.2.4.4) + actionmailbox (6.0.3.4) + actionpack (= 6.0.3.4) + activejob (= 6.0.3.4) + activerecord (= 6.0.3.4) + activestorage (= 6.0.3.4) + activesupport (= 6.0.3.4) + mail (>= 2.7.1) + actionmailer (6.0.3.4) + actionpack (= 6.0.3.4) + actionview (= 6.0.3.4) + activejob (= 6.0.3.4) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.4.4) - actionview (= 5.2.4.4) - activesupport (= 5.2.4.4) + actionpack (6.0.3.4) + actionview (= 6.0.3.4) + activesupport (= 6.0.3.4) rack (~> 2.0, >= 2.0.8) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.4.4) - activesupport (= 5.2.4.4) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (6.0.3.4) + actionpack (= 6.0.3.4) + activerecord (= 6.0.3.4) + activestorage (= 6.0.3.4) + activesupport (= 6.0.3.4) + nokogiri (>= 1.8.5) + actionview (6.0.3.4) + activesupport (= 6.0.3.4) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.4.4) - activesupport (= 5.2.4.4) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (6.0.3.4) + activesupport (= 6.0.3.4) globalid (>= 0.3.6) - activemodel (5.2.4.4) - activesupport (= 5.2.4.4) - activerecord (5.2.4.4) - activemodel (= 5.2.4.4) - activesupport (= 5.2.4.4) - arel (>= 9.0) - activestorage (5.2.4.4) - actionpack (= 5.2.4.4) - activerecord (= 5.2.4.4) + activemodel (6.0.3.4) + activesupport (= 6.0.3.4) + activerecord (6.0.3.4) + activemodel (= 6.0.3.4) + activesupport (= 6.0.3.4) + activestorage (6.0.3.4) + actionpack (= 6.0.3.4) + activejob (= 6.0.3.4) + activerecord (= 6.0.3.4) marcel (~> 0.3.1) - activesupport (5.2.4.4) + activesupport (6.0.3.4) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.4.0) + zeitwerk (~> 2.2, >= 2.2.2) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) analytics-ruby (2.2.8) apitome (0.3.0) kramdown railties - arel (9.0.0) ast (2.4.1) autoprefixer-rails (10.0.1.0) execjs aws-eventstream (1.1.0) - aws-partitions (1.381.0) + aws-partitions (1.382.0) aws-sdk (3.0.1) aws-sdk-resources (~> 3) - aws-sdk-accessanalyzer (1.12.0) + aws-sdk-accessanalyzer (1.13.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-acm (1.38.0) @@ -139,7 +153,7 @@ GEM aws-sdk-braket (1.4.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-budgets (1.35.0) + aws-sdk-budgets (1.36.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-chime (1.37.0) @@ -246,10 +260,10 @@ GEM aws-sdk-costandusagereportservice (1.28.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-costexplorer (1.51.0) + aws-sdk-costexplorer (1.52.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-databasemigrationservice (1.44.0) + aws-sdk-databasemigrationservice (1.45.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-dataexchange (1.10.0) @@ -363,13 +377,13 @@ GEM aws-sdk-globalaccelerator (1.23.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-glue (1.74.0) + aws-sdk-glue (1.75.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-greengrass (1.37.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-groundstation (1.14.0) + aws-sdk-groundstation (1.15.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-guardduty (1.42.0) @@ -396,7 +410,7 @@ GEM aws-sdk-inspector (1.32.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-iot (1.58.0) + aws-sdk-iot (1.59.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-iot1clickdevicesservice (1.26.0) @@ -489,7 +503,7 @@ GEM aws-sdk-macie (1.25.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-macie2 (1.12.0) + aws-sdk-macie2 (1.13.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-managedblockchain (1.17.0) @@ -603,7 +617,7 @@ GEM aws-sdk-ram (1.22.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-rds (1.103.0) + aws-sdk-rds (1.104.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-rdsdataservice (1.23.0) @@ -615,7 +629,7 @@ GEM aws-sdk-redshiftdataapiservice (1.2.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-rekognition (1.46.0) + aws-sdk-rekognition (1.47.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-resourcegroups (1.32.0) @@ -937,7 +951,7 @@ GEM aws-sdk-sqs (1.34.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-ssm (1.93.0) + aws-sdk-ssm (1.94.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-ssoadmin (1.3.0) @@ -976,7 +990,7 @@ GEM aws-sdk-transcribestreamingservice (1.22.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-transfer (1.27.0) + aws-sdk-transfer (1.28.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-translate (1.28.0) @@ -997,16 +1011,16 @@ GEM aws-sdk-worklink (1.21.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-workmail (1.31.0) + aws-sdk-workmail (1.32.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-workmailmessageflow (1.9.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-workspaces (1.47.0) + aws-sdk-workspaces (1.48.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-xray (1.33.0) + aws-sdk-xray (1.34.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sigv2 (1.0.1) @@ -1066,16 +1080,16 @@ GEM factory_bot_rails (6.1.0) factory_bot (~> 6.1.0) railties (>= 5.0.0) - faraday (0.9.2) + faraday (1.0.1) multipart-post (>= 1.2, < 3) ffi (1.13.1) flamegraph (0.9.5) font-awesome-rails (4.7.0.5) railties (>= 3.2, < 6.1) foreman (0.87.2) - github-markup (1.6.1) + github-markup (3.0.4) github_url (0.2.1) - gitlab (4.14.1) + gitlab (4.16.1) httparty (~> 0.14, >= 0.14.0) terminal-table (~> 1.5, >= 1.5.1) globalid (0.4.2) @@ -1105,10 +1119,13 @@ GEM jwt (2.2.2) kramdown (2.3.0) rexml - launchy (2.4.3) - addressable (~> 2.3) + launchy (2.5.0) + addressable (~> 2.7) letter_opener (1.7.0) launchy (~> 2.2) + listen (3.2.1) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) loofah (2.7.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -1125,7 +1142,7 @@ GEM mime-types-data (3.2020.0512) mimemagic (0.3.5) mini_mime (1.0.2) - mini_portile2 (2.2.0) + mini_portile2 (2.4.0) minitest (5.14.2) msgpack (1.3.3) multi_json (1.15.0) @@ -1133,10 +1150,11 @@ GEM multipart-post (2.1.1) mustache (1.1.1) nio4r (2.5.4) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) - octokit (4.3.0) - sawyer (~> 0.7.0, >= 0.5.3) + nokogiri (1.10.10) + mini_portile2 (~> 2.4.0) + octokit (4.18.0) + faraday (>= 0.9) + sawyer (~> 0.8.0, >= 0.5.3) overcommit (0.57.0) childprocess (>= 0.6.3, < 5) iniparse (~> 1.4) @@ -1146,7 +1164,8 @@ GEM ast (~> 2.4.1) pg (1.2.3) popper_js (1.16.0) - psych (2.2.4) + psych (3.2.0) + public_suffix (4.0.6) puma (5.0.2) nio4r (~> 2.0) pundit (2.1.0) @@ -1160,18 +1179,20 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.4.4) - actioncable (= 5.2.4.4) - actionmailer (= 5.2.4.4) - actionpack (= 5.2.4.4) - actionview (= 5.2.4.4) - activejob (= 5.2.4.4) - activemodel (= 5.2.4.4) - activerecord (= 5.2.4.4) - activestorage (= 5.2.4.4) - activesupport (= 5.2.4.4) + rails (6.0.3.4) + actioncable (= 6.0.3.4) + actionmailbox (= 6.0.3.4) + actionmailer (= 6.0.3.4) + actionpack (= 6.0.3.4) + actiontext (= 6.0.3.4) + actionview (= 6.0.3.4) + activejob (= 6.0.3.4) + activemodel (= 6.0.3.4) + activerecord (= 6.0.3.4) + activestorage (= 6.0.3.4) + activesupport (= 6.0.3.4) bundler (>= 1.3.0) - railties (= 5.2.4.4) + railties (= 6.0.3.4) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -1182,21 +1203,24 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (5.2.4.4) - actionpack (= 5.2.4.4) - activesupport (= 5.2.4.4) + railties (6.0.3.4) + actionpack (= 6.0.3.4) + activesupport (= 6.0.3.4) method_source rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) + thor (>= 0.20.3, < 2.0) rainbow (3.0.0) rake (13.0.1) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) react-rails (2.6.1) babel-transpiler (>= 0.7.0) connection_pool execjs railties (>= 3.2) tilt - redcarpet (3.3.4) + redcarpet (3.5.0) redis (4.2.2) regexp_parser (1.8.2) rexml (3.2.4) @@ -1252,9 +1276,9 @@ GEM sprockets (> 3.0) sprockets-rails tilt - sawyer (0.7.0) - addressable (>= 2.3.5, < 2.5) - faraday (~> 0.8, < 0.10) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) scout_apm (2.6.9) parser selenium-webdriver (3.142.7) @@ -1312,6 +1336,7 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) + zeitwerk (2.4.0) zip-zip (0.3) rubyzip (>= 1.0.0) @@ -1347,6 +1372,7 @@ DEPENDENCIES json_spec jwt letter_opener + listen mathjax-rails memory_profiler octokit @@ -1357,7 +1383,7 @@ DEPENDENCIES pundit rack-attack rack-mini-profiler - rails (~> 5.2.4) + rails rails-controller-testing react-rails redis diff --git a/bin/bundle b/bin/bundle index 66e9889..f19acf5 100755 --- a/bin/bundle +++ b/bin/bundle @@ -1,3 +1,3 @@ #!/usr/bin/env ruby -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) load Gem.bin_path('bundler', 'bundle') diff --git a/bin/setup b/bin/setup index 78c4e86..94fd4d7 100755 --- a/bin/setup +++ b/bin/setup @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -21,7 +20,6 @@ chdir APP_ROOT do # Install JavaScript dependencies if using Yarn # system('bin/yarn') - # puts "\n== Copying sample files ==" # unless File.exist?('config/database.yml') # cp 'config/database.yml.sample', 'config/database.yml' diff --git a/bin/update b/bin/update index a8e4462..58bfaed 100755 --- a/bin/update +++ b/bin/update @@ -1,10 +1,9 @@ #!/usr/bin/env ruby -require 'pathname' require 'fileutils' include FileUtils # path to your application root. -APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) +APP_ROOT = File.expand_path('..', __dir__) def system!(*args) system(*args) || abort("\n== Command #{args} failed ==") @@ -18,6 +17,9 @@ chdir APP_ROOT do system! 'gem install bundler --conservative' system('bundle check') || system!('bundle install') + # Install JavaScript dependencies if using Yarn + # system('bin/yarn') + puts "\n== Updating database ==" system! 'bin/rails db:migrate' diff --git a/bin/yarn b/bin/yarn index c2bacef..460dd56 100755 --- a/bin/yarn +++ b/bin/yarn @@ -1,8 +1,8 @@ #!/usr/bin/env ruby -VENDOR_PATH = File.expand_path('..', __dir__) -Dir.chdir(VENDOR_PATH) do +APP_ROOT = File.expand_path('..', __dir__) +Dir.chdir(APP_ROOT) do begin - exec "yarnpkg #{ARGV.join(" ")}" + exec "yarnpkg", *ARGV rescue Errno::ENOENT $stderr.puts "Yarn executable was not detected in the system." $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" diff --git a/config/application.rb b/config/application.rb index 4aa0ed1..ef9d3f5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -5,9 +5,11 @@ require "rails" require "active_model/railtie" require "active_job/railtie" require "active_record/railtie" +require "active_storage/engine" require "action_controller/railtie" require "action_mailer/railtie" require "action_view/railtie" +# require "action_cable/engine" require "sprockets/railtie" # require "rails/test_unit/railtie" require "sidekiq/web" diff --git a/config/boot.rb b/config/boot.rb index 2820116..ec779bb 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,3 +1,4 @@ -ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) -require "bundler/setup" # Set up gems listed in the Gemfile. +require 'bundler/setup' # Set up gems listed in the Gemfile. +#require 'bootsnap/setup' # Speed up boot time by caching expensive operations. diff --git a/config/environment.rb b/config/environment.rb index cac5315..426333b 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,5 @@ # Load the Rails application. -require_relative "application" +require_relative 'application' # Initialize the Rails application. Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 42e6d03..790abda 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,7 +1,4 @@ Rails.application.configure do - # Verifies that versions and hashed value of the package contents in the project's package.json - - config.webpacker.check_yarn_integrity = true # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on @@ -16,12 +13,13 @@ Rails.application.configure do config.consider_all_requests_local = true # Enable/disable caching. By default caching is disabled. - if Rails.root.join("tmp/caching-dev.txt").exist? + # Run rails dev:cache to toggle caching. + if Rails.root.join('tmp', 'caching-dev.txt').exist? config.action_controller.perform_caching = true config.cache_store = :memory_store config.public_file_server.headers = { - "Cache-Control" => "public, max-age=#{2.days.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{2.days.to_i}" } else config.action_controller.perform_caching = false @@ -29,10 +27,11 @@ Rails.application.configure do config.cache_store = :null_store end + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + # Don't care if the mailer can't send. - config.action_mailer.delivery_method = :letter_opener - config.action_mailer.raise_delivery_errors = true - config.action_mailer.perform_caching = false + config.action_mailer.raise_delivery_errors = false config.action_mailer.perform_caching = false @@ -42,6 +41,9 @@ Rails.application.configure do # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load + # Highlight code that triggered database queries in logs. + config.active_record.verbose_query_logs = true + # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. @@ -55,7 +57,7 @@ Rails.application.configure do # Use an evented file watcher to asynchronously detect changes in source code, # routes, locales, etc. This feature depends on the listen gem. - # config.file_watcher = ActiveSupport::EventedFileUpdateChecker + config.file_watcher = ActiveSupport::EventedFileUpdateChecker # Set default host for url_helpers Rails.application.routes.default_url_options[:host] = "http://localhost:3003" diff --git a/config/environments/production.rb b/config/environments/production.rb index 9e68ea7..cc9244a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,6 +1,4 @@ Rails.application.configure do - # Verifies that versions and hashed value of the package contents in the project's package.json - config.webpacker.check_yarn_integrity = false # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. @@ -16,25 +14,21 @@ Rails.application.configure do config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Attempt to read encrypted secrets from `config/secrets.yml.enc`. - # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or - # `config/secrets.yml.key`. - config.read_encrypted_secrets = true + # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] + # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). + # config.require_master_key = true # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. - config.public_file_server.enabled = true + config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier - config.assets.css_compressor = :sass + # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false - # Generate digests for assets URLs. - config.assets.digest = true - # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb # Enable serving of images, stylesheets, and JavaScripts from an asset server. @@ -44,8 +38,11 @@ Rails.application.configure do # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + # Store uploaded files on the local file system (see config/storage.yml for options) + config.active_storage.service = :local + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. - config.force_ssl = false + # config.force_ssl = true # Enable stdout logger. config.logger = Logger.new(STDOUT) @@ -55,7 +52,7 @@ Rails.application.configure do config.log_level = :debug # Prepend all log lines with the following tags. - config.log_tags = [:request_id] + config.log_tags = [ :request_id ] # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -63,6 +60,7 @@ Rails.application.configure do # Use a real queuing backend for Active Job (and separate queues per environment) # config.active_job.queue_adapter = :resque # config.active_job.queue_name_prefix = "forge_#{Rails.env}" + config.action_mailer.perform_caching = false # Ignore bad email addresses and do not raise email delivery errors. @@ -91,18 +89,4 @@ Rails.application.configure do # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false - - if ENV["ACTIONMAILER_HOST"].present? - Rails.application.routes.default_url_options[:host] = ENV["ACTIONMAILER_HOST"] - end end - -ActionMailer::Base.smtp_settings = { - user_name: Rails.application.secrets.sendgrid_username, - password: Rails.application.secrets.sendgrid_password, - domain: Rails.application.secrets.actionmailer_host, - address: "smtp.sendgrid.net", - port: 587, - authentication: :plain, - enable_starttls_auto: true -} diff --git a/config/environments/test.rb b/config/environments/test.rb index 7d1d7f6..b1fea1e 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -15,7 +15,7 @@ Rails.application.configure do # Configure public file server for tests with Cache-Control for performance. config.public_file_server.enabled = true config.public_file_server.headers = { - "Cache-Control" => "public, max-age=#{1.hour.seconds.to_i}" + 'Cache-Control' => "public, max-age=#{1.hour.to_i}" } # Show full error reports and disable caching. @@ -27,6 +27,10 @@ Rails.application.configure do # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false + + # Store uploaded files on the local file system in a temporary directory + config.active_storage.service = :test + config.action_mailer.perform_caching = false # Tell Action Mailer not to deliver emails to the real world. @@ -39,7 +43,7 @@ Rails.application.configure do # Raises error for missing translations # config.action_view.raise_on_missing_translations = true - # + config.active_job.queue_adapter = :test # Set default host for url_helpers diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index d633520..4b828e8 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -1,14 +1,14 @@ # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. -Rails.application.config.assets.version = "1.0" +Rails.application.config.assets.version = '1.0' # Add additional assets to the asset load path. # Rails.application.config.assets.paths << Emoji.images_path # Add Yarn node_modules folder to the asset load path. -Rails.application.config.assets.paths << Rails.root.join("node_modules") +Rails.application.config.assets.paths << Rails.root.join('node_modules') # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in the app/assets # folder are already added. -Rails.application.config.assets.precompile += %w[mobile.css mobile.js] +# Rails.application.config.assets.precompile += %w( admin.js admin.css ) diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb new file mode 100644 index 0000000..d3bcaa5 --- /dev/null +++ b/config/initializers/content_security_policy.rb @@ -0,0 +1,25 @@ +# Be sure to restart your server when you modify this file. + +# Define an application-wide content security policy +# For further information see the following documentation +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy + +# Rails.application.config.content_security_policy do |policy| +# policy.default_src :self, :https +# policy.font_src :self, :https, :data +# policy.img_src :self, :https, :data +# policy.object_src :none +# policy.script_src :self, :https +# policy.style_src :self, :https + +# # Specify URI for violation reports +# # policy.report_uri "/csp-violation-report-endpoint" +# end + +# If you are using UJS then enable automatic nonce generation +# Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } + +# Report CSP violations to a specified URI +# For further information see the following documentation: +# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only +# Rails.application.config.content_security_policy_report_only = true diff --git a/config/initializers/new_framework_defaults_5_2.rb b/config/initializers/new_framework_defaults_5_2.rb new file mode 100644 index 0000000..c383d07 --- /dev/null +++ b/config/initializers/new_framework_defaults_5_2.rb @@ -0,0 +1,38 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains migration options to ease your Rails 5.2 upgrade. +# +# Once upgraded flip defaults one by one to migrate to the new default. +# +# Read the Guide for Upgrading Ruby on Rails for more info on each option. + +# Make Active Record use stable #cache_key alongside new #cache_version method. +# This is needed for recyclable cache keys. +# Rails.application.config.active_record.cache_versioning = true + +# Use AES-256-GCM authenticated encryption for encrypted cookies. +# Also, embed cookie expiry in signed or encrypted cookies for increased security. +# +# This option is not backwards compatible with earlier Rails versions. +# It's best enabled when your entire app is migrated and stable on 5.2. +# +# Existing cookies will be converted on read then written with the new scheme. +# Rails.application.config.action_dispatch.use_authenticated_cookie_encryption = true + +# Use AES-256-GCM authenticated encryption as default cipher for encrypting messages +# instead of AES-256-CBC, when use_authenticated_message_encryption is set to true. +# Rails.application.config.active_support.use_authenticated_message_encryption = true + +# Add default protection from forgery to ActionController::Base instead of in +# ApplicationController. +# Rails.application.config.action_controller.default_protect_from_forgery = true + +# Store boolean values are in sqlite3 databases as 1 and 0 instead of 't' and +# 'f' after migrating old data. +# Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true + +# Use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. +# Rails.application.config.active_support.use_sha1_digests = true + +# Make `form_with` generate id attributes for any generated HTML tags. +# Rails.application.config.action_view.form_with_generates_ids = true diff --git a/config/puma.rb b/config/puma.rb index ba9b984..b210207 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -15,43 +15,23 @@ port ENV.fetch("PORT") { 3000 } # environment ENV.fetch("RAILS_ENV") { "development" } +# Specifies the `pidfile` that Puma will use. +pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } + # Specifies the number of `workers` to boot in clustered mode. # Workers are forked webserver processes. If using threads and workers together # the concurrency of the application would be max `threads` * `workers`. # Workers do not work on JRuby or Windows (both of which do not support # processes). # -workers ENV.fetch("WEB_CONCURRENCY") { 2 } +# workers ENV.fetch("WEB_CONCURRENCY") { 2 } # Use the `preload_app!` method when specifying a `workers` number. # This directive tells Puma to first boot the application and load code # before forking the application. This takes advantage of Copy On Write -# process behavior so workers use less memory. If you use this option -# you need to make sure to reconnect any threads in the `on_worker_boot` -# block. +# process behavior so workers use less memory. # # preload_app! -# If you are preloading your application and using Active Record, it's -# recommended that you close any connections to the database before workers -# are forked to prevent connection leakage. -# -# before_fork do -# ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord) -# end - -# The code in the `on_worker_boot` will be called if you are using -# clustered mode by specifying a number of `workers`. After each worker -# process is booted, this block will be run. If you are using the `preload_app!` -# option, you will want to use this block to reconnect to any threads -# or connections that may have been created at application boot, as Ruby -# cannot share connections between processes. -# -before_fork do - # Used for heroku metrics for ruby - Barnes.start - # ActiveRecord::Base.establish_connection if defined?(ActiveRecord) -end - # Allow puma to be restarted by `rails restart` command. plugin :tmp_restart diff --git a/config/storage.yml b/config/storage.yml new file mode 100644 index 0000000..d32f76e --- /dev/null +++ b/config/storage.yml @@ -0,0 +1,34 @@ +test: + service: Disk + root: <%= Rails.root.join("tmp/storage") %> + +local: + service: Disk + root: <%= Rails.root.join("storage") %> + +# Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) +# amazon: +# service: S3 +# access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> +# secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> +# region: us-east-1 +# bucket: your_own_bucket + +# Remember not to checkin your GCS keyfile to a repository +# google: +# service: GCS +# project: your_project +# credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> +# bucket: your_own_bucket + +# Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) +# microsoft: +# service: AzureStorage +# storage_account_name: your_account_name +# storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> +# container: your_container_name + +# mirror: +# service: Mirror +# primary: local +# mirrors: [ amazon, google, microsoft ] diff --git a/gems/block-parser/Gemfile.lock b/gems/block-parser/Gemfile.lock index 24076a7..3d84f82 100644 --- a/gems/block-parser/Gemfile.lock +++ b/gems/block-parser/Gemfile.lock @@ -2,16 +2,16 @@ PATH remote: . specs: block_parser (0.1.0) - activemodel (> 5.2) - commonmarker (= 0.21.0) - github-markup (= 1.6.1) - github_url (= 0.2.1) - gitlab (= 4.14.1) - nokogiri (= 1.8.0) - octokit (= 4.3.0) - psych (= 2.2.4) - redcarpet (= 3.3.4) - rspec_junit_formatter (>= 0.4.1) + activemodel + commonmarker + github-markup + github_url + gitlab + nokogiri + octokit + psych + redcarpet + rspec_junit_formatter GEM remote: https://rubygems.org/ @@ -24,18 +24,19 @@ GEM minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.4.0) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) ast (2.4.1) byebug (11.1.3) commonmarker (0.21.0) ruby-enum (~> 0.5) concurrent-ruby (1.1.7) diff-lcs (1.4.4) - faraday (0.9.2) + faraday (1.0.1) multipart-post (>= 1.2, < 3) - github-markup (1.6.1) + github-markup (3.0.4) github_url (0.2.1) - gitlab (4.14.1) + gitlab (4.16.1) httparty (~> 0.14, >= 0.14.0) terminal-table (~> 1.5, >= 1.5.1) httparty (0.18.1) @@ -46,21 +47,23 @@ GEM mime-types (3.3.1) mime-types-data (~> 3.2015) mime-types-data (3.2020.0512) - mini_portile2 (2.2.0) + mini_portile2 (2.4.0) minitest (5.14.2) multi_xml (0.6.0) multipart-post (2.1.1) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) - octokit (4.3.0) - sawyer (~> 0.7.0, >= 0.5.3) + nokogiri (1.10.10) + mini_portile2 (~> 2.4.0) + octokit (4.18.0) + faraday (>= 0.9) + sawyer (~> 0.8.0, >= 0.5.3) parallel (1.19.2) parser (2.7.2.0) ast (~> 2.4.1) - psych (2.2.4) + psych (3.2.0) + public_suffix (4.0.6) rainbow (3.0.0) rake (13.0.1) - redcarpet (3.3.4) + redcarpet (3.5.0) regexp_parser (1.8.2) rexml (3.2.4) rspec (3.9.0) @@ -92,9 +95,9 @@ GEM ruby-enum (0.8.0) i18n ruby-progressbar (1.10.1) - sawyer (0.7.0) - addressable (>= 2.3.5, < 2.5) - faraday (~> 0.8, < 0.10) + sawyer (0.8.2) + addressable (>= 2.3.5) + faraday (> 0.8, < 2.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) @@ -108,11 +111,11 @@ PLATFORMS DEPENDENCIES block_parser! - bundler (~> 2.0) - byebug (= 11.1.3) - rake (~> 13.0) - rspec (~> 3.8) - rubocop (>= 0.93) + bundler + byebug + rake + rspec + rubocop BUNDLED WITH 2.1.4 diff --git a/gems/block-parser/block_parser.gemspec b/gems/block-parser/block_parser.gemspec index 88f0fe1..5d1cbe3 100644 --- a/gems/block-parser/block_parser.gemspec +++ b/gems/block-parser/block_parser.gemspec @@ -26,20 +26,20 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^bin/}) { |f| f[3..-1] } spec.require_paths = ["lib"] - spec.add_development_dependency "bundler", "~> 2.0" - spec.add_development_dependency "byebug", "11.1.3" - spec.add_development_dependency "rake", "~> 13.0" - spec.add_development_dependency "rspec", "~> 3.8" - spec.add_development_dependency "rubocop", ">= 0.93" + spec.add_development_dependency "bundler" + spec.add_development_dependency "byebug" + spec.add_development_dependency "rake" + spec.add_development_dependency "rspec" + spec.add_development_dependency "rubocop" - spec.add_dependency "activemodel", "> 5.2" - spec.add_dependency "commonmarker", "0.21.0" - spec.add_dependency "github-markup", "1.6.1" - spec.add_dependency "github_url", "0.2.1" - spec.add_dependency "gitlab", "4.14.1" - spec.add_dependency "nokogiri", "1.8.0" - spec.add_dependency "octokit", "4.3.0" - spec.add_dependency "psych", "2.2.4" - spec.add_dependency "redcarpet", "3.3.4" - spec.add_dependency "rspec_junit_formatter", ">= 0.4.1" + spec.add_dependency "activemodel" + spec.add_dependency "commonmarker" + spec.add_dependency "github-markup" + spec.add_dependency "github_url" + spec.add_dependency "gitlab" + spec.add_dependency "nokogiri" + spec.add_dependency "octokit" + spec.add_dependency "psych" + spec.add_dependency "redcarpet" + spec.add_dependency "rspec_junit_formatter" end -- GitLab From 80110ba710a4503e33263a893c49d64d5f5dd07b Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 15 Oct 2020 11:24:46 -1000 Subject: [PATCH 174/287] Fix conversion of datetime to string --- app/presenters/checkpoint_submission_presenter/for_index.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/presenters/checkpoint_submission_presenter/for_index.rb b/app/presenters/checkpoint_submission_presenter/for_index.rb index 5be1a2d..48fc9d8 100644 --- a/app/presenters/checkpoint_submission_presenter/for_index.rb +++ b/app/presenters/checkpoint_submission_presenter/for_index.rb @@ -95,7 +95,7 @@ class CheckpointSubmissionPresenter::ForIndex state: submission["state"], correct_points: submission["correct_points"], total_points: submission["total_points"], - updated_at: DateTime.parse(submission["updated_at"]).in_time_zone(Rails.application.config.time_zone), + updated_at: DateTime.parse(submission["updated_at"].to_s).in_time_zone(Rails.application.config.time_zone), url: url_for_checkpoint_submission(submission["id"]), grader_name: submission["first_name"].blank? ? "" : "#{submission['first_name']} #{submission['last_name']}" } -- GitLab From af6712a314959b8aa8aecb3906e97490eb976d7b Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 15 Oct 2020 13:05:54 -1000 Subject: [PATCH 175/287] Fix update_attributes deprecation warning --- .../cohorts/cohort_releases_controller.rb | 2 +- .../submitted_challenge_answers_controller.rb | 2 +- .../submitted_challenge_answers_controller.rb | 2 +- app/jobs/content_file_visit_job.rb | 2 +- app/models/user.rb | 2 +- app/services/assessment_service.rb | 14 +++++++------- .../create_submitted_challenge_answer_service.rb | 12 ++++++------ .../20180412223312_populate_used_by_application.rb | 4 ++-- db/migrate/20180813214453_populate_mode_cohorts.rb | 2 +- lib/tasks/content_visibility_update.rake | 2 +- 10 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/controllers/cohorts/cohort_releases_controller.rb b/app/controllers/cohorts/cohort_releases_controller.rb index 99a2210..76bf5c8 100644 --- a/app/controllers/cohorts/cohort_releases_controller.rb +++ b/app/controllers/cohorts/cohort_releases_controller.rb @@ -104,7 +104,7 @@ module Cohorts current_cohort_release.update(release_id: release_at_sha.id, pending_release_id: nil, use_latest_release: false) flash_info = { type: "success", msg: msg } elsif release_at_sha.present? && !release_at_sha.successfully_synced? - release_at_sha.update_attributes( + release_at_sha.update( state: Release::STATES[:pending], sync_errors: [] ) diff --git a/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb b/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb index 968e4d8..5dec944 100644 --- a/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb +++ b/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb @@ -215,7 +215,7 @@ module Cohorts authorize(current_submitted_challenge_answer) if current_submitted_challenge_answer.processing? - current_submitted_challenge_answer.update_attributes( + current_submitted_challenge_answer.update( status: SubmittedChallengeAnswer::STATUSES[:canceled], test_results: "Tests canceled by student." ) diff --git a/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb b/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb index 8eedbb0..751a64b 100644 --- a/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb +++ b/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb @@ -21,7 +21,7 @@ module Webhooks status = params[:status] correct = status == SubmittedChallengeAnswer::STATUSES[:correct] points = correct ? submitted_challenge_answer.challenge.points : 0 - submitted_challenge_answer.update_attributes( + submitted_challenge_answer.update( status: status, test_results: results, points: points, diff --git a/app/jobs/content_file_visit_job.rb b/app/jobs/content_file_visit_job.rb index c6ee45b..4931a20 100644 --- a/app/jobs/content_file_visit_job.rb +++ b/app/jobs/content_file_visit_job.rb @@ -19,7 +19,7 @@ class ContentFileVisitJob < ApplicationJob standard_uid: standard_uid ) - visit.update_attributes( + visit.update( visit_count: visit.visit_count + 1, challenge_count: content_file.challenges.count, content_file_path: content_file.path diff --git a/app/models/user.rb b/app/models/user.rb index 6f3563c..0145dc5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -110,7 +110,7 @@ class User < ApplicationRecord def sandbox sb = Cohort.find_by(user_id: id) if sb - sb.update_attributes(name: "Preview") if sb.name != "Preview" + sb.update(name: "Preview") if sb.name != "Preview" else sb = Cohort.create(uid: "#{id}-#{uid}", name: "Preview", diff --git a/app/services/assessment_service.rb b/app/services/assessment_service.rb index 49f228d..589c245 100644 --- a/app/services/assessment_service.rb +++ b/app/services/assessment_service.rb @@ -36,9 +36,9 @@ class AssessmentService # end # rescue Faraday::ConnectionFailed => e # Honeybadger.notify(e) - # submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") and return + # submitted_challenge_answer.update(status: "failed", test_results: "Connection failed") and return # end - # submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") if response.status != 200 + # submitted_challenge_answer.update(status: "failed", test_results: "Connection failed") if response.status != 200 # # response end @@ -60,13 +60,13 @@ class AssessmentService }.to_json end - submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") if response.status != 200 + submitted_challenge_answer.update(status: "failed", test_results: "Connection failed") if response.status != 200 response rescue Faraday::ConnectionFailed => e Honeybadger.notify(e) - submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") + submitted_challenge_answer.update(status: "failed", test_results: "Connection failed") rescue GithubUrl::Invalid - submitted_challenge_answer.update_attributes( + submitted_challenge_answer.update( status: SubmittedChallengeAnswer::STATUSES[:invalid_fork], test_results: "Unexpected URL. Please verify that you submitted a full URL." ) @@ -92,9 +92,9 @@ class AssessmentService end rescue Faraday::ConnectionFailed => e Honeybadger.notify(e) - submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") and return + submitted_challenge_answer.update(status: "failed", test_results: "Connection failed") and return end - submitted_challenge_answer.update_attributes(status: "failed", test_results: "Connection failed") if response.status != 200 + submitted_challenge_answer.update(status: "failed", test_results: "Connection failed") if response.status != 200 response end diff --git a/app/services/create_submitted_challenge_answer_service.rb b/app/services/create_submitted_challenge_answer_service.rb index 5c03958..5840c8b 100644 --- a/app/services/create_submitted_challenge_answer_service.rb +++ b/app/services/create_submitted_challenge_answer_service.rb @@ -115,7 +115,7 @@ module CreateSubmittedChallengeAnswerService def create_project_evaluation_job(challenge, cohort_id, submitted_challenge_answer) if submitted_challenge_answer.answer.empty? - submitted_challenge_answer.update_attributes( + submitted_challenge_answer.update( status: SubmittedChallengeAnswer::STATUSES[:incorrect], test_results: "Please verify that you submitted a full URL.", points: 0 @@ -126,7 +126,7 @@ module CreateSubmittedChallengeAnswerService def create_code_snippet_job(challenge, cohort_id, submitted_challenge_answer) if submitted_challenge_answer.answer.empty? - submitted_challenge_answer.update_attributes( + submitted_challenge_answer.update( status: SubmittedChallengeAnswer::STATUSES[:incorrect], test_results: "Please submit code", points: 0 @@ -137,7 +137,7 @@ module CreateSubmittedChallengeAnswerService def create_custom_snippet_job(challenge, cohort_id, submitted_challenge_answer) if submitted_challenge_answer.answer.empty? - submitted_challenge_answer.update_attributes( + submitted_challenge_answer.update( status: SubmittedChallengeAnswer::STATUSES[:incorrect], test_results: "Please submit code", points: 0 @@ -150,17 +150,17 @@ module CreateSubmittedChallengeAnswerService validate_fork(submitted_challenge_answer, challenge_upstream_path) if validate_fork EvaluateProjectJob.perform_later(submitted_challenge_answer.id, cohort_id) rescue Octokit::InvalidRepository - submitted_challenge_answer.update_attributes( + submitted_challenge_answer.update( status: "invalid_fork", test_results: "Unexpected URL. Be sure you have both user and repo." ) rescue Octokit::NotFound - submitted_challenge_answer.update_attributes( + submitted_challenge_answer.update( status: "invalid_fork", test_results: "Unexpected URL. A repository was not found for that submission url." ) rescue GithubUrl::Invalid, InvalidBranchError, InvalidForkError, ParentMatchError, FolderMatchError => e - submitted_challenge_answer.update_attributes( + submitted_challenge_answer.update( status: "invalid_fork", test_results: "Unexpected URL. #{e.message}" ) diff --git a/db/migrate/20180412223312_populate_used_by_application.rb b/db/migrate/20180412223312_populate_used_by_application.rb index 3824dfe..e75dac7 100644 --- a/db/migrate/20180412223312_populate_used_by_application.rb +++ b/db/migrate/20180412223312_populate_used_by_application.rb @@ -6,9 +6,9 @@ class PopulateUsedByApplication < ActiveRecord::Migration[5.1] def change CohortMigration.all.each do |p| if p.learn_v2 - p.update_attributes(used_by_application: "Learn V2") + p.update(used_by_application: "Learn V2") else - p.update_attributes(used_by_application: "Learn") + p.update(used_by_application: "Learn") end end end diff --git a/db/migrate/20180813214453_populate_mode_cohorts.rb b/db/migrate/20180813214453_populate_mode_cohorts.rb index cb7a56f..586d669 100644 --- a/db/migrate/20180813214453_populate_mode_cohorts.rb +++ b/db/migrate/20180813214453_populate_mode_cohorts.rb @@ -5,7 +5,7 @@ class PopulateModeCohorts < ActiveRecord::Migration[5.1] def change CohortMigration.all.each do |p| - p.update_attributes(mode: Cohort::MODES[:mastery]) + p.update(mode: Cohort::MODES[:mastery]) end end end diff --git a/lib/tasks/content_visibility_update.rake b/lib/tasks/content_visibility_update.rake index 5b851ec..c744d90 100644 --- a/lib/tasks/content_visibility_update.rake +++ b/lib/tasks/content_visibility_update.rake @@ -2,7 +2,7 @@ namespace :content_visibility do desc "Updates the content visibility to default, only run 1 time" task default_all_to_hidden: :environment do ContentVisibility.all.each do |cv| - cv.update_attributes(visibility_type: ContentVisibility::VISIBILITY_TYPES[:hidden]) + cv.update(visibility_type: ContentVisibility::VISIBILITY_TYPES[:hidden]) end end end -- GitLab From 13ff93fcc98b0aeaa7d2efe417e81e1e785de8a0 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 15 Oct 2020 13:25:46 -1000 Subject: [PATCH 176/287] Fix NOT deprecation warnings --- .../cohorts/content_files/checkpoint_submissions_controller.rb | 2 +- app/models/block.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb b/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb index 491b2be..131ec5a 100644 --- a/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb +++ b/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb @@ -189,7 +189,7 @@ module Cohorts content_file_block_id: current_content_file.standard.release.block_id, user_id: user_ids, cohort_id: checkpoint_submission.cohort_id). - where.not(id: not_in_ids + previous_submission_ids_by_users, state: CheckpointSubmission::STATES[:started]). + where.not(id: not_in_ids + previous_submission_ids_by_users).where.not(state: CheckpointSubmission::STATES[:started]). select("DISTINCT ON (checkpoint_submissions.user_id) checkpoint_submissions.id, checkpoint_submissions.state"). order("checkpoint_submissions.user_id, created_at DESC").to_a.select {|cs| cs.state == "needs_review"}.last if oldest_ungraded_submission diff --git a/app/models/block.rb b/app/models/block.rb index 778a93f..6a95462 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -6,7 +6,7 @@ class Block < ApplicationRecord has_many :releases - scope(:without_preview, -> { where.not(title: "preview", repo_name: "preview") }) + scope(:without_preview, -> { where.not(title: "preview").where.not(repo_name: "preview") }) scope(:active, -> { where(archived_at: nil) }) def cohorts -- GitLab From b33fd4ecffc9466b6bdfa9c8ef4c322ee91cdae6 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 15 Oct 2020 13:30:46 -1000 Subject: [PATCH 177/287] updating db/schema files --- db/schema.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index f205c10..9afa02c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2,11 +2,11 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your -# database schema. If you need to create the application database on another -# system, you should be using db:schema:load, not running all the migrations -# from scratch. The latter is a flawed and unsustainable approach (the more migrations -# you'll amass, the slower it'll run and the greater likelihood for issues). +# This file is the source Rails uses to define your schema when running `rails +# db:schema:load`. When creating a new database, `rails db:schema:load` tends to +# be faster and is potentially less error prone than running all of your +# migrations from scratch. Old migrations may fail to apply correctly if those +# migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. -- GitLab From 13b46856ee12baa1045cd48c7859ff717d76a196 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 15 Oct 2020 16:06:57 -1000 Subject: [PATCH 178/287] changing public file serve setting --- config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index cc9244a..bfe198d 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -20,7 +20,7 @@ Rails.application.configure do # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. - config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? + config.public_file_server.enabled = true # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier -- GitLab From 4e4ab1ac156f8a38d7b6b703a69a1ffe2b9b1bd0 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Mon, 19 Oct 2020 17:18:19 -1000 Subject: [PATCH 180/287] Removed cohorts_users update and delete paths --- .../api/v1/cohorts/users_controller.rb | 101 ------------------ 1 file changed, 101 deletions(-) diff --git a/app/controllers/api/v1/cohorts/users_controller.rb b/app/controllers/api/v1/cohorts/users_controller.rb index ad3efca..9dc65df 100644 --- a/app/controllers/api/v1/cohorts/users_controller.rb +++ b/app/controllers/api/v1/cohorts/users_controller.rb @@ -30,34 +30,6 @@ class Api::V1::Cohorts::UsersController < Api::ApplicationController json_response({ error: e.message }, 400) and return end - def update - render_method_not_allowed_response and return unless request.patch? - authorize(current_cohort, :enroll?) - - if target_user&.student_or_instructor_of?(current_cohort.id) - enroll_existing_user(target_user) - json_response({ status: "ok" }, :ok) - else - json_response({ status: "404" }, 404) - end - rescue StandardError => e - json_response({ error: e.message }, 400) and return - end - - def destroy - render_method_not_allowed_response and return unless request.delete? - authorize(current_cohort, :enroll?) - - if target_user&.student_or_instructor_of?(current_cohort.id) - unenroll_existing_user - json_response({ status: "ok" }, :ok) - else - json_response({ status: "404" }, 404) - end - rescue StandardError => e - json_response({ error: e.message }, 400) and return - end - private def current_cohort @@ -92,82 +64,9 @@ class Api::V1::Cohorts::UsersController < Api::ApplicationController end end - def invite_user - # TODO: AuthApi fix - # client = AuthApi::Client.new("user.invite user.write user.product_roles") - # options = { - # user: { - # first_name: params[:first_name], - # last_name: params[:last_name], - # email: params[:email], - # registrations: [{ - # product_uid: current_cohort.uid, - # roles: enroll_roles - # }] - # } - # } - # - # resp = client.invite_user(options) - # AuthResolverService.resolve(resp) if resp&.uid - # resp&.uid - 'todo' - end - - def enroll_roles - [ - params[:instructor] == true ? "instructor" : nil, - params[:onboard] == true ? "auth.product_admin" : nil - ].compact - end - - def enroll_existing_user(user) - # TODO: AuthApi fix - - # client = AuthApi::Client.new("user.write user.product_roles") - # options = { - # id: user.uid, - # user: { - # registrations: [{ - # product_uid: current_cohort.uid, - # roles: enroll_roles - # }] - # } - # } - # - # resp = client.update_user(options) - # AuthResolverService.resolve(resp) if resp&.uid - # resp&.uid - 'todo' - end - - def unenroll_existing_user - # TODO: AuthApi fix - - # client = AuthApi::Client.new("user.write user.unregister") - # options = { - # id: target_user.uid, - # user: { - # registrations: [{ - # product_uid: current_cohort.uid, - # _destroy: true - # }] - # } - # } - # - # resp = client.update_user(options) - # AuthResolverService.resolve(resp) if resp&.uid - 'todo' - end - def param_errors validation_response = [] - # if params[:first_name].blank? - # validation_response.push "first_name can't be blank" - # end - # if params[:last_name].blank? - # validation_response.push "last_name can't be blank" - # end if params[:email].blank? validation_response.push "email can't be blank" end -- GitLab From 9592d9283378059142070b4c11166114ae2abcf3 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 27 Oct 2020 10:46:24 -1000 Subject: [PATCH 182/287] adding temp values for aws secret and access key. --- config/secrets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/secrets.yml b/config/secrets.yml index bd0e92d..a3a095e 100644 --- a/config/secrets.yml +++ b/config/secrets.yml @@ -21,8 +21,8 @@ shared: aws_sqlsnippets_password: <%= ENV["AWS_SQLSNIPPETS_PASSWORD"] %> aws_sqlsnippets_user: <%= ENV["AWS_SQLSNIPPETS_USER"] %> aws_sqlsnippets_host: <%= ENV["AWS_SQLSNIPPETS_HOST"] %> - glearn_access_key_id: <%= ENV['accesskey'] %> - glearn_secret_access_key: <%= ENV['secretkey'] %> + glearn_access_key_id: <%= ENV['accesskey'] || "temp" %> + glearn_secret_access_key: <%= ENV['secretkey'] || "temp" %> glearn_key_prefix: <%= ENV['GLEARN_KEY_PREFIX'] || "forge" %> glearn_bucket_name: <%= ENV["APP_S3_BUCKET"] || "temp" %> s3_region: <%= ENV["S3_REGION"] || "us-gov-west-1" %> -- GitLab From 49d08b8d4dddcd8ba1df97b23249e9cc96901c6f Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 22 Oct 2020 15:45:50 -1000 Subject: [PATCH 183/287] more testing changes --- app/controllers/api/v1/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/users_controller.rb b/app/controllers/api/v1/users_controller.rb index 245729e..1712099 100644 --- a/app/controllers/api/v1/users_controller.rb +++ b/app/controllers/api/v1/users_controller.rb @@ -32,7 +32,7 @@ class Api::V1::UsersController < Api::ApplicationController render_method_not_allowed_response and return unless request.post? authorize(current_user, :learn_cli_metadata?) - + byebug @api_interaction_metadata = { cli_benchmark: user_params.as_json } -- GitLab From 1ecb758c0ac6212e3df28ad855fb9bad8ae216b8 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Tue, 27 Oct 2020 11:23:24 -1000 Subject: [PATCH 184/287] Some small testing changes --- app/services/platform_one_auth_resolver_service.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/services/platform_one_auth_resolver_service.rb b/app/services/platform_one_auth_resolver_service.rb index 7ad8286..f6bde0d 100644 --- a/app/services/platform_one_auth_resolver_service.rb +++ b/app/services/platform_one_auth_resolver_service.rb @@ -32,8 +32,7 @@ class PlatformOneAuthResolverService ) unless user.valid? - puts user.errors.full_messages - raise CreateUserError + raise CreateUserError.new(user.errors.full_messages.join(', ')) end user -- GitLab From 0f82a74996f7d9a503840a0a2b5e3a9fa2b25cdb Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 5 Nov 2020 09:41:11 -1000 Subject: [PATCH 185/287] Change to add python 3.8 --- .../components/challenges/challenge_block/ChallengeBlock.tsx | 2 +- .../challenge_validators/code_snippet_challenge_validator.rb | 4 ++-- .../code_snippet_challenge_validator_spec.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx b/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx index 0bfcf1e..8bc8131 100644 --- a/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx +++ b/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx @@ -538,7 +538,7 @@ export default class ChallengeBlock extends React.Component { editorMode() { const { language } = this.props.challenge; - return language === 'python2.7' || language === 'python3.6' ? 'python' : language; + return language === 'python2.7' || language === 'python3.6' || language === 'python3.8' ? 'python' : language; } toggleFakeProcessing() { diff --git a/gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb b/gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb index 28ed635..f137bba 100644 --- a/gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb +++ b/gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb @@ -7,8 +7,8 @@ module BlockParser :language, presence: true, inclusion: { - in: ["java", "javascript", "python2.7", "python3.6", "sql"], - message: "language must be java, javascript, python2.7, or python3.6, or sql" + in: ["java", "javascript", "python2.7", "python3.6", "python3.8", "sql"], + message: "language must be java, javascript, python3.6, or python3.8" } ) validates :tests, presence: true diff --git a/gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb b/gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb index 15a5342..cb6fa75 100644 --- a/gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb +++ b/gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb @@ -54,13 +54,13 @@ describe BlockParser::ChallengeValidators::CodeSnippetChallengeValidator do challenge_validator = described_class.new(code_snippet_json) expect(challenge_validator.valid?).to eq(false) - expect(challenge_validator.errors[:language]).to eq(["can't be blank", "language must be java, javascript, python2.7, or python3.6, or sql"]) + expect(challenge_validator.errors[:language]).to eq(["can't be blank", "language must be java, javascript, python3.6, or python3.8"]) code_snippet_json[:language] = "Lua" challenge_validator = described_class.new(code_snippet_json) expect(challenge_validator.valid?).to eq(false) - expect(challenge_validator.errors[:language]).to eq(["language must be java, javascript, python2.7, or python3.6, or sql"]) + expect(challenge_validator.errors[:language]).to eq(["language must be java, javascript, python3.6, or python3.8"]) end it "requires tests" do -- GitLab From c3ece3606e95470ec2c1b012eb3b3dcc452e6143 Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Thu, 5 Nov 2020 16:02:42 -1000 Subject: [PATCH 186/287] Convert to IB directory format --- .Dockerfile.swp | Bin 0 -> 20480 bytes .circleci/config.yml | 97 - Dockerfile | 81 +- Dockerfile.base | 47 - Dockerfile.old | 169 ++ Jenkinsfile | 14 + download.json | 13 + .browserslistrc => scripts/.browserslistrc | 0 .env.example => scripts/.env.example | 0 .eslintrc.json => scripts/.eslintrc.json | 0 .haml-lint.yml => scripts/.haml-lint.yml | 0 .nvmrc => scripts/.nvmrc | 0 .overcommit.yml => scripts/.overcommit.yml | 0 .postcssrc.yml => scripts/.postcssrc.yml | 0 .rubocop.yml => scripts/.rubocop.yml | 0 .ruby-version => scripts/.ruby-version | 0 .stylelintrc => scripts/.stylelintrc | 0 Gemfile => scripts/Gemfile | 0 Gemfile.lock => scripts/Gemfile.lock | 0 Procfile => scripts/Procfile | 0 Procfile.local => scripts/Procfile.local | 0 README.md => scripts/README.md | 0 Rakefile => scripts/Rakefile | 0 app.json => scripts/app.json | 0 .../app}/assets/config/manifest.js | 0 .../app}/assets/images/favicon.ico | Bin .../app}/assets/images/loader-black.svg | 0 .../app}/assets/images/loader-cyan.svg | 0 .../app}/assets/images/loader-white.svg | 0 {app => scripts/app}/assets/images/lost.jpg | Bin .../app}/assets/images/spinner.gif | Bin .../assets/images/svg/baseline-notes-24px.svg | 0 .../app}/assets/images/svg/g-learn-lockup.svg | 0 .../app}/assets/images/svg/galvanize-logo.svg | 0 .../app}/assets/images/svg/github-icon.svg | 0 .../app}/assets/images/svg/mobile-logo.svg | 0 .../assets/images/svg/octicon-git-branch.svg | 0 .../images/svg/svg-sprite-action-symbol.svg | 0 .../images/svg/svg-sprite-av-symbol.svg | 0 .../images/svg/svg-sprite-device-symbol.svg | 0 .../images/svg/svg-sprite-image-symbol.svg | 0 .../svg/svg-sprite-navigation-symbol.svg | 0 .../app}/assets/javascripts/application.js | 0 .../app}/assets/javascripts/mobile.js | 0 .../app}/assets/stylesheets/application.scss | 0 .../app}/assets/stylesheets/base.scss | 0 .../assets/stylesheets/bootstrap-custom.scss | 0 .../app}/assets/stylesheets/cohorts.scss | 0 .../components/_404-container.scss | 0 .../stylesheets/components/_action-menu.scss | 0 .../components/_activity-dashboard.scss | 0 .../components/_activity-feed.scss | 0 .../components/_api-interactions.scss | 0 .../stylesheets/components/_api_token.scss | 0 .../components/_auth-style-search.scss | 0 .../stylesheets/components/_blocks-index.scss | 0 .../stylesheets/components/_blocks-stats.scss | 0 .../stylesheets/components/_button.scss | 0 .../stylesheets/components/_callouts.scss | 0 .../components/_challenge-block.scss | 0 .../components/_challenge_status.scss | 0 .../components/_checkbox-input.scss | 0 .../components/_checkpoint-landing.scss | 0 .../components/_checkpoint-submission.scss | 0 ...heckpoint-submissions-columns-student.scss | 0 .../_checkpoint-submissions-columns.scss | 0 .../_checkpoint-submissions-index.scss | 0 .../_checkpoint_student_scores.scss | 0 .../_checkpoint_submisstion_state.scss | 0 .../components/_checkpoint_toolbar.scss | 0 .../stylesheets/components/_code-block.scss | 0 .../components/_cohort-submissions-table.scss | 0 .../components/_cohort_releases.scss | 0 .../stylesheets/components/_cohorts.scss | 0 .../components/_content-file-show.scss | 0 .../components/_content-file-sidebar.scss | 0 .../_curriculum-checkpoint-summary.scss | 0 .../components/_curriculum-progress.scss | 0 .../stylesheets/components/_curriculum.scss | 0 .../components/_dropdown-component.scss | 0 .../components/_external-link.scss | 0 .../components/_flash-message.scss | 0 .../stylesheets/components/_footer.scss | 0 .../components/_grade-buttons.scss | 0 .../components/_integer_picker.scss | 0 .../components/_lp-style-button.scss | 0 .../components/_mastery-table.scss | 0 .../assets/stylesheets/components/_modal.scss | 0 .../components/_navigation-dropdown.scss | 0 .../components/_notifications.scss | 0 .../stylesheets/components/_pagination.scss | 0 .../assets/stylesheets/components/_pill.scss | 0 .../components/_point_grade_buttons.scss | 0 .../components/_primary-header.scss | 0 .../components/_primary-navigation.scss | 0 .../stylesheets/components/_progress.scss | 0 .../components/_progress_thresholds_key.scss | 0 .../_progress_thresholds_modal.scss | 0 .../_progress_thresholds_slider.scss | 0 .../stylesheets/components/_search-bar.scss | 0 .../components/_secondary-navigation.scss | 0 .../stylesheets/components/_slideshow.scss | 0 .../components/_sort-dropdown-component.scss | 0 .../components/_standard-card.scss | 0 .../components/_standard-cards.scss | 0 .../components/_standards-mastery-beans.scss | 0 .../components/_status_picker.scss | 0 .../components/_student-mastery-table.scss | 0 .../components/_student-name-bar.scss | 0 .../_submissions-dashboard-table.scss | 0 .../stylesheets/components/_svg-icon.scss | 0 .../stylesheets/components/_thresholds.scss | 0 .../components/_universal-list.scss | 0 .../components/_universal-row.scss | 0 .../components/_universal-table.scss | 0 .../stylesheets/components/_user-avatar.scss | 0 .../stylesheets/components/_video-player.scss | 0 .../assets/stylesheets/components/badge.scss | 0 .../components/challenge-detail-comments.scss | 0 .../components/challenge-detail-view.scss | 0 .../components/challenge-timeline.scss | 0 .../components/new-comment-form.scss | 0 .../stylesheets/components/partnerup.scss | 0 .../components/progress-table.scss | 0 .../stylesheets/hopscotch-overrides.scss | 0 .../app}/assets/stylesheets/mixins.scss | 0 .../app}/assets/stylesheets/mobile.scss | 0 .../mobile/_submissions-dashboard-table.scss | 0 .../app}/assets/stylesheets/typography.scss | 0 .../app}/assets/stylesheets/variables.scss | 0 .../activity_feed_item_component_props.rb | 0 .../notifications_component_props.rb | 0 .../standard_card_component_props.rb | 0 .../controllers/api/application_controller.rb | 0 .../api/v1/blocks/releases_controller.rb | 0 .../controllers/api/v1/blocks_controller.rb | 0 .../blocks/content_files_controller.rb | 0 .../api/v1/cohorts/blocks/units_controller.rb | 0 .../api/v1/cohorts/cohorts_controller.rb | 0 .../api/v1/cohorts/users_controller.rb | 0 .../api/v1/content_files_controller.rb | 0 .../api/v1/pineapple_controller.rb | 0 .../controllers/api/v1/releases_controller.rb | 0 .../controllers/api/v1/users_controller.rb | 0 .../controllers/application_controller.rb | 0 .../controllers/blocks/releases_controller.rb | 0 .../app}/controllers/blocks_controller.rb | 0 .../blocks/content_files_controller.rb | 0 .../activities_controller.rb | 0 .../cohorts/cohort_releases_controller.rb | 0 .../checkpoint_submissions_controller.rb | 0 .../submitted_challenge_answers_controller.rb | 0 .../cohorts/content_files_controller.rb | 0 .../cohorts/pairings_controller.rb | 0 .../cohorts/standards_controller.rb | 0 .../activities_controller.rb | 0 .../cohorts/users/challenges_controller.rb | 0 .../cohorts/users/performances_controller.rb | 0 .../controllers/cohorts/users_controller.rb | 0 .../app}/controllers/cohorts_controller.rb | 0 .../concerns/api/content_visibility_crud.rb | 0 .../concerns/api/exception_handler.rb | 0 .../app}/controllers/concerns/api/response.rb | 0 .../app}/controllers/home_controller.rb | 0 .../controllers/notifications_controller.rb | 0 .../app}/controllers/permalinks_controller.rb | 0 .../app}/controllers/sessions_controller.rb | 0 .../app}/controllers/users_controller.rb | 0 .../submitted_challenge_answers_controller.rb | 0 .../app}/exporters/performance_exporter.rb | 0 .../app}/exporters/progress_exporter.rb | 0 {app => scripts/app}/finders/block_finder.rb | 0 .../finders/checkpoint_submission_finder.rb | 0 .../app}/finders/cohort_user_finder.rb | 0 .../app}/finders/content_file_finder.rb | 0 .../app}/finders/performance_finder.rb | 0 .../app}/finders/release_finder.rb | 0 .../app}/finders/standard_finder.rb | 0 .../submitted_challenge_answer_finder.rb | 0 {app => scripts/app}/finders/user_finder.rb | 0 .../app}/helpers/application_helper.rb | 0 {app => scripts/app}/helpers/json_helper.rb | 0 .../helpers/secondary_navigation_helper.rb | 0 .../helpers/standard_navigation_helper.rb | 0 {app => scripts/app}/javascript/api.d.ts | 0 .../app}/javascript/components/AceEditor.tsx | 0 .../app}/javascript/components/Badge.tsx | 0 .../app}/javascript/components/Button.tsx | 0 .../app}/javascript/components/ButtonTo.tsx | 0 .../app}/javascript/components/Dropdown.tsx | 0 .../app}/javascript/components/Icon.tsx | 0 .../app}/javascript/components/Loading.tsx | 0 .../app}/javascript/components/Marked.tsx | 0 .../app}/javascript/components/Menu.tsx | 0 .../javascript/components/Notifications.tsx | 0 .../javascript/components/ProcessingIcon.tsx | 0 .../app}/javascript/components/RowKebab.tsx | 0 .../javascript/components/ScoreCircle.tsx | 0 .../javascript/components/SidebarProgress.tsx | 0 .../javascript/components/SortDropdown.tsx | 0 .../javascript/components/StandardBean.tsx | 0 .../javascript/components/SvgRenderer.tsx | 0 .../app}/javascript/components/Timestamp.tsx | 0 .../app}/javascript/components/UserAvatar.tsx | 0 .../components/activities/NewActivityForm.tsx | 0 .../components/api/ApiInteractions.tsx | 0 .../javascript/components/api/ApiToken.tsx | 0 .../components/blocks/BlockPage-v2.tsx | 0 .../components/blocks/BlocksIndex-v2.tsx | 0 .../components/blocks/BlocksNewModal.tsx | 0 .../components/blocks/BlocksNewRelease.tsx | 0 .../components/blocks/BlocksRow.tsx | 0 .../components/blocks/BlocksShow.tsx | 0 .../components/blocks/BlocksStats.tsx | 0 .../components/blocks/CohortsTooltip.tsx | 0 .../components/challenges/AvatarBar.tsx | 0 .../challenges/ChallengeDetailView.tsx | 0 .../components/challenges/ChallengeShow.tsx | 0 .../challenge_block/ChallengeActionBlock.tsx | 0 .../challenge_block/ChallengeBlock.tsx | 0 .../ChallengeDetailActivities.tsx | 0 .../ChallengeExplanationBlock.tsx | 0 .../ChallengeFeedbackBlock.tsx | 0 .../challenge_block/ChallengeHintsBlock.tsx | 0 .../challenge_block/ChallengeInputs.tsx | 0 .../ChallengeLocalTestResults.tsx | 0 .../challenge_block/ChallengeRubricBlock.tsx | 0 .../challenge_block/ChallengeStatus.tsx | 0 .../challenge_block/ChallengeStatusBar.tsx | 0 .../challenge_block/ChallengeTestResults.tsx | 0 .../challenge_block/ChallengeTests.tsx | 0 .../challenge_block/ChallengeTimeLine.tsx | 0 .../challenge_block/GradeIndicator.tsx | 0 .../challenge_block/GradedTimestamp.tsx | 0 .../challenge_block/StatusPicker.tsx | 0 .../challenges/local/run-local-challenge.ts | 0 .../components/challenges/local/sandbox.ts | 0 .../challenges/local/stack-traces.ts | 0 .../CheckpointAfterSubmitButton.tsx | 0 .../CheckpointAfterSubmitModal.tsx | 0 .../CheckpointAfterSubmitModalError.tsx | 0 .../CheckpointSubmissionChallenges.tsx | 0 .../checkpoints/CheckpointSubmissionShow.tsx | 0 .../checkpoints/CheckpointSubmissionState.tsx | 0 .../CheckpointSubmissionStudentNameBar.tsx | 0 .../components/cohorts/CohortsIndex.tsx | 0 .../components/cohorts/UsersNew.tsx | 0 .../activity_dashboard/ActivityDashboard.tsx | 0 .../cohorts/activity_feed/ActivityFeed.tsx | 0 .../cohort_releases/ReleaseVersionsTable.tsx | 0 .../cohort_submissions/CohortSubmissions.tsx | 0 .../CohortSubmissionsChallengeItem.tsx | 0 .../CohortSubmissionsLessonRow.tsx | 0 .../CohortSubmissionsPerformanceModal.tsx | 0 .../CohortSubmissionsStandardRow.tsx | 0 .../CohortSubmissionsStudentColumn.tsx | 0 .../CohortSubmissionsStudentNameBar.tsx | 0 .../CohortSubmissionsStudentStandard.tsx | 0 .../CohortSubmissionsTable.tsx | 0 .../CohortSubmissionsUnitPercent.tsx | 0 .../cohorts/mastery/MasteryTable.tsx | 0 .../components/cohorts/mastery/MetricRow.tsx | 0 .../cohorts/mastery/MetricsBody.tsx | 0 .../cohorts/mastery/PerformanceCell.tsx | 0 .../cohorts/mastery/PerformanceRow.tsx | 0 .../cohorts/mastery/ReleaseBody.tsx | 0 .../cohorts/mastery/StandardRow.tsx | 0 .../cohorts/mastery/StudentHeader.tsx | 0 .../cohorts/mastery/StudentMasteryTable.tsx | 0 .../cohorts/mastery/StudentReleaseRow.tsx | 0 .../components/cohorts/pairing/GroupPairs.tsx | 0 .../cohorts/pairing/InnerStudentList.tsx | 0 .../components/cohorts/pairing/NewPairing.tsx | 0 .../cohorts/pairing/PairingBoard.tsx | 0 .../cohorts/pairing/StudentItem.tsx | 0 .../cohorts/pairing/StudentList.tsx | 0 .../progress/MasteryProgressionBar.tsx | 0 .../progress/ProgressThresholdCellHeaders.tsx | 0 .../cohorts/progress/StudentProgress.tsx | 0 .../cohorts/progress/StudentProgressBar.tsx | 0 .../cohorts/progress/StudentProgressCells.tsx | 0 .../cohorts/progress/StudentProgressRow.tsx | 0 .../progress/StudentProgressSortArrow.tsx | 0 .../cohorts/settings/BranchReleaseModal.tsx | 0 .../settings/CohortBlockReleaseRow.tsx | 0 .../cohorts/settings/CohortContentTab.tsx | 0 .../cohorts/settings/CohortInfo.tsx | 0 .../cohorts/settings/CohortSettingsResync.tsx | 0 .../cohorts/settings/CohortSettingsTabs.tsx | 0 .../settings/CohortVisibilitySection.tsx | 0 .../settings/CurriculumSettingsTab.tsx | 0 .../cohorts/settings/ReleaseVersionModal.tsx | 0 .../cohorts/settings/ReleaseVersionRow.tsx | 0 .../cohorts/settings/UserCohortKebab.tsx | 0 .../cohorts/settings/UserImport.tsx | 0 .../components/cohorts/settings/UserKebab.tsx | 0 .../AnswerStatusRollup.tsx | 0 .../HeaderStandardContainer.tsx | 0 .../StandardContainer.tsx | 0 .../SubmissionsDashboard.tsx | 0 .../SubmissionsDashboardChallengeItem.tsx | 0 .../SubmissionsDashboardContentFileRow.tsx | 0 .../SubmissionsDashboardStudentColumn.tsx | 0 .../SubmissionsDashboardStudentNameBar.tsx | 0 .../SubmissionsDashboardTable.tsx | 0 .../components/content_files/ActionMenus.tsx | 0 .../components/content_files/Lesson.tsx | 0 .../content_files/MarkdownRenderer.tsx | 0 .../components/content_files/PDFRenderer.tsx | 0 .../components/content_files/SideBar.tsx | 0 .../content_files/SubmissionRenderer.tsx | 0 .../content_files/checkpoints/Checkpoint.tsx | 0 .../checkpoints/CheckpointActionBar.tsx | 0 .../checkpoints/CheckpointDetails.tsx | 0 .../checkpoints/CheckpointLanding.tsx | 0 .../CheckpointLandingAttribute.tsx | 0 .../checkpoints/CheckpointPairs.tsx | 0 .../content_files/checkpoints/PairAvatars.tsx | 0 .../student_scores/CheckpointStudentRow.tsx | 0 .../CheckpointStudentScores.tsx | 0 .../curriculum/CheckpointSummary.tsx | 0 .../curriculum/CohortCurriculum.tsx | 0 .../curriculum/CurriculumStandardCard.tsx | 0 .../curriculum/ProgressIndicators.tsx | 0 .../components/curriculum/StandardBeans.tsx | 0 .../curriculum/StandardsRenderer.tsx | 0 .../StudentOverallProgressDoughnut.tsx | 0 .../app}/javascript/components/lib/ace.ts | 0 .../notifications/NotificationsIcon.tsx | 0 .../shared/ActionMenu/ActionMenu.tsx | 0 .../components/shared/Button/Button.tsx | 0 .../ChallengePoints/ChallengePoints.tsx | 0 .../ChallengePoints/PartialCreditBaton.tsx | 0 .../shared/ChallengePoints/SpinText.tsx | 0 .../IntegerPicker/IntegerPicker.tsx | 0 .../components/shared/DonutRing/DonutRing.tsx | 0 .../components/ColorDonut/ColorDonut.tsx | 0 .../CompleteDonut/CompleteDonut.tsx | 0 .../UngradedDonut/UngradedDonut.tsx | 0 .../UngradedDonut/UngradedDonut.tsx | 0 .../ErrorMessagesTable/ErrorMessagesTable.tsx | 0 .../shared/FlashAlert/FlashAlert.tsx | 0 .../shared/FormatNumber/FormatNumber.tsx | 0 .../components/shared/Icons/AlertSign.tsx | 0 .../components/shared/Icons/Clear.tsx | 0 .../components/shared/Icons/CloseButton.tsx | 0 .../components/shared/Icons/Done.tsx | 0 .../shared/Icons/KeyboardArrowDown.tsx | 0 .../shared/Icons/KeyboardArrowUp.tsx | 0 .../components/shared/Icons/SettingsCog.tsx | 0 .../components/shared/Icons/StatusCircle.tsx | 0 .../MasteryProgressBar/MasteryProgressBar.tsx | 0 .../components/shared/Modal/Modal.tsx | 0 .../shared/Pagination/Pagination.tsx | 0 .../PercentageProgressBar.tsx | 0 .../components/shared/Pill/Pill.tsx | 0 .../PointGradeButtons/PointGradeButtons.tsx | 0 .../shared/PointGradeButtons/SpinText.tsx | 0 .../shared/ProgressBar/ProgressBar.tsx | 0 .../ProgressThresholdsKey.tsx | 0 .../ProgressThresholdsModal.tsx | 0 .../ProgressThresholdsSlider.tsx | 0 .../components/shared/SearchBar/SearchBar.tsx | 0 .../components/shared/Slideshow/Slideshow.tsx | 0 .../shared/UngradedDonut/UngradedDonut.tsx | 0 .../shared/UniversalList/UniversalList.tsx | 0 .../shared/UniversalRow/UniversalRow.tsx | 0 .../shared/UniversalTable/UniversalTable.tsx | 0 .../standards/CompletionScoringBlock.tsx | 0 .../standards/MasteryScoringBlock.tsx | 0 .../components/standards/StandardCard.tsx | 0 .../standards/StandardScoreButton.tsx | 0 .../standards/StandardScoringBlock.tsx | 0 .../standards/StandardTopicsRollups.tsx | 0 .../components/users/UsersIndex.tsx | 0 .../components/vendor/ReactTooltip.js | 0 .../app}/javascript/generated/routes.ts | 0 {app => scripts/app}/javascript/globals.ts | 0 {app => scripts/app}/javascript/lib/http.ts | 0 {app => scripts/app}/javascript/lib/utils.ts | 0 .../app}/javascript/packs/application.js | 0 .../app}/javascript/packs/server_rendering.js | 0 {app => scripts/app}/jobs/application_job.rb | 0 .../app}/jobs/branch_release_notifier_job.rb | 0 .../jobs/checkpoint_paired_submission_job.rb | 0 .../content_file_default_visibility_job.rb | 0 .../app}/jobs/content_file_visit_job.rb | 0 .../app}/jobs/create_api_interaction_job.rb | 0 .../app}/jobs/create_release_job.rb | 0 .../app}/jobs/evaluate_code_snippet_job.rb | 0 .../app}/jobs/evaluate_custom_snippet_job.rb | 0 .../app}/jobs/evaluate_project_job.rb | 0 .../app}/jobs/grade_timed_checkpoint_job.rb | 0 .../app}/jobs/import_student_work_job.rb | 0 .../app}/jobs/java_code_evaluation_job.rb | 0 .../jobs/javascript_code_evaluation_job.rb | 0 {app => scripts/app}/jobs/progress_csv_job.rb | 0 .../app}/jobs/python_code_evaluation_job.rb | 0 .../app}/jobs/release_notifier_job.rb | 0 .../app}/jobs/resync_course_job.rb | 0 .../app}/jobs/set_block_caches_job.rb | 0 .../jobs/slack_challenge_performance_job.rb | 2 +- .../app}/jobs/slack_dev_notify_job.rb | 0 .../app}/jobs/slack_ds_prep_job.rb | 0 {app => scripts/app}/jobs/slack_job.rb | 0 .../app}/jobs/slack_se_prep_job.rb | 0 .../app}/jobs/slack_student_job.rb | 0 .../app}/jobs/student_progress_auth_job.rb | 0 .../app}/jobs/switch_to_branch_job.rb | 0 .../app}/mailers/application_mailer.rb | 0 {app => scripts/app}/mailers/user_mailer.rb | 0 {app => scripts/app}/models/activity.rb | 0 .../app}/models/api_interaction.rb | 0 .../app}/models/application_record.rb | 0 {app => scripts/app}/models/block.rb | 0 {app => scripts/app}/models/challenge.rb | 0 .../app}/models/checkpoint_submission.rb | 0 {app => scripts/app}/models/cohort.rb | 0 {app => scripts/app}/models/cohort_release.rb | 0 {app => scripts/app}/models/cohort_user.rb | 0 .../app}/models/concerns/findable_by_uid.rb | 0 .../app}/models/concerns/read_only_model.rb | 0 {app => scripts/app}/models/content_file.rb | 0 .../app}/models/content_visibility.rb | 0 {app => scripts/app}/models/job_result.rb | 0 {app => scripts/app}/models/lesson_visit.rb | 0 {app => scripts/app}/models/notification.rb | 0 {app => scripts/app}/models/pairing.rb | 0 {app => scripts/app}/models/performance.rb | 0 {app => scripts/app}/models/release.rb | 0 .../app}/models/resync_job_result.rb | 0 {app => scripts/app}/models/section.rb | 0 {app => scripts/app}/models/standard.rb | 0 .../app}/models/submitted_challenge_answer.rb | 0 {app => scripts/app}/models/unit.rb | 0 {app => scripts/app}/models/user.rb | 0 .../models/user_last_viewed_standard_path.rb | 0 .../app}/policies/activity_policy.rb | 0 .../app}/policies/application_policy.rb | 0 {app => scripts/app}/policies/block_policy.rb | 0 .../policies/checkpoint_submission_policy.rb | 0 .../app}/policies/cohort_policy.rb | 0 .../app}/policies/cohort_release_policy.rb | 0 .../app}/policies/cohort_user_policy.rb | 0 .../app}/policies/content_file_policy.rb | 0 .../app}/policies/notification_policy.rb | 0 .../app}/policies/performance_policy.rb | 0 .../app}/policies/release_policy.rb | 0 .../submitted_challenge_answer_policy.rb | 0 {app => scripts/app}/policies/user_policy.rb | 0 .../app}/presenters/activity_presenter.rb | 0 .../presenters/block_presenter/for_block.rb | 0 .../block_presenter/for_cohort_releases.rb | 0 .../for_cohort_releases_new.rb | 0 .../block_presenter/for_releases.rb | 0 ...h_submitted_challenge_answers_presenter.rb | 0 .../checkpoint_submission_presenter.rb | 0 .../for_index.rb | 0 .../for_student_row.rb | 0 .../for_cohort_setup.rb | 0 .../presenters/cohort_setup/visibility.rb | 0 .../for_curriculum_last_viewed.rb | 0 .../content_file_presenter/for_footer.rb | 0 .../content_file_presenter/for_show.rb | 0 .../content_file_presenter/for_sidebar.rb | 0 .../app}/presenters/performance_presenter.rb | 0 .../standard_card_component_props.rb | 0 .../for_challenge_detail_view.rb | 0 .../for_checkpoint_submission.rb | 0 .../standard_presenter/for_standard_card.rb | 0 .../for_submissions_dashboard.rb | 0 .../presenters/stat_progress_presenter.rb | 0 .../presenters/student_progress_presenter.rb | 0 .../submissions_dashboard_presenter.rb | 0 .../submitted_challenge_answer_presenter.rb | 0 .../presenters/user_presenter/for_avatar.rb | 0 .../services/activity_aggregator_service.rb | 0 .../app}/services/assessment_service.rb | 0 .../services/auto_assign_release_service.rb | 0 .../services/checkpoint_submission_service.rb | 0 .../cohort_standard_progress_service.rb | 0 .../cohort_student_progress_service.rb | 0 .../completion_mode_percent_service.rb | 0 .../app}/services/course_validator.rb | 0 ...eate_submitted_challenge_answer_service.rb | 0 .../services/curriculum_progress_service.rb | 0 .../download_github_repository_service.rb | 0 .../download_gitlab_repository_service.rb | 0 .../services/download_repository_service.rb | 0 .../download_s3_repository_service.rb | 0 .../services/encoded_image_link_service.rb | 0 .../app}/services/git_url_service.rb | 0 .../app}/services/mastery_average_service.rb | 0 .../services/mastery_mode_percent_service.rb | 0 .../app}/services/mock_mixpanel.rb | 0 .../app}/services/notification_service.rb | 0 .../platform_one_auth_resolver_service.rb | 0 .../services/preview_content_file_service.rb | 0 .../services/release_destroyer_service.rb | 0 .../app}/services/release_helper_service.rb | 0 .../app}/services/resync_course_service.rb | 0 .../services/s3_asset_uploader_service.rb | 0 .../app}/services/segment_track_service.rb | 0 .../app}/services/sql_challenge_db_service.rb | 0 .../services/standard_submissions_service.rb | 0 .../app}/views/api_interactions.html.haml | 0 .../app}/views/api_token.html.haml | 0 .../app}/views/apitome/docs/_headers.html.erb | 0 .../app}/views/apitome/docs/_params.html.erb | 0 .../app}/views/blocks/blockpagev2.html.haml | 0 .../app}/views/blocks/index.html.haml | 0 .../app}/views/blocks/new.html.haml | 0 .../app}/views/blocks/releases/new.html.haml | 0 .../app}/views/blocks/show.html.haml | 0 .../views/cohorts/_cohort_edit_form.html.haml | 0 .../app}/views/cohorts/_cohort_info.html.haml | 0 .../_completion_progress_donut.html.haml | 0 .../app}/views/cohorts/_user_table.html.haml | 0 .../cohorts/activity_dashboard.html.haml | 0 .../blocks/content_files/_footer.html.haml | 0 .../blocks/content_files/show.html.haml | 0 .../cohorts/cohort_releases/index.html.haml | 0 .../app}/views/cohorts/content.html.haml | 0 .../checkpoint_submissions/show.html.haml | 0 .../app}/views/cohorts/course_stats.html.haml | 0 .../app}/views/cohorts/edit.html.haml | 0 .../app}/views/cohorts/error.html.haml | 0 .../app}/views/cohorts/feed.html.haml | 0 .../app}/views/cohorts/index.html.haml | 0 .../app}/views/cohorts/new.html.haml | 0 .../app}/views/cohorts/partnerup.html.haml | 0 .../app}/views/cohorts/setup.html.haml | 0 .../app}/views/cohorts/show.html.haml | 0 .../checkpoint_submissions/index.html.haml | 0 .../views/cohorts/unit_progress.html.haml | 0 .../app}/views/cohorts/users.html.haml | 0 .../cohorts/users/challenges/show.html.haml | 0 .../cohorts/users/mastery/index.html.haml | 0 .../users/submissions_dashboard.html.haml | 0 .../app}/views/error_404.html.haml | 0 .../app}/views/error_500.html.haml | 0 .../app}/views/home/index.html.haml | 0 .../layouts/_primary_navigation.html.haml | 0 .../layouts/_secondary_navigation.html.haml | 0 .../app}/views/layouts/application.html.haml | 0 .../app}/views/layouts/mailer.html.erb | 0 .../app}/views/layouts/mailer.text.erb | 0 .../app}/views/permalinks/permalink.html.haml | 0 .../views/shared/_content_file_js.html.haml | 0 .../shared/_hopscotch_callbacks_js.html.haml | 0 .../app}/views/shared/_intercom_js.html.haml | 0 .../app}/views/shared/_segment_js.html.haml | 0 .../app}/views/user_mailer/send_file.html | 0 .../user_mailer/user_import_work.html.haml | 0 .../app}/views/users/edit.html.haml | 0 .../app}/views/users/edit_user.html.haml | 0 .../app}/views/users/index.html.haml | 0 .../app}/views/users/new.html.haml | 0 babel.config.js => scripts/babel.config.js | 0 {bin => scripts/bin}/bundle | 0 {bin => scripts/bin}/rails | 0 {bin => scripts/bin}/rake | 0 {bin => scripts/bin}/setup | 0 {bin => scripts/bin}/update | 0 {bin => scripts/bin}/webpack | 0 {bin => scripts/bin}/webpack-dev-server | 0 {bin => scripts/bin}/yarn | 0 config.ru => scripts/config.ru | 0 {config => scripts/config}/application.rb | 0 {config => scripts/config}/boot.rb | 0 {config => scripts/config}/database.yml | 0 {config => scripts/config}/environment.rb | 0 .../config}/environments/development.rb | 0 .../config}/environments/production.rb | 0 .../config}/environments/test.rb | 0 .../config}/get-routes-audit.md | 0 {config => scripts/config}/honeybadger.yml | 0 .../config}/initializers/apitome.rb | 0 .../application_controller_renderer.rb | 0 .../config}/initializers/assets.rb | 0 .../config}/initializers/auth_api.rb | 0 .../config}/initializers/aws.rb | 0 .../initializers/backtrace_silencers.rb | 0 .../initializers/content_security_policy.rb | 0 .../initializers/cookies_serializer.rb | 0 .../initializers/filter_parameter_logging.rb | 0 .../config}/initializers/inflections.rb | 0 .../config}/initializers/mime_types.rb | 0 .../new_framework_defaults_5_1.rb | 0 .../new_framework_defaults_5_2.rb | 0 .../config}/initializers/octokit.rb | 0 .../config}/initializers/pagy.rb | 0 .../config}/initializers/pundit.rb | 0 .../config}/initializers/rack_attack.rb | 0 .../config}/initializers/segment.rb | 0 .../config}/initializers/sidekiq.rb | 0 .../config}/initializers/wrap_parameters.rb | 0 {config => scripts/config}/locales/en.yml | 0 {config => scripts/config}/puma.rb | 0 {config => scripts/config}/routes.rb | 0 {config => scripts/config}/secrets.yml | 0 {config => scripts/config}/sidekiq.yml | 0 {config => scripts/config}/spring.rb | 0 {config => scripts/config}/storage.yml | 0 .../config}/webpack/development.js | 0 .../config}/webpack/environment.js | 0 .../config}/webpack/loaders/typescript.js | 0 .../config}/webpack/production.js | 0 {config => scripts/config}/webpack/test.js | 0 {config => scripts/config}/webpacker.yml | 0 .../db}/drawio_schema_with_explanations.xml | 0 .../migrate/20171003191909_create_users.rb | 0 .../20171005135550_add_admin_to_users.rb | 0 ...05140042_rename_users_profile_image_url.rb | 0 .../migrate/20171006195948_create_blocks.rb | 0 .../migrate/20171009194124_create_releases.rb | 0 ...20171011232501_add_sync_errors_to_block.rb | 0 .../20171012200106_create_standards.rb | 0 .../migrate/20171016192627_create_cohorts.rb | 0 .../20171017205306_create_cohort_users.rb | 0 ...s_from_label_and_pretty_name_on_cohorts.rb | 0 .../20171019165527_create_content_files.rb | 0 .../20171019192005_create_cohort_releases.rb | 0 .../20171023211301_create_challenges.rb | 0 ...024202210_create_checkpoint_submissions.rb | 0 ...3630_create_submitted_challenge_answers.rb | 0 .../20171025211916_create_performances.rb | 0 ...25223250_add_autoscore_to_content_files.rb | 0 ...171026193413_add_title_to_content_files.rb | 0 ...71102144822_cohort_release_unique_index.rb | 0 ...14_add_unique_constraint_to_block_title.rb | 0 ...2171128_add_position_to_cohort_releases.rb | 0 .../20171127223701_create_activities.rb | 0 ...3_create_user_last_viewed_standard_path.rb | 0 ...20171130185636_add_uid_to_content_files.rb | 0 .../20171205211624_add_slack_data_to_user.rb | 0 ...213519_add_standard_uid_to_performances.rb | 0 ..._relative_display_name_to_content_files.rb | 0 .../20180110223429_add_auth_roles_to_user.rb | 0 ...denormalize_submitted_challenge_answers.rb | 0 ...20180110233303_add_roles_to_cohort_user.rb | 0 ...4504_add_last_viewed_cohort_id_to_users.rb | 0 ...0116174519_add_github_user_name_to_user.rb | 0 ...16181937_add_taught_in_learn_to_cohorts.rb | 0 ...80116213927_remove_forge_admin_and_role.rb | 0 ...cohort_id_to_submitted_challenge_answer.rb | 0 ...1642_denormalize_checkpoint_submissions.rb | 0 .../20180125212203_rename_taught_in_learn.rb | 0 .../20180125222044_change_learn_v2_default.rb | 0 .../20180131220605_create_notifications.rb | 0 ...180202225951_add_github_sha_to_releases.rb | 0 ...d_use_latest_release_to_cohort_releases.rb | 0 ...add_docker_directory_path_to_challenges.rb | 0 ...80316222140_rename_cohort_title_to_name.rb | 0 ...0180320162849_add_deleted_at_to_cohorts.rb | 0 ...02212114_remove_pretty_name_from_cohort.rb | 0 ...4504_add_used_by_application_to_cohorts.rb | 0 ...0412223312_populate_used_by_application.rb | 0 ..._add_feature_branch_columns_to_releases.rb | 0 ...d_pending_release_id_to_cohort_releases.rb | 0 ...180813205413_add_readme_text_to_release.rb | 0 .../20180813213358_add_mode_to_cohort.rb | 0 .../20180813214453_populate_mode_cohorts.rb | 0 ...rt_id_to_user_last_viewed_standard_path.rb | 0 .../20180824200753_create_lesson_visits.rb | 0 ...112215710_add_preferred_campus_to_users.rb | 0 .../20181114190340_create_job_results.rb | 0 .../migrate/20181120221622_create_sections.rb | 0 ...02104_add_section_id_to_cohort_releases.rb | 0 ...add_challenge_completion_to_performance.rb | 0 ...1213180900_add_show_tests_to_challenges.rb | 0 ...81214175640_create_content_visibilities.rb | 0 ...add_default_visibility_to_content_files.rb | 0 .../migrate/20190410171829_create_pairings.rb | 0 ...190423171448_add_data_path_to_challenge.rb | 0 ...10_add_last_setup_visit_to_cohort_users.rb | 0 ...23958_add_max_attempts_to_content_files.rb | 0 ...70059_add_allowed_attempts_to_challenge.rb | 0 ...000_remove_max_attempts_from_challenges.rb | 0 ...ints_and_success_criteria_to_challenges.rb | 0 ...1_drop_learn_version_columns_on_cohorts.rb | 0 .../20190829203949_add_settings_to_cohorts.rb | 0 ...dd_points_to_submitted_challenge_answer.rb | 0 ...03_add_points_to_checkpoint_submissions.rb | 0 ...uccess_criteria_to_rubric_on_challenges.rb | 0 ...20191001214559_add_topics_to_challenges.rb | 0 ...1001221324_add_perf_data_to_submissions.rb | 0 ...1008211403_add_release_id_to_challenges.rb | 0 .../20191009214248_add_user_id_to_release.rb | 0 ...visibility_type_to_content_visibilities.rb | 0 .../20191031212058_add_api_token_to_user.rb | 0 .../20191108211234_add_preview_to_releases.rb | 0 .../20191114211938_create_api_interactions.rb | 0 ...19225902_add_sandbox_boolean_to_cohorts.rb | 0 .../20191206230913_add_sync_warnings.rb | 0 ...165026_add_metadata_to_api_interactions.rb | 0 ...add_assign_partial_credig_to_challenges.rb | 0 ..._add_end_time_to_checkpoint_submissions.rb | 0 ...201253_change_cohort_default_percentage.rb | 0 ...13201649_add_time_limit_to_content_file.rb | 0 ..._submitted_at_to_checkpoint_submissions.rb | 0 ..._add_allow_paired_submissions_to_cohort.rb | 0 ...ubmission_ids_to_checkpoint_submissinos.rb | 0 ...0200430165251_add_external_to_challenge.rb | 0 .../20200702155846_add_cache_to_blocks.rb | 0 ...0200707164932_add_archived_at_to_blocks.rb | 0 ...20_add_block_location_details_to_blocks.rb | 0 ...00724195251_add_whitelabeled_to_cohorts.rb | 0 ...4247_remove_block_uniqueness_pg_columns.rb | 0 ...13_add_student_import_status_to_cohorts.rb | 0 ...addd_docker_directory_zip_to_challenges.rb | 0 ...0828165130_default_block_org_tog_school.rb | 0 ...0149_remove_null_constraints_from_users.rb | 0 ...0929004708_remove_null_email_from_users.rb | 0 {db => scripts/db}/schema.rb | 0 {db => scripts/db}/seeds.rb | 0 scripts/delete-blocks | 11 + {doc => scripts/doc}/api.md | 0 .../api/cohorts/creating_a_new_cohort.json | 0 .../cohorts/viewing_curriculum_details.json | 0 .../content/update_content_visibility.json | 0 .../creating_a_user_and_their_enrollment.json | 0 .../unenrolling_a_user_from_a_cohort.json | 0 .../updating_a_user's_enrollment_roles.json | 0 .../viewing_cohort_enrollments.json | 0 {doc => scripts/doc}/api/index.json | 0 .../api/units/update_unit_visibility.json | 0 .../doc}/api/users/regenerate_user_token.json | 0 .../entrypoint-server.sh | 0 .../gems}/block-parser/Dockerfile | 0 {gems => scripts/gems}/block-parser/Gemfile | 0 .../gems}/block-parser/Gemfile.lock | 0 {gems => scripts/gems}/block-parser/README.md | 0 {gems => scripts/gems}/block-parser/Rakefile | 0 .../gems}/block-parser/bin/console | 0 .../gems}/block-parser/bin/parse_block | 0 {gems => scripts/gems}/block-parser/bin/setup | 0 .../gems}/block-parser/block_parser-0.1.0.gem | Bin .../gems}/block-parser/block_parser.gemspec | 0 {gems => scripts/gems}/block-parser/build | 0 .../gems}/block-parser/lib/block_parser.rb | 0 .../lib/block_parser/build_challenge.rb | 0 .../lib/block_parser/build_html.rb | 0 .../block_parser/build_html_from_md_json.rb | 0 .../lib/block_parser/build_image_link.rb | 0 .../lib/block_parser/build_link.rb | 0 .../build_title_from_filename_and_html.rb | 0 .../challenge_validators/challenge.rb | 0 .../challenge_validator.rb | 0 .../checkbox_challenge_validator.rb | 0 .../code_snippet_challenge_validator.rb | 0 .../custom_snippet_challenge_validator.rb | 0 .../local_snippet_challenge_validator.rb | 0 .../multiple_choice_challenge_validator.rb | 0 .../number_challenge_validator.rb | 0 .../paragraph_challenge_validator.rb | 0 .../poll_challenge_validator.rb | 0 .../project_challenge_validator.rb | 0 .../short_answer_challenge_validator.rb | 0 .../testable_project_challenge_validator.rb | 0 .../lib/block_parser/convert_md_to_json.rb | 0 .../lib/block_parser/parse_directory.rb | 0 .../lib/block_parser/parse_markdown_file.rb | 0 .../lib/block_parser/parse_standards.rb | 0 .../block-parser/lib/block_parser/version.rb | 0 .../block-parser/pkg/block_parser-0.1.0.gem | Bin .../block-parser/spec/build_challenge_spec.rb | 0 .../spec/build_html_from_md_json_spec.rb | 0 .../block-parser/spec/build_html_spec.rb | 0 .../spec/build_image_link_spec.rb | 0 .../block-parser/spec/build_link_spec.rb | 0 ...build_title_from_filename_and_html_spec.rb | 0 .../challenge_validators/challenge_spec.rb | 0 .../challenge_validator_spec.rb | 0 .../checkbox_challenge_validator_spec.rb | 0 .../code_snippet_challenge_validator_spec.rb | 0 ...custom_snippet_challenge_validator_spec.rb | 0 ...ultiple_choice_challenge_validator_spec.rb | 0 .../number_challenge_validator_spec.rb | 0 .../project_challenge_validator_spec.rb | 0 .../short_answer_challenge_validator_spec.rb | 0 ...stable_project_challenge_validator_spec.rb | 0 .../spec/convert_md_to_json_spec.rb | 0 .../bad-challenges.md | 0 .../config.yaml | 0 .../config.yaml | 0 .../no-challenges.md | 0 .../config.yaml | 0 .../no-lessons-nor-checkpoints/config.yaml | 0 .../no-lessons-nor-checkpoints/resources.md | 0 .../config.yaml | 0 .../spec/fixtures/sample-iframe.md | 0 .../fixtures/test-block-repo-yml/config.yml | 0 .../fixtures/test-block-repo-yml/dummy.pdf | Bin .../test-block-repo-yml/folder/sibling.md | 0 .../test-block-repo-yml/folder/target.md | 0 .../images/galvanize-logo.png | Bin .../images/register_klass.gif | Bin .../fixtures/test-block-repo-yml/lessoN.md | 0 .../test-block-repo-yml/markdown-smoketest.md | 0 .../fixtures/test-block-repo-yml/target.md | 0 .../spec/fixtures/test-block-repo/README.md | 0 .../spec/fixtures/test-block-repo/config.yaml | 0 .../test-block-repo/folder/sibling.md | 0 .../fixtures/test-block-repo/folder/target.md | 0 .../test-block-repo/images/galvanize-logo.png | Bin .../test-block-repo/images/register_klass.gif | Bin .../fixtures/test-block-repo/ipynb-test.ipynb | 0 .../fixtures/test-block-repo/ipynb-test.md | 0 .../spec/fixtures/test-block-repo/lessoN.md | 0 .../test-block-repo/markdown-smoketest.md | 0 .../spec/fixtures/test-block-repo/target.md | 0 .../test-block-two-configs/config.yaml | 0 .../test-block-two-configs/config.yml | 0 .../Dockerfile | 0 .../test.sh | 0 .../config.yaml | 0 .../block-parser/spec/parse_directory_spec.rb | 0 .../spec/parse_markdown_file_spec.rb | 0 .../block-parser/spec/parse_standards_spec.rb | 0 .../gems}/block-parser/spec/spec_helper.rb | 0 .../block-parser/spec/support/freeloader.rb | 0 .../block-parser/spec/support/mocktokit.rb | 0 .../gems}/block-parser/spec/tmp/.keep | 0 .../gems}/block-parser/validate-block | 0 .../lib}/tasks/challenge_release_ids.rake | 0 .../lib}/tasks/checkpoint_resubmission.rake | 0 .../tasks/checkpoint_submission_points.rake | 0 .../lib}/tasks/content_visibility_update.rake | 0 .../lib}/tasks/course_progress.rake | 0 .../lib}/tasks/course_yaml_backfill.rake | 0 .../lib}/tasks/grade_checkpoints.rake | 0 .../lib}/tasks/object_space_count.rake | 0 {lib => scripts/lib}/tasks/prep_notifier.rake | 0 .../lib}/tasks/set_block_caches.rake | 0 {lib => scripts/lib}/tasks/spec.rake | 0 {lib => scripts/lib}/tasks/ts_routes.rake | 0 lintspec.sh => scripts/lintspec.sh | 0 package.json => scripts/package.json | 0 .../postcss.config.js | 0 {public => scripts/public}/404.html | 0 {public => scripts/public}/422.html | 0 {public => scripts/public}/500.html | 0 .../public}/apple-touch-icon-152x152.png | 0 .../apple-touch-icon-precomposed-152x152.png | 0 .../public}/apple-touch-icon-precomposed.png | 0 .../public}/apple-touch-icon.png | 0 .../assets/images/hopscotch-sprite-green.png | Bin .../assets/images/hopscotch-sprite-orange.png | Bin .../public}/assets/images/jupyter-logo.png | Bin .../images/svg/checkpoint-is-rejected.svg | 0 .../images/svg/checkpoint-is-scored.svg | 0 .../images/svg/checkpoint-is-submitted.svg | 0 .../public}/assets/images/svg/github-icon.svg | 0 .../assets/images/svg/gitlab-icon-rgb.svg | 0 .../assets/images/svg/octicon-git-branch.svg | 0 .../images/svg/redpriority_high-24px.svg | 0 .../assets/images/svg/svg-link_off-24px.svg | 0 .../images/svg/svg-sprite-action-symbol.svg | 0 .../images/svg/svg-sprite-alert-symbol.svg | 0 .../images/svg/svg-sprite-av-symbol.svg | 0 .../images/svg/svg-sprite-content-symbol.svg | 0 .../images/svg/svg-sprite-custom-symbol.svg | 0 .../svg/svg-sprite-custom_material-symbol.svg | 0 .../images/svg/svg-sprite-device-symbol.svg | 0 .../images/svg/svg-sprite-file-symbol.svg | 0 .../images/svg/svg-sprite-hardware-symbol.svg | 0 .../svg/svg-sprite-navigation-symbol.svg | 0 .../svg/svg-sprite-notification-symbol.svg | 0 .../images/svg/svg-sprite-social-symbol.svg | 0 {public => scripts/public}/favicon.ico | 0 .../javascripts/apitome/application.js | 0 {public => scripts/public}/robots.txt | 0 {public => scripts/public}/sandbox/chai.js | 0 .../public}/sandbox/challenge-worker.js | 0 .../public}/sandbox/jasmine/boot.js | 0 .../public}/sandbox/jasmine/jasmine.js | 0 .../public}/sandbox/mocha/boot.js | 0 .../public}/sandbox/mocha/mocha.js | 0 .../public}/sandbox/mocha/test.html | 0 .../public}/sandbox/sandbox.html | 0 .../public}/sandbox/stacktrace.js | 0 {public => scripts/public}/sandbox/worker.js | 0 .../stylesheets/apitome/application.css | 0 requirements.txt => scripts/requirements.txt | 0 scripts/{ => scripts}/sh/cohort_curriculum.sh | 0 .../sh/content_file_mark_hidden.sh | 0 .../sh/content_file_mark_visible.sh | 0 scripts/{ => scripts}/sh/unit_mark_hidden.sh | 0 scripts/{ => scripts}/sh/unit_mark_visible.sh | 0 .../{ => scripts}/sql/checkpoint_answers.sql | 0 .../sql/cohort_challenges_with_answers.sql | 0 .../sql/cohort_prune_for_testing.sql | 0 scripts/{ => scripts}/sql/percent_metrics.sql | 0 .../{ => scripts}/sql/scrub_cohort_data.sql | 0 scripts/serviceentry.yaml | 15 + ...activity_feed_item_component_props_spec.rb | 154 ++ .../notifications_component_props_spec.rb | 40 + .../standard_card_component_props_spec.rb | 29 + .../api/v1/blocks/releases_controller_spec.rb | 45 + .../api/v1/blocks_controller_spec.rb | 122 ++ .../blocks/content_files_controller_spec.rb | 338 +++ .../cohorts/blocks/units_controller_spec.rb | 59 + .../api/v1/cohorts/cohorts_controller_spec.rb | 165 ++ .../api/v1/cohorts/users_controller_spec.rb | 148 ++ .../api/v1/content_files_controller_spec.rb | 83 + .../api/v1/users_controller_spec.rb | 162 ++ .../application_controller_spec.rb | 26 + .../blocks/releases_controller_spec.rb | 56 + .../controllers/blocks_controller_spec.rb | 54 + .../blocks/content_files_controller_spec.rb | 234 ++ .../activities_controller_spec.rb | 113 + .../cohort_releases_controller_spec.rb | 162 ++ .../checkpoint_submissions_controller_spec.rb | 492 +++++ ...itted_challenge_answers_controller_spec.rb | 857 ++++++++ .../cohorts/content_files_controller_spec.rb | 407 ++++ .../cohorts/pairings_controller_spec.rb | 61 + .../cohorts/standards_controller_spec.rb | 116 + .../activities_controller_spec.rb | 99 + .../users/challenges_controller_spec.rb | 207 ++ .../users/performances_controller_spec.rb | 69 + .../cohorts/users_controller_spec.rb | 137 ++ .../controllers/cohorts_controller_spec.rb | 618 ++++++ .../spec/controllers/home_controller_spec.rb | 116 + .../notifications_controller_spec.rb | 67 + .../controllers/permalinks_controller_spec.rb | 179 ++ .../controllers/sessions_controller_spec.rb | 70 + .../spec/controllers/users_controller_spec.rb | 187 ++ ...itted_challenge_answers_controller_spec.rb | 151 ++ .../exporters/performance_exporter_spec.rb | 44 + scripts/spec/factories.rb | 246 +++ .../blocks/management_feature_spec.rb | 76 + .../cohorts/activity_dashboard_spec.rb | 50 + .../cohorts/checkpoint_submissions_spec.rb | 181 ++ .../cohorts/curriculum_feature_spec.rb | 360 ++++ .../features/cohorts/feed_feature_spec.rb | 25 + .../cohorts/management_feature_spec.rb | 742 +++++++ .../spec/features/cohorts/sandboxes_spec.rb | 37 + .../features/cohorts/student_progress_spec.rb | 320 +++ .../spec/features/cohorts/submissions_spec.rb | 491 +++++ .../cohorts/users/challenges_feature_spec.rb | 361 ++++ .../submissions_dashboard_feature_spec.rb | 52 + .../checkpoint_assessments_spec.rb | 632 ++++++ .../checkpoint_submissions_spec.rb | 377 ++++ .../content_files/navigation_feature_spec.rb | 127 ++ .../features/content_files/permalinks_spec.rb | 62 + .../content_files/view_feature_spec.rb | 1886 +++++++++++++++++ .../spec/features/home/header_feature_spec.rb | 181 ++ scripts/spec/features_helper.rb | 96 + scripts/spec/finders/block_finder_spec.rb | 30 + .../checkpoint_submission_finder_spec.rb | 183 ++ .../spec/finders/cohort_user_finder_spec.rb | 33 + .../spec/finders/content_file_finder_spec.rb | 99 + .../spec/finders/performance_finder_spec.rb | 167 ++ scripts/spec/finders/release_finder_spec.rb | 74 + scripts/spec/finders/standard_finder_spec.rb | 41 + .../submitted_challenge_answer_finder_spec.rb | 72 + scripts/spec/finders/user_finder_spec.rb | 15 + .../fixtures/preview-content-file/preview.md | 105 + ..._docker_directories_test-docker-folder.zip | Bin 0 -> 212 bytes .../spec/fixtures/test-block-repo/README.md | 3 + .../test-block-repo/challenges-smoketest.md | 567 +++++ .../spec/fixtures/test-block-repo/config.yaml | 18 + .../test-docker-folder/Dockerfile | 0 .../test-docker-folder/test.sh | 0 .../test-block-repo/folder/sibling.md | 3 + .../fixtures/test-block-repo/folder/target.md | 1 + .../test-block-repo/images/galvanize-logo.png | Bin 0 -> 4134 bytes .../test-block-repo/images/register_klass.gif | Bin 0 -> 20804 bytes .../test-block-repo/markdown-smoketesT.md | 107 + .../spec/fixtures/test-block-repo/target.md | 1 + .../spec/fixtures/test-block-repo/test.sql | 0 .../vcr_cassettes/github-branch-success.yml | 206 ++ .../github-course-yaml-not-found.yml | 70 + .../github-course-yaml-success-2.yml | 285 +++ .../github-course-yaml-success.yml | 295 +++ .../vcr_cassettes/github-not-found.yml | 69 + .../vcr_cassettes/github-repo-success.yml | 199 ++ .../gitlab-course-yaml-branch-failure.yml | 118 ++ .../gitlab-course-yaml-success.yml | 281 +++ .../vcr_cassettes/gitlab-not-found.yml | 111 + .../fixtures/vcr_cassettes/gitlab-success.yml | 178 ++ .../resync-course-job-success.yml | 587 +++++ scripts/spec/helpers/json_helper_spec.rb | 66 + .../mastery_mode_percent_helper_spec.rb | 46 + .../standard_navigation_helper_spec.rb | 288 +++ scripts/spec/integration_helper.rb | 16 + .../jobs/branch_release_notifier_job_spec.rb | 33 + .../checkpoint_paired_submission_job_spec.rb | 53 + ...ontent_file_default_visibility_job_spec.rb | 41 + .../spec/jobs/content_file_visit_job_spec.rb | 66 + .../jobs/create_api_interaction_job_spec.rb | 39 + scripts/spec/jobs/create_release_job_spec.rb | 231 ++ .../jobs/evaluate_code_snippet_job_spec.rb | 19 + .../jobs/evaluate_custom_snippet_job_spec.rb | 19 + .../spec/jobs/evaluate_project_job_spec.rb | 20 + .../jobs/grade_timed_checkpoint_job_spec.rb | 99 + .../spec/jobs/import_student_work_job_spec.rb | 78 + .../spec/jobs/release_notifier_job_spec.rb | 33 + scripts/spec/jobs/resync_course_job_spec.rb | 95 + .../spec/jobs/set_block_caches_job_spec.rb | 29 + scripts/spec/jobs/slack_ds_prep_job_spec.rb | 19 + scripts/spec/jobs/slack_se_prep_job_spec.rb | 19 + scripts/spec/jobs/slack_student_job_spec.rb | 21 + .../spec/jobs/switch_to_branch_job_spec.rb | 185 ++ scripts/spec/models/activity_spec.rb | 11 + scripts/spec/models/api_interaction_spec.rb | 16 + scripts/spec/models/block_spec.rb | 80 + scripts/spec/models/challenge_spec.rb | 184 ++ .../spec/models/checkpoint_submission_spec.rb | 276 +++ scripts/spec/models/cohort_release_spec.rb | 40 + scripts/spec/models/cohort_spec.rb | 278 +++ scripts/spec/models/cohort_user_spec.rb | 35 + scripts/spec/models/content_file_spec.rb | 90 + .../spec/models/content_visibility_spec.rb | 55 + scripts/spec/models/lesson_visit_spec.rb | 89 + scripts/spec/models/notification_spec.rb | 21 + scripts/spec/models/pairing_spec.rb | 10 + scripts/spec/models/performance_spec.rb | 56 + scripts/spec/models/release_spec.rb | 149 ++ scripts/spec/models/resync_job_result_spec.rb | 60 + scripts/spec/models/section_spec.rb | 7 + scripts/spec/models/standard_spec.rb | 52 + .../models/submitted_challenge_answer_spec.rb | 189 ++ .../user_last_viewed_standard_path_spec.rb | 21 + scripts/spec/models/user_spec.rb | 336 +++ scripts/spec/policies/cohort_policy_spec.rb | 41 + .../spec/policies/content_file_policy_spec.rb | 65 + scripts/spec/policies/user_policy_spec.rb | 42 + .../for_cohort_releases_new_spec.rb | 27 + .../for_cohort_releases_spec.rb | 77 + .../block_presenter/for_releases_spec.rb | 18 + ...mitted_challenge_answers_presenter_spec.rb | 169 ++ .../for_index_spec.rb | 161 ++ .../checkpoint_submission_presenter_spec.rb | 66 + .../for_curriculum_last_viewed_spec.rb | 35 + .../content_file_presenter/for_show_spec.rb | 232 ++ .../for_sidebar_spec.rb | 15 + .../for_checkpoint_submission_spec.rb | 29 + .../for_standard_card_spec.rb | 151 ++ .../student_progress_presenter_spec.rb | 186 ++ .../submissions_dashboard_presenter_spec.rb | 68 + ...bmitted_challenge_answer_presenter_spec.rb | 130 ++ .../activity_aggregator_service_spec.rb | 259 +++ .../spec/services/assessment_service_spec.rb | 335 +++ .../auto_assign_release_service_spec.rb | 27 + .../checkpoint_submission_service_spec.rb | 135 ++ .../cohort_standard_progress_service_spec.rb | 216 ++ .../spec/services/course_validator_spec.rb | 221 ++ .../curriculum_progress_service_spec.rb | 268 +++ .../download_repository_service_spec.rb | 78 + scripts/spec/services/git_url_service_spec.rb | 208 ++ .../services/mastery_average_service_spec.rb | 23 + .../services/notification_service_spec.rb | 122 ++ ...platform_one_auth_resolver_service_spec.rb | 88 + .../preview_content_file_service_spec.rb | 24 + .../release_destroyer_service_spec.rb | 45 + .../services/resync_course_service_spec.rb | 200 ++ .../s3_asset_uploader_service_spec.rb | 18 + .../standard_submissions_service_spec.rb | 483 +++++ scripts/spec/spec_helper.rb | 76 + scripts/spec/support/json_spec_matchers.rb | 76 + scripts/spec/support/mocktokit.rb | 31 + .../spec/support/object_creation_methods.rb | 21 + tsconfig.json => scripts/tsconfig.json | 0 .../javascripts/bootstrap-datepicker.js | 0 .../vendor}/assets/javascripts/hopscotch.js | 0 .../assets/javascripts/react-tooltip.js | 0 .../stylesheets/bootstrap-datepicker.css | 0 .../vendor}/assets/stylesheets/hopscotch.css | 0 yarn.lock => scripts/yarn.lock | 0 1071 files changed, 24398 insertions(+), 174 deletions(-) create mode 100644 .Dockerfile.swp delete mode 100644 .circleci/config.yml delete mode 100644 Dockerfile.base create mode 100644 Dockerfile.old create mode 100644 Jenkinsfile create mode 100644 download.json rename .browserslistrc => scripts/.browserslistrc (100%) rename .env.example => scripts/.env.example (100%) rename .eslintrc.json => scripts/.eslintrc.json (100%) rename .haml-lint.yml => scripts/.haml-lint.yml (100%) rename .nvmrc => scripts/.nvmrc (100%) rename .overcommit.yml => scripts/.overcommit.yml (100%) rename .postcssrc.yml => scripts/.postcssrc.yml (100%) rename .rubocop.yml => scripts/.rubocop.yml (100%) rename .ruby-version => scripts/.ruby-version (100%) rename .stylelintrc => scripts/.stylelintrc (100%) rename Gemfile => scripts/Gemfile (100%) rename Gemfile.lock => scripts/Gemfile.lock (100%) rename Procfile => scripts/Procfile (100%) rename Procfile.local => scripts/Procfile.local (100%) rename README.md => scripts/README.md (100%) rename Rakefile => scripts/Rakefile (100%) rename app.json => scripts/app.json (100%) rename {app => scripts/app}/assets/config/manifest.js (100%) rename {app => scripts/app}/assets/images/favicon.ico (100%) rename {app => scripts/app}/assets/images/loader-black.svg (100%) rename {app => scripts/app}/assets/images/loader-cyan.svg (100%) rename {app => scripts/app}/assets/images/loader-white.svg (100%) rename {app => scripts/app}/assets/images/lost.jpg (100%) rename {app => scripts/app}/assets/images/spinner.gif (100%) rename {app => scripts/app}/assets/images/svg/baseline-notes-24px.svg (100%) rename {app => scripts/app}/assets/images/svg/g-learn-lockup.svg (100%) rename {app => scripts/app}/assets/images/svg/galvanize-logo.svg (100%) rename {app => scripts/app}/assets/images/svg/github-icon.svg (100%) rename {app => scripts/app}/assets/images/svg/mobile-logo.svg (100%) rename {app => scripts/app}/assets/images/svg/octicon-git-branch.svg (100%) rename {app => scripts/app}/assets/images/svg/svg-sprite-action-symbol.svg (100%) rename {app => scripts/app}/assets/images/svg/svg-sprite-av-symbol.svg (100%) rename {app => scripts/app}/assets/images/svg/svg-sprite-device-symbol.svg (100%) rename {app => scripts/app}/assets/images/svg/svg-sprite-image-symbol.svg (100%) rename {app => scripts/app}/assets/images/svg/svg-sprite-navigation-symbol.svg (100%) rename {app => scripts/app}/assets/javascripts/application.js (100%) rename {app => scripts/app}/assets/javascripts/mobile.js (100%) rename {app => scripts/app}/assets/stylesheets/application.scss (100%) rename {app => scripts/app}/assets/stylesheets/base.scss (100%) rename {app => scripts/app}/assets/stylesheets/bootstrap-custom.scss (100%) rename {app => scripts/app}/assets/stylesheets/cohorts.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_404-container.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_action-menu.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_activity-dashboard.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_activity-feed.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_api-interactions.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_api_token.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_auth-style-search.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_blocks-index.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_blocks-stats.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_button.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_callouts.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_challenge-block.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_challenge_status.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_checkbox-input.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_checkpoint-landing.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_checkpoint-submission.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_checkpoint-submissions-columns-student.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_checkpoint-submissions-columns.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_checkpoint-submissions-index.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_checkpoint_student_scores.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_checkpoint_submisstion_state.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_checkpoint_toolbar.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_code-block.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_cohort-submissions-table.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_cohort_releases.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_cohorts.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_content-file-show.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_content-file-sidebar.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_curriculum-checkpoint-summary.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_curriculum-progress.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_curriculum.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_dropdown-component.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_external-link.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_flash-message.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_footer.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_grade-buttons.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_integer_picker.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_lp-style-button.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_mastery-table.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_modal.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_navigation-dropdown.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_notifications.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_pagination.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_pill.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_point_grade_buttons.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_primary-header.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_primary-navigation.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_progress.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_progress_thresholds_key.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_progress_thresholds_modal.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_progress_thresholds_slider.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_search-bar.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_secondary-navigation.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_slideshow.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_sort-dropdown-component.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_standard-card.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_standard-cards.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_standards-mastery-beans.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_status_picker.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_student-mastery-table.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_student-name-bar.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_submissions-dashboard-table.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_svg-icon.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_thresholds.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_universal-list.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_universal-row.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_universal-table.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_user-avatar.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/_video-player.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/badge.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/challenge-detail-comments.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/challenge-detail-view.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/challenge-timeline.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/new-comment-form.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/partnerup.scss (100%) rename {app => scripts/app}/assets/stylesheets/components/progress-table.scss (100%) rename {app => scripts/app}/assets/stylesheets/hopscotch-overrides.scss (100%) rename {app => scripts/app}/assets/stylesheets/mixins.scss (100%) rename {app => scripts/app}/assets/stylesheets/mobile.scss (100%) rename {app => scripts/app}/assets/stylesheets/mobile/_submissions-dashboard-table.scss (100%) rename {app => scripts/app}/assets/stylesheets/typography.scss (100%) rename {app => scripts/app}/assets/stylesheets/variables.scss (100%) rename {app => scripts/app}/component_props/activity_feed_item_component_props.rb (100%) rename {app => scripts/app}/component_props/notifications_component_props.rb (100%) rename {app => scripts/app}/component_props/standard_card_component_props.rb (100%) rename {app => scripts/app}/controllers/api/application_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/blocks/releases_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/blocks_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/cohorts/blocks/content_files_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/cohorts/blocks/units_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/cohorts/cohorts_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/cohorts/users_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/content_files_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/pineapple_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/releases_controller.rb (100%) rename {app => scripts/app}/controllers/api/v1/users_controller.rb (100%) rename {app => scripts/app}/controllers/application_controller.rb (100%) rename {app => scripts/app}/controllers/blocks/releases_controller.rb (100%) rename {app => scripts/app}/controllers/blocks_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/blocks/content_files_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/checkpoint_submissions/activities_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/cohort_releases_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/content_files/checkpoint_submissions_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/content_files_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/pairings_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/standards_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/submitted_challenge_answers/activities_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/users/challenges_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/users/performances_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts/users_controller.rb (100%) rename {app => scripts/app}/controllers/cohorts_controller.rb (100%) rename {app => scripts/app}/controllers/concerns/api/content_visibility_crud.rb (100%) rename {app => scripts/app}/controllers/concerns/api/exception_handler.rb (100%) rename {app => scripts/app}/controllers/concerns/api/response.rb (100%) rename {app => scripts/app}/controllers/home_controller.rb (100%) rename {app => scripts/app}/controllers/notifications_controller.rb (100%) rename {app => scripts/app}/controllers/permalinks_controller.rb (100%) rename {app => scripts/app}/controllers/sessions_controller.rb (100%) rename {app => scripts/app}/controllers/users_controller.rb (100%) rename {app => scripts/app}/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb (100%) rename {app => scripts/app}/exporters/performance_exporter.rb (100%) rename {app => scripts/app}/exporters/progress_exporter.rb (100%) rename {app => scripts/app}/finders/block_finder.rb (100%) rename {app => scripts/app}/finders/checkpoint_submission_finder.rb (100%) rename {app => scripts/app}/finders/cohort_user_finder.rb (100%) rename {app => scripts/app}/finders/content_file_finder.rb (100%) rename {app => scripts/app}/finders/performance_finder.rb (100%) rename {app => scripts/app}/finders/release_finder.rb (100%) rename {app => scripts/app}/finders/standard_finder.rb (100%) rename {app => scripts/app}/finders/submitted_challenge_answer_finder.rb (100%) rename {app => scripts/app}/finders/user_finder.rb (100%) rename {app => scripts/app}/helpers/application_helper.rb (100%) rename {app => scripts/app}/helpers/json_helper.rb (100%) rename {app => scripts/app}/helpers/secondary_navigation_helper.rb (100%) rename {app => scripts/app}/helpers/standard_navigation_helper.rb (100%) rename {app => scripts/app}/javascript/api.d.ts (100%) rename {app => scripts/app}/javascript/components/AceEditor.tsx (100%) rename {app => scripts/app}/javascript/components/Badge.tsx (100%) rename {app => scripts/app}/javascript/components/Button.tsx (100%) rename {app => scripts/app}/javascript/components/ButtonTo.tsx (100%) rename {app => scripts/app}/javascript/components/Dropdown.tsx (100%) rename {app => scripts/app}/javascript/components/Icon.tsx (100%) rename {app => scripts/app}/javascript/components/Loading.tsx (100%) rename {app => scripts/app}/javascript/components/Marked.tsx (100%) rename {app => scripts/app}/javascript/components/Menu.tsx (100%) rename {app => scripts/app}/javascript/components/Notifications.tsx (100%) rename {app => scripts/app}/javascript/components/ProcessingIcon.tsx (100%) rename {app => scripts/app}/javascript/components/RowKebab.tsx (100%) rename {app => scripts/app}/javascript/components/ScoreCircle.tsx (100%) rename {app => scripts/app}/javascript/components/SidebarProgress.tsx (100%) rename {app => scripts/app}/javascript/components/SortDropdown.tsx (100%) rename {app => scripts/app}/javascript/components/StandardBean.tsx (100%) rename {app => scripts/app}/javascript/components/SvgRenderer.tsx (100%) rename {app => scripts/app}/javascript/components/Timestamp.tsx (100%) rename {app => scripts/app}/javascript/components/UserAvatar.tsx (100%) rename {app => scripts/app}/javascript/components/activities/NewActivityForm.tsx (100%) rename {app => scripts/app}/javascript/components/api/ApiInteractions.tsx (100%) rename {app => scripts/app}/javascript/components/api/ApiToken.tsx (100%) rename {app => scripts/app}/javascript/components/blocks/BlockPage-v2.tsx (100%) rename {app => scripts/app}/javascript/components/blocks/BlocksIndex-v2.tsx (100%) rename {app => scripts/app}/javascript/components/blocks/BlocksNewModal.tsx (100%) rename {app => scripts/app}/javascript/components/blocks/BlocksNewRelease.tsx (100%) rename {app => scripts/app}/javascript/components/blocks/BlocksRow.tsx (100%) rename {app => scripts/app}/javascript/components/blocks/BlocksShow.tsx (100%) rename {app => scripts/app}/javascript/components/blocks/BlocksStats.tsx (100%) rename {app => scripts/app}/javascript/components/blocks/CohortsTooltip.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/AvatarBar.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/ChallengeDetailView.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/ChallengeShow.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeActionBlock.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeBlock.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeDetailActivities.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeExplanationBlock.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeFeedbackBlock.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeHintsBlock.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeInputs.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeLocalTestResults.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeRubricBlock.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeStatus.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeStatusBar.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeTestResults.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeTests.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/ChallengeTimeLine.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/GradeIndicator.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/GradedTimestamp.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/challenge_block/StatusPicker.tsx (100%) rename {app => scripts/app}/javascript/components/challenges/local/run-local-challenge.ts (100%) rename {app => scripts/app}/javascript/components/challenges/local/sandbox.ts (100%) rename {app => scripts/app}/javascript/components/challenges/local/stack-traces.ts (100%) rename {app => scripts/app}/javascript/components/checkpoints/CheckpointAfterSubmitButton.tsx (100%) rename {app => scripts/app}/javascript/components/checkpoints/CheckpointAfterSubmitModal.tsx (100%) rename {app => scripts/app}/javascript/components/checkpoints/CheckpointAfterSubmitModalError.tsx (100%) rename {app => scripts/app}/javascript/components/checkpoints/CheckpointSubmissionChallenges.tsx (100%) rename {app => scripts/app}/javascript/components/checkpoints/CheckpointSubmissionShow.tsx (100%) rename {app => scripts/app}/javascript/components/checkpoints/CheckpointSubmissionState.tsx (100%) rename {app => scripts/app}/javascript/components/checkpoints/CheckpointSubmissionStudentNameBar.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/CohortsIndex.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/UsersNew.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/activity_dashboard/ActivityDashboard.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/activity_feed/ActivityFeed.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_releases/ReleaseVersionsTable.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissionsChallengeItem.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissionsLessonRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissionsPerformanceModal.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStandardRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentColumn.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentNameBar.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentStandard.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissionsTable.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/cohort_submissions/CohortSubmissionsUnitPercent.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/MasteryTable.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/MetricRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/MetricsBody.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/PerformanceCell.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/PerformanceRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/ReleaseBody.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/StandardRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/StudentHeader.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/StudentMasteryTable.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/mastery/StudentReleaseRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/pairing/GroupPairs.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/pairing/InnerStudentList.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/pairing/NewPairing.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/pairing/PairingBoard.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/pairing/StudentItem.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/pairing/StudentList.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/progress/MasteryProgressionBar.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/progress/ProgressThresholdCellHeaders.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/progress/StudentProgress.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/progress/StudentProgressBar.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/progress/StudentProgressCells.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/progress/StudentProgressRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/progress/StudentProgressSortArrow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/BranchReleaseModal.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/CohortContentTab.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/CohortInfo.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/CohortSettingsResync.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/CohortSettingsTabs.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/CohortVisibilitySection.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/CurriculumSettingsTab.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/ReleaseVersionModal.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/ReleaseVersionRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/UserCohortKebab.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/UserImport.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/settings/UserKebab.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/submissions_dashboard/AnswerStatusRollup.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/submissions_dashboard/HeaderStandardContainer.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/submissions_dashboard/StandardContainer.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardChallengeItem.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardContentFileRow.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentColumn.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentNameBar.tsx (100%) rename {app => scripts/app}/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardTable.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/ActionMenus.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/Lesson.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/MarkdownRenderer.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/PDFRenderer.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/SideBar.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/SubmissionRenderer.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/checkpoints/Checkpoint.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/checkpoints/CheckpointDetails.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/checkpoints/CheckpointLanding.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/checkpoints/CheckpointLandingAttribute.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/checkpoints/CheckpointPairs.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/checkpoints/PairAvatars.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentRow.tsx (100%) rename {app => scripts/app}/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentScores.tsx (100%) rename {app => scripts/app}/javascript/components/curriculum/CheckpointSummary.tsx (100%) rename {app => scripts/app}/javascript/components/curriculum/CohortCurriculum.tsx (100%) rename {app => scripts/app}/javascript/components/curriculum/CurriculumStandardCard.tsx (100%) rename {app => scripts/app}/javascript/components/curriculum/ProgressIndicators.tsx (100%) rename {app => scripts/app}/javascript/components/curriculum/StandardBeans.tsx (100%) rename {app => scripts/app}/javascript/components/curriculum/StandardsRenderer.tsx (100%) rename {app => scripts/app}/javascript/components/curriculum/StudentOverallProgressDoughnut.tsx (100%) rename {app => scripts/app}/javascript/components/lib/ace.ts (100%) rename {app => scripts/app}/javascript/components/notifications/NotificationsIcon.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ActionMenu/ActionMenu.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Button/Button.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ChallengePoints/ChallengePoints.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ChallengePoints/PartialCreditBaton.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ChallengePoints/SpinText.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ChallengePoints/components/IntegerPicker/IntegerPicker.tsx (100%) rename {app => scripts/app}/javascript/components/shared/DonutRing/DonutRing.tsx (100%) rename {app => scripts/app}/javascript/components/shared/DonutRing/components/ColorDonut/ColorDonut.tsx (100%) rename {app => scripts/app}/javascript/components/shared/DonutRing/components/CompleteDonut/CompleteDonut.tsx (100%) rename {app => scripts/app}/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut.tsx (100%) rename {app => scripts/app}/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut/UngradedDonut.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ErrorMessagesTable/ErrorMessagesTable.tsx (100%) rename {app => scripts/app}/javascript/components/shared/FlashAlert/FlashAlert.tsx (100%) rename {app => scripts/app}/javascript/components/shared/FormatNumber/FormatNumber.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Icons/AlertSign.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Icons/Clear.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Icons/CloseButton.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Icons/Done.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Icons/KeyboardArrowDown.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Icons/KeyboardArrowUp.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Icons/SettingsCog.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Icons/StatusCircle.tsx (100%) rename {app => scripts/app}/javascript/components/shared/MasteryProgressBar/MasteryProgressBar.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Modal/Modal.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Pagination/Pagination.tsx (100%) rename {app => scripts/app}/javascript/components/shared/PercentageProgressBar/PercentageProgressBar.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Pill/Pill.tsx (100%) rename {app => scripts/app}/javascript/components/shared/PointGradeButtons/PointGradeButtons.tsx (100%) rename {app => scripts/app}/javascript/components/shared/PointGradeButtons/SpinText.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ProgressBar/ProgressBar.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ProgressThresholdsKey/ProgressThresholdsKey.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ProgressThresholdsModal/ProgressThresholdsModal.tsx (100%) rename {app => scripts/app}/javascript/components/shared/ProgressThresholdsSlider/ProgressThresholdsSlider.tsx (100%) rename {app => scripts/app}/javascript/components/shared/SearchBar/SearchBar.tsx (100%) rename {app => scripts/app}/javascript/components/shared/Slideshow/Slideshow.tsx (100%) rename {app => scripts/app}/javascript/components/shared/UngradedDonut/UngradedDonut.tsx (100%) rename {app => scripts/app}/javascript/components/shared/UniversalList/UniversalList.tsx (100%) rename {app => scripts/app}/javascript/components/shared/UniversalRow/UniversalRow.tsx (100%) rename {app => scripts/app}/javascript/components/shared/UniversalTable/UniversalTable.tsx (100%) rename {app => scripts/app}/javascript/components/standards/CompletionScoringBlock.tsx (100%) rename {app => scripts/app}/javascript/components/standards/MasteryScoringBlock.tsx (100%) rename {app => scripts/app}/javascript/components/standards/StandardCard.tsx (100%) rename {app => scripts/app}/javascript/components/standards/StandardScoreButton.tsx (100%) rename {app => scripts/app}/javascript/components/standards/StandardScoringBlock.tsx (100%) rename {app => scripts/app}/javascript/components/standards/StandardTopicsRollups.tsx (100%) rename {app => scripts/app}/javascript/components/users/UsersIndex.tsx (100%) rename {app => scripts/app}/javascript/components/vendor/ReactTooltip.js (100%) rename {app => scripts/app}/javascript/generated/routes.ts (100%) rename {app => scripts/app}/javascript/globals.ts (100%) rename {app => scripts/app}/javascript/lib/http.ts (100%) rename {app => scripts/app}/javascript/lib/utils.ts (100%) rename {app => scripts/app}/javascript/packs/application.js (100%) rename {app => scripts/app}/javascript/packs/server_rendering.js (100%) rename {app => scripts/app}/jobs/application_job.rb (100%) rename {app => scripts/app}/jobs/branch_release_notifier_job.rb (100%) rename {app => scripts/app}/jobs/checkpoint_paired_submission_job.rb (100%) rename {app => scripts/app}/jobs/content_file_default_visibility_job.rb (100%) rename {app => scripts/app}/jobs/content_file_visit_job.rb (100%) rename {app => scripts/app}/jobs/create_api_interaction_job.rb (100%) rename {app => scripts/app}/jobs/create_release_job.rb (100%) rename {app => scripts/app}/jobs/evaluate_code_snippet_job.rb (100%) rename {app => scripts/app}/jobs/evaluate_custom_snippet_job.rb (100%) rename {app => scripts/app}/jobs/evaluate_project_job.rb (100%) rename {app => scripts/app}/jobs/grade_timed_checkpoint_job.rb (100%) rename {app => scripts/app}/jobs/import_student_work_job.rb (100%) rename {app => scripts/app}/jobs/java_code_evaluation_job.rb (100%) rename {app => scripts/app}/jobs/javascript_code_evaluation_job.rb (100%) rename {app => scripts/app}/jobs/progress_csv_job.rb (100%) rename {app => scripts/app}/jobs/python_code_evaluation_job.rb (100%) rename {app => scripts/app}/jobs/release_notifier_job.rb (100%) rename {app => scripts/app}/jobs/resync_course_job.rb (100%) rename {app => scripts/app}/jobs/set_block_caches_job.rb (100%) rename {app => scripts/app}/jobs/slack_challenge_performance_job.rb (95%) rename {app => scripts/app}/jobs/slack_dev_notify_job.rb (100%) rename {app => scripts/app}/jobs/slack_ds_prep_job.rb (100%) rename {app => scripts/app}/jobs/slack_job.rb (100%) rename {app => scripts/app}/jobs/slack_se_prep_job.rb (100%) rename {app => scripts/app}/jobs/slack_student_job.rb (100%) rename {app => scripts/app}/jobs/student_progress_auth_job.rb (100%) rename {app => scripts/app}/jobs/switch_to_branch_job.rb (100%) rename {app => scripts/app}/mailers/application_mailer.rb (100%) rename {app => scripts/app}/mailers/user_mailer.rb (100%) rename {app => scripts/app}/models/activity.rb (100%) rename {app => scripts/app}/models/api_interaction.rb (100%) rename {app => scripts/app}/models/application_record.rb (100%) rename {app => scripts/app}/models/block.rb (100%) rename {app => scripts/app}/models/challenge.rb (100%) rename {app => scripts/app}/models/checkpoint_submission.rb (100%) rename {app => scripts/app}/models/cohort.rb (100%) rename {app => scripts/app}/models/cohort_release.rb (100%) rename {app => scripts/app}/models/cohort_user.rb (100%) rename {app => scripts/app}/models/concerns/findable_by_uid.rb (100%) rename {app => scripts/app}/models/concerns/read_only_model.rb (100%) rename {app => scripts/app}/models/content_file.rb (100%) rename {app => scripts/app}/models/content_visibility.rb (100%) rename {app => scripts/app}/models/job_result.rb (100%) rename {app => scripts/app}/models/lesson_visit.rb (100%) rename {app => scripts/app}/models/notification.rb (100%) rename {app => scripts/app}/models/pairing.rb (100%) rename {app => scripts/app}/models/performance.rb (100%) rename {app => scripts/app}/models/release.rb (100%) rename {app => scripts/app}/models/resync_job_result.rb (100%) rename {app => scripts/app}/models/section.rb (100%) rename {app => scripts/app}/models/standard.rb (100%) rename {app => scripts/app}/models/submitted_challenge_answer.rb (100%) rename {app => scripts/app}/models/unit.rb (100%) rename {app => scripts/app}/models/user.rb (100%) rename {app => scripts/app}/models/user_last_viewed_standard_path.rb (100%) rename {app => scripts/app}/policies/activity_policy.rb (100%) rename {app => scripts/app}/policies/application_policy.rb (100%) rename {app => scripts/app}/policies/block_policy.rb (100%) rename {app => scripts/app}/policies/checkpoint_submission_policy.rb (100%) rename {app => scripts/app}/policies/cohort_policy.rb (100%) rename {app => scripts/app}/policies/cohort_release_policy.rb (100%) rename {app => scripts/app}/policies/cohort_user_policy.rb (100%) rename {app => scripts/app}/policies/content_file_policy.rb (100%) rename {app => scripts/app}/policies/notification_policy.rb (100%) rename {app => scripts/app}/policies/performance_policy.rb (100%) rename {app => scripts/app}/policies/release_policy.rb (100%) rename {app => scripts/app}/policies/submitted_challenge_answer_policy.rb (100%) rename {app => scripts/app}/policies/user_policy.rb (100%) rename {app => scripts/app}/presenters/activity_presenter.rb (100%) rename {app => scripts/app}/presenters/block_presenter/for_block.rb (100%) rename {app => scripts/app}/presenters/block_presenter/for_cohort_releases.rb (100%) rename {app => scripts/app}/presenters/block_presenter/for_cohort_releases_new.rb (100%) rename {app => scripts/app}/presenters/block_presenter/for_releases.rb (100%) rename {app => scripts/app}/presenters/challenge_with_submitted_challenge_answers_presenter.rb (100%) rename {app => scripts/app}/presenters/checkpoint_submission_presenter.rb (100%) rename {app => scripts/app}/presenters/checkpoint_submission_presenter/for_index.rb (100%) rename {app => scripts/app}/presenters/checkpoint_submission_presenter/for_student_row.rb (100%) rename {app => scripts/app}/presenters/cohort_release_presenter/for_cohort_setup.rb (100%) rename {app => scripts/app}/presenters/cohort_setup/visibility.rb (100%) rename {app => scripts/app}/presenters/content_file_presenter/for_curriculum_last_viewed.rb (100%) rename {app => scripts/app}/presenters/content_file_presenter/for_footer.rb (100%) rename {app => scripts/app}/presenters/content_file_presenter/for_show.rb (100%) rename {app => scripts/app}/presenters/content_file_presenter/for_sidebar.rb (100%) rename {app => scripts/app}/presenters/performance_presenter.rb (100%) rename {app => scripts/app}/presenters/standard_card_component_props.rb (100%) rename {app => scripts/app}/presenters/standard_presenter/for_challenge_detail_view.rb (100%) rename {app => scripts/app}/presenters/standard_presenter/for_checkpoint_submission.rb (100%) rename {app => scripts/app}/presenters/standard_presenter/for_standard_card.rb (100%) rename {app => scripts/app}/presenters/standard_presenter/for_submissions_dashboard.rb (100%) rename {app => scripts/app}/presenters/stat_progress_presenter.rb (100%) rename {app => scripts/app}/presenters/student_progress_presenter.rb (100%) rename {app => scripts/app}/presenters/submissions_dashboard_presenter.rb (100%) rename {app => scripts/app}/presenters/submitted_challenge_answer_presenter.rb (100%) rename {app => scripts/app}/presenters/user_presenter/for_avatar.rb (100%) rename {app => scripts/app}/services/activity_aggregator_service.rb (100%) rename {app => scripts/app}/services/assessment_service.rb (100%) rename {app => scripts/app}/services/auto_assign_release_service.rb (100%) rename {app => scripts/app}/services/checkpoint_submission_service.rb (100%) rename {app => scripts/app}/services/cohort_standard_progress_service.rb (100%) rename {app => scripts/app}/services/cohort_student_progress_service.rb (100%) rename {app => scripts/app}/services/completion_mode_percent_service.rb (100%) rename {app => scripts/app}/services/course_validator.rb (100%) rename {app => scripts/app}/services/create_submitted_challenge_answer_service.rb (100%) rename {app => scripts/app}/services/curriculum_progress_service.rb (100%) rename {app => scripts/app}/services/download_github_repository_service.rb (100%) rename {app => scripts/app}/services/download_gitlab_repository_service.rb (100%) rename {app => scripts/app}/services/download_repository_service.rb (100%) rename {app => scripts/app}/services/download_s3_repository_service.rb (100%) rename {app => scripts/app}/services/encoded_image_link_service.rb (100%) rename {app => scripts/app}/services/git_url_service.rb (100%) rename {app => scripts/app}/services/mastery_average_service.rb (100%) rename {app => scripts/app}/services/mastery_mode_percent_service.rb (100%) rename {app => scripts/app}/services/mock_mixpanel.rb (100%) rename {app => scripts/app}/services/notification_service.rb (100%) rename {app => scripts/app}/services/platform_one_auth_resolver_service.rb (100%) rename {app => scripts/app}/services/preview_content_file_service.rb (100%) rename {app => scripts/app}/services/release_destroyer_service.rb (100%) rename {app => scripts/app}/services/release_helper_service.rb (100%) rename {app => scripts/app}/services/resync_course_service.rb (100%) rename {app => scripts/app}/services/s3_asset_uploader_service.rb (100%) rename {app => scripts/app}/services/segment_track_service.rb (100%) rename {app => scripts/app}/services/sql_challenge_db_service.rb (100%) rename {app => scripts/app}/services/standard_submissions_service.rb (100%) rename {app => scripts/app}/views/api_interactions.html.haml (100%) rename {app => scripts/app}/views/api_token.html.haml (100%) rename {app => scripts/app}/views/apitome/docs/_headers.html.erb (100%) rename {app => scripts/app}/views/apitome/docs/_params.html.erb (100%) rename {app => scripts/app}/views/blocks/blockpagev2.html.haml (100%) rename {app => scripts/app}/views/blocks/index.html.haml (100%) rename {app => scripts/app}/views/blocks/new.html.haml (100%) rename {app => scripts/app}/views/blocks/releases/new.html.haml (100%) rename {app => scripts/app}/views/blocks/show.html.haml (100%) rename {app => scripts/app}/views/cohorts/_cohort_edit_form.html.haml (100%) rename {app => scripts/app}/views/cohorts/_cohort_info.html.haml (100%) rename {app => scripts/app}/views/cohorts/_completion_progress_donut.html.haml (100%) rename {app => scripts/app}/views/cohorts/_user_table.html.haml (100%) rename {app => scripts/app}/views/cohorts/activity_dashboard.html.haml (100%) rename {app => scripts/app}/views/cohorts/blocks/content_files/_footer.html.haml (100%) rename {app => scripts/app}/views/cohorts/blocks/content_files/show.html.haml (100%) rename {app => scripts/app}/views/cohorts/cohort_releases/index.html.haml (100%) rename {app => scripts/app}/views/cohorts/content.html.haml (100%) rename {app => scripts/app}/views/cohorts/content_files/checkpoint_submissions/show.html.haml (100%) rename {app => scripts/app}/views/cohorts/course_stats.html.haml (100%) rename {app => scripts/app}/views/cohorts/edit.html.haml (100%) rename {app => scripts/app}/views/cohorts/error.html.haml (100%) rename {app => scripts/app}/views/cohorts/feed.html.haml (100%) rename {app => scripts/app}/views/cohorts/index.html.haml (100%) rename {app => scripts/app}/views/cohorts/new.html.haml (100%) rename {app => scripts/app}/views/cohorts/partnerup.html.haml (100%) rename {app => scripts/app}/views/cohorts/setup.html.haml (100%) rename {app => scripts/app}/views/cohorts/show.html.haml (100%) rename {app => scripts/app}/views/cohorts/standards/checkpoint_submissions/index.html.haml (100%) rename {app => scripts/app}/views/cohorts/unit_progress.html.haml (100%) rename {app => scripts/app}/views/cohorts/users.html.haml (100%) rename {app => scripts/app}/views/cohorts/users/challenges/show.html.haml (100%) rename {app => scripts/app}/views/cohorts/users/mastery/index.html.haml (100%) rename {app => scripts/app}/views/cohorts/users/submissions_dashboard.html.haml (100%) rename {app => scripts/app}/views/error_404.html.haml (100%) rename {app => scripts/app}/views/error_500.html.haml (100%) rename {app => scripts/app}/views/home/index.html.haml (100%) rename {app => scripts/app}/views/layouts/_primary_navigation.html.haml (100%) rename {app => scripts/app}/views/layouts/_secondary_navigation.html.haml (100%) rename {app => scripts/app}/views/layouts/application.html.haml (100%) rename {app => scripts/app}/views/layouts/mailer.html.erb (100%) rename {app => scripts/app}/views/layouts/mailer.text.erb (100%) rename {app => scripts/app}/views/permalinks/permalink.html.haml (100%) rename {app => scripts/app}/views/shared/_content_file_js.html.haml (100%) rename {app => scripts/app}/views/shared/_hopscotch_callbacks_js.html.haml (100%) rename {app => scripts/app}/views/shared/_intercom_js.html.haml (100%) rename {app => scripts/app}/views/shared/_segment_js.html.haml (100%) rename {app => scripts/app}/views/user_mailer/send_file.html (100%) rename {app => scripts/app}/views/user_mailer/user_import_work.html.haml (100%) rename {app => scripts/app}/views/users/edit.html.haml (100%) rename {app => scripts/app}/views/users/edit_user.html.haml (100%) rename {app => scripts/app}/views/users/index.html.haml (100%) rename {app => scripts/app}/views/users/new.html.haml (100%) rename babel.config.js => scripts/babel.config.js (100%) rename {bin => scripts/bin}/bundle (100%) rename {bin => scripts/bin}/rails (100%) rename {bin => scripts/bin}/rake (100%) rename {bin => scripts/bin}/setup (100%) rename {bin => scripts/bin}/update (100%) rename {bin => scripts/bin}/webpack (100%) rename {bin => scripts/bin}/webpack-dev-server (100%) rename {bin => scripts/bin}/yarn (100%) rename config.ru => scripts/config.ru (100%) rename {config => scripts/config}/application.rb (100%) rename {config => scripts/config}/boot.rb (100%) rename {config => scripts/config}/database.yml (100%) rename {config => scripts/config}/environment.rb (100%) rename {config => scripts/config}/environments/development.rb (100%) rename {config => scripts/config}/environments/production.rb (100%) rename {config => scripts/config}/environments/test.rb (100%) rename {config => scripts/config}/get-routes-audit.md (100%) rename {config => scripts/config}/honeybadger.yml (100%) rename {config => scripts/config}/initializers/apitome.rb (100%) rename {config => scripts/config}/initializers/application_controller_renderer.rb (100%) rename {config => scripts/config}/initializers/assets.rb (100%) rename {config => scripts/config}/initializers/auth_api.rb (100%) rename {config => scripts/config}/initializers/aws.rb (100%) rename {config => scripts/config}/initializers/backtrace_silencers.rb (100%) rename {config => scripts/config}/initializers/content_security_policy.rb (100%) rename {config => scripts/config}/initializers/cookies_serializer.rb (100%) rename {config => scripts/config}/initializers/filter_parameter_logging.rb (100%) rename {config => scripts/config}/initializers/inflections.rb (100%) rename {config => scripts/config}/initializers/mime_types.rb (100%) rename {config => scripts/config}/initializers/new_framework_defaults_5_1.rb (100%) rename {config => scripts/config}/initializers/new_framework_defaults_5_2.rb (100%) rename {config => scripts/config}/initializers/octokit.rb (100%) rename {config => scripts/config}/initializers/pagy.rb (100%) rename {config => scripts/config}/initializers/pundit.rb (100%) rename {config => scripts/config}/initializers/rack_attack.rb (100%) rename {config => scripts/config}/initializers/segment.rb (100%) rename {config => scripts/config}/initializers/sidekiq.rb (100%) rename {config => scripts/config}/initializers/wrap_parameters.rb (100%) rename {config => scripts/config}/locales/en.yml (100%) rename {config => scripts/config}/puma.rb (100%) rename {config => scripts/config}/routes.rb (100%) rename {config => scripts/config}/secrets.yml (100%) rename {config => scripts/config}/sidekiq.yml (100%) rename {config => scripts/config}/spring.rb (100%) rename {config => scripts/config}/storage.yml (100%) rename {config => scripts/config}/webpack/development.js (100%) rename {config => scripts/config}/webpack/environment.js (100%) rename {config => scripts/config}/webpack/loaders/typescript.js (100%) rename {config => scripts/config}/webpack/production.js (100%) rename {config => scripts/config}/webpack/test.js (100%) rename {config => scripts/config}/webpacker.yml (100%) rename {db => scripts/db}/drawio_schema_with_explanations.xml (100%) rename {db => scripts/db}/migrate/20171003191909_create_users.rb (100%) rename {db => scripts/db}/migrate/20171005135550_add_admin_to_users.rb (100%) rename {db => scripts/db}/migrate/20171005140042_rename_users_profile_image_url.rb (100%) rename {db => scripts/db}/migrate/20171006195948_create_blocks.rb (100%) rename {db => scripts/db}/migrate/20171009194124_create_releases.rb (100%) rename {db => scripts/db}/migrate/20171011232501_add_sync_errors_to_block.rb (100%) rename {db => scripts/db}/migrate/20171012200106_create_standards.rb (100%) rename {db => scripts/db}/migrate/20171016192627_create_cohorts.rb (100%) rename {db => scripts/db}/migrate/20171017205306_create_cohort_users.rb (100%) rename {db => scripts/db}/migrate/20171017221501_remove_presence_indices_from_label_and_pretty_name_on_cohorts.rb (100%) rename {db => scripts/db}/migrate/20171019165527_create_content_files.rb (100%) rename {db => scripts/db}/migrate/20171019192005_create_cohort_releases.rb (100%) rename {db => scripts/db}/migrate/20171023211301_create_challenges.rb (100%) rename {db => scripts/db}/migrate/20171024202210_create_checkpoint_submissions.rb (100%) rename {db => scripts/db}/migrate/20171024203630_create_submitted_challenge_answers.rb (100%) rename {db => scripts/db}/migrate/20171025211916_create_performances.rb (100%) rename {db => scripts/db}/migrate/20171025223250_add_autoscore_to_content_files.rb (100%) rename {db => scripts/db}/migrate/20171026193413_add_title_to_content_files.rb (100%) rename {db => scripts/db}/migrate/20171102144822_cohort_release_unique_index.rb (100%) rename {db => scripts/db}/migrate/20171102165314_add_unique_constraint_to_block_title.rb (100%) rename {db => scripts/db}/migrate/20171102171128_add_position_to_cohort_releases.rb (100%) rename {db => scripts/db}/migrate/20171127223701_create_activities.rb (100%) rename {db => scripts/db}/migrate/20171130183523_create_user_last_viewed_standard_path.rb (100%) rename {db => scripts/db}/migrate/20171130185636_add_uid_to_content_files.rb (100%) rename {db => scripts/db}/migrate/20171205211624_add_slack_data_to_user.rb (100%) rename {db => scripts/db}/migrate/20180104213519_add_standard_uid_to_performances.rb (100%) rename {db => scripts/db}/migrate/20180110174447_add_type_relative_display_name_to_content_files.rb (100%) rename {db => scripts/db}/migrate/20180110223429_add_auth_roles_to_user.rb (100%) rename {db => scripts/db}/migrate/20180110224150_denormalize_submitted_challenge_answers.rb (100%) rename {db => scripts/db}/migrate/20180110233303_add_roles_to_cohort_user.rb (100%) rename {db => scripts/db}/migrate/20180112234504_add_last_viewed_cohort_id_to_users.rb (100%) rename {db => scripts/db}/migrate/20180116174519_add_github_user_name_to_user.rb (100%) rename {db => scripts/db}/migrate/20180116181937_add_taught_in_learn_to_cohorts.rb (100%) rename {db => scripts/db}/migrate/20180116213927_remove_forge_admin_and_role.rb (100%) rename {db => scripts/db}/migrate/20180119184121_add_cohort_id_to_submitted_challenge_answer.rb (100%) rename {db => scripts/db}/migrate/20180124221642_denormalize_checkpoint_submissions.rb (100%) rename {db => scripts/db}/migrate/20180125212203_rename_taught_in_learn.rb (100%) rename {db => scripts/db}/migrate/20180125222044_change_learn_v2_default.rb (100%) rename {db => scripts/db}/migrate/20180131220605_create_notifications.rb (100%) rename {db => scripts/db}/migrate/20180202225951_add_github_sha_to_releases.rb (100%) rename {db => scripts/db}/migrate/20180209175941_add_use_latest_release_to_cohort_releases.rb (100%) rename {db => scripts/db}/migrate/20180313220132_add_docker_directory_path_to_challenges.rb (100%) rename {db => scripts/db}/migrate/20180316222140_rename_cohort_title_to_name.rb (100%) rename {db => scripts/db}/migrate/20180320162849_add_deleted_at_to_cohorts.rb (100%) rename {db => scripts/db}/migrate/20180402212114_remove_pretty_name_from_cohort.rb (100%) rename {db => scripts/db}/migrate/20180412214504_add_used_by_application_to_cohorts.rb (100%) rename {db => scripts/db}/migrate/20180412223312_populate_used_by_application.rb (100%) rename {db => scripts/db}/migrate/20180504193033_add_feature_branch_columns_to_releases.rb (100%) rename {db => scripts/db}/migrate/20180504200226_add_pending_release_id_to_cohort_releases.rb (100%) rename {db => scripts/db}/migrate/20180813205413_add_readme_text_to_release.rb (100%) rename {db => scripts/db}/migrate/20180813213358_add_mode_to_cohort.rb (100%) rename {db => scripts/db}/migrate/20180813214453_populate_mode_cohorts.rb (100%) rename {db => scripts/db}/migrate/20180817181129_add_cohort_id_to_user_last_viewed_standard_path.rb (100%) rename {db => scripts/db}/migrate/20180824200753_create_lesson_visits.rb (100%) rename {db => scripts/db}/migrate/20181112215710_add_preferred_campus_to_users.rb (100%) rename {db => scripts/db}/migrate/20181114190340_create_job_results.rb (100%) rename {db => scripts/db}/migrate/20181120221622_create_sections.rb (100%) rename {db => scripts/db}/migrate/20181121202104_add_section_id_to_cohort_releases.rb (100%) rename {db => scripts/db}/migrate/20181203215524_add_challenge_completion_to_performance.rb (100%) rename {db => scripts/db}/migrate/20181213180900_add_show_tests_to_challenges.rb (100%) rename {db => scripts/db}/migrate/20181214175640_create_content_visibilities.rb (100%) rename {db => scripts/db}/migrate/20181220005015_add_default_visibility_to_content_files.rb (100%) rename {db => scripts/db}/migrate/20190410171829_create_pairings.rb (100%) rename {db => scripts/db}/migrate/20190423171448_add_data_path_to_challenge.rb (100%) rename {db => scripts/db}/migrate/20190510193410_add_last_setup_visit_to_cohort_users.rb (100%) rename {db => scripts/db}/migrate/20190520223958_add_max_attempts_to_content_files.rb (100%) rename {db => scripts/db}/migrate/20190529170059_add_allowed_attempts_to_challenge.rb (100%) rename {db => scripts/db}/migrate/20190624222000_remove_max_attempts_from_challenges.rb (100%) rename {db => scripts/db}/migrate/20190625173725_add_points_and_success_criteria_to_challenges.rb (100%) rename {db => scripts/db}/migrate/20190709170851_drop_learn_version_columns_on_cohorts.rb (100%) rename {db => scripts/db}/migrate/20190829203949_add_settings_to_cohorts.rb (100%) rename {db => scripts/db}/migrate/20190910161219_add_points_to_submitted_challenge_answer.rb (100%) rename {db => scripts/db}/migrate/20190910161303_add_points_to_checkpoint_submissions.rb (100%) rename {db => scripts/db}/migrate/20190919173956_change_success_criteria_to_rubric_on_challenges.rb (100%) rename {db => scripts/db}/migrate/20191001214559_add_topics_to_challenges.rb (100%) rename {db => scripts/db}/migrate/20191001221324_add_perf_data_to_submissions.rb (100%) rename {db => scripts/db}/migrate/20191008211403_add_release_id_to_challenges.rb (100%) rename {db => scripts/db}/migrate/20191009214248_add_user_id_to_release.rb (100%) rename {db => scripts/db}/migrate/20191024200444_add_visibility_type_to_content_visibilities.rb (100%) rename {db => scripts/db}/migrate/20191031212058_add_api_token_to_user.rb (100%) rename {db => scripts/db}/migrate/20191108211234_add_preview_to_releases.rb (100%) rename {db => scripts/db}/migrate/20191114211938_create_api_interactions.rb (100%) rename {db => scripts/db}/migrate/20191119225902_add_sandbox_boolean_to_cohorts.rb (100%) rename {db => scripts/db}/migrate/20191206230913_add_sync_warnings.rb (100%) rename {db => scripts/db}/migrate/20191209165026_add_metadata_to_api_interactions.rb (100%) rename {db => scripts/db}/migrate/20191218232717_add_assign_partial_credig_to_challenges.rb (100%) rename {db => scripts/db}/migrate/20200108184027_add_end_time_to_checkpoint_submissions.rb (100%) rename {db => scripts/db}/migrate/20200110201253_change_cohort_default_percentage.rb (100%) rename {db => scripts/db}/migrate/20200213201649_add_time_limit_to_content_file.rb (100%) rename {db => scripts/db}/migrate/20200310222235_add_submitted_at_to_checkpoint_submissions.rb (100%) rename {db => scripts/db}/migrate/20200408212051_add_allow_paired_submissions_to_cohort.rb (100%) rename {db => scripts/db}/migrate/20200416155234_add_pair_submission_ids_to_checkpoint_submissinos.rb (100%) rename {db => scripts/db}/migrate/20200430165251_add_external_to_challenge.rb (100%) rename {db => scripts/db}/migrate/20200702155846_add_cache_to_blocks.rb (100%) rename {db => scripts/db}/migrate/20200707164932_add_archived_at_to_blocks.rb (100%) rename {db => scripts/db}/migrate/20200720213420_add_block_location_details_to_blocks.rb (100%) rename {db => scripts/db}/migrate/20200724195251_add_whitelabeled_to_cohorts.rb (100%) rename {db => scripts/db}/migrate/20200729214247_remove_block_uniqueness_pg_columns.rb (100%) rename {db => scripts/db}/migrate/20200811215713_add_student_import_status_to_cohorts.rb (100%) rename {db => scripts/db}/migrate/20200818172419_addd_docker_directory_zip_to_challenges.rb (100%) rename {db => scripts/db}/migrate/20200828165130_default_block_org_tog_school.rb (100%) rename {db => scripts/db}/migrate/20200925230149_remove_null_constraints_from_users.rb (100%) rename {db => scripts/db}/migrate/20200929004708_remove_null_email_from_users.rb (100%) rename {db => scripts/db}/schema.rb (100%) rename {db => scripts/db}/seeds.rb (100%) create mode 100644 scripts/delete-blocks rename {doc => scripts/doc}/api.md (100%) rename {doc => scripts/doc}/api/cohorts/creating_a_new_cohort.json (100%) rename {doc => scripts/doc}/api/cohorts/viewing_curriculum_details.json (100%) rename {doc => scripts/doc}/api/content/update_content_visibility.json (100%) rename {doc => scripts/doc}/api/enrollments/creating_a_user_and_their_enrollment.json (100%) rename {doc => scripts/doc}/api/enrollments/unenrolling_a_user_from_a_cohort.json (100%) rename {doc => scripts/doc}/api/enrollments/updating_a_user's_enrollment_roles.json (100%) rename {doc => scripts/doc}/api/enrollments/viewing_cohort_enrollments.json (100%) rename {doc => scripts/doc}/api/index.json (100%) rename {doc => scripts/doc}/api/units/update_unit_visibility.json (100%) rename {doc => scripts/doc}/api/users/regenerate_user_token.json (100%) rename entrypoint-server.sh => scripts/entrypoint-server.sh (100%) rename {gems => scripts/gems}/block-parser/Dockerfile (100%) rename {gems => scripts/gems}/block-parser/Gemfile (100%) rename {gems => scripts/gems}/block-parser/Gemfile.lock (100%) rename {gems => scripts/gems}/block-parser/README.md (100%) rename {gems => scripts/gems}/block-parser/Rakefile (100%) rename {gems => scripts/gems}/block-parser/bin/console (100%) rename {gems => scripts/gems}/block-parser/bin/parse_block (100%) rename {gems => scripts/gems}/block-parser/bin/setup (100%) rename {gems => scripts/gems}/block-parser/block_parser-0.1.0.gem (100%) rename {gems => scripts/gems}/block-parser/block_parser.gemspec (100%) rename {gems => scripts/gems}/block-parser/build (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/build_challenge.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/build_html.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/build_html_from_md_json.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/build_image_link.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/build_link.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/build_title_from_filename_and_html.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/challenge.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/number_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/poll_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/project_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/convert_md_to_json.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/parse_directory.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/parse_markdown_file.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/parse_standards.rb (100%) rename {gems => scripts/gems}/block-parser/lib/block_parser/version.rb (100%) rename {gems => scripts/gems}/block-parser/pkg/block_parser-0.1.0.gem (100%) rename {gems => scripts/gems}/block-parser/spec/build_challenge_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/build_html_from_md_json_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/build_html_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/build_image_link_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/build_link_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/build_title_from_filename_and_html_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/challenge_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/challenge_validator_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/checkbox_challenge_validator_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/number_challenge_validator_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/project_challenge_validator_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/short_answer_challenge_validator_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/challenge_validators/testable_project_challenge_validator_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/convert_md_to_json_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/invalid-config-test-block-repo/config.yaml (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/no-lessons-nor-checkpoints/config.yaml (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/no-lessons-nor-checkpoints/resources.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/no-standards-config-test-block-repo/config.yaml (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/sample-iframe.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo-yml/config.yml (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo-yml/dummy.pdf (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo-yml/folder/sibling.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo-yml/folder/target.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo-yml/images/register_klass.gif (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo-yml/lessoN.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo-yml/markdown-smoketest.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo-yml/target.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/README.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/config.yaml (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/folder/sibling.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/folder/target.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/images/galvanize-logo.png (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/images/register_klass.gif (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/ipynb-test.ipynb (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/ipynb-test.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/lessoN.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/markdown-smoketest.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-repo/target.md (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-two-configs/config.yaml (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/test-block-two-configs/config.yml (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh (100%) rename {gems => scripts/gems}/block-parser/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml (100%) rename {gems => scripts/gems}/block-parser/spec/parse_directory_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/parse_markdown_file_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/parse_standards_spec.rb (100%) rename {gems => scripts/gems}/block-parser/spec/spec_helper.rb (100%) rename {gems => scripts/gems}/block-parser/spec/support/freeloader.rb (100%) rename {gems => scripts/gems}/block-parser/spec/support/mocktokit.rb (100%) rename {gems => scripts/gems}/block-parser/spec/tmp/.keep (100%) rename {gems => scripts/gems}/block-parser/validate-block (100%) rename {lib => scripts/lib}/tasks/challenge_release_ids.rake (100%) rename {lib => scripts/lib}/tasks/checkpoint_resubmission.rake (100%) rename {lib => scripts/lib}/tasks/checkpoint_submission_points.rake (100%) rename {lib => scripts/lib}/tasks/content_visibility_update.rake (100%) rename {lib => scripts/lib}/tasks/course_progress.rake (100%) rename {lib => scripts/lib}/tasks/course_yaml_backfill.rake (100%) rename {lib => scripts/lib}/tasks/grade_checkpoints.rake (100%) rename {lib => scripts/lib}/tasks/object_space_count.rake (100%) rename {lib => scripts/lib}/tasks/prep_notifier.rake (100%) rename {lib => scripts/lib}/tasks/set_block_caches.rake (100%) rename {lib => scripts/lib}/tasks/spec.rake (100%) rename {lib => scripts/lib}/tasks/ts_routes.rake (100%) rename lintspec.sh => scripts/lintspec.sh (100%) rename package.json => scripts/package.json (100%) rename postcss.config.js => scripts/postcss.config.js (100%) rename {public => scripts/public}/404.html (100%) rename {public => scripts/public}/422.html (100%) rename {public => scripts/public}/500.html (100%) rename {public => scripts/public}/apple-touch-icon-152x152.png (100%) rename {public => scripts/public}/apple-touch-icon-precomposed-152x152.png (100%) rename {public => scripts/public}/apple-touch-icon-precomposed.png (100%) rename {public => scripts/public}/apple-touch-icon.png (100%) rename {public => scripts/public}/assets/images/hopscotch-sprite-green.png (100%) rename {public => scripts/public}/assets/images/hopscotch-sprite-orange.png (100%) rename {public => scripts/public}/assets/images/jupyter-logo.png (100%) rename {public => scripts/public}/assets/images/svg/checkpoint-is-rejected.svg (100%) rename {public => scripts/public}/assets/images/svg/checkpoint-is-scored.svg (100%) rename {public => scripts/public}/assets/images/svg/checkpoint-is-submitted.svg (100%) rename {public => scripts/public}/assets/images/svg/github-icon.svg (100%) rename {public => scripts/public}/assets/images/svg/gitlab-icon-rgb.svg (100%) rename {public => scripts/public}/assets/images/svg/octicon-git-branch.svg (100%) rename {public => scripts/public}/assets/images/svg/redpriority_high-24px.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-link_off-24px.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-action-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-alert-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-av-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-content-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-custom-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-custom_material-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-device-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-file-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-hardware-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-navigation-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-notification-symbol.svg (100%) rename {public => scripts/public}/assets/images/svg/svg-sprite-social-symbol.svg (100%) rename {public => scripts/public}/favicon.ico (100%) rename {public => scripts/public}/javascripts/apitome/application.js (100%) rename {public => scripts/public}/robots.txt (100%) rename {public => scripts/public}/sandbox/chai.js (100%) rename {public => scripts/public}/sandbox/challenge-worker.js (100%) rename {public => scripts/public}/sandbox/jasmine/boot.js (100%) rename {public => scripts/public}/sandbox/jasmine/jasmine.js (100%) rename {public => scripts/public}/sandbox/mocha/boot.js (100%) rename {public => scripts/public}/sandbox/mocha/mocha.js (100%) rename {public => scripts/public}/sandbox/mocha/test.html (100%) rename {public => scripts/public}/sandbox/sandbox.html (100%) rename {public => scripts/public}/sandbox/stacktrace.js (100%) rename {public => scripts/public}/sandbox/worker.js (100%) rename {public => scripts/public}/stylesheets/apitome/application.css (100%) rename requirements.txt => scripts/requirements.txt (100%) rename scripts/{ => scripts}/sh/cohort_curriculum.sh (100%) rename scripts/{ => scripts}/sh/content_file_mark_hidden.sh (100%) rename scripts/{ => scripts}/sh/content_file_mark_visible.sh (100%) rename scripts/{ => scripts}/sh/unit_mark_hidden.sh (100%) rename scripts/{ => scripts}/sh/unit_mark_visible.sh (100%) rename scripts/{ => scripts}/sql/checkpoint_answers.sql (100%) rename scripts/{ => scripts}/sql/cohort_challenges_with_answers.sql (100%) rename scripts/{ => scripts}/sql/cohort_prune_for_testing.sql (100%) rename scripts/{ => scripts}/sql/percent_metrics.sql (100%) rename scripts/{ => scripts}/sql/scrub_cohort_data.sql (100%) create mode 100644 scripts/serviceentry.yaml create mode 100644 scripts/spec/component_props/activity_feed_item_component_props_spec.rb create mode 100644 scripts/spec/component_props/notifications_component_props_spec.rb create mode 100644 scripts/spec/component_props/standard_card_component_props_spec.rb create mode 100644 scripts/spec/controllers/api/v1/blocks/releases_controller_spec.rb create mode 100644 scripts/spec/controllers/api/v1/blocks_controller_spec.rb create mode 100644 scripts/spec/controllers/api/v1/cohorts/blocks/content_files_controller_spec.rb create mode 100644 scripts/spec/controllers/api/v1/cohorts/blocks/units_controller_spec.rb create mode 100644 scripts/spec/controllers/api/v1/cohorts/cohorts_controller_spec.rb create mode 100644 scripts/spec/controllers/api/v1/cohorts/users_controller_spec.rb create mode 100644 scripts/spec/controllers/api/v1/content_files_controller_spec.rb create mode 100644 scripts/spec/controllers/api/v1/users_controller_spec.rb create mode 100644 scripts/spec/controllers/application_controller_spec.rb create mode 100644 scripts/spec/controllers/blocks/releases_controller_spec.rb create mode 100644 scripts/spec/controllers/blocks_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/blocks/content_files_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/checkpoint_submissions/activities_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/cohort_releases_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/content_files/checkpoint_submissions_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/content_files/submitted_challenge_answers_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/content_files_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/pairings_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/standards_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/submitted_challenge_answers/activities_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/users/challenges_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/users/performances_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts/users_controller_spec.rb create mode 100644 scripts/spec/controllers/cohorts_controller_spec.rb create mode 100644 scripts/spec/controllers/home_controller_spec.rb create mode 100644 scripts/spec/controllers/notifications_controller_spec.rb create mode 100644 scripts/spec/controllers/permalinks_controller_spec.rb create mode 100644 scripts/spec/controllers/sessions_controller_spec.rb create mode 100644 scripts/spec/controllers/users_controller_spec.rb create mode 100644 scripts/spec/controllers/webhooks/assessments_service/submitted_challenge_answers_controller_spec.rb create mode 100644 scripts/spec/exporters/performance_exporter_spec.rb create mode 100644 scripts/spec/factories.rb create mode 100644 scripts/spec/features/blocks/management_feature_spec.rb create mode 100644 scripts/spec/features/cohorts/activity_dashboard_spec.rb create mode 100644 scripts/spec/features/cohorts/checkpoint_submissions_spec.rb create mode 100644 scripts/spec/features/cohorts/curriculum_feature_spec.rb create mode 100644 scripts/spec/features/cohorts/feed_feature_spec.rb create mode 100644 scripts/spec/features/cohorts/management_feature_spec.rb create mode 100644 scripts/spec/features/cohorts/sandboxes_spec.rb create mode 100644 scripts/spec/features/cohorts/student_progress_spec.rb create mode 100644 scripts/spec/features/cohorts/submissions_spec.rb create mode 100644 scripts/spec/features/cohorts/users/challenges_feature_spec.rb create mode 100644 scripts/spec/features/cohorts/users/submissions_dashboard_feature_spec.rb create mode 100644 scripts/spec/features/content_files/checkpoint_assessments_spec.rb create mode 100644 scripts/spec/features/content_files/checkpoint_submissions_spec.rb create mode 100644 scripts/spec/features/content_files/navigation_feature_spec.rb create mode 100644 scripts/spec/features/content_files/permalinks_spec.rb create mode 100644 scripts/spec/features/content_files/view_feature_spec.rb create mode 100644 scripts/spec/features/home/header_feature_spec.rb create mode 100644 scripts/spec/features_helper.rb create mode 100644 scripts/spec/finders/block_finder_spec.rb create mode 100644 scripts/spec/finders/checkpoint_submission_finder_spec.rb create mode 100644 scripts/spec/finders/cohort_user_finder_spec.rb create mode 100644 scripts/spec/finders/content_file_finder_spec.rb create mode 100644 scripts/spec/finders/performance_finder_spec.rb create mode 100644 scripts/spec/finders/release_finder_spec.rb create mode 100644 scripts/spec/finders/standard_finder_spec.rb create mode 100644 scripts/spec/finders/submitted_challenge_answer_finder_spec.rb create mode 100644 scripts/spec/finders/user_finder_spec.rb create mode 100644 scripts/spec/fixtures/preview-content-file/preview.md create mode 100644 scripts/spec/fixtures/test-block-repo/._docker_directories_test-docker-folder.zip create mode 100644 scripts/spec/fixtures/test-block-repo/README.md create mode 100644 scripts/spec/fixtures/test-block-repo/challenges-smoketest.md create mode 100644 scripts/spec/fixtures/test-block-repo/config.yaml create mode 100644 scripts/spec/fixtures/test-block-repo/docker_directories/test-docker-folder/Dockerfile create mode 100644 scripts/spec/fixtures/test-block-repo/docker_directories/test-docker-folder/test.sh create mode 100644 scripts/spec/fixtures/test-block-repo/folder/sibling.md create mode 100644 scripts/spec/fixtures/test-block-repo/folder/target.md create mode 100644 scripts/spec/fixtures/test-block-repo/images/galvanize-logo.png create mode 100644 scripts/spec/fixtures/test-block-repo/images/register_klass.gif create mode 100644 scripts/spec/fixtures/test-block-repo/markdown-smoketesT.md create mode 100644 scripts/spec/fixtures/test-block-repo/target.md create mode 100644 scripts/spec/fixtures/test-block-repo/test.sql create mode 100644 scripts/spec/fixtures/vcr_cassettes/github-branch-success.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/github-course-yaml-not-found.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/github-course-yaml-success-2.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/github-course-yaml-success.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/github-not-found.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/github-repo-success.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-branch-failure.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-success.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/gitlab-not-found.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/gitlab-success.yml create mode 100644 scripts/spec/fixtures/vcr_cassettes/resync-course-job-success.yml create mode 100644 scripts/spec/helpers/json_helper_spec.rb create mode 100644 scripts/spec/helpers/mastery_mode_percent_helper_spec.rb create mode 100644 scripts/spec/helpers/standard_navigation_helper_spec.rb create mode 100644 scripts/spec/integration_helper.rb create mode 100644 scripts/spec/jobs/branch_release_notifier_job_spec.rb create mode 100644 scripts/spec/jobs/checkpoint_paired_submission_job_spec.rb create mode 100644 scripts/spec/jobs/content_file_default_visibility_job_spec.rb create mode 100644 scripts/spec/jobs/content_file_visit_job_spec.rb create mode 100644 scripts/spec/jobs/create_api_interaction_job_spec.rb create mode 100644 scripts/spec/jobs/create_release_job_spec.rb create mode 100644 scripts/spec/jobs/evaluate_code_snippet_job_spec.rb create mode 100644 scripts/spec/jobs/evaluate_custom_snippet_job_spec.rb create mode 100644 scripts/spec/jobs/evaluate_project_job_spec.rb create mode 100644 scripts/spec/jobs/grade_timed_checkpoint_job_spec.rb create mode 100644 scripts/spec/jobs/import_student_work_job_spec.rb create mode 100644 scripts/spec/jobs/release_notifier_job_spec.rb create mode 100644 scripts/spec/jobs/resync_course_job_spec.rb create mode 100644 scripts/spec/jobs/set_block_caches_job_spec.rb create mode 100644 scripts/spec/jobs/slack_ds_prep_job_spec.rb create mode 100644 scripts/spec/jobs/slack_se_prep_job_spec.rb create mode 100644 scripts/spec/jobs/slack_student_job_spec.rb create mode 100644 scripts/spec/jobs/switch_to_branch_job_spec.rb create mode 100644 scripts/spec/models/activity_spec.rb create mode 100644 scripts/spec/models/api_interaction_spec.rb create mode 100644 scripts/spec/models/block_spec.rb create mode 100644 scripts/spec/models/challenge_spec.rb create mode 100644 scripts/spec/models/checkpoint_submission_spec.rb create mode 100644 scripts/spec/models/cohort_release_spec.rb create mode 100644 scripts/spec/models/cohort_spec.rb create mode 100644 scripts/spec/models/cohort_user_spec.rb create mode 100644 scripts/spec/models/content_file_spec.rb create mode 100644 scripts/spec/models/content_visibility_spec.rb create mode 100644 scripts/spec/models/lesson_visit_spec.rb create mode 100644 scripts/spec/models/notification_spec.rb create mode 100644 scripts/spec/models/pairing_spec.rb create mode 100644 scripts/spec/models/performance_spec.rb create mode 100644 scripts/spec/models/release_spec.rb create mode 100644 scripts/spec/models/resync_job_result_spec.rb create mode 100644 scripts/spec/models/section_spec.rb create mode 100644 scripts/spec/models/standard_spec.rb create mode 100644 scripts/spec/models/submitted_challenge_answer_spec.rb create mode 100644 scripts/spec/models/user_last_viewed_standard_path_spec.rb create mode 100644 scripts/spec/models/user_spec.rb create mode 100644 scripts/spec/policies/cohort_policy_spec.rb create mode 100644 scripts/spec/policies/content_file_policy_spec.rb create mode 100644 scripts/spec/policies/user_policy_spec.rb create mode 100644 scripts/spec/presenters/block_presenter/for_cohort_releases_new_spec.rb create mode 100644 scripts/spec/presenters/block_presenter/for_cohort_releases_spec.rb create mode 100644 scripts/spec/presenters/block_presenter/for_releases_spec.rb create mode 100644 scripts/spec/presenters/challenge_with_submitted_challenge_answers_presenter_spec.rb create mode 100644 scripts/spec/presenters/checkpoint_submission_presenter/for_index_spec.rb create mode 100644 scripts/spec/presenters/checkpoint_submission_presenter_spec.rb create mode 100644 scripts/spec/presenters/content_file_presenter/for_curriculum_last_viewed_spec.rb create mode 100644 scripts/spec/presenters/content_file_presenter/for_show_spec.rb create mode 100644 scripts/spec/presenters/content_file_presenter/for_sidebar_spec.rb create mode 100644 scripts/spec/presenters/standard_presenter/for_checkpoint_submission_spec.rb create mode 100644 scripts/spec/presenters/standard_presenter/for_standard_card_spec.rb create mode 100644 scripts/spec/presenters/student_progress_presenter_spec.rb create mode 100644 scripts/spec/presenters/submissions_dashboard_presenter_spec.rb create mode 100644 scripts/spec/presenters/submitted_challenge_answer_presenter_spec.rb create mode 100644 scripts/spec/services/activity_aggregator_service_spec.rb create mode 100644 scripts/spec/services/assessment_service_spec.rb create mode 100644 scripts/spec/services/auto_assign_release_service_spec.rb create mode 100644 scripts/spec/services/checkpoint_submission_service_spec.rb create mode 100644 scripts/spec/services/cohort_standard_progress_service_spec.rb create mode 100644 scripts/spec/services/course_validator_spec.rb create mode 100644 scripts/spec/services/curriculum_progress_service_spec.rb create mode 100644 scripts/spec/services/download_repository_service_spec.rb create mode 100644 scripts/spec/services/git_url_service_spec.rb create mode 100644 scripts/spec/services/mastery_average_service_spec.rb create mode 100644 scripts/spec/services/notification_service_spec.rb create mode 100644 scripts/spec/services/platform_one_auth_resolver_service_spec.rb create mode 100644 scripts/spec/services/preview_content_file_service_spec.rb create mode 100644 scripts/spec/services/release_destroyer_service_spec.rb create mode 100644 scripts/spec/services/resync_course_service_spec.rb create mode 100644 scripts/spec/services/s3_asset_uploader_service_spec.rb create mode 100644 scripts/spec/services/standard_submissions_service_spec.rb create mode 100644 scripts/spec/spec_helper.rb create mode 100644 scripts/spec/support/json_spec_matchers.rb create mode 100644 scripts/spec/support/mocktokit.rb create mode 100644 scripts/spec/support/object_creation_methods.rb rename tsconfig.json => scripts/tsconfig.json (100%) rename {vendor => scripts/vendor}/assets/javascripts/bootstrap-datepicker.js (100%) rename {vendor => scripts/vendor}/assets/javascripts/hopscotch.js (100%) rename {vendor => scripts/vendor}/assets/javascripts/react-tooltip.js (100%) rename {vendor => scripts/vendor}/assets/stylesheets/bootstrap-datepicker.css (100%) rename {vendor => scripts/vendor}/assets/stylesheets/hopscotch.css (100%) rename yarn.lock => scripts/yarn.lock (100%) diff --git a/.Dockerfile.swp b/.Dockerfile.swp new file mode 100644 index 0000000000000000000000000000000000000000..e38d85e67f1c1580477d7e6a7572442646f48eab GIT binary patch literal 20480 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 I#zBmK1JXg1W&i*H literal 0 HcmV?d00001 diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 837f2f3..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,97 +0,0 @@ -version: 2.0 - -defaults: &defaults - docker: - - image: circleci/ruby:2.6.0-node-browsers - environment: - - PG_HOST: localhost - - PG_USER: ubuntu - - RAILS_ENV: test - - RACK_ENV: test - - image: circleci/postgres:9.5-alpine - environment: - POSTGRES_USER: ubuntu - POSTGRES_DB: forge_test - - image: redis - working_directory: ~/forge - -jobs: - install_dependencies: - <<: *defaults - parallelism: 1 - steps: - - checkout - - attach_workspace: - at: ~/forge - - restore_cache: - key: v1-bundle-{{ checksum "Gemfile.lock" }} - - run: bundle install --path vendor/bundle - - save_cache: - key: v1-bundle-{{ checksum "Gemfile.lock" }} - paths: - - ~/forge/vendor/bundle - - persist_to_workspace: - root: . - paths: vendor/bundle - - restore_cache: - key: v1-yarn-{{ checksum "package.json" }} - - run: - name: Yarn Install JS Dependencies - command: yarn install - - save_cache: - key: v1-yarn-{{ checksum "package.json" }} - paths: - - ~/forge/node_modules - - persist_to_workspace: - root: . - paths: node_modules - - lint: - <<: *defaults - parallelism: 1 - steps: - - checkout - - attach_workspace: - at: ~/forge - - run: bundle --path vendor/bundle - - run: - name: Run Rubocop - command: bundle exec rubocop - - run: - name: Run tsc - command: ./node_modules/.bin/tsc --version && ./node_modules/.bin/tsc - - rake_test: - <<: *defaults - parallelism: 1 - steps: - - checkout - - attach_workspace: - at: ~/forge - - run: bundle --path vendor/bundle - - run: - name: Create DB - command: bundle exec rake db:create db:schema:load - environment: - DATABASE_URL: "postgres://ubuntu@localhost:5432/forge_test" - - run: - name: Run Rspec Tests - command: | - TESTFILES=$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings) - bundle exec rspec --profile 10 --format RspecJunitFormatter --out ~/forge/test-results/rspec.xml --format progress -- ${TESTFILES} - environment: - DATABASE_URL: "postgres://ubuntu@localhost:5432/forge_test" - - store_test_results: - path: ~/forge/test-results - -workflows: - version: 2 - build-and-deploy: - jobs: - - install_dependencies - - lint: - requires: - - install_dependencies - - rake_test: - requires: - - install_dependencies diff --git a/Dockerfile b/Dockerfile index 613216e..26c4aa2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,57 @@ -# Stage 1: Install libararies -FROM centos:8 as builder +### Iron Bank Settings +# ARG BASE_REGISTRY=registry1.dsop.io +# ARG BASE_IMAGE=ironbank/opensource/ruby/ruby26 +# ARG BASE_TAG=2.6.6 + +### Platform 1 Pipeline Settings +ARG BASE_REGISTRY=registry.il2.dsop.io +ARG BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 +ARG BASE_TAG=2.6.6.212 + +# Stage 1: Build redis from source. +FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} as builder USER 0 -## Setup the yarn repo. -#RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo -#RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg - -## Install the required packages. -RUN dnf update -y && \ - dnf install -y \ - redis -# make \ -# patch \ -# git \ -# yarn \ -# zlib-devel \ -# libpq-devel \ -# libxml2-devel \ -# libxslt-devel \ -# gcc +# Install what we can via dnf +RUN dnf update -y && dnf install -y \ + autoconf \ + automake \ + bzip2 \ + curl \ + diffutils \ + gcc-c++ \ + libedit \ + libffi-devel \ + libtool \ + make \ + openssl-devel \ + patch \ + readline \ + zlib \ + zlib-devel + +# Uncomment to test in Platform 1 +RUN curl -O https://download.redis.io/releases/redis-6.0.9.tar.gz + +# Build redis from source +#COPY redis-6.0.9.tar.gz . +RUN tar xzf redis-6.0.9.tar.gz +WORKDIR redis-6.0.9/deps +RUN make hiredis jemalloc linenoise lua +WORKDIR .. +RUN make +RUN make install +WORKDIR .. # Stage 2: Setup the Image. -FROM registry.il2.dsop.io/platform-one/devops/pipeline-templates/ironbank/ruby26:2.6.6.212 +FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg -RUN dnf update -y && \ - dnf install -y \ +RUN dnf update -y && dnf install -y \ + curl \ make \ patch \ git \ @@ -41,7 +64,7 @@ RUN dnf update -y && \ gcc-c++ # Redis CLI Binary. -COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli +COPY --from=builder /usr/local/bin/redis-cli /usr/local/bin/redis-cli ## Patch Binary. #COPY --from=builder /usr/bin/patch /usr/bin/patch @@ -130,13 +153,13 @@ COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli #COPY --from=builder /usr/include /usr/include # Install Node -ADD .nvmrc . -RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n +RUN curl -L -O https://raw.githubusercontent.com/tj/n/master/bin/n +COPY ./scripts/.nvmrc . RUN bash n auto # Setup our environment -WORKDIR /usr/src/app -ADD . . +WORKDIR /app +ADD ./scripts . # Add write permissions. RUN chown -R 1001 . @@ -163,7 +186,7 @@ RUN bundle exec rake assets:precompile RUN bundle exec rake assets:clean # Add the node_modules to the path. -ENV PATH /usr/src/app/node_modules/.bin:$PATH +ENV PATH /app/node_modules/.bin:$PATH # Set the entry point. -ENTRYPOINT ["./entrypoint-server.sh"] \ No newline at end of file +ENTRYPOINT ["./entrypoint-server.sh"] diff --git a/Dockerfile.base b/Dockerfile.base deleted file mode 100644 index dc797ed..0000000 --- a/Dockerfile.base +++ /dev/null @@ -1,47 +0,0 @@ -FROM bitnami/ruby:2.6.6 - -# Install what we can through OS package management -RUN apt-get -y update -RUN apt-get -y install --no-install-recommends \ - curl \ - software-properties-common \ - postgresql-client\ - libpq-dev \ - libxml2-dev \ - libxslt1-dev \ - qt5-default \ - libqt5webkit5-dev \ - xvfb - -# Install the right version of nodejs -RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - -RUN apt-get -y install --no-install-recommends nodejs -RUN apt-get -q clean -RUN npm cache clean -f -RUN npm install -g n -RUN n stable -RUN update-alternatives --install /usr/bin/node node /usr/bin/nodejs 10 - -# Copy in all the application dependencies -ADD package.json /usr/src/app/package.json -RUN npm install - -# Set the user -RUN groupadd -r ruby -RUN useradd --no-log-init -r -g ruby ruby -# USER ruby -RUN whoami - -# Setup our environment -WORKDIR /usr/src/app -ENV RAILS_ENV production - -# Stuff that changes -ADD . . -ADD Gemfile /usr/src/app/Gemfile -ADD Gemfile.lock /usr/src/app/Gemfile.lock -# RUN gem install bundler --version 2.1.4 -RUN gem install rake -v '13.0.1' --source 'https://rubygems.org/' -RUN bundle install -ENV PATH /usr/src/app/node_modules/.bin:$PATH -CMD ["./entrypoint-server.sh"] diff --git a/Dockerfile.old b/Dockerfile.old new file mode 100644 index 0000000..613216e --- /dev/null +++ b/Dockerfile.old @@ -0,0 +1,169 @@ +# Stage 1: Install libararies +FROM centos:8 as builder +USER 0 + +## Setup the yarn repo. +#RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo +#RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg + +## Install the required packages. +RUN dnf update -y && \ + dnf install -y \ + redis +# make \ +# patch \ +# git \ +# yarn \ +# zlib-devel \ +# libpq-devel \ +# libxml2-devel \ +# libxslt-devel \ +# gcc + +# Stage 2: Setup the Image. +FROM registry.il2.dsop.io/platform-one/devops/pipeline-templates/ironbank/ruby26:2.6.6.212 +USER 0 + +RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo +RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg + +RUN dnf update -y && \ + dnf install -y \ + make \ + patch \ + git \ + yarn \ + zlib-devel \ + libpq-devel \ + libxml2-devel \ + libxslt-devel \ + gcc \ + gcc-c++ + +# Redis CLI Binary. +COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli + +## Patch Binary. +#COPY --from=builder /usr/bin/patch /usr/bin/patch +# +## Make Binary. +#COPY --from=builder /usr/bin/make /usr/bin/make +# +## Git Binaries. +#COPY --from=builder /usr/bin/git* /usr/bin/ +# +## Yarn Binaries. +#COPY --from=builder /usr/share/yarn /usr/share/yarn +#RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarn +#RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarnpkg +# +## libz Dependencies. +#COPY --from=builder /usr/lib64/libz* /usr/lib64/ +# +## Postgres Dependencies +#COPY --from=builder /usr/bin/pkgconf /usr/bin/pkgconf +#COPY --from=builder /usr/bin/pg_config /usr/bin/pg_config +#COPY --from=builder /usr/lib64/libpq* /usr/lib64/ +#COPY --from=builder /usr/lib64/libpkgconf* /usr/lib64/ +#COPY --from=builder /usr/include/libpq /usr/include/libpq +#COPY --from=builder /usr/include/libpq* /usr/include/ +#COPY --from=builder /usr/include/pg* /usr/include/ +#COPY --from=builder /usr/include/pgsql /usr/include/pgsql +#COPY --from=builder /usr/include/postgres* /usr/include/ +# +## libxml2 Dependencies. +#COPY --from=builder /usr/lib64/libxml* /usr/lib64/ +#COPY --from=builder /usr/lib64/liblz* /usr/lib64/ +#COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ +#COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ +#COPY --from=builder /usr/include/lzma /usr/include/lzma +#COPY --from=builder /usr/include/zlib* /usr/include/ +#COPY --from=builder /usr/include/zconf* /usr/include/ +#COPY --from=builder /usr/include/libxml2 /usr/include/libxml2 +#COPY --from=builder /usr/lib64/xml2Conf.sh /usr/lib64/xml2Conf.sh +# +## libxslt Dependencies. +#COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libexslt* /usr/lib64/ +#COPY --from=builder /usr/include/libxslt /usr/include/libxslt +#COPY --from=builder /usr/include/libexslt /usr/include/libexslt +#COPY --from=builder /usr/include/gcrypt* /usr/include/ +#COPY --from=builder /usr/include/gpg* /usr/include/ +#COPY --from=builder /usr/lib64/xsltConf.sh /usr/lib64/xsltConf.sh + +## GCC Dependencies. +#COPY --from=builder /usr/bin/gcc* /usr/bin/ +#COPY --from=builder /usr/lib/gcc /usr/lib/gcc +#COPY --from=builder /usr/bin/as /usr/bin/as +#COPY --from=builder /usr/bin/ld /usr/bin/ld +#COPY --from=builder /usr/libexec/gcc /usr/libexec/gcc +#COPY --from=builder /usr/lib64/libmpc* /usr/lib64/ +#COPY --from=builder /usr/lib64/libopcodes* /usr/lib64/ +#COPY --from=builder /usr/lib64/libbfd* /usr/lib64/ +#COPY --from=builder /usr/lib64/libc.so* /usr/lib64/ +#COPY --from=builder /usr/lib64/libc_nonshared* /usr/lib64/ +#COPY --from=builder /usr/lib64/libcrypt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libgomp* /usr/lib64/ +#COPY --from=builder /usr/lib64/libgpg* /usr/lib64/ +#COPY --from=builder /usr/lib64/libgcrypt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libgcc* /usr/lib64/ +#COPY --from=builder /usr/lib64/crt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ +#COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ +#COPY --from=builder /usr/lib64/libmcheck* /usr/lib64/ +#COPY --from=builder /usr/lib64/Mcrt1* /usr/lib64/ +#COPY --from=builder /usr/lib64/Scrt1* /usr/lib64/ +#COPY --from=builder /usr/lib64/gcrt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libanl* /usr/lib64/ +#COPY --from=builder /usr/lib64/libdl* /usr/lib64/ +#COPY --from=builder /usr/lib64/libg* /usr/lib64/ +#COPY --from=builder /usr/lib64/libisl* /usr/lib64/ +#COPY --from=builder /usr/lib64/liblzma* /usr/lib64/ +#COPY --from=builder /usr/lib64/libmvec* /usr/lib64/ +#COPY --from=builder /usr/lib64/libpthread* /usr/lib64/ +#COPY --from=builder /usr/lib64/libresolv* /usr/lib64/ +#COPY --from=builder /usr/lib64/librt* /usr/lib64/ +#COPY --from=builder /usr/lib64/libthread_db* /usr/lib64/ +#COPY --from=builder /usr/lib64/libutil* /usr/lib64/ +#COPY --from=builder /usr/lib64/libz* /usr/lib64/ +#COPY --from=builder /usr/lib64/rcrt* /usr/lib64/ +#COPY --from=builder /usr/include /usr/include + +# Install Node +ADD .nvmrc . +RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n +RUN bash n auto + +# Setup our environment +WORKDIR /usr/src/app +ADD . . + +# Add write permissions. +RUN chown -R 1001 . + +# Become the ruby user +USER 1001 + +# Set the Rails environment variable. +ENV RAILS_ENV production + +# Install Rails Dependecies. +RUN gem install bundler:2.1.4 + +# Run the bundle install +RUN bundle install + +## Run Yarn Install +RUN yarn install + +# Precompile assets +RUN bundle exec rake assets:precompile + +# Asset Clean +RUN bundle exec rake assets:clean + +# Add the node_modules to the path. +ENV PATH /usr/src/app/node_modules/.bin:$PATH + +# Set the entry point. +ENTRYPOINT ["./entrypoint-server.sh"] \ No newline at end of file diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..ec2a93f --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,14 @@ +@Library('DCCSCR@master') _ +dccscrPipeline(version: "0.1.0") + +/* The above format is required for all production submissions. +* The version will be the image tag associated with this container for this branch. +* +* You may limit the job to the build stage or the scanning stage only: +* dccscrPipeline(version: "1.4.2", scan: false) +* dccscrPipeline(version: "1.0.0", build: false) +* +* You may also use a different branch of the jenkins-shared-library. +* Only do this if you know what you are doing: +* @Library('DCCSCR@feature1_branch') _ */ + diff --git a/download.json b/download.json new file mode 100644 index 0000000..39b3156 --- /dev/null +++ b/download.json @@ -0,0 +1,13 @@ +{ "resources": + [ + { + "url" : "https://download.redis.io/releases/redis-6.0.9.tar.gz", + "filename": "redis-6.0.9.tar.gz", + "validation": + { + "type": "sha256", + "value": "dc2bdcf81c620e9f09cfd12e85d3bc631c897b2db7a55218fd8a65eaa37f86dd" + } + } + ] +} diff --git a/.browserslistrc b/scripts/.browserslistrc similarity index 100% rename from .browserslistrc rename to scripts/.browserslistrc diff --git a/.env.example b/scripts/.env.example similarity index 100% rename from .env.example rename to scripts/.env.example diff --git a/.eslintrc.json b/scripts/.eslintrc.json similarity index 100% rename from .eslintrc.json rename to scripts/.eslintrc.json diff --git a/.haml-lint.yml b/scripts/.haml-lint.yml similarity index 100% rename from .haml-lint.yml rename to scripts/.haml-lint.yml diff --git a/.nvmrc b/scripts/.nvmrc similarity index 100% rename from .nvmrc rename to scripts/.nvmrc diff --git a/.overcommit.yml b/scripts/.overcommit.yml similarity index 100% rename from .overcommit.yml rename to scripts/.overcommit.yml diff --git a/.postcssrc.yml b/scripts/.postcssrc.yml similarity index 100% rename from .postcssrc.yml rename to scripts/.postcssrc.yml diff --git a/.rubocop.yml b/scripts/.rubocop.yml similarity index 100% rename from .rubocop.yml rename to scripts/.rubocop.yml diff --git a/.ruby-version b/scripts/.ruby-version similarity index 100% rename from .ruby-version rename to scripts/.ruby-version diff --git a/.stylelintrc b/scripts/.stylelintrc similarity index 100% rename from .stylelintrc rename to scripts/.stylelintrc diff --git a/Gemfile b/scripts/Gemfile similarity index 100% rename from Gemfile rename to scripts/Gemfile diff --git a/Gemfile.lock b/scripts/Gemfile.lock similarity index 100% rename from Gemfile.lock rename to scripts/Gemfile.lock diff --git a/Procfile b/scripts/Procfile similarity index 100% rename from Procfile rename to scripts/Procfile diff --git a/Procfile.local b/scripts/Procfile.local similarity index 100% rename from Procfile.local rename to scripts/Procfile.local diff --git a/README.md b/scripts/README.md similarity index 100% rename from README.md rename to scripts/README.md diff --git a/Rakefile b/scripts/Rakefile similarity index 100% rename from Rakefile rename to scripts/Rakefile diff --git a/app.json b/scripts/app.json similarity index 100% rename from app.json rename to scripts/app.json diff --git a/app/assets/config/manifest.js b/scripts/app/assets/config/manifest.js similarity index 100% rename from app/assets/config/manifest.js rename to scripts/app/assets/config/manifest.js diff --git a/app/assets/images/favicon.ico b/scripts/app/assets/images/favicon.ico similarity index 100% rename from app/assets/images/favicon.ico rename to scripts/app/assets/images/favicon.ico diff --git a/app/assets/images/loader-black.svg b/scripts/app/assets/images/loader-black.svg similarity index 100% rename from app/assets/images/loader-black.svg rename to scripts/app/assets/images/loader-black.svg diff --git a/app/assets/images/loader-cyan.svg b/scripts/app/assets/images/loader-cyan.svg similarity index 100% rename from app/assets/images/loader-cyan.svg rename to scripts/app/assets/images/loader-cyan.svg diff --git a/app/assets/images/loader-white.svg b/scripts/app/assets/images/loader-white.svg similarity index 100% rename from app/assets/images/loader-white.svg rename to scripts/app/assets/images/loader-white.svg diff --git a/app/assets/images/lost.jpg b/scripts/app/assets/images/lost.jpg similarity index 100% rename from app/assets/images/lost.jpg rename to scripts/app/assets/images/lost.jpg diff --git a/app/assets/images/spinner.gif b/scripts/app/assets/images/spinner.gif similarity index 100% rename from app/assets/images/spinner.gif rename to scripts/app/assets/images/spinner.gif diff --git a/app/assets/images/svg/baseline-notes-24px.svg b/scripts/app/assets/images/svg/baseline-notes-24px.svg similarity index 100% rename from app/assets/images/svg/baseline-notes-24px.svg rename to scripts/app/assets/images/svg/baseline-notes-24px.svg diff --git a/app/assets/images/svg/g-learn-lockup.svg b/scripts/app/assets/images/svg/g-learn-lockup.svg similarity index 100% rename from app/assets/images/svg/g-learn-lockup.svg rename to scripts/app/assets/images/svg/g-learn-lockup.svg diff --git a/app/assets/images/svg/galvanize-logo.svg b/scripts/app/assets/images/svg/galvanize-logo.svg similarity index 100% rename from app/assets/images/svg/galvanize-logo.svg rename to scripts/app/assets/images/svg/galvanize-logo.svg diff --git a/app/assets/images/svg/github-icon.svg b/scripts/app/assets/images/svg/github-icon.svg similarity index 100% rename from app/assets/images/svg/github-icon.svg rename to scripts/app/assets/images/svg/github-icon.svg diff --git a/app/assets/images/svg/mobile-logo.svg b/scripts/app/assets/images/svg/mobile-logo.svg similarity index 100% rename from app/assets/images/svg/mobile-logo.svg rename to scripts/app/assets/images/svg/mobile-logo.svg diff --git a/app/assets/images/svg/octicon-git-branch.svg b/scripts/app/assets/images/svg/octicon-git-branch.svg similarity index 100% rename from app/assets/images/svg/octicon-git-branch.svg rename to scripts/app/assets/images/svg/octicon-git-branch.svg diff --git a/app/assets/images/svg/svg-sprite-action-symbol.svg b/scripts/app/assets/images/svg/svg-sprite-action-symbol.svg similarity index 100% rename from app/assets/images/svg/svg-sprite-action-symbol.svg rename to scripts/app/assets/images/svg/svg-sprite-action-symbol.svg diff --git a/app/assets/images/svg/svg-sprite-av-symbol.svg b/scripts/app/assets/images/svg/svg-sprite-av-symbol.svg similarity index 100% rename from app/assets/images/svg/svg-sprite-av-symbol.svg rename to scripts/app/assets/images/svg/svg-sprite-av-symbol.svg diff --git a/app/assets/images/svg/svg-sprite-device-symbol.svg b/scripts/app/assets/images/svg/svg-sprite-device-symbol.svg similarity index 100% rename from app/assets/images/svg/svg-sprite-device-symbol.svg rename to scripts/app/assets/images/svg/svg-sprite-device-symbol.svg diff --git a/app/assets/images/svg/svg-sprite-image-symbol.svg b/scripts/app/assets/images/svg/svg-sprite-image-symbol.svg similarity index 100% rename from app/assets/images/svg/svg-sprite-image-symbol.svg rename to scripts/app/assets/images/svg/svg-sprite-image-symbol.svg diff --git a/app/assets/images/svg/svg-sprite-navigation-symbol.svg b/scripts/app/assets/images/svg/svg-sprite-navigation-symbol.svg similarity index 100% rename from app/assets/images/svg/svg-sprite-navigation-symbol.svg rename to scripts/app/assets/images/svg/svg-sprite-navigation-symbol.svg diff --git a/app/assets/javascripts/application.js b/scripts/app/assets/javascripts/application.js similarity index 100% rename from app/assets/javascripts/application.js rename to scripts/app/assets/javascripts/application.js diff --git a/app/assets/javascripts/mobile.js b/scripts/app/assets/javascripts/mobile.js similarity index 100% rename from app/assets/javascripts/mobile.js rename to scripts/app/assets/javascripts/mobile.js diff --git a/app/assets/stylesheets/application.scss b/scripts/app/assets/stylesheets/application.scss similarity index 100% rename from app/assets/stylesheets/application.scss rename to scripts/app/assets/stylesheets/application.scss diff --git a/app/assets/stylesheets/base.scss b/scripts/app/assets/stylesheets/base.scss similarity index 100% rename from app/assets/stylesheets/base.scss rename to scripts/app/assets/stylesheets/base.scss diff --git a/app/assets/stylesheets/bootstrap-custom.scss b/scripts/app/assets/stylesheets/bootstrap-custom.scss similarity index 100% rename from app/assets/stylesheets/bootstrap-custom.scss rename to scripts/app/assets/stylesheets/bootstrap-custom.scss diff --git a/app/assets/stylesheets/cohorts.scss b/scripts/app/assets/stylesheets/cohorts.scss similarity index 100% rename from app/assets/stylesheets/cohorts.scss rename to scripts/app/assets/stylesheets/cohorts.scss diff --git a/app/assets/stylesheets/components/_404-container.scss b/scripts/app/assets/stylesheets/components/_404-container.scss similarity index 100% rename from app/assets/stylesheets/components/_404-container.scss rename to scripts/app/assets/stylesheets/components/_404-container.scss diff --git a/app/assets/stylesheets/components/_action-menu.scss b/scripts/app/assets/stylesheets/components/_action-menu.scss similarity index 100% rename from app/assets/stylesheets/components/_action-menu.scss rename to scripts/app/assets/stylesheets/components/_action-menu.scss diff --git a/app/assets/stylesheets/components/_activity-dashboard.scss b/scripts/app/assets/stylesheets/components/_activity-dashboard.scss similarity index 100% rename from app/assets/stylesheets/components/_activity-dashboard.scss rename to scripts/app/assets/stylesheets/components/_activity-dashboard.scss diff --git a/app/assets/stylesheets/components/_activity-feed.scss b/scripts/app/assets/stylesheets/components/_activity-feed.scss similarity index 100% rename from app/assets/stylesheets/components/_activity-feed.scss rename to scripts/app/assets/stylesheets/components/_activity-feed.scss diff --git a/app/assets/stylesheets/components/_api-interactions.scss b/scripts/app/assets/stylesheets/components/_api-interactions.scss similarity index 100% rename from app/assets/stylesheets/components/_api-interactions.scss rename to scripts/app/assets/stylesheets/components/_api-interactions.scss diff --git a/app/assets/stylesheets/components/_api_token.scss b/scripts/app/assets/stylesheets/components/_api_token.scss similarity index 100% rename from app/assets/stylesheets/components/_api_token.scss rename to scripts/app/assets/stylesheets/components/_api_token.scss diff --git a/app/assets/stylesheets/components/_auth-style-search.scss b/scripts/app/assets/stylesheets/components/_auth-style-search.scss similarity index 100% rename from app/assets/stylesheets/components/_auth-style-search.scss rename to scripts/app/assets/stylesheets/components/_auth-style-search.scss diff --git a/app/assets/stylesheets/components/_blocks-index.scss b/scripts/app/assets/stylesheets/components/_blocks-index.scss similarity index 100% rename from app/assets/stylesheets/components/_blocks-index.scss rename to scripts/app/assets/stylesheets/components/_blocks-index.scss diff --git a/app/assets/stylesheets/components/_blocks-stats.scss b/scripts/app/assets/stylesheets/components/_blocks-stats.scss similarity index 100% rename from app/assets/stylesheets/components/_blocks-stats.scss rename to scripts/app/assets/stylesheets/components/_blocks-stats.scss diff --git a/app/assets/stylesheets/components/_button.scss b/scripts/app/assets/stylesheets/components/_button.scss similarity index 100% rename from app/assets/stylesheets/components/_button.scss rename to scripts/app/assets/stylesheets/components/_button.scss diff --git a/app/assets/stylesheets/components/_callouts.scss b/scripts/app/assets/stylesheets/components/_callouts.scss similarity index 100% rename from app/assets/stylesheets/components/_callouts.scss rename to scripts/app/assets/stylesheets/components/_callouts.scss diff --git a/app/assets/stylesheets/components/_challenge-block.scss b/scripts/app/assets/stylesheets/components/_challenge-block.scss similarity index 100% rename from app/assets/stylesheets/components/_challenge-block.scss rename to scripts/app/assets/stylesheets/components/_challenge-block.scss diff --git a/app/assets/stylesheets/components/_challenge_status.scss b/scripts/app/assets/stylesheets/components/_challenge_status.scss similarity index 100% rename from app/assets/stylesheets/components/_challenge_status.scss rename to scripts/app/assets/stylesheets/components/_challenge_status.scss diff --git a/app/assets/stylesheets/components/_checkbox-input.scss b/scripts/app/assets/stylesheets/components/_checkbox-input.scss similarity index 100% rename from app/assets/stylesheets/components/_checkbox-input.scss rename to scripts/app/assets/stylesheets/components/_checkbox-input.scss diff --git a/app/assets/stylesheets/components/_checkpoint-landing.scss b/scripts/app/assets/stylesheets/components/_checkpoint-landing.scss similarity index 100% rename from app/assets/stylesheets/components/_checkpoint-landing.scss rename to scripts/app/assets/stylesheets/components/_checkpoint-landing.scss diff --git a/app/assets/stylesheets/components/_checkpoint-submission.scss b/scripts/app/assets/stylesheets/components/_checkpoint-submission.scss similarity index 100% rename from app/assets/stylesheets/components/_checkpoint-submission.scss rename to scripts/app/assets/stylesheets/components/_checkpoint-submission.scss diff --git a/app/assets/stylesheets/components/_checkpoint-submissions-columns-student.scss b/scripts/app/assets/stylesheets/components/_checkpoint-submissions-columns-student.scss similarity index 100% rename from app/assets/stylesheets/components/_checkpoint-submissions-columns-student.scss rename to scripts/app/assets/stylesheets/components/_checkpoint-submissions-columns-student.scss diff --git a/app/assets/stylesheets/components/_checkpoint-submissions-columns.scss b/scripts/app/assets/stylesheets/components/_checkpoint-submissions-columns.scss similarity index 100% rename from app/assets/stylesheets/components/_checkpoint-submissions-columns.scss rename to scripts/app/assets/stylesheets/components/_checkpoint-submissions-columns.scss diff --git a/app/assets/stylesheets/components/_checkpoint-submissions-index.scss b/scripts/app/assets/stylesheets/components/_checkpoint-submissions-index.scss similarity index 100% rename from app/assets/stylesheets/components/_checkpoint-submissions-index.scss rename to scripts/app/assets/stylesheets/components/_checkpoint-submissions-index.scss diff --git a/app/assets/stylesheets/components/_checkpoint_student_scores.scss b/scripts/app/assets/stylesheets/components/_checkpoint_student_scores.scss similarity index 100% rename from app/assets/stylesheets/components/_checkpoint_student_scores.scss rename to scripts/app/assets/stylesheets/components/_checkpoint_student_scores.scss diff --git a/app/assets/stylesheets/components/_checkpoint_submisstion_state.scss b/scripts/app/assets/stylesheets/components/_checkpoint_submisstion_state.scss similarity index 100% rename from app/assets/stylesheets/components/_checkpoint_submisstion_state.scss rename to scripts/app/assets/stylesheets/components/_checkpoint_submisstion_state.scss diff --git a/app/assets/stylesheets/components/_checkpoint_toolbar.scss b/scripts/app/assets/stylesheets/components/_checkpoint_toolbar.scss similarity index 100% rename from app/assets/stylesheets/components/_checkpoint_toolbar.scss rename to scripts/app/assets/stylesheets/components/_checkpoint_toolbar.scss diff --git a/app/assets/stylesheets/components/_code-block.scss b/scripts/app/assets/stylesheets/components/_code-block.scss similarity index 100% rename from app/assets/stylesheets/components/_code-block.scss rename to scripts/app/assets/stylesheets/components/_code-block.scss diff --git a/app/assets/stylesheets/components/_cohort-submissions-table.scss b/scripts/app/assets/stylesheets/components/_cohort-submissions-table.scss similarity index 100% rename from app/assets/stylesheets/components/_cohort-submissions-table.scss rename to scripts/app/assets/stylesheets/components/_cohort-submissions-table.scss diff --git a/app/assets/stylesheets/components/_cohort_releases.scss b/scripts/app/assets/stylesheets/components/_cohort_releases.scss similarity index 100% rename from app/assets/stylesheets/components/_cohort_releases.scss rename to scripts/app/assets/stylesheets/components/_cohort_releases.scss diff --git a/app/assets/stylesheets/components/_cohorts.scss b/scripts/app/assets/stylesheets/components/_cohorts.scss similarity index 100% rename from app/assets/stylesheets/components/_cohorts.scss rename to scripts/app/assets/stylesheets/components/_cohorts.scss diff --git a/app/assets/stylesheets/components/_content-file-show.scss b/scripts/app/assets/stylesheets/components/_content-file-show.scss similarity index 100% rename from app/assets/stylesheets/components/_content-file-show.scss rename to scripts/app/assets/stylesheets/components/_content-file-show.scss diff --git a/app/assets/stylesheets/components/_content-file-sidebar.scss b/scripts/app/assets/stylesheets/components/_content-file-sidebar.scss similarity index 100% rename from app/assets/stylesheets/components/_content-file-sidebar.scss rename to scripts/app/assets/stylesheets/components/_content-file-sidebar.scss diff --git a/app/assets/stylesheets/components/_curriculum-checkpoint-summary.scss b/scripts/app/assets/stylesheets/components/_curriculum-checkpoint-summary.scss similarity index 100% rename from app/assets/stylesheets/components/_curriculum-checkpoint-summary.scss rename to scripts/app/assets/stylesheets/components/_curriculum-checkpoint-summary.scss diff --git a/app/assets/stylesheets/components/_curriculum-progress.scss b/scripts/app/assets/stylesheets/components/_curriculum-progress.scss similarity index 100% rename from app/assets/stylesheets/components/_curriculum-progress.scss rename to scripts/app/assets/stylesheets/components/_curriculum-progress.scss diff --git a/app/assets/stylesheets/components/_curriculum.scss b/scripts/app/assets/stylesheets/components/_curriculum.scss similarity index 100% rename from app/assets/stylesheets/components/_curriculum.scss rename to scripts/app/assets/stylesheets/components/_curriculum.scss diff --git a/app/assets/stylesheets/components/_dropdown-component.scss b/scripts/app/assets/stylesheets/components/_dropdown-component.scss similarity index 100% rename from app/assets/stylesheets/components/_dropdown-component.scss rename to scripts/app/assets/stylesheets/components/_dropdown-component.scss diff --git a/app/assets/stylesheets/components/_external-link.scss b/scripts/app/assets/stylesheets/components/_external-link.scss similarity index 100% rename from app/assets/stylesheets/components/_external-link.scss rename to scripts/app/assets/stylesheets/components/_external-link.scss diff --git a/app/assets/stylesheets/components/_flash-message.scss b/scripts/app/assets/stylesheets/components/_flash-message.scss similarity index 100% rename from app/assets/stylesheets/components/_flash-message.scss rename to scripts/app/assets/stylesheets/components/_flash-message.scss diff --git a/app/assets/stylesheets/components/_footer.scss b/scripts/app/assets/stylesheets/components/_footer.scss similarity index 100% rename from app/assets/stylesheets/components/_footer.scss rename to scripts/app/assets/stylesheets/components/_footer.scss diff --git a/app/assets/stylesheets/components/_grade-buttons.scss b/scripts/app/assets/stylesheets/components/_grade-buttons.scss similarity index 100% rename from app/assets/stylesheets/components/_grade-buttons.scss rename to scripts/app/assets/stylesheets/components/_grade-buttons.scss diff --git a/app/assets/stylesheets/components/_integer_picker.scss b/scripts/app/assets/stylesheets/components/_integer_picker.scss similarity index 100% rename from app/assets/stylesheets/components/_integer_picker.scss rename to scripts/app/assets/stylesheets/components/_integer_picker.scss diff --git a/app/assets/stylesheets/components/_lp-style-button.scss b/scripts/app/assets/stylesheets/components/_lp-style-button.scss similarity index 100% rename from app/assets/stylesheets/components/_lp-style-button.scss rename to scripts/app/assets/stylesheets/components/_lp-style-button.scss diff --git a/app/assets/stylesheets/components/_mastery-table.scss b/scripts/app/assets/stylesheets/components/_mastery-table.scss similarity index 100% rename from app/assets/stylesheets/components/_mastery-table.scss rename to scripts/app/assets/stylesheets/components/_mastery-table.scss diff --git a/app/assets/stylesheets/components/_modal.scss b/scripts/app/assets/stylesheets/components/_modal.scss similarity index 100% rename from app/assets/stylesheets/components/_modal.scss rename to scripts/app/assets/stylesheets/components/_modal.scss diff --git a/app/assets/stylesheets/components/_navigation-dropdown.scss b/scripts/app/assets/stylesheets/components/_navigation-dropdown.scss similarity index 100% rename from app/assets/stylesheets/components/_navigation-dropdown.scss rename to scripts/app/assets/stylesheets/components/_navigation-dropdown.scss diff --git a/app/assets/stylesheets/components/_notifications.scss b/scripts/app/assets/stylesheets/components/_notifications.scss similarity index 100% rename from app/assets/stylesheets/components/_notifications.scss rename to scripts/app/assets/stylesheets/components/_notifications.scss diff --git a/app/assets/stylesheets/components/_pagination.scss b/scripts/app/assets/stylesheets/components/_pagination.scss similarity index 100% rename from app/assets/stylesheets/components/_pagination.scss rename to scripts/app/assets/stylesheets/components/_pagination.scss diff --git a/app/assets/stylesheets/components/_pill.scss b/scripts/app/assets/stylesheets/components/_pill.scss similarity index 100% rename from app/assets/stylesheets/components/_pill.scss rename to scripts/app/assets/stylesheets/components/_pill.scss diff --git a/app/assets/stylesheets/components/_point_grade_buttons.scss b/scripts/app/assets/stylesheets/components/_point_grade_buttons.scss similarity index 100% rename from app/assets/stylesheets/components/_point_grade_buttons.scss rename to scripts/app/assets/stylesheets/components/_point_grade_buttons.scss diff --git a/app/assets/stylesheets/components/_primary-header.scss b/scripts/app/assets/stylesheets/components/_primary-header.scss similarity index 100% rename from app/assets/stylesheets/components/_primary-header.scss rename to scripts/app/assets/stylesheets/components/_primary-header.scss diff --git a/app/assets/stylesheets/components/_primary-navigation.scss b/scripts/app/assets/stylesheets/components/_primary-navigation.scss similarity index 100% rename from app/assets/stylesheets/components/_primary-navigation.scss rename to scripts/app/assets/stylesheets/components/_primary-navigation.scss diff --git a/app/assets/stylesheets/components/_progress.scss b/scripts/app/assets/stylesheets/components/_progress.scss similarity index 100% rename from app/assets/stylesheets/components/_progress.scss rename to scripts/app/assets/stylesheets/components/_progress.scss diff --git a/app/assets/stylesheets/components/_progress_thresholds_key.scss b/scripts/app/assets/stylesheets/components/_progress_thresholds_key.scss similarity index 100% rename from app/assets/stylesheets/components/_progress_thresholds_key.scss rename to scripts/app/assets/stylesheets/components/_progress_thresholds_key.scss diff --git a/app/assets/stylesheets/components/_progress_thresholds_modal.scss b/scripts/app/assets/stylesheets/components/_progress_thresholds_modal.scss similarity index 100% rename from app/assets/stylesheets/components/_progress_thresholds_modal.scss rename to scripts/app/assets/stylesheets/components/_progress_thresholds_modal.scss diff --git a/app/assets/stylesheets/components/_progress_thresholds_slider.scss b/scripts/app/assets/stylesheets/components/_progress_thresholds_slider.scss similarity index 100% rename from app/assets/stylesheets/components/_progress_thresholds_slider.scss rename to scripts/app/assets/stylesheets/components/_progress_thresholds_slider.scss diff --git a/app/assets/stylesheets/components/_search-bar.scss b/scripts/app/assets/stylesheets/components/_search-bar.scss similarity index 100% rename from app/assets/stylesheets/components/_search-bar.scss rename to scripts/app/assets/stylesheets/components/_search-bar.scss diff --git a/app/assets/stylesheets/components/_secondary-navigation.scss b/scripts/app/assets/stylesheets/components/_secondary-navigation.scss similarity index 100% rename from app/assets/stylesheets/components/_secondary-navigation.scss rename to scripts/app/assets/stylesheets/components/_secondary-navigation.scss diff --git a/app/assets/stylesheets/components/_slideshow.scss b/scripts/app/assets/stylesheets/components/_slideshow.scss similarity index 100% rename from app/assets/stylesheets/components/_slideshow.scss rename to scripts/app/assets/stylesheets/components/_slideshow.scss diff --git a/app/assets/stylesheets/components/_sort-dropdown-component.scss b/scripts/app/assets/stylesheets/components/_sort-dropdown-component.scss similarity index 100% rename from app/assets/stylesheets/components/_sort-dropdown-component.scss rename to scripts/app/assets/stylesheets/components/_sort-dropdown-component.scss diff --git a/app/assets/stylesheets/components/_standard-card.scss b/scripts/app/assets/stylesheets/components/_standard-card.scss similarity index 100% rename from app/assets/stylesheets/components/_standard-card.scss rename to scripts/app/assets/stylesheets/components/_standard-card.scss diff --git a/app/assets/stylesheets/components/_standard-cards.scss b/scripts/app/assets/stylesheets/components/_standard-cards.scss similarity index 100% rename from app/assets/stylesheets/components/_standard-cards.scss rename to scripts/app/assets/stylesheets/components/_standard-cards.scss diff --git a/app/assets/stylesheets/components/_standards-mastery-beans.scss b/scripts/app/assets/stylesheets/components/_standards-mastery-beans.scss similarity index 100% rename from app/assets/stylesheets/components/_standards-mastery-beans.scss rename to scripts/app/assets/stylesheets/components/_standards-mastery-beans.scss diff --git a/app/assets/stylesheets/components/_status_picker.scss b/scripts/app/assets/stylesheets/components/_status_picker.scss similarity index 100% rename from app/assets/stylesheets/components/_status_picker.scss rename to scripts/app/assets/stylesheets/components/_status_picker.scss diff --git a/app/assets/stylesheets/components/_student-mastery-table.scss b/scripts/app/assets/stylesheets/components/_student-mastery-table.scss similarity index 100% rename from app/assets/stylesheets/components/_student-mastery-table.scss rename to scripts/app/assets/stylesheets/components/_student-mastery-table.scss diff --git a/app/assets/stylesheets/components/_student-name-bar.scss b/scripts/app/assets/stylesheets/components/_student-name-bar.scss similarity index 100% rename from app/assets/stylesheets/components/_student-name-bar.scss rename to scripts/app/assets/stylesheets/components/_student-name-bar.scss diff --git a/app/assets/stylesheets/components/_submissions-dashboard-table.scss b/scripts/app/assets/stylesheets/components/_submissions-dashboard-table.scss similarity index 100% rename from app/assets/stylesheets/components/_submissions-dashboard-table.scss rename to scripts/app/assets/stylesheets/components/_submissions-dashboard-table.scss diff --git a/app/assets/stylesheets/components/_svg-icon.scss b/scripts/app/assets/stylesheets/components/_svg-icon.scss similarity index 100% rename from app/assets/stylesheets/components/_svg-icon.scss rename to scripts/app/assets/stylesheets/components/_svg-icon.scss diff --git a/app/assets/stylesheets/components/_thresholds.scss b/scripts/app/assets/stylesheets/components/_thresholds.scss similarity index 100% rename from app/assets/stylesheets/components/_thresholds.scss rename to scripts/app/assets/stylesheets/components/_thresholds.scss diff --git a/app/assets/stylesheets/components/_universal-list.scss b/scripts/app/assets/stylesheets/components/_universal-list.scss similarity index 100% rename from app/assets/stylesheets/components/_universal-list.scss rename to scripts/app/assets/stylesheets/components/_universal-list.scss diff --git a/app/assets/stylesheets/components/_universal-row.scss b/scripts/app/assets/stylesheets/components/_universal-row.scss similarity index 100% rename from app/assets/stylesheets/components/_universal-row.scss rename to scripts/app/assets/stylesheets/components/_universal-row.scss diff --git a/app/assets/stylesheets/components/_universal-table.scss b/scripts/app/assets/stylesheets/components/_universal-table.scss similarity index 100% rename from app/assets/stylesheets/components/_universal-table.scss rename to scripts/app/assets/stylesheets/components/_universal-table.scss diff --git a/app/assets/stylesheets/components/_user-avatar.scss b/scripts/app/assets/stylesheets/components/_user-avatar.scss similarity index 100% rename from app/assets/stylesheets/components/_user-avatar.scss rename to scripts/app/assets/stylesheets/components/_user-avatar.scss diff --git a/app/assets/stylesheets/components/_video-player.scss b/scripts/app/assets/stylesheets/components/_video-player.scss similarity index 100% rename from app/assets/stylesheets/components/_video-player.scss rename to scripts/app/assets/stylesheets/components/_video-player.scss diff --git a/app/assets/stylesheets/components/badge.scss b/scripts/app/assets/stylesheets/components/badge.scss similarity index 100% rename from app/assets/stylesheets/components/badge.scss rename to scripts/app/assets/stylesheets/components/badge.scss diff --git a/app/assets/stylesheets/components/challenge-detail-comments.scss b/scripts/app/assets/stylesheets/components/challenge-detail-comments.scss similarity index 100% rename from app/assets/stylesheets/components/challenge-detail-comments.scss rename to scripts/app/assets/stylesheets/components/challenge-detail-comments.scss diff --git a/app/assets/stylesheets/components/challenge-detail-view.scss b/scripts/app/assets/stylesheets/components/challenge-detail-view.scss similarity index 100% rename from app/assets/stylesheets/components/challenge-detail-view.scss rename to scripts/app/assets/stylesheets/components/challenge-detail-view.scss diff --git a/app/assets/stylesheets/components/challenge-timeline.scss b/scripts/app/assets/stylesheets/components/challenge-timeline.scss similarity index 100% rename from app/assets/stylesheets/components/challenge-timeline.scss rename to scripts/app/assets/stylesheets/components/challenge-timeline.scss diff --git a/app/assets/stylesheets/components/new-comment-form.scss b/scripts/app/assets/stylesheets/components/new-comment-form.scss similarity index 100% rename from app/assets/stylesheets/components/new-comment-form.scss rename to scripts/app/assets/stylesheets/components/new-comment-form.scss diff --git a/app/assets/stylesheets/components/partnerup.scss b/scripts/app/assets/stylesheets/components/partnerup.scss similarity index 100% rename from app/assets/stylesheets/components/partnerup.scss rename to scripts/app/assets/stylesheets/components/partnerup.scss diff --git a/app/assets/stylesheets/components/progress-table.scss b/scripts/app/assets/stylesheets/components/progress-table.scss similarity index 100% rename from app/assets/stylesheets/components/progress-table.scss rename to scripts/app/assets/stylesheets/components/progress-table.scss diff --git a/app/assets/stylesheets/hopscotch-overrides.scss b/scripts/app/assets/stylesheets/hopscotch-overrides.scss similarity index 100% rename from app/assets/stylesheets/hopscotch-overrides.scss rename to scripts/app/assets/stylesheets/hopscotch-overrides.scss diff --git a/app/assets/stylesheets/mixins.scss b/scripts/app/assets/stylesheets/mixins.scss similarity index 100% rename from app/assets/stylesheets/mixins.scss rename to scripts/app/assets/stylesheets/mixins.scss diff --git a/app/assets/stylesheets/mobile.scss b/scripts/app/assets/stylesheets/mobile.scss similarity index 100% rename from app/assets/stylesheets/mobile.scss rename to scripts/app/assets/stylesheets/mobile.scss diff --git a/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss b/scripts/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss similarity index 100% rename from app/assets/stylesheets/mobile/_submissions-dashboard-table.scss rename to scripts/app/assets/stylesheets/mobile/_submissions-dashboard-table.scss diff --git a/app/assets/stylesheets/typography.scss b/scripts/app/assets/stylesheets/typography.scss similarity index 100% rename from app/assets/stylesheets/typography.scss rename to scripts/app/assets/stylesheets/typography.scss diff --git a/app/assets/stylesheets/variables.scss b/scripts/app/assets/stylesheets/variables.scss similarity index 100% rename from app/assets/stylesheets/variables.scss rename to scripts/app/assets/stylesheets/variables.scss diff --git a/app/component_props/activity_feed_item_component_props.rb b/scripts/app/component_props/activity_feed_item_component_props.rb similarity index 100% rename from app/component_props/activity_feed_item_component_props.rb rename to scripts/app/component_props/activity_feed_item_component_props.rb diff --git a/app/component_props/notifications_component_props.rb b/scripts/app/component_props/notifications_component_props.rb similarity index 100% rename from app/component_props/notifications_component_props.rb rename to scripts/app/component_props/notifications_component_props.rb diff --git a/app/component_props/standard_card_component_props.rb b/scripts/app/component_props/standard_card_component_props.rb similarity index 100% rename from app/component_props/standard_card_component_props.rb rename to scripts/app/component_props/standard_card_component_props.rb diff --git a/app/controllers/api/application_controller.rb b/scripts/app/controllers/api/application_controller.rb similarity index 100% rename from app/controllers/api/application_controller.rb rename to scripts/app/controllers/api/application_controller.rb diff --git a/app/controllers/api/v1/blocks/releases_controller.rb b/scripts/app/controllers/api/v1/blocks/releases_controller.rb similarity index 100% rename from app/controllers/api/v1/blocks/releases_controller.rb rename to scripts/app/controllers/api/v1/blocks/releases_controller.rb diff --git a/app/controllers/api/v1/blocks_controller.rb b/scripts/app/controllers/api/v1/blocks_controller.rb similarity index 100% rename from app/controllers/api/v1/blocks_controller.rb rename to scripts/app/controllers/api/v1/blocks_controller.rb diff --git a/app/controllers/api/v1/cohorts/blocks/content_files_controller.rb b/scripts/app/controllers/api/v1/cohorts/blocks/content_files_controller.rb similarity index 100% rename from app/controllers/api/v1/cohorts/blocks/content_files_controller.rb rename to scripts/app/controllers/api/v1/cohorts/blocks/content_files_controller.rb diff --git a/app/controllers/api/v1/cohorts/blocks/units_controller.rb b/scripts/app/controllers/api/v1/cohorts/blocks/units_controller.rb similarity index 100% rename from app/controllers/api/v1/cohorts/blocks/units_controller.rb rename to scripts/app/controllers/api/v1/cohorts/blocks/units_controller.rb diff --git a/app/controllers/api/v1/cohorts/cohorts_controller.rb b/scripts/app/controllers/api/v1/cohorts/cohorts_controller.rb similarity index 100% rename from app/controllers/api/v1/cohorts/cohorts_controller.rb rename to scripts/app/controllers/api/v1/cohorts/cohorts_controller.rb diff --git a/app/controllers/api/v1/cohorts/users_controller.rb b/scripts/app/controllers/api/v1/cohorts/users_controller.rb similarity index 100% rename from app/controllers/api/v1/cohorts/users_controller.rb rename to scripts/app/controllers/api/v1/cohorts/users_controller.rb diff --git a/app/controllers/api/v1/content_files_controller.rb b/scripts/app/controllers/api/v1/content_files_controller.rb similarity index 100% rename from app/controllers/api/v1/content_files_controller.rb rename to scripts/app/controllers/api/v1/content_files_controller.rb diff --git a/app/controllers/api/v1/pineapple_controller.rb b/scripts/app/controllers/api/v1/pineapple_controller.rb similarity index 100% rename from app/controllers/api/v1/pineapple_controller.rb rename to scripts/app/controllers/api/v1/pineapple_controller.rb diff --git a/app/controllers/api/v1/releases_controller.rb b/scripts/app/controllers/api/v1/releases_controller.rb similarity index 100% rename from app/controllers/api/v1/releases_controller.rb rename to scripts/app/controllers/api/v1/releases_controller.rb diff --git a/app/controllers/api/v1/users_controller.rb b/scripts/app/controllers/api/v1/users_controller.rb similarity index 100% rename from app/controllers/api/v1/users_controller.rb rename to scripts/app/controllers/api/v1/users_controller.rb diff --git a/app/controllers/application_controller.rb b/scripts/app/controllers/application_controller.rb similarity index 100% rename from app/controllers/application_controller.rb rename to scripts/app/controllers/application_controller.rb diff --git a/app/controllers/blocks/releases_controller.rb b/scripts/app/controllers/blocks/releases_controller.rb similarity index 100% rename from app/controllers/blocks/releases_controller.rb rename to scripts/app/controllers/blocks/releases_controller.rb diff --git a/app/controllers/blocks_controller.rb b/scripts/app/controllers/blocks_controller.rb similarity index 100% rename from app/controllers/blocks_controller.rb rename to scripts/app/controllers/blocks_controller.rb diff --git a/app/controllers/cohorts/blocks/content_files_controller.rb b/scripts/app/controllers/cohorts/blocks/content_files_controller.rb similarity index 100% rename from app/controllers/cohorts/blocks/content_files_controller.rb rename to scripts/app/controllers/cohorts/blocks/content_files_controller.rb diff --git a/app/controllers/cohorts/checkpoint_submissions/activities_controller.rb b/scripts/app/controllers/cohorts/checkpoint_submissions/activities_controller.rb similarity index 100% rename from app/controllers/cohorts/checkpoint_submissions/activities_controller.rb rename to scripts/app/controllers/cohorts/checkpoint_submissions/activities_controller.rb diff --git a/app/controllers/cohorts/cohort_releases_controller.rb b/scripts/app/controllers/cohorts/cohort_releases_controller.rb similarity index 100% rename from app/controllers/cohorts/cohort_releases_controller.rb rename to scripts/app/controllers/cohorts/cohort_releases_controller.rb diff --git a/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb b/scripts/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb similarity index 100% rename from app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb rename to scripts/app/controllers/cohorts/content_files/checkpoint_submissions_controller.rb diff --git a/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb b/scripts/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb similarity index 100% rename from app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb rename to scripts/app/controllers/cohorts/content_files/submitted_challenge_answers_controller.rb diff --git a/app/controllers/cohorts/content_files_controller.rb b/scripts/app/controllers/cohorts/content_files_controller.rb similarity index 100% rename from app/controllers/cohorts/content_files_controller.rb rename to scripts/app/controllers/cohorts/content_files_controller.rb diff --git a/app/controllers/cohorts/pairings_controller.rb b/scripts/app/controllers/cohorts/pairings_controller.rb similarity index 100% rename from app/controllers/cohorts/pairings_controller.rb rename to scripts/app/controllers/cohorts/pairings_controller.rb diff --git a/app/controllers/cohorts/standards_controller.rb b/scripts/app/controllers/cohorts/standards_controller.rb similarity index 100% rename from app/controllers/cohorts/standards_controller.rb rename to scripts/app/controllers/cohorts/standards_controller.rb diff --git a/app/controllers/cohorts/submitted_challenge_answers/activities_controller.rb b/scripts/app/controllers/cohorts/submitted_challenge_answers/activities_controller.rb similarity index 100% rename from app/controllers/cohorts/submitted_challenge_answers/activities_controller.rb rename to scripts/app/controllers/cohorts/submitted_challenge_answers/activities_controller.rb diff --git a/app/controllers/cohorts/users/challenges_controller.rb b/scripts/app/controllers/cohorts/users/challenges_controller.rb similarity index 100% rename from app/controllers/cohorts/users/challenges_controller.rb rename to scripts/app/controllers/cohorts/users/challenges_controller.rb diff --git a/app/controllers/cohorts/users/performances_controller.rb b/scripts/app/controllers/cohorts/users/performances_controller.rb similarity index 100% rename from app/controllers/cohorts/users/performances_controller.rb rename to scripts/app/controllers/cohorts/users/performances_controller.rb diff --git a/app/controllers/cohorts/users_controller.rb b/scripts/app/controllers/cohorts/users_controller.rb similarity index 100% rename from app/controllers/cohorts/users_controller.rb rename to scripts/app/controllers/cohorts/users_controller.rb diff --git a/app/controllers/cohorts_controller.rb b/scripts/app/controllers/cohorts_controller.rb similarity index 100% rename from app/controllers/cohorts_controller.rb rename to scripts/app/controllers/cohorts_controller.rb diff --git a/app/controllers/concerns/api/content_visibility_crud.rb b/scripts/app/controllers/concerns/api/content_visibility_crud.rb similarity index 100% rename from app/controllers/concerns/api/content_visibility_crud.rb rename to scripts/app/controllers/concerns/api/content_visibility_crud.rb diff --git a/app/controllers/concerns/api/exception_handler.rb b/scripts/app/controllers/concerns/api/exception_handler.rb similarity index 100% rename from app/controllers/concerns/api/exception_handler.rb rename to scripts/app/controllers/concerns/api/exception_handler.rb diff --git a/app/controllers/concerns/api/response.rb b/scripts/app/controllers/concerns/api/response.rb similarity index 100% rename from app/controllers/concerns/api/response.rb rename to scripts/app/controllers/concerns/api/response.rb diff --git a/app/controllers/home_controller.rb b/scripts/app/controllers/home_controller.rb similarity index 100% rename from app/controllers/home_controller.rb rename to scripts/app/controllers/home_controller.rb diff --git a/app/controllers/notifications_controller.rb b/scripts/app/controllers/notifications_controller.rb similarity index 100% rename from app/controllers/notifications_controller.rb rename to scripts/app/controllers/notifications_controller.rb diff --git a/app/controllers/permalinks_controller.rb b/scripts/app/controllers/permalinks_controller.rb similarity index 100% rename from app/controllers/permalinks_controller.rb rename to scripts/app/controllers/permalinks_controller.rb diff --git a/app/controllers/sessions_controller.rb b/scripts/app/controllers/sessions_controller.rb similarity index 100% rename from app/controllers/sessions_controller.rb rename to scripts/app/controllers/sessions_controller.rb diff --git a/app/controllers/users_controller.rb b/scripts/app/controllers/users_controller.rb similarity index 100% rename from app/controllers/users_controller.rb rename to scripts/app/controllers/users_controller.rb diff --git a/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb b/scripts/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb similarity index 100% rename from app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb rename to scripts/app/controllers/webhooks/assessments_service/submitted_challenge_answers_controller.rb diff --git a/app/exporters/performance_exporter.rb b/scripts/app/exporters/performance_exporter.rb similarity index 100% rename from app/exporters/performance_exporter.rb rename to scripts/app/exporters/performance_exporter.rb diff --git a/app/exporters/progress_exporter.rb b/scripts/app/exporters/progress_exporter.rb similarity index 100% rename from app/exporters/progress_exporter.rb rename to scripts/app/exporters/progress_exporter.rb diff --git a/app/finders/block_finder.rb b/scripts/app/finders/block_finder.rb similarity index 100% rename from app/finders/block_finder.rb rename to scripts/app/finders/block_finder.rb diff --git a/app/finders/checkpoint_submission_finder.rb b/scripts/app/finders/checkpoint_submission_finder.rb similarity index 100% rename from app/finders/checkpoint_submission_finder.rb rename to scripts/app/finders/checkpoint_submission_finder.rb diff --git a/app/finders/cohort_user_finder.rb b/scripts/app/finders/cohort_user_finder.rb similarity index 100% rename from app/finders/cohort_user_finder.rb rename to scripts/app/finders/cohort_user_finder.rb diff --git a/app/finders/content_file_finder.rb b/scripts/app/finders/content_file_finder.rb similarity index 100% rename from app/finders/content_file_finder.rb rename to scripts/app/finders/content_file_finder.rb diff --git a/app/finders/performance_finder.rb b/scripts/app/finders/performance_finder.rb similarity index 100% rename from app/finders/performance_finder.rb rename to scripts/app/finders/performance_finder.rb diff --git a/app/finders/release_finder.rb b/scripts/app/finders/release_finder.rb similarity index 100% rename from app/finders/release_finder.rb rename to scripts/app/finders/release_finder.rb diff --git a/app/finders/standard_finder.rb b/scripts/app/finders/standard_finder.rb similarity index 100% rename from app/finders/standard_finder.rb rename to scripts/app/finders/standard_finder.rb diff --git a/app/finders/submitted_challenge_answer_finder.rb b/scripts/app/finders/submitted_challenge_answer_finder.rb similarity index 100% rename from app/finders/submitted_challenge_answer_finder.rb rename to scripts/app/finders/submitted_challenge_answer_finder.rb diff --git a/app/finders/user_finder.rb b/scripts/app/finders/user_finder.rb similarity index 100% rename from app/finders/user_finder.rb rename to scripts/app/finders/user_finder.rb diff --git a/app/helpers/application_helper.rb b/scripts/app/helpers/application_helper.rb similarity index 100% rename from app/helpers/application_helper.rb rename to scripts/app/helpers/application_helper.rb diff --git a/app/helpers/json_helper.rb b/scripts/app/helpers/json_helper.rb similarity index 100% rename from app/helpers/json_helper.rb rename to scripts/app/helpers/json_helper.rb diff --git a/app/helpers/secondary_navigation_helper.rb b/scripts/app/helpers/secondary_navigation_helper.rb similarity index 100% rename from app/helpers/secondary_navigation_helper.rb rename to scripts/app/helpers/secondary_navigation_helper.rb diff --git a/app/helpers/standard_navigation_helper.rb b/scripts/app/helpers/standard_navigation_helper.rb similarity index 100% rename from app/helpers/standard_navigation_helper.rb rename to scripts/app/helpers/standard_navigation_helper.rb diff --git a/app/javascript/api.d.ts b/scripts/app/javascript/api.d.ts similarity index 100% rename from app/javascript/api.d.ts rename to scripts/app/javascript/api.d.ts diff --git a/app/javascript/components/AceEditor.tsx b/scripts/app/javascript/components/AceEditor.tsx similarity index 100% rename from app/javascript/components/AceEditor.tsx rename to scripts/app/javascript/components/AceEditor.tsx diff --git a/app/javascript/components/Badge.tsx b/scripts/app/javascript/components/Badge.tsx similarity index 100% rename from app/javascript/components/Badge.tsx rename to scripts/app/javascript/components/Badge.tsx diff --git a/app/javascript/components/Button.tsx b/scripts/app/javascript/components/Button.tsx similarity index 100% rename from app/javascript/components/Button.tsx rename to scripts/app/javascript/components/Button.tsx diff --git a/app/javascript/components/ButtonTo.tsx b/scripts/app/javascript/components/ButtonTo.tsx similarity index 100% rename from app/javascript/components/ButtonTo.tsx rename to scripts/app/javascript/components/ButtonTo.tsx diff --git a/app/javascript/components/Dropdown.tsx b/scripts/app/javascript/components/Dropdown.tsx similarity index 100% rename from app/javascript/components/Dropdown.tsx rename to scripts/app/javascript/components/Dropdown.tsx diff --git a/app/javascript/components/Icon.tsx b/scripts/app/javascript/components/Icon.tsx similarity index 100% rename from app/javascript/components/Icon.tsx rename to scripts/app/javascript/components/Icon.tsx diff --git a/app/javascript/components/Loading.tsx b/scripts/app/javascript/components/Loading.tsx similarity index 100% rename from app/javascript/components/Loading.tsx rename to scripts/app/javascript/components/Loading.tsx diff --git a/app/javascript/components/Marked.tsx b/scripts/app/javascript/components/Marked.tsx similarity index 100% rename from app/javascript/components/Marked.tsx rename to scripts/app/javascript/components/Marked.tsx diff --git a/app/javascript/components/Menu.tsx b/scripts/app/javascript/components/Menu.tsx similarity index 100% rename from app/javascript/components/Menu.tsx rename to scripts/app/javascript/components/Menu.tsx diff --git a/app/javascript/components/Notifications.tsx b/scripts/app/javascript/components/Notifications.tsx similarity index 100% rename from app/javascript/components/Notifications.tsx rename to scripts/app/javascript/components/Notifications.tsx diff --git a/app/javascript/components/ProcessingIcon.tsx b/scripts/app/javascript/components/ProcessingIcon.tsx similarity index 100% rename from app/javascript/components/ProcessingIcon.tsx rename to scripts/app/javascript/components/ProcessingIcon.tsx diff --git a/app/javascript/components/RowKebab.tsx b/scripts/app/javascript/components/RowKebab.tsx similarity index 100% rename from app/javascript/components/RowKebab.tsx rename to scripts/app/javascript/components/RowKebab.tsx diff --git a/app/javascript/components/ScoreCircle.tsx b/scripts/app/javascript/components/ScoreCircle.tsx similarity index 100% rename from app/javascript/components/ScoreCircle.tsx rename to scripts/app/javascript/components/ScoreCircle.tsx diff --git a/app/javascript/components/SidebarProgress.tsx b/scripts/app/javascript/components/SidebarProgress.tsx similarity index 100% rename from app/javascript/components/SidebarProgress.tsx rename to scripts/app/javascript/components/SidebarProgress.tsx diff --git a/app/javascript/components/SortDropdown.tsx b/scripts/app/javascript/components/SortDropdown.tsx similarity index 100% rename from app/javascript/components/SortDropdown.tsx rename to scripts/app/javascript/components/SortDropdown.tsx diff --git a/app/javascript/components/StandardBean.tsx b/scripts/app/javascript/components/StandardBean.tsx similarity index 100% rename from app/javascript/components/StandardBean.tsx rename to scripts/app/javascript/components/StandardBean.tsx diff --git a/app/javascript/components/SvgRenderer.tsx b/scripts/app/javascript/components/SvgRenderer.tsx similarity index 100% rename from app/javascript/components/SvgRenderer.tsx rename to scripts/app/javascript/components/SvgRenderer.tsx diff --git a/app/javascript/components/Timestamp.tsx b/scripts/app/javascript/components/Timestamp.tsx similarity index 100% rename from app/javascript/components/Timestamp.tsx rename to scripts/app/javascript/components/Timestamp.tsx diff --git a/app/javascript/components/UserAvatar.tsx b/scripts/app/javascript/components/UserAvatar.tsx similarity index 100% rename from app/javascript/components/UserAvatar.tsx rename to scripts/app/javascript/components/UserAvatar.tsx diff --git a/app/javascript/components/activities/NewActivityForm.tsx b/scripts/app/javascript/components/activities/NewActivityForm.tsx similarity index 100% rename from app/javascript/components/activities/NewActivityForm.tsx rename to scripts/app/javascript/components/activities/NewActivityForm.tsx diff --git a/app/javascript/components/api/ApiInteractions.tsx b/scripts/app/javascript/components/api/ApiInteractions.tsx similarity index 100% rename from app/javascript/components/api/ApiInteractions.tsx rename to scripts/app/javascript/components/api/ApiInteractions.tsx diff --git a/app/javascript/components/api/ApiToken.tsx b/scripts/app/javascript/components/api/ApiToken.tsx similarity index 100% rename from app/javascript/components/api/ApiToken.tsx rename to scripts/app/javascript/components/api/ApiToken.tsx diff --git a/app/javascript/components/blocks/BlockPage-v2.tsx b/scripts/app/javascript/components/blocks/BlockPage-v2.tsx similarity index 100% rename from app/javascript/components/blocks/BlockPage-v2.tsx rename to scripts/app/javascript/components/blocks/BlockPage-v2.tsx diff --git a/app/javascript/components/blocks/BlocksIndex-v2.tsx b/scripts/app/javascript/components/blocks/BlocksIndex-v2.tsx similarity index 100% rename from app/javascript/components/blocks/BlocksIndex-v2.tsx rename to scripts/app/javascript/components/blocks/BlocksIndex-v2.tsx diff --git a/app/javascript/components/blocks/BlocksNewModal.tsx b/scripts/app/javascript/components/blocks/BlocksNewModal.tsx similarity index 100% rename from app/javascript/components/blocks/BlocksNewModal.tsx rename to scripts/app/javascript/components/blocks/BlocksNewModal.tsx diff --git a/app/javascript/components/blocks/BlocksNewRelease.tsx b/scripts/app/javascript/components/blocks/BlocksNewRelease.tsx similarity index 100% rename from app/javascript/components/blocks/BlocksNewRelease.tsx rename to scripts/app/javascript/components/blocks/BlocksNewRelease.tsx diff --git a/app/javascript/components/blocks/BlocksRow.tsx b/scripts/app/javascript/components/blocks/BlocksRow.tsx similarity index 100% rename from app/javascript/components/blocks/BlocksRow.tsx rename to scripts/app/javascript/components/blocks/BlocksRow.tsx diff --git a/app/javascript/components/blocks/BlocksShow.tsx b/scripts/app/javascript/components/blocks/BlocksShow.tsx similarity index 100% rename from app/javascript/components/blocks/BlocksShow.tsx rename to scripts/app/javascript/components/blocks/BlocksShow.tsx diff --git a/app/javascript/components/blocks/BlocksStats.tsx b/scripts/app/javascript/components/blocks/BlocksStats.tsx similarity index 100% rename from app/javascript/components/blocks/BlocksStats.tsx rename to scripts/app/javascript/components/blocks/BlocksStats.tsx diff --git a/app/javascript/components/blocks/CohortsTooltip.tsx b/scripts/app/javascript/components/blocks/CohortsTooltip.tsx similarity index 100% rename from app/javascript/components/blocks/CohortsTooltip.tsx rename to scripts/app/javascript/components/blocks/CohortsTooltip.tsx diff --git a/app/javascript/components/challenges/AvatarBar.tsx b/scripts/app/javascript/components/challenges/AvatarBar.tsx similarity index 100% rename from app/javascript/components/challenges/AvatarBar.tsx rename to scripts/app/javascript/components/challenges/AvatarBar.tsx diff --git a/app/javascript/components/challenges/ChallengeDetailView.tsx b/scripts/app/javascript/components/challenges/ChallengeDetailView.tsx similarity index 100% rename from app/javascript/components/challenges/ChallengeDetailView.tsx rename to scripts/app/javascript/components/challenges/ChallengeDetailView.tsx diff --git a/app/javascript/components/challenges/ChallengeShow.tsx b/scripts/app/javascript/components/challenges/ChallengeShow.tsx similarity index 100% rename from app/javascript/components/challenges/ChallengeShow.tsx rename to scripts/app/javascript/components/challenges/ChallengeShow.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeActionBlock.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeActionBlock.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeActionBlock.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeActionBlock.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeBlock.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeDetailActivities.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeDetailActivities.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeDetailActivities.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeDetailActivities.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeExplanationBlock.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeExplanationBlock.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeExplanationBlock.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeExplanationBlock.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeFeedbackBlock.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeFeedbackBlock.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeFeedbackBlock.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeFeedbackBlock.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeHintsBlock.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeHintsBlock.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeHintsBlock.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeHintsBlock.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeInputs.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeInputs.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeInputs.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeInputs.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeLocalTestResults.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeLocalTestResults.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeLocalTestResults.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeLocalTestResults.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeRubricBlock.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeRubricBlock.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeRubricBlock.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeRubricBlock.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeStatus.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeStatus.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeStatus.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeStatus.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeStatusBar.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeStatusBar.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeStatusBar.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeStatusBar.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeTestResults.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeTestResults.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeTestResults.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeTestResults.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeTests.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeTests.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeTests.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeTests.tsx diff --git a/app/javascript/components/challenges/challenge_block/ChallengeTimeLine.tsx b/scripts/app/javascript/components/challenges/challenge_block/ChallengeTimeLine.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/ChallengeTimeLine.tsx rename to scripts/app/javascript/components/challenges/challenge_block/ChallengeTimeLine.tsx diff --git a/app/javascript/components/challenges/challenge_block/GradeIndicator.tsx b/scripts/app/javascript/components/challenges/challenge_block/GradeIndicator.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/GradeIndicator.tsx rename to scripts/app/javascript/components/challenges/challenge_block/GradeIndicator.tsx diff --git a/app/javascript/components/challenges/challenge_block/GradedTimestamp.tsx b/scripts/app/javascript/components/challenges/challenge_block/GradedTimestamp.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/GradedTimestamp.tsx rename to scripts/app/javascript/components/challenges/challenge_block/GradedTimestamp.tsx diff --git a/app/javascript/components/challenges/challenge_block/StatusPicker.tsx b/scripts/app/javascript/components/challenges/challenge_block/StatusPicker.tsx similarity index 100% rename from app/javascript/components/challenges/challenge_block/StatusPicker.tsx rename to scripts/app/javascript/components/challenges/challenge_block/StatusPicker.tsx diff --git a/app/javascript/components/challenges/local/run-local-challenge.ts b/scripts/app/javascript/components/challenges/local/run-local-challenge.ts similarity index 100% rename from app/javascript/components/challenges/local/run-local-challenge.ts rename to scripts/app/javascript/components/challenges/local/run-local-challenge.ts diff --git a/app/javascript/components/challenges/local/sandbox.ts b/scripts/app/javascript/components/challenges/local/sandbox.ts similarity index 100% rename from app/javascript/components/challenges/local/sandbox.ts rename to scripts/app/javascript/components/challenges/local/sandbox.ts diff --git a/app/javascript/components/challenges/local/stack-traces.ts b/scripts/app/javascript/components/challenges/local/stack-traces.ts similarity index 100% rename from app/javascript/components/challenges/local/stack-traces.ts rename to scripts/app/javascript/components/challenges/local/stack-traces.ts diff --git a/app/javascript/components/checkpoints/CheckpointAfterSubmitButton.tsx b/scripts/app/javascript/components/checkpoints/CheckpointAfterSubmitButton.tsx similarity index 100% rename from app/javascript/components/checkpoints/CheckpointAfterSubmitButton.tsx rename to scripts/app/javascript/components/checkpoints/CheckpointAfterSubmitButton.tsx diff --git a/app/javascript/components/checkpoints/CheckpointAfterSubmitModal.tsx b/scripts/app/javascript/components/checkpoints/CheckpointAfterSubmitModal.tsx similarity index 100% rename from app/javascript/components/checkpoints/CheckpointAfterSubmitModal.tsx rename to scripts/app/javascript/components/checkpoints/CheckpointAfterSubmitModal.tsx diff --git a/app/javascript/components/checkpoints/CheckpointAfterSubmitModalError.tsx b/scripts/app/javascript/components/checkpoints/CheckpointAfterSubmitModalError.tsx similarity index 100% rename from app/javascript/components/checkpoints/CheckpointAfterSubmitModalError.tsx rename to scripts/app/javascript/components/checkpoints/CheckpointAfterSubmitModalError.tsx diff --git a/app/javascript/components/checkpoints/CheckpointSubmissionChallenges.tsx b/scripts/app/javascript/components/checkpoints/CheckpointSubmissionChallenges.tsx similarity index 100% rename from app/javascript/components/checkpoints/CheckpointSubmissionChallenges.tsx rename to scripts/app/javascript/components/checkpoints/CheckpointSubmissionChallenges.tsx diff --git a/app/javascript/components/checkpoints/CheckpointSubmissionShow.tsx b/scripts/app/javascript/components/checkpoints/CheckpointSubmissionShow.tsx similarity index 100% rename from app/javascript/components/checkpoints/CheckpointSubmissionShow.tsx rename to scripts/app/javascript/components/checkpoints/CheckpointSubmissionShow.tsx diff --git a/app/javascript/components/checkpoints/CheckpointSubmissionState.tsx b/scripts/app/javascript/components/checkpoints/CheckpointSubmissionState.tsx similarity index 100% rename from app/javascript/components/checkpoints/CheckpointSubmissionState.tsx rename to scripts/app/javascript/components/checkpoints/CheckpointSubmissionState.tsx diff --git a/app/javascript/components/checkpoints/CheckpointSubmissionStudentNameBar.tsx b/scripts/app/javascript/components/checkpoints/CheckpointSubmissionStudentNameBar.tsx similarity index 100% rename from app/javascript/components/checkpoints/CheckpointSubmissionStudentNameBar.tsx rename to scripts/app/javascript/components/checkpoints/CheckpointSubmissionStudentNameBar.tsx diff --git a/app/javascript/components/cohorts/CohortsIndex.tsx b/scripts/app/javascript/components/cohorts/CohortsIndex.tsx similarity index 100% rename from app/javascript/components/cohorts/CohortsIndex.tsx rename to scripts/app/javascript/components/cohorts/CohortsIndex.tsx diff --git a/app/javascript/components/cohorts/UsersNew.tsx b/scripts/app/javascript/components/cohorts/UsersNew.tsx similarity index 100% rename from app/javascript/components/cohorts/UsersNew.tsx rename to scripts/app/javascript/components/cohorts/UsersNew.tsx diff --git a/app/javascript/components/cohorts/activity_dashboard/ActivityDashboard.tsx b/scripts/app/javascript/components/cohorts/activity_dashboard/ActivityDashboard.tsx similarity index 100% rename from app/javascript/components/cohorts/activity_dashboard/ActivityDashboard.tsx rename to scripts/app/javascript/components/cohorts/activity_dashboard/ActivityDashboard.tsx diff --git a/app/javascript/components/cohorts/activity_feed/ActivityFeed.tsx b/scripts/app/javascript/components/cohorts/activity_feed/ActivityFeed.tsx similarity index 100% rename from app/javascript/components/cohorts/activity_feed/ActivityFeed.tsx rename to scripts/app/javascript/components/cohorts/activity_feed/ActivityFeed.tsx diff --git a/app/javascript/components/cohorts/cohort_releases/ReleaseVersionsTable.tsx b/scripts/app/javascript/components/cohorts/cohort_releases/ReleaseVersionsTable.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_releases/ReleaseVersionsTable.tsx rename to scripts/app/javascript/components/cohorts/cohort_releases/ReleaseVersionsTable.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissions.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsChallengeItem.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsChallengeItem.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsChallengeItem.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsChallengeItem.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsLessonRow.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsLessonRow.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsLessonRow.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsLessonRow.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsPerformanceModal.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsPerformanceModal.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsPerformanceModal.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsPerformanceModal.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStandardRow.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStandardRow.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStandardRow.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStandardRow.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentColumn.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentColumn.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentColumn.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentColumn.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentNameBar.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentNameBar.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentNameBar.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentNameBar.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentStandard.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentStandard.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentStandard.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsStudentStandard.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsTable.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsTable.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsTable.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsTable.tsx diff --git a/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsUnitPercent.tsx b/scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsUnitPercent.tsx similarity index 100% rename from app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsUnitPercent.tsx rename to scripts/app/javascript/components/cohorts/cohort_submissions/CohortSubmissionsUnitPercent.tsx diff --git a/app/javascript/components/cohorts/mastery/MasteryTable.tsx b/scripts/app/javascript/components/cohorts/mastery/MasteryTable.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/MasteryTable.tsx rename to scripts/app/javascript/components/cohorts/mastery/MasteryTable.tsx diff --git a/app/javascript/components/cohorts/mastery/MetricRow.tsx b/scripts/app/javascript/components/cohorts/mastery/MetricRow.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/MetricRow.tsx rename to scripts/app/javascript/components/cohorts/mastery/MetricRow.tsx diff --git a/app/javascript/components/cohorts/mastery/MetricsBody.tsx b/scripts/app/javascript/components/cohorts/mastery/MetricsBody.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/MetricsBody.tsx rename to scripts/app/javascript/components/cohorts/mastery/MetricsBody.tsx diff --git a/app/javascript/components/cohorts/mastery/PerformanceCell.tsx b/scripts/app/javascript/components/cohorts/mastery/PerformanceCell.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/PerformanceCell.tsx rename to scripts/app/javascript/components/cohorts/mastery/PerformanceCell.tsx diff --git a/app/javascript/components/cohorts/mastery/PerformanceRow.tsx b/scripts/app/javascript/components/cohorts/mastery/PerformanceRow.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/PerformanceRow.tsx rename to scripts/app/javascript/components/cohorts/mastery/PerformanceRow.tsx diff --git a/app/javascript/components/cohorts/mastery/ReleaseBody.tsx b/scripts/app/javascript/components/cohorts/mastery/ReleaseBody.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/ReleaseBody.tsx rename to scripts/app/javascript/components/cohorts/mastery/ReleaseBody.tsx diff --git a/app/javascript/components/cohorts/mastery/StandardRow.tsx b/scripts/app/javascript/components/cohorts/mastery/StandardRow.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/StandardRow.tsx rename to scripts/app/javascript/components/cohorts/mastery/StandardRow.tsx diff --git a/app/javascript/components/cohorts/mastery/StudentHeader.tsx b/scripts/app/javascript/components/cohorts/mastery/StudentHeader.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/StudentHeader.tsx rename to scripts/app/javascript/components/cohorts/mastery/StudentHeader.tsx diff --git a/app/javascript/components/cohorts/mastery/StudentMasteryTable.tsx b/scripts/app/javascript/components/cohorts/mastery/StudentMasteryTable.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/StudentMasteryTable.tsx rename to scripts/app/javascript/components/cohorts/mastery/StudentMasteryTable.tsx diff --git a/app/javascript/components/cohorts/mastery/StudentReleaseRow.tsx b/scripts/app/javascript/components/cohorts/mastery/StudentReleaseRow.tsx similarity index 100% rename from app/javascript/components/cohorts/mastery/StudentReleaseRow.tsx rename to scripts/app/javascript/components/cohorts/mastery/StudentReleaseRow.tsx diff --git a/app/javascript/components/cohorts/pairing/GroupPairs.tsx b/scripts/app/javascript/components/cohorts/pairing/GroupPairs.tsx similarity index 100% rename from app/javascript/components/cohorts/pairing/GroupPairs.tsx rename to scripts/app/javascript/components/cohorts/pairing/GroupPairs.tsx diff --git a/app/javascript/components/cohorts/pairing/InnerStudentList.tsx b/scripts/app/javascript/components/cohorts/pairing/InnerStudentList.tsx similarity index 100% rename from app/javascript/components/cohorts/pairing/InnerStudentList.tsx rename to scripts/app/javascript/components/cohorts/pairing/InnerStudentList.tsx diff --git a/app/javascript/components/cohorts/pairing/NewPairing.tsx b/scripts/app/javascript/components/cohorts/pairing/NewPairing.tsx similarity index 100% rename from app/javascript/components/cohorts/pairing/NewPairing.tsx rename to scripts/app/javascript/components/cohorts/pairing/NewPairing.tsx diff --git a/app/javascript/components/cohorts/pairing/PairingBoard.tsx b/scripts/app/javascript/components/cohorts/pairing/PairingBoard.tsx similarity index 100% rename from app/javascript/components/cohorts/pairing/PairingBoard.tsx rename to scripts/app/javascript/components/cohorts/pairing/PairingBoard.tsx diff --git a/app/javascript/components/cohorts/pairing/StudentItem.tsx b/scripts/app/javascript/components/cohorts/pairing/StudentItem.tsx similarity index 100% rename from app/javascript/components/cohorts/pairing/StudentItem.tsx rename to scripts/app/javascript/components/cohorts/pairing/StudentItem.tsx diff --git a/app/javascript/components/cohorts/pairing/StudentList.tsx b/scripts/app/javascript/components/cohorts/pairing/StudentList.tsx similarity index 100% rename from app/javascript/components/cohorts/pairing/StudentList.tsx rename to scripts/app/javascript/components/cohorts/pairing/StudentList.tsx diff --git a/app/javascript/components/cohorts/progress/MasteryProgressionBar.tsx b/scripts/app/javascript/components/cohorts/progress/MasteryProgressionBar.tsx similarity index 100% rename from app/javascript/components/cohorts/progress/MasteryProgressionBar.tsx rename to scripts/app/javascript/components/cohorts/progress/MasteryProgressionBar.tsx diff --git a/app/javascript/components/cohorts/progress/ProgressThresholdCellHeaders.tsx b/scripts/app/javascript/components/cohorts/progress/ProgressThresholdCellHeaders.tsx similarity index 100% rename from app/javascript/components/cohorts/progress/ProgressThresholdCellHeaders.tsx rename to scripts/app/javascript/components/cohorts/progress/ProgressThresholdCellHeaders.tsx diff --git a/app/javascript/components/cohorts/progress/StudentProgress.tsx b/scripts/app/javascript/components/cohorts/progress/StudentProgress.tsx similarity index 100% rename from app/javascript/components/cohorts/progress/StudentProgress.tsx rename to scripts/app/javascript/components/cohorts/progress/StudentProgress.tsx diff --git a/app/javascript/components/cohorts/progress/StudentProgressBar.tsx b/scripts/app/javascript/components/cohorts/progress/StudentProgressBar.tsx similarity index 100% rename from app/javascript/components/cohorts/progress/StudentProgressBar.tsx rename to scripts/app/javascript/components/cohorts/progress/StudentProgressBar.tsx diff --git a/app/javascript/components/cohorts/progress/StudentProgressCells.tsx b/scripts/app/javascript/components/cohorts/progress/StudentProgressCells.tsx similarity index 100% rename from app/javascript/components/cohorts/progress/StudentProgressCells.tsx rename to scripts/app/javascript/components/cohorts/progress/StudentProgressCells.tsx diff --git a/app/javascript/components/cohorts/progress/StudentProgressRow.tsx b/scripts/app/javascript/components/cohorts/progress/StudentProgressRow.tsx similarity index 100% rename from app/javascript/components/cohorts/progress/StudentProgressRow.tsx rename to scripts/app/javascript/components/cohorts/progress/StudentProgressRow.tsx diff --git a/app/javascript/components/cohorts/progress/StudentProgressSortArrow.tsx b/scripts/app/javascript/components/cohorts/progress/StudentProgressSortArrow.tsx similarity index 100% rename from app/javascript/components/cohorts/progress/StudentProgressSortArrow.tsx rename to scripts/app/javascript/components/cohorts/progress/StudentProgressSortArrow.tsx diff --git a/app/javascript/components/cohorts/settings/BranchReleaseModal.tsx b/scripts/app/javascript/components/cohorts/settings/BranchReleaseModal.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/BranchReleaseModal.tsx rename to scripts/app/javascript/components/cohorts/settings/BranchReleaseModal.tsx diff --git a/app/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx b/scripts/app/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx rename to scripts/app/javascript/components/cohorts/settings/CohortBlockReleaseRow.tsx diff --git a/app/javascript/components/cohorts/settings/CohortContentTab.tsx b/scripts/app/javascript/components/cohorts/settings/CohortContentTab.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/CohortContentTab.tsx rename to scripts/app/javascript/components/cohorts/settings/CohortContentTab.tsx diff --git a/app/javascript/components/cohorts/settings/CohortInfo.tsx b/scripts/app/javascript/components/cohorts/settings/CohortInfo.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/CohortInfo.tsx rename to scripts/app/javascript/components/cohorts/settings/CohortInfo.tsx diff --git a/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx b/scripts/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/CohortSettingsResync.tsx rename to scripts/app/javascript/components/cohorts/settings/CohortSettingsResync.tsx diff --git a/app/javascript/components/cohorts/settings/CohortSettingsTabs.tsx b/scripts/app/javascript/components/cohorts/settings/CohortSettingsTabs.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/CohortSettingsTabs.tsx rename to scripts/app/javascript/components/cohorts/settings/CohortSettingsTabs.tsx diff --git a/app/javascript/components/cohorts/settings/CohortVisibilitySection.tsx b/scripts/app/javascript/components/cohorts/settings/CohortVisibilitySection.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/CohortVisibilitySection.tsx rename to scripts/app/javascript/components/cohorts/settings/CohortVisibilitySection.tsx diff --git a/app/javascript/components/cohorts/settings/CurriculumSettingsTab.tsx b/scripts/app/javascript/components/cohorts/settings/CurriculumSettingsTab.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/CurriculumSettingsTab.tsx rename to scripts/app/javascript/components/cohorts/settings/CurriculumSettingsTab.tsx diff --git a/app/javascript/components/cohorts/settings/ReleaseVersionModal.tsx b/scripts/app/javascript/components/cohorts/settings/ReleaseVersionModal.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/ReleaseVersionModal.tsx rename to scripts/app/javascript/components/cohorts/settings/ReleaseVersionModal.tsx diff --git a/app/javascript/components/cohorts/settings/ReleaseVersionRow.tsx b/scripts/app/javascript/components/cohorts/settings/ReleaseVersionRow.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/ReleaseVersionRow.tsx rename to scripts/app/javascript/components/cohorts/settings/ReleaseVersionRow.tsx diff --git a/app/javascript/components/cohorts/settings/UserCohortKebab.tsx b/scripts/app/javascript/components/cohorts/settings/UserCohortKebab.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/UserCohortKebab.tsx rename to scripts/app/javascript/components/cohorts/settings/UserCohortKebab.tsx diff --git a/app/javascript/components/cohorts/settings/UserImport.tsx b/scripts/app/javascript/components/cohorts/settings/UserImport.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/UserImport.tsx rename to scripts/app/javascript/components/cohorts/settings/UserImport.tsx diff --git a/app/javascript/components/cohorts/settings/UserKebab.tsx b/scripts/app/javascript/components/cohorts/settings/UserKebab.tsx similarity index 100% rename from app/javascript/components/cohorts/settings/UserKebab.tsx rename to scripts/app/javascript/components/cohorts/settings/UserKebab.tsx diff --git a/app/javascript/components/cohorts/submissions_dashboard/AnswerStatusRollup.tsx b/scripts/app/javascript/components/cohorts/submissions_dashboard/AnswerStatusRollup.tsx similarity index 100% rename from app/javascript/components/cohorts/submissions_dashboard/AnswerStatusRollup.tsx rename to scripts/app/javascript/components/cohorts/submissions_dashboard/AnswerStatusRollup.tsx diff --git a/app/javascript/components/cohorts/submissions_dashboard/HeaderStandardContainer.tsx b/scripts/app/javascript/components/cohorts/submissions_dashboard/HeaderStandardContainer.tsx similarity index 100% rename from app/javascript/components/cohorts/submissions_dashboard/HeaderStandardContainer.tsx rename to scripts/app/javascript/components/cohorts/submissions_dashboard/HeaderStandardContainer.tsx diff --git a/app/javascript/components/cohorts/submissions_dashboard/StandardContainer.tsx b/scripts/app/javascript/components/cohorts/submissions_dashboard/StandardContainer.tsx similarity index 100% rename from app/javascript/components/cohorts/submissions_dashboard/StandardContainer.tsx rename to scripts/app/javascript/components/cohorts/submissions_dashboard/StandardContainer.tsx diff --git a/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx b/scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx similarity index 100% rename from app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx rename to scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboard.tsx diff --git a/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardChallengeItem.tsx b/scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardChallengeItem.tsx similarity index 100% rename from app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardChallengeItem.tsx rename to scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardChallengeItem.tsx diff --git a/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardContentFileRow.tsx b/scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardContentFileRow.tsx similarity index 100% rename from app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardContentFileRow.tsx rename to scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardContentFileRow.tsx diff --git a/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentColumn.tsx b/scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentColumn.tsx similarity index 100% rename from app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentColumn.tsx rename to scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentColumn.tsx diff --git a/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentNameBar.tsx b/scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentNameBar.tsx similarity index 100% rename from app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentNameBar.tsx rename to scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardStudentNameBar.tsx diff --git a/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardTable.tsx b/scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardTable.tsx similarity index 100% rename from app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardTable.tsx rename to scripts/app/javascript/components/cohorts/submissions_dashboard/SubmissionsDashboardTable.tsx diff --git a/app/javascript/components/content_files/ActionMenus.tsx b/scripts/app/javascript/components/content_files/ActionMenus.tsx similarity index 100% rename from app/javascript/components/content_files/ActionMenus.tsx rename to scripts/app/javascript/components/content_files/ActionMenus.tsx diff --git a/app/javascript/components/content_files/Lesson.tsx b/scripts/app/javascript/components/content_files/Lesson.tsx similarity index 100% rename from app/javascript/components/content_files/Lesson.tsx rename to scripts/app/javascript/components/content_files/Lesson.tsx diff --git a/app/javascript/components/content_files/MarkdownRenderer.tsx b/scripts/app/javascript/components/content_files/MarkdownRenderer.tsx similarity index 100% rename from app/javascript/components/content_files/MarkdownRenderer.tsx rename to scripts/app/javascript/components/content_files/MarkdownRenderer.tsx diff --git a/app/javascript/components/content_files/PDFRenderer.tsx b/scripts/app/javascript/components/content_files/PDFRenderer.tsx similarity index 100% rename from app/javascript/components/content_files/PDFRenderer.tsx rename to scripts/app/javascript/components/content_files/PDFRenderer.tsx diff --git a/app/javascript/components/content_files/SideBar.tsx b/scripts/app/javascript/components/content_files/SideBar.tsx similarity index 100% rename from app/javascript/components/content_files/SideBar.tsx rename to scripts/app/javascript/components/content_files/SideBar.tsx diff --git a/app/javascript/components/content_files/SubmissionRenderer.tsx b/scripts/app/javascript/components/content_files/SubmissionRenderer.tsx similarity index 100% rename from app/javascript/components/content_files/SubmissionRenderer.tsx rename to scripts/app/javascript/components/content_files/SubmissionRenderer.tsx diff --git a/app/javascript/components/content_files/checkpoints/Checkpoint.tsx b/scripts/app/javascript/components/content_files/checkpoints/Checkpoint.tsx similarity index 100% rename from app/javascript/components/content_files/checkpoints/Checkpoint.tsx rename to scripts/app/javascript/components/content_files/checkpoints/Checkpoint.tsx diff --git a/app/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx b/scripts/app/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx similarity index 100% rename from app/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx rename to scripts/app/javascript/components/content_files/checkpoints/CheckpointActionBar.tsx diff --git a/app/javascript/components/content_files/checkpoints/CheckpointDetails.tsx b/scripts/app/javascript/components/content_files/checkpoints/CheckpointDetails.tsx similarity index 100% rename from app/javascript/components/content_files/checkpoints/CheckpointDetails.tsx rename to scripts/app/javascript/components/content_files/checkpoints/CheckpointDetails.tsx diff --git a/app/javascript/components/content_files/checkpoints/CheckpointLanding.tsx b/scripts/app/javascript/components/content_files/checkpoints/CheckpointLanding.tsx similarity index 100% rename from app/javascript/components/content_files/checkpoints/CheckpointLanding.tsx rename to scripts/app/javascript/components/content_files/checkpoints/CheckpointLanding.tsx diff --git a/app/javascript/components/content_files/checkpoints/CheckpointLandingAttribute.tsx b/scripts/app/javascript/components/content_files/checkpoints/CheckpointLandingAttribute.tsx similarity index 100% rename from app/javascript/components/content_files/checkpoints/CheckpointLandingAttribute.tsx rename to scripts/app/javascript/components/content_files/checkpoints/CheckpointLandingAttribute.tsx diff --git a/app/javascript/components/content_files/checkpoints/CheckpointPairs.tsx b/scripts/app/javascript/components/content_files/checkpoints/CheckpointPairs.tsx similarity index 100% rename from app/javascript/components/content_files/checkpoints/CheckpointPairs.tsx rename to scripts/app/javascript/components/content_files/checkpoints/CheckpointPairs.tsx diff --git a/app/javascript/components/content_files/checkpoints/PairAvatars.tsx b/scripts/app/javascript/components/content_files/checkpoints/PairAvatars.tsx similarity index 100% rename from app/javascript/components/content_files/checkpoints/PairAvatars.tsx rename to scripts/app/javascript/components/content_files/checkpoints/PairAvatars.tsx diff --git a/app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentRow.tsx b/scripts/app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentRow.tsx similarity index 100% rename from app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentRow.tsx rename to scripts/app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentRow.tsx diff --git a/app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentScores.tsx b/scripts/app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentScores.tsx similarity index 100% rename from app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentScores.tsx rename to scripts/app/javascript/components/content_files/checkpoints/student_scores/CheckpointStudentScores.tsx diff --git a/app/javascript/components/curriculum/CheckpointSummary.tsx b/scripts/app/javascript/components/curriculum/CheckpointSummary.tsx similarity index 100% rename from app/javascript/components/curriculum/CheckpointSummary.tsx rename to scripts/app/javascript/components/curriculum/CheckpointSummary.tsx diff --git a/app/javascript/components/curriculum/CohortCurriculum.tsx b/scripts/app/javascript/components/curriculum/CohortCurriculum.tsx similarity index 100% rename from app/javascript/components/curriculum/CohortCurriculum.tsx rename to scripts/app/javascript/components/curriculum/CohortCurriculum.tsx diff --git a/app/javascript/components/curriculum/CurriculumStandardCard.tsx b/scripts/app/javascript/components/curriculum/CurriculumStandardCard.tsx similarity index 100% rename from app/javascript/components/curriculum/CurriculumStandardCard.tsx rename to scripts/app/javascript/components/curriculum/CurriculumStandardCard.tsx diff --git a/app/javascript/components/curriculum/ProgressIndicators.tsx b/scripts/app/javascript/components/curriculum/ProgressIndicators.tsx similarity index 100% rename from app/javascript/components/curriculum/ProgressIndicators.tsx rename to scripts/app/javascript/components/curriculum/ProgressIndicators.tsx diff --git a/app/javascript/components/curriculum/StandardBeans.tsx b/scripts/app/javascript/components/curriculum/StandardBeans.tsx similarity index 100% rename from app/javascript/components/curriculum/StandardBeans.tsx rename to scripts/app/javascript/components/curriculum/StandardBeans.tsx diff --git a/app/javascript/components/curriculum/StandardsRenderer.tsx b/scripts/app/javascript/components/curriculum/StandardsRenderer.tsx similarity index 100% rename from app/javascript/components/curriculum/StandardsRenderer.tsx rename to scripts/app/javascript/components/curriculum/StandardsRenderer.tsx diff --git a/app/javascript/components/curriculum/StudentOverallProgressDoughnut.tsx b/scripts/app/javascript/components/curriculum/StudentOverallProgressDoughnut.tsx similarity index 100% rename from app/javascript/components/curriculum/StudentOverallProgressDoughnut.tsx rename to scripts/app/javascript/components/curriculum/StudentOverallProgressDoughnut.tsx diff --git a/app/javascript/components/lib/ace.ts b/scripts/app/javascript/components/lib/ace.ts similarity index 100% rename from app/javascript/components/lib/ace.ts rename to scripts/app/javascript/components/lib/ace.ts diff --git a/app/javascript/components/notifications/NotificationsIcon.tsx b/scripts/app/javascript/components/notifications/NotificationsIcon.tsx similarity index 100% rename from app/javascript/components/notifications/NotificationsIcon.tsx rename to scripts/app/javascript/components/notifications/NotificationsIcon.tsx diff --git a/app/javascript/components/shared/ActionMenu/ActionMenu.tsx b/scripts/app/javascript/components/shared/ActionMenu/ActionMenu.tsx similarity index 100% rename from app/javascript/components/shared/ActionMenu/ActionMenu.tsx rename to scripts/app/javascript/components/shared/ActionMenu/ActionMenu.tsx diff --git a/app/javascript/components/shared/Button/Button.tsx b/scripts/app/javascript/components/shared/Button/Button.tsx similarity index 100% rename from app/javascript/components/shared/Button/Button.tsx rename to scripts/app/javascript/components/shared/Button/Button.tsx diff --git a/app/javascript/components/shared/ChallengePoints/ChallengePoints.tsx b/scripts/app/javascript/components/shared/ChallengePoints/ChallengePoints.tsx similarity index 100% rename from app/javascript/components/shared/ChallengePoints/ChallengePoints.tsx rename to scripts/app/javascript/components/shared/ChallengePoints/ChallengePoints.tsx diff --git a/app/javascript/components/shared/ChallengePoints/PartialCreditBaton.tsx b/scripts/app/javascript/components/shared/ChallengePoints/PartialCreditBaton.tsx similarity index 100% rename from app/javascript/components/shared/ChallengePoints/PartialCreditBaton.tsx rename to scripts/app/javascript/components/shared/ChallengePoints/PartialCreditBaton.tsx diff --git a/app/javascript/components/shared/ChallengePoints/SpinText.tsx b/scripts/app/javascript/components/shared/ChallengePoints/SpinText.tsx similarity index 100% rename from app/javascript/components/shared/ChallengePoints/SpinText.tsx rename to scripts/app/javascript/components/shared/ChallengePoints/SpinText.tsx diff --git a/app/javascript/components/shared/ChallengePoints/components/IntegerPicker/IntegerPicker.tsx b/scripts/app/javascript/components/shared/ChallengePoints/components/IntegerPicker/IntegerPicker.tsx similarity index 100% rename from app/javascript/components/shared/ChallengePoints/components/IntegerPicker/IntegerPicker.tsx rename to scripts/app/javascript/components/shared/ChallengePoints/components/IntegerPicker/IntegerPicker.tsx diff --git a/app/javascript/components/shared/DonutRing/DonutRing.tsx b/scripts/app/javascript/components/shared/DonutRing/DonutRing.tsx similarity index 100% rename from app/javascript/components/shared/DonutRing/DonutRing.tsx rename to scripts/app/javascript/components/shared/DonutRing/DonutRing.tsx diff --git a/app/javascript/components/shared/DonutRing/components/ColorDonut/ColorDonut.tsx b/scripts/app/javascript/components/shared/DonutRing/components/ColorDonut/ColorDonut.tsx similarity index 100% rename from app/javascript/components/shared/DonutRing/components/ColorDonut/ColorDonut.tsx rename to scripts/app/javascript/components/shared/DonutRing/components/ColorDonut/ColorDonut.tsx diff --git a/app/javascript/components/shared/DonutRing/components/CompleteDonut/CompleteDonut.tsx b/scripts/app/javascript/components/shared/DonutRing/components/CompleteDonut/CompleteDonut.tsx similarity index 100% rename from app/javascript/components/shared/DonutRing/components/CompleteDonut/CompleteDonut.tsx rename to scripts/app/javascript/components/shared/DonutRing/components/CompleteDonut/CompleteDonut.tsx diff --git a/app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut.tsx b/scripts/app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut.tsx similarity index 100% rename from app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut.tsx rename to scripts/app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut.tsx diff --git a/app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut/UngradedDonut.tsx b/scripts/app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut/UngradedDonut.tsx similarity index 100% rename from app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut/UngradedDonut.tsx rename to scripts/app/javascript/components/shared/DonutRing/components/UngradedDonut/UngradedDonut/UngradedDonut.tsx diff --git a/app/javascript/components/shared/ErrorMessagesTable/ErrorMessagesTable.tsx b/scripts/app/javascript/components/shared/ErrorMessagesTable/ErrorMessagesTable.tsx similarity index 100% rename from app/javascript/components/shared/ErrorMessagesTable/ErrorMessagesTable.tsx rename to scripts/app/javascript/components/shared/ErrorMessagesTable/ErrorMessagesTable.tsx diff --git a/app/javascript/components/shared/FlashAlert/FlashAlert.tsx b/scripts/app/javascript/components/shared/FlashAlert/FlashAlert.tsx similarity index 100% rename from app/javascript/components/shared/FlashAlert/FlashAlert.tsx rename to scripts/app/javascript/components/shared/FlashAlert/FlashAlert.tsx diff --git a/app/javascript/components/shared/FormatNumber/FormatNumber.tsx b/scripts/app/javascript/components/shared/FormatNumber/FormatNumber.tsx similarity index 100% rename from app/javascript/components/shared/FormatNumber/FormatNumber.tsx rename to scripts/app/javascript/components/shared/FormatNumber/FormatNumber.tsx diff --git a/app/javascript/components/shared/Icons/AlertSign.tsx b/scripts/app/javascript/components/shared/Icons/AlertSign.tsx similarity index 100% rename from app/javascript/components/shared/Icons/AlertSign.tsx rename to scripts/app/javascript/components/shared/Icons/AlertSign.tsx diff --git a/app/javascript/components/shared/Icons/Clear.tsx b/scripts/app/javascript/components/shared/Icons/Clear.tsx similarity index 100% rename from app/javascript/components/shared/Icons/Clear.tsx rename to scripts/app/javascript/components/shared/Icons/Clear.tsx diff --git a/app/javascript/components/shared/Icons/CloseButton.tsx b/scripts/app/javascript/components/shared/Icons/CloseButton.tsx similarity index 100% rename from app/javascript/components/shared/Icons/CloseButton.tsx rename to scripts/app/javascript/components/shared/Icons/CloseButton.tsx diff --git a/app/javascript/components/shared/Icons/Done.tsx b/scripts/app/javascript/components/shared/Icons/Done.tsx similarity index 100% rename from app/javascript/components/shared/Icons/Done.tsx rename to scripts/app/javascript/components/shared/Icons/Done.tsx diff --git a/app/javascript/components/shared/Icons/KeyboardArrowDown.tsx b/scripts/app/javascript/components/shared/Icons/KeyboardArrowDown.tsx similarity index 100% rename from app/javascript/components/shared/Icons/KeyboardArrowDown.tsx rename to scripts/app/javascript/components/shared/Icons/KeyboardArrowDown.tsx diff --git a/app/javascript/components/shared/Icons/KeyboardArrowUp.tsx b/scripts/app/javascript/components/shared/Icons/KeyboardArrowUp.tsx similarity index 100% rename from app/javascript/components/shared/Icons/KeyboardArrowUp.tsx rename to scripts/app/javascript/components/shared/Icons/KeyboardArrowUp.tsx diff --git a/app/javascript/components/shared/Icons/SettingsCog.tsx b/scripts/app/javascript/components/shared/Icons/SettingsCog.tsx similarity index 100% rename from app/javascript/components/shared/Icons/SettingsCog.tsx rename to scripts/app/javascript/components/shared/Icons/SettingsCog.tsx diff --git a/app/javascript/components/shared/Icons/StatusCircle.tsx b/scripts/app/javascript/components/shared/Icons/StatusCircle.tsx similarity index 100% rename from app/javascript/components/shared/Icons/StatusCircle.tsx rename to scripts/app/javascript/components/shared/Icons/StatusCircle.tsx diff --git a/app/javascript/components/shared/MasteryProgressBar/MasteryProgressBar.tsx b/scripts/app/javascript/components/shared/MasteryProgressBar/MasteryProgressBar.tsx similarity index 100% rename from app/javascript/components/shared/MasteryProgressBar/MasteryProgressBar.tsx rename to scripts/app/javascript/components/shared/MasteryProgressBar/MasteryProgressBar.tsx diff --git a/app/javascript/components/shared/Modal/Modal.tsx b/scripts/app/javascript/components/shared/Modal/Modal.tsx similarity index 100% rename from app/javascript/components/shared/Modal/Modal.tsx rename to scripts/app/javascript/components/shared/Modal/Modal.tsx diff --git a/app/javascript/components/shared/Pagination/Pagination.tsx b/scripts/app/javascript/components/shared/Pagination/Pagination.tsx similarity index 100% rename from app/javascript/components/shared/Pagination/Pagination.tsx rename to scripts/app/javascript/components/shared/Pagination/Pagination.tsx diff --git a/app/javascript/components/shared/PercentageProgressBar/PercentageProgressBar.tsx b/scripts/app/javascript/components/shared/PercentageProgressBar/PercentageProgressBar.tsx similarity index 100% rename from app/javascript/components/shared/PercentageProgressBar/PercentageProgressBar.tsx rename to scripts/app/javascript/components/shared/PercentageProgressBar/PercentageProgressBar.tsx diff --git a/app/javascript/components/shared/Pill/Pill.tsx b/scripts/app/javascript/components/shared/Pill/Pill.tsx similarity index 100% rename from app/javascript/components/shared/Pill/Pill.tsx rename to scripts/app/javascript/components/shared/Pill/Pill.tsx diff --git a/app/javascript/components/shared/PointGradeButtons/PointGradeButtons.tsx b/scripts/app/javascript/components/shared/PointGradeButtons/PointGradeButtons.tsx similarity index 100% rename from app/javascript/components/shared/PointGradeButtons/PointGradeButtons.tsx rename to scripts/app/javascript/components/shared/PointGradeButtons/PointGradeButtons.tsx diff --git a/app/javascript/components/shared/PointGradeButtons/SpinText.tsx b/scripts/app/javascript/components/shared/PointGradeButtons/SpinText.tsx similarity index 100% rename from app/javascript/components/shared/PointGradeButtons/SpinText.tsx rename to scripts/app/javascript/components/shared/PointGradeButtons/SpinText.tsx diff --git a/app/javascript/components/shared/ProgressBar/ProgressBar.tsx b/scripts/app/javascript/components/shared/ProgressBar/ProgressBar.tsx similarity index 100% rename from app/javascript/components/shared/ProgressBar/ProgressBar.tsx rename to scripts/app/javascript/components/shared/ProgressBar/ProgressBar.tsx diff --git a/app/javascript/components/shared/ProgressThresholdsKey/ProgressThresholdsKey.tsx b/scripts/app/javascript/components/shared/ProgressThresholdsKey/ProgressThresholdsKey.tsx similarity index 100% rename from app/javascript/components/shared/ProgressThresholdsKey/ProgressThresholdsKey.tsx rename to scripts/app/javascript/components/shared/ProgressThresholdsKey/ProgressThresholdsKey.tsx diff --git a/app/javascript/components/shared/ProgressThresholdsModal/ProgressThresholdsModal.tsx b/scripts/app/javascript/components/shared/ProgressThresholdsModal/ProgressThresholdsModal.tsx similarity index 100% rename from app/javascript/components/shared/ProgressThresholdsModal/ProgressThresholdsModal.tsx rename to scripts/app/javascript/components/shared/ProgressThresholdsModal/ProgressThresholdsModal.tsx diff --git a/app/javascript/components/shared/ProgressThresholdsSlider/ProgressThresholdsSlider.tsx b/scripts/app/javascript/components/shared/ProgressThresholdsSlider/ProgressThresholdsSlider.tsx similarity index 100% rename from app/javascript/components/shared/ProgressThresholdsSlider/ProgressThresholdsSlider.tsx rename to scripts/app/javascript/components/shared/ProgressThresholdsSlider/ProgressThresholdsSlider.tsx diff --git a/app/javascript/components/shared/SearchBar/SearchBar.tsx b/scripts/app/javascript/components/shared/SearchBar/SearchBar.tsx similarity index 100% rename from app/javascript/components/shared/SearchBar/SearchBar.tsx rename to scripts/app/javascript/components/shared/SearchBar/SearchBar.tsx diff --git a/app/javascript/components/shared/Slideshow/Slideshow.tsx b/scripts/app/javascript/components/shared/Slideshow/Slideshow.tsx similarity index 100% rename from app/javascript/components/shared/Slideshow/Slideshow.tsx rename to scripts/app/javascript/components/shared/Slideshow/Slideshow.tsx diff --git a/app/javascript/components/shared/UngradedDonut/UngradedDonut.tsx b/scripts/app/javascript/components/shared/UngradedDonut/UngradedDonut.tsx similarity index 100% rename from app/javascript/components/shared/UngradedDonut/UngradedDonut.tsx rename to scripts/app/javascript/components/shared/UngradedDonut/UngradedDonut.tsx diff --git a/app/javascript/components/shared/UniversalList/UniversalList.tsx b/scripts/app/javascript/components/shared/UniversalList/UniversalList.tsx similarity index 100% rename from app/javascript/components/shared/UniversalList/UniversalList.tsx rename to scripts/app/javascript/components/shared/UniversalList/UniversalList.tsx diff --git a/app/javascript/components/shared/UniversalRow/UniversalRow.tsx b/scripts/app/javascript/components/shared/UniversalRow/UniversalRow.tsx similarity index 100% rename from app/javascript/components/shared/UniversalRow/UniversalRow.tsx rename to scripts/app/javascript/components/shared/UniversalRow/UniversalRow.tsx diff --git a/app/javascript/components/shared/UniversalTable/UniversalTable.tsx b/scripts/app/javascript/components/shared/UniversalTable/UniversalTable.tsx similarity index 100% rename from app/javascript/components/shared/UniversalTable/UniversalTable.tsx rename to scripts/app/javascript/components/shared/UniversalTable/UniversalTable.tsx diff --git a/app/javascript/components/standards/CompletionScoringBlock.tsx b/scripts/app/javascript/components/standards/CompletionScoringBlock.tsx similarity index 100% rename from app/javascript/components/standards/CompletionScoringBlock.tsx rename to scripts/app/javascript/components/standards/CompletionScoringBlock.tsx diff --git a/app/javascript/components/standards/MasteryScoringBlock.tsx b/scripts/app/javascript/components/standards/MasteryScoringBlock.tsx similarity index 100% rename from app/javascript/components/standards/MasteryScoringBlock.tsx rename to scripts/app/javascript/components/standards/MasteryScoringBlock.tsx diff --git a/app/javascript/components/standards/StandardCard.tsx b/scripts/app/javascript/components/standards/StandardCard.tsx similarity index 100% rename from app/javascript/components/standards/StandardCard.tsx rename to scripts/app/javascript/components/standards/StandardCard.tsx diff --git a/app/javascript/components/standards/StandardScoreButton.tsx b/scripts/app/javascript/components/standards/StandardScoreButton.tsx similarity index 100% rename from app/javascript/components/standards/StandardScoreButton.tsx rename to scripts/app/javascript/components/standards/StandardScoreButton.tsx diff --git a/app/javascript/components/standards/StandardScoringBlock.tsx b/scripts/app/javascript/components/standards/StandardScoringBlock.tsx similarity index 100% rename from app/javascript/components/standards/StandardScoringBlock.tsx rename to scripts/app/javascript/components/standards/StandardScoringBlock.tsx diff --git a/app/javascript/components/standards/StandardTopicsRollups.tsx b/scripts/app/javascript/components/standards/StandardTopicsRollups.tsx similarity index 100% rename from app/javascript/components/standards/StandardTopicsRollups.tsx rename to scripts/app/javascript/components/standards/StandardTopicsRollups.tsx diff --git a/app/javascript/components/users/UsersIndex.tsx b/scripts/app/javascript/components/users/UsersIndex.tsx similarity index 100% rename from app/javascript/components/users/UsersIndex.tsx rename to scripts/app/javascript/components/users/UsersIndex.tsx diff --git a/app/javascript/components/vendor/ReactTooltip.js b/scripts/app/javascript/components/vendor/ReactTooltip.js similarity index 100% rename from app/javascript/components/vendor/ReactTooltip.js rename to scripts/app/javascript/components/vendor/ReactTooltip.js diff --git a/app/javascript/generated/routes.ts b/scripts/app/javascript/generated/routes.ts similarity index 100% rename from app/javascript/generated/routes.ts rename to scripts/app/javascript/generated/routes.ts diff --git a/app/javascript/globals.ts b/scripts/app/javascript/globals.ts similarity index 100% rename from app/javascript/globals.ts rename to scripts/app/javascript/globals.ts diff --git a/app/javascript/lib/http.ts b/scripts/app/javascript/lib/http.ts similarity index 100% rename from app/javascript/lib/http.ts rename to scripts/app/javascript/lib/http.ts diff --git a/app/javascript/lib/utils.ts b/scripts/app/javascript/lib/utils.ts similarity index 100% rename from app/javascript/lib/utils.ts rename to scripts/app/javascript/lib/utils.ts diff --git a/app/javascript/packs/application.js b/scripts/app/javascript/packs/application.js similarity index 100% rename from app/javascript/packs/application.js rename to scripts/app/javascript/packs/application.js diff --git a/app/javascript/packs/server_rendering.js b/scripts/app/javascript/packs/server_rendering.js similarity index 100% rename from app/javascript/packs/server_rendering.js rename to scripts/app/javascript/packs/server_rendering.js diff --git a/app/jobs/application_job.rb b/scripts/app/jobs/application_job.rb similarity index 100% rename from app/jobs/application_job.rb rename to scripts/app/jobs/application_job.rb diff --git a/app/jobs/branch_release_notifier_job.rb b/scripts/app/jobs/branch_release_notifier_job.rb similarity index 100% rename from app/jobs/branch_release_notifier_job.rb rename to scripts/app/jobs/branch_release_notifier_job.rb diff --git a/app/jobs/checkpoint_paired_submission_job.rb b/scripts/app/jobs/checkpoint_paired_submission_job.rb similarity index 100% rename from app/jobs/checkpoint_paired_submission_job.rb rename to scripts/app/jobs/checkpoint_paired_submission_job.rb diff --git a/app/jobs/content_file_default_visibility_job.rb b/scripts/app/jobs/content_file_default_visibility_job.rb similarity index 100% rename from app/jobs/content_file_default_visibility_job.rb rename to scripts/app/jobs/content_file_default_visibility_job.rb diff --git a/app/jobs/content_file_visit_job.rb b/scripts/app/jobs/content_file_visit_job.rb similarity index 100% rename from app/jobs/content_file_visit_job.rb rename to scripts/app/jobs/content_file_visit_job.rb diff --git a/app/jobs/create_api_interaction_job.rb b/scripts/app/jobs/create_api_interaction_job.rb similarity index 100% rename from app/jobs/create_api_interaction_job.rb rename to scripts/app/jobs/create_api_interaction_job.rb diff --git a/app/jobs/create_release_job.rb b/scripts/app/jobs/create_release_job.rb similarity index 100% rename from app/jobs/create_release_job.rb rename to scripts/app/jobs/create_release_job.rb diff --git a/app/jobs/evaluate_code_snippet_job.rb b/scripts/app/jobs/evaluate_code_snippet_job.rb similarity index 100% rename from app/jobs/evaluate_code_snippet_job.rb rename to scripts/app/jobs/evaluate_code_snippet_job.rb diff --git a/app/jobs/evaluate_custom_snippet_job.rb b/scripts/app/jobs/evaluate_custom_snippet_job.rb similarity index 100% rename from app/jobs/evaluate_custom_snippet_job.rb rename to scripts/app/jobs/evaluate_custom_snippet_job.rb diff --git a/app/jobs/evaluate_project_job.rb b/scripts/app/jobs/evaluate_project_job.rb similarity index 100% rename from app/jobs/evaluate_project_job.rb rename to scripts/app/jobs/evaluate_project_job.rb diff --git a/app/jobs/grade_timed_checkpoint_job.rb b/scripts/app/jobs/grade_timed_checkpoint_job.rb similarity index 100% rename from app/jobs/grade_timed_checkpoint_job.rb rename to scripts/app/jobs/grade_timed_checkpoint_job.rb diff --git a/app/jobs/import_student_work_job.rb b/scripts/app/jobs/import_student_work_job.rb similarity index 100% rename from app/jobs/import_student_work_job.rb rename to scripts/app/jobs/import_student_work_job.rb diff --git a/app/jobs/java_code_evaluation_job.rb b/scripts/app/jobs/java_code_evaluation_job.rb similarity index 100% rename from app/jobs/java_code_evaluation_job.rb rename to scripts/app/jobs/java_code_evaluation_job.rb diff --git a/app/jobs/javascript_code_evaluation_job.rb b/scripts/app/jobs/javascript_code_evaluation_job.rb similarity index 100% rename from app/jobs/javascript_code_evaluation_job.rb rename to scripts/app/jobs/javascript_code_evaluation_job.rb diff --git a/app/jobs/progress_csv_job.rb b/scripts/app/jobs/progress_csv_job.rb similarity index 100% rename from app/jobs/progress_csv_job.rb rename to scripts/app/jobs/progress_csv_job.rb diff --git a/app/jobs/python_code_evaluation_job.rb b/scripts/app/jobs/python_code_evaluation_job.rb similarity index 100% rename from app/jobs/python_code_evaluation_job.rb rename to scripts/app/jobs/python_code_evaluation_job.rb diff --git a/app/jobs/release_notifier_job.rb b/scripts/app/jobs/release_notifier_job.rb similarity index 100% rename from app/jobs/release_notifier_job.rb rename to scripts/app/jobs/release_notifier_job.rb diff --git a/app/jobs/resync_course_job.rb b/scripts/app/jobs/resync_course_job.rb similarity index 100% rename from app/jobs/resync_course_job.rb rename to scripts/app/jobs/resync_course_job.rb diff --git a/app/jobs/set_block_caches_job.rb b/scripts/app/jobs/set_block_caches_job.rb similarity index 100% rename from app/jobs/set_block_caches_job.rb rename to scripts/app/jobs/set_block_caches_job.rb diff --git a/app/jobs/slack_challenge_performance_job.rb b/scripts/app/jobs/slack_challenge_performance_job.rb similarity index 95% rename from app/jobs/slack_challenge_performance_job.rb rename to scripts/app/jobs/slack_challenge_performance_job.rb index 8fd8b8a..b63b9ea 100644 --- a/app/jobs/slack_challenge_performance_job.rb +++ b/scripts/app/jobs/slack_challenge_performance_job.rb @@ -24,6 +24,6 @@ class SlackChallengePerformanceJob < SlackJob end def webhook_params - "T02DNK3PH/BAYH03V1S/dMw9H1C6cnxLYcaW0qB2hwDv" + "" end end diff --git a/app/jobs/slack_dev_notify_job.rb b/scripts/app/jobs/slack_dev_notify_job.rb similarity index 100% rename from app/jobs/slack_dev_notify_job.rb rename to scripts/app/jobs/slack_dev_notify_job.rb diff --git a/app/jobs/slack_ds_prep_job.rb b/scripts/app/jobs/slack_ds_prep_job.rb similarity index 100% rename from app/jobs/slack_ds_prep_job.rb rename to scripts/app/jobs/slack_ds_prep_job.rb diff --git a/app/jobs/slack_job.rb b/scripts/app/jobs/slack_job.rb similarity index 100% rename from app/jobs/slack_job.rb rename to scripts/app/jobs/slack_job.rb diff --git a/app/jobs/slack_se_prep_job.rb b/scripts/app/jobs/slack_se_prep_job.rb similarity index 100% rename from app/jobs/slack_se_prep_job.rb rename to scripts/app/jobs/slack_se_prep_job.rb diff --git a/app/jobs/slack_student_job.rb b/scripts/app/jobs/slack_student_job.rb similarity index 100% rename from app/jobs/slack_student_job.rb rename to scripts/app/jobs/slack_student_job.rb diff --git a/app/jobs/student_progress_auth_job.rb b/scripts/app/jobs/student_progress_auth_job.rb similarity index 100% rename from app/jobs/student_progress_auth_job.rb rename to scripts/app/jobs/student_progress_auth_job.rb diff --git a/app/jobs/switch_to_branch_job.rb b/scripts/app/jobs/switch_to_branch_job.rb similarity index 100% rename from app/jobs/switch_to_branch_job.rb rename to scripts/app/jobs/switch_to_branch_job.rb diff --git a/app/mailers/application_mailer.rb b/scripts/app/mailers/application_mailer.rb similarity index 100% rename from app/mailers/application_mailer.rb rename to scripts/app/mailers/application_mailer.rb diff --git a/app/mailers/user_mailer.rb b/scripts/app/mailers/user_mailer.rb similarity index 100% rename from app/mailers/user_mailer.rb rename to scripts/app/mailers/user_mailer.rb diff --git a/app/models/activity.rb b/scripts/app/models/activity.rb similarity index 100% rename from app/models/activity.rb rename to scripts/app/models/activity.rb diff --git a/app/models/api_interaction.rb b/scripts/app/models/api_interaction.rb similarity index 100% rename from app/models/api_interaction.rb rename to scripts/app/models/api_interaction.rb diff --git a/app/models/application_record.rb b/scripts/app/models/application_record.rb similarity index 100% rename from app/models/application_record.rb rename to scripts/app/models/application_record.rb diff --git a/app/models/block.rb b/scripts/app/models/block.rb similarity index 100% rename from app/models/block.rb rename to scripts/app/models/block.rb diff --git a/app/models/challenge.rb b/scripts/app/models/challenge.rb similarity index 100% rename from app/models/challenge.rb rename to scripts/app/models/challenge.rb diff --git a/app/models/checkpoint_submission.rb b/scripts/app/models/checkpoint_submission.rb similarity index 100% rename from app/models/checkpoint_submission.rb rename to scripts/app/models/checkpoint_submission.rb diff --git a/app/models/cohort.rb b/scripts/app/models/cohort.rb similarity index 100% rename from app/models/cohort.rb rename to scripts/app/models/cohort.rb diff --git a/app/models/cohort_release.rb b/scripts/app/models/cohort_release.rb similarity index 100% rename from app/models/cohort_release.rb rename to scripts/app/models/cohort_release.rb diff --git a/app/models/cohort_user.rb b/scripts/app/models/cohort_user.rb similarity index 100% rename from app/models/cohort_user.rb rename to scripts/app/models/cohort_user.rb diff --git a/app/models/concerns/findable_by_uid.rb b/scripts/app/models/concerns/findable_by_uid.rb similarity index 100% rename from app/models/concerns/findable_by_uid.rb rename to scripts/app/models/concerns/findable_by_uid.rb diff --git a/app/models/concerns/read_only_model.rb b/scripts/app/models/concerns/read_only_model.rb similarity index 100% rename from app/models/concerns/read_only_model.rb rename to scripts/app/models/concerns/read_only_model.rb diff --git a/app/models/content_file.rb b/scripts/app/models/content_file.rb similarity index 100% rename from app/models/content_file.rb rename to scripts/app/models/content_file.rb diff --git a/app/models/content_visibility.rb b/scripts/app/models/content_visibility.rb similarity index 100% rename from app/models/content_visibility.rb rename to scripts/app/models/content_visibility.rb diff --git a/app/models/job_result.rb b/scripts/app/models/job_result.rb similarity index 100% rename from app/models/job_result.rb rename to scripts/app/models/job_result.rb diff --git a/app/models/lesson_visit.rb b/scripts/app/models/lesson_visit.rb similarity index 100% rename from app/models/lesson_visit.rb rename to scripts/app/models/lesson_visit.rb diff --git a/app/models/notification.rb b/scripts/app/models/notification.rb similarity index 100% rename from app/models/notification.rb rename to scripts/app/models/notification.rb diff --git a/app/models/pairing.rb b/scripts/app/models/pairing.rb similarity index 100% rename from app/models/pairing.rb rename to scripts/app/models/pairing.rb diff --git a/app/models/performance.rb b/scripts/app/models/performance.rb similarity index 100% rename from app/models/performance.rb rename to scripts/app/models/performance.rb diff --git a/app/models/release.rb b/scripts/app/models/release.rb similarity index 100% rename from app/models/release.rb rename to scripts/app/models/release.rb diff --git a/app/models/resync_job_result.rb b/scripts/app/models/resync_job_result.rb similarity index 100% rename from app/models/resync_job_result.rb rename to scripts/app/models/resync_job_result.rb diff --git a/app/models/section.rb b/scripts/app/models/section.rb similarity index 100% rename from app/models/section.rb rename to scripts/app/models/section.rb diff --git a/app/models/standard.rb b/scripts/app/models/standard.rb similarity index 100% rename from app/models/standard.rb rename to scripts/app/models/standard.rb diff --git a/app/models/submitted_challenge_answer.rb b/scripts/app/models/submitted_challenge_answer.rb similarity index 100% rename from app/models/submitted_challenge_answer.rb rename to scripts/app/models/submitted_challenge_answer.rb diff --git a/app/models/unit.rb b/scripts/app/models/unit.rb similarity index 100% rename from app/models/unit.rb rename to scripts/app/models/unit.rb diff --git a/app/models/user.rb b/scripts/app/models/user.rb similarity index 100% rename from app/models/user.rb rename to scripts/app/models/user.rb diff --git a/app/models/user_last_viewed_standard_path.rb b/scripts/app/models/user_last_viewed_standard_path.rb similarity index 100% rename from app/models/user_last_viewed_standard_path.rb rename to scripts/app/models/user_last_viewed_standard_path.rb diff --git a/app/policies/activity_policy.rb b/scripts/app/policies/activity_policy.rb similarity index 100% rename from app/policies/activity_policy.rb rename to scripts/app/policies/activity_policy.rb diff --git a/app/policies/application_policy.rb b/scripts/app/policies/application_policy.rb similarity index 100% rename from app/policies/application_policy.rb rename to scripts/app/policies/application_policy.rb diff --git a/app/policies/block_policy.rb b/scripts/app/policies/block_policy.rb similarity index 100% rename from app/policies/block_policy.rb rename to scripts/app/policies/block_policy.rb diff --git a/app/policies/checkpoint_submission_policy.rb b/scripts/app/policies/checkpoint_submission_policy.rb similarity index 100% rename from app/policies/checkpoint_submission_policy.rb rename to scripts/app/policies/checkpoint_submission_policy.rb diff --git a/app/policies/cohort_policy.rb b/scripts/app/policies/cohort_policy.rb similarity index 100% rename from app/policies/cohort_policy.rb rename to scripts/app/policies/cohort_policy.rb diff --git a/app/policies/cohort_release_policy.rb b/scripts/app/policies/cohort_release_policy.rb similarity index 100% rename from app/policies/cohort_release_policy.rb rename to scripts/app/policies/cohort_release_policy.rb diff --git a/app/policies/cohort_user_policy.rb b/scripts/app/policies/cohort_user_policy.rb similarity index 100% rename from app/policies/cohort_user_policy.rb rename to scripts/app/policies/cohort_user_policy.rb diff --git a/app/policies/content_file_policy.rb b/scripts/app/policies/content_file_policy.rb similarity index 100% rename from app/policies/content_file_policy.rb rename to scripts/app/policies/content_file_policy.rb diff --git a/app/policies/notification_policy.rb b/scripts/app/policies/notification_policy.rb similarity index 100% rename from app/policies/notification_policy.rb rename to scripts/app/policies/notification_policy.rb diff --git a/app/policies/performance_policy.rb b/scripts/app/policies/performance_policy.rb similarity index 100% rename from app/policies/performance_policy.rb rename to scripts/app/policies/performance_policy.rb diff --git a/app/policies/release_policy.rb b/scripts/app/policies/release_policy.rb similarity index 100% rename from app/policies/release_policy.rb rename to scripts/app/policies/release_policy.rb diff --git a/app/policies/submitted_challenge_answer_policy.rb b/scripts/app/policies/submitted_challenge_answer_policy.rb similarity index 100% rename from app/policies/submitted_challenge_answer_policy.rb rename to scripts/app/policies/submitted_challenge_answer_policy.rb diff --git a/app/policies/user_policy.rb b/scripts/app/policies/user_policy.rb similarity index 100% rename from app/policies/user_policy.rb rename to scripts/app/policies/user_policy.rb diff --git a/app/presenters/activity_presenter.rb b/scripts/app/presenters/activity_presenter.rb similarity index 100% rename from app/presenters/activity_presenter.rb rename to scripts/app/presenters/activity_presenter.rb diff --git a/app/presenters/block_presenter/for_block.rb b/scripts/app/presenters/block_presenter/for_block.rb similarity index 100% rename from app/presenters/block_presenter/for_block.rb rename to scripts/app/presenters/block_presenter/for_block.rb diff --git a/app/presenters/block_presenter/for_cohort_releases.rb b/scripts/app/presenters/block_presenter/for_cohort_releases.rb similarity index 100% rename from app/presenters/block_presenter/for_cohort_releases.rb rename to scripts/app/presenters/block_presenter/for_cohort_releases.rb diff --git a/app/presenters/block_presenter/for_cohort_releases_new.rb b/scripts/app/presenters/block_presenter/for_cohort_releases_new.rb similarity index 100% rename from app/presenters/block_presenter/for_cohort_releases_new.rb rename to scripts/app/presenters/block_presenter/for_cohort_releases_new.rb diff --git a/app/presenters/block_presenter/for_releases.rb b/scripts/app/presenters/block_presenter/for_releases.rb similarity index 100% rename from app/presenters/block_presenter/for_releases.rb rename to scripts/app/presenters/block_presenter/for_releases.rb diff --git a/app/presenters/challenge_with_submitted_challenge_answers_presenter.rb b/scripts/app/presenters/challenge_with_submitted_challenge_answers_presenter.rb similarity index 100% rename from app/presenters/challenge_with_submitted_challenge_answers_presenter.rb rename to scripts/app/presenters/challenge_with_submitted_challenge_answers_presenter.rb diff --git a/app/presenters/checkpoint_submission_presenter.rb b/scripts/app/presenters/checkpoint_submission_presenter.rb similarity index 100% rename from app/presenters/checkpoint_submission_presenter.rb rename to scripts/app/presenters/checkpoint_submission_presenter.rb diff --git a/app/presenters/checkpoint_submission_presenter/for_index.rb b/scripts/app/presenters/checkpoint_submission_presenter/for_index.rb similarity index 100% rename from app/presenters/checkpoint_submission_presenter/for_index.rb rename to scripts/app/presenters/checkpoint_submission_presenter/for_index.rb diff --git a/app/presenters/checkpoint_submission_presenter/for_student_row.rb b/scripts/app/presenters/checkpoint_submission_presenter/for_student_row.rb similarity index 100% rename from app/presenters/checkpoint_submission_presenter/for_student_row.rb rename to scripts/app/presenters/checkpoint_submission_presenter/for_student_row.rb diff --git a/app/presenters/cohort_release_presenter/for_cohort_setup.rb b/scripts/app/presenters/cohort_release_presenter/for_cohort_setup.rb similarity index 100% rename from app/presenters/cohort_release_presenter/for_cohort_setup.rb rename to scripts/app/presenters/cohort_release_presenter/for_cohort_setup.rb diff --git a/app/presenters/cohort_setup/visibility.rb b/scripts/app/presenters/cohort_setup/visibility.rb similarity index 100% rename from app/presenters/cohort_setup/visibility.rb rename to scripts/app/presenters/cohort_setup/visibility.rb diff --git a/app/presenters/content_file_presenter/for_curriculum_last_viewed.rb b/scripts/app/presenters/content_file_presenter/for_curriculum_last_viewed.rb similarity index 100% rename from app/presenters/content_file_presenter/for_curriculum_last_viewed.rb rename to scripts/app/presenters/content_file_presenter/for_curriculum_last_viewed.rb diff --git a/app/presenters/content_file_presenter/for_footer.rb b/scripts/app/presenters/content_file_presenter/for_footer.rb similarity index 100% rename from app/presenters/content_file_presenter/for_footer.rb rename to scripts/app/presenters/content_file_presenter/for_footer.rb diff --git a/app/presenters/content_file_presenter/for_show.rb b/scripts/app/presenters/content_file_presenter/for_show.rb similarity index 100% rename from app/presenters/content_file_presenter/for_show.rb rename to scripts/app/presenters/content_file_presenter/for_show.rb diff --git a/app/presenters/content_file_presenter/for_sidebar.rb b/scripts/app/presenters/content_file_presenter/for_sidebar.rb similarity index 100% rename from app/presenters/content_file_presenter/for_sidebar.rb rename to scripts/app/presenters/content_file_presenter/for_sidebar.rb diff --git a/app/presenters/performance_presenter.rb b/scripts/app/presenters/performance_presenter.rb similarity index 100% rename from app/presenters/performance_presenter.rb rename to scripts/app/presenters/performance_presenter.rb diff --git a/app/presenters/standard_card_component_props.rb b/scripts/app/presenters/standard_card_component_props.rb similarity index 100% rename from app/presenters/standard_card_component_props.rb rename to scripts/app/presenters/standard_card_component_props.rb diff --git a/app/presenters/standard_presenter/for_challenge_detail_view.rb b/scripts/app/presenters/standard_presenter/for_challenge_detail_view.rb similarity index 100% rename from app/presenters/standard_presenter/for_challenge_detail_view.rb rename to scripts/app/presenters/standard_presenter/for_challenge_detail_view.rb diff --git a/app/presenters/standard_presenter/for_checkpoint_submission.rb b/scripts/app/presenters/standard_presenter/for_checkpoint_submission.rb similarity index 100% rename from app/presenters/standard_presenter/for_checkpoint_submission.rb rename to scripts/app/presenters/standard_presenter/for_checkpoint_submission.rb diff --git a/app/presenters/standard_presenter/for_standard_card.rb b/scripts/app/presenters/standard_presenter/for_standard_card.rb similarity index 100% rename from app/presenters/standard_presenter/for_standard_card.rb rename to scripts/app/presenters/standard_presenter/for_standard_card.rb diff --git a/app/presenters/standard_presenter/for_submissions_dashboard.rb b/scripts/app/presenters/standard_presenter/for_submissions_dashboard.rb similarity index 100% rename from app/presenters/standard_presenter/for_submissions_dashboard.rb rename to scripts/app/presenters/standard_presenter/for_submissions_dashboard.rb diff --git a/app/presenters/stat_progress_presenter.rb b/scripts/app/presenters/stat_progress_presenter.rb similarity index 100% rename from app/presenters/stat_progress_presenter.rb rename to scripts/app/presenters/stat_progress_presenter.rb diff --git a/app/presenters/student_progress_presenter.rb b/scripts/app/presenters/student_progress_presenter.rb similarity index 100% rename from app/presenters/student_progress_presenter.rb rename to scripts/app/presenters/student_progress_presenter.rb diff --git a/app/presenters/submissions_dashboard_presenter.rb b/scripts/app/presenters/submissions_dashboard_presenter.rb similarity index 100% rename from app/presenters/submissions_dashboard_presenter.rb rename to scripts/app/presenters/submissions_dashboard_presenter.rb diff --git a/app/presenters/submitted_challenge_answer_presenter.rb b/scripts/app/presenters/submitted_challenge_answer_presenter.rb similarity index 100% rename from app/presenters/submitted_challenge_answer_presenter.rb rename to scripts/app/presenters/submitted_challenge_answer_presenter.rb diff --git a/app/presenters/user_presenter/for_avatar.rb b/scripts/app/presenters/user_presenter/for_avatar.rb similarity index 100% rename from app/presenters/user_presenter/for_avatar.rb rename to scripts/app/presenters/user_presenter/for_avatar.rb diff --git a/app/services/activity_aggregator_service.rb b/scripts/app/services/activity_aggregator_service.rb similarity index 100% rename from app/services/activity_aggregator_service.rb rename to scripts/app/services/activity_aggregator_service.rb diff --git a/app/services/assessment_service.rb b/scripts/app/services/assessment_service.rb similarity index 100% rename from app/services/assessment_service.rb rename to scripts/app/services/assessment_service.rb diff --git a/app/services/auto_assign_release_service.rb b/scripts/app/services/auto_assign_release_service.rb similarity index 100% rename from app/services/auto_assign_release_service.rb rename to scripts/app/services/auto_assign_release_service.rb diff --git a/app/services/checkpoint_submission_service.rb b/scripts/app/services/checkpoint_submission_service.rb similarity index 100% rename from app/services/checkpoint_submission_service.rb rename to scripts/app/services/checkpoint_submission_service.rb diff --git a/app/services/cohort_standard_progress_service.rb b/scripts/app/services/cohort_standard_progress_service.rb similarity index 100% rename from app/services/cohort_standard_progress_service.rb rename to scripts/app/services/cohort_standard_progress_service.rb diff --git a/app/services/cohort_student_progress_service.rb b/scripts/app/services/cohort_student_progress_service.rb similarity index 100% rename from app/services/cohort_student_progress_service.rb rename to scripts/app/services/cohort_student_progress_service.rb diff --git a/app/services/completion_mode_percent_service.rb b/scripts/app/services/completion_mode_percent_service.rb similarity index 100% rename from app/services/completion_mode_percent_service.rb rename to scripts/app/services/completion_mode_percent_service.rb diff --git a/app/services/course_validator.rb b/scripts/app/services/course_validator.rb similarity index 100% rename from app/services/course_validator.rb rename to scripts/app/services/course_validator.rb diff --git a/app/services/create_submitted_challenge_answer_service.rb b/scripts/app/services/create_submitted_challenge_answer_service.rb similarity index 100% rename from app/services/create_submitted_challenge_answer_service.rb rename to scripts/app/services/create_submitted_challenge_answer_service.rb diff --git a/app/services/curriculum_progress_service.rb b/scripts/app/services/curriculum_progress_service.rb similarity index 100% rename from app/services/curriculum_progress_service.rb rename to scripts/app/services/curriculum_progress_service.rb diff --git a/app/services/download_github_repository_service.rb b/scripts/app/services/download_github_repository_service.rb similarity index 100% rename from app/services/download_github_repository_service.rb rename to scripts/app/services/download_github_repository_service.rb diff --git a/app/services/download_gitlab_repository_service.rb b/scripts/app/services/download_gitlab_repository_service.rb similarity index 100% rename from app/services/download_gitlab_repository_service.rb rename to scripts/app/services/download_gitlab_repository_service.rb diff --git a/app/services/download_repository_service.rb b/scripts/app/services/download_repository_service.rb similarity index 100% rename from app/services/download_repository_service.rb rename to scripts/app/services/download_repository_service.rb diff --git a/app/services/download_s3_repository_service.rb b/scripts/app/services/download_s3_repository_service.rb similarity index 100% rename from app/services/download_s3_repository_service.rb rename to scripts/app/services/download_s3_repository_service.rb diff --git a/app/services/encoded_image_link_service.rb b/scripts/app/services/encoded_image_link_service.rb similarity index 100% rename from app/services/encoded_image_link_service.rb rename to scripts/app/services/encoded_image_link_service.rb diff --git a/app/services/git_url_service.rb b/scripts/app/services/git_url_service.rb similarity index 100% rename from app/services/git_url_service.rb rename to scripts/app/services/git_url_service.rb diff --git a/app/services/mastery_average_service.rb b/scripts/app/services/mastery_average_service.rb similarity index 100% rename from app/services/mastery_average_service.rb rename to scripts/app/services/mastery_average_service.rb diff --git a/app/services/mastery_mode_percent_service.rb b/scripts/app/services/mastery_mode_percent_service.rb similarity index 100% rename from app/services/mastery_mode_percent_service.rb rename to scripts/app/services/mastery_mode_percent_service.rb diff --git a/app/services/mock_mixpanel.rb b/scripts/app/services/mock_mixpanel.rb similarity index 100% rename from app/services/mock_mixpanel.rb rename to scripts/app/services/mock_mixpanel.rb diff --git a/app/services/notification_service.rb b/scripts/app/services/notification_service.rb similarity index 100% rename from app/services/notification_service.rb rename to scripts/app/services/notification_service.rb diff --git a/app/services/platform_one_auth_resolver_service.rb b/scripts/app/services/platform_one_auth_resolver_service.rb similarity index 100% rename from app/services/platform_one_auth_resolver_service.rb rename to scripts/app/services/platform_one_auth_resolver_service.rb diff --git a/app/services/preview_content_file_service.rb b/scripts/app/services/preview_content_file_service.rb similarity index 100% rename from app/services/preview_content_file_service.rb rename to scripts/app/services/preview_content_file_service.rb diff --git a/app/services/release_destroyer_service.rb b/scripts/app/services/release_destroyer_service.rb similarity index 100% rename from app/services/release_destroyer_service.rb rename to scripts/app/services/release_destroyer_service.rb diff --git a/app/services/release_helper_service.rb b/scripts/app/services/release_helper_service.rb similarity index 100% rename from app/services/release_helper_service.rb rename to scripts/app/services/release_helper_service.rb diff --git a/app/services/resync_course_service.rb b/scripts/app/services/resync_course_service.rb similarity index 100% rename from app/services/resync_course_service.rb rename to scripts/app/services/resync_course_service.rb diff --git a/app/services/s3_asset_uploader_service.rb b/scripts/app/services/s3_asset_uploader_service.rb similarity index 100% rename from app/services/s3_asset_uploader_service.rb rename to scripts/app/services/s3_asset_uploader_service.rb diff --git a/app/services/segment_track_service.rb b/scripts/app/services/segment_track_service.rb similarity index 100% rename from app/services/segment_track_service.rb rename to scripts/app/services/segment_track_service.rb diff --git a/app/services/sql_challenge_db_service.rb b/scripts/app/services/sql_challenge_db_service.rb similarity index 100% rename from app/services/sql_challenge_db_service.rb rename to scripts/app/services/sql_challenge_db_service.rb diff --git a/app/services/standard_submissions_service.rb b/scripts/app/services/standard_submissions_service.rb similarity index 100% rename from app/services/standard_submissions_service.rb rename to scripts/app/services/standard_submissions_service.rb diff --git a/app/views/api_interactions.html.haml b/scripts/app/views/api_interactions.html.haml similarity index 100% rename from app/views/api_interactions.html.haml rename to scripts/app/views/api_interactions.html.haml diff --git a/app/views/api_token.html.haml b/scripts/app/views/api_token.html.haml similarity index 100% rename from app/views/api_token.html.haml rename to scripts/app/views/api_token.html.haml diff --git a/app/views/apitome/docs/_headers.html.erb b/scripts/app/views/apitome/docs/_headers.html.erb similarity index 100% rename from app/views/apitome/docs/_headers.html.erb rename to scripts/app/views/apitome/docs/_headers.html.erb diff --git a/app/views/apitome/docs/_params.html.erb b/scripts/app/views/apitome/docs/_params.html.erb similarity index 100% rename from app/views/apitome/docs/_params.html.erb rename to scripts/app/views/apitome/docs/_params.html.erb diff --git a/app/views/blocks/blockpagev2.html.haml b/scripts/app/views/blocks/blockpagev2.html.haml similarity index 100% rename from app/views/blocks/blockpagev2.html.haml rename to scripts/app/views/blocks/blockpagev2.html.haml diff --git a/app/views/blocks/index.html.haml b/scripts/app/views/blocks/index.html.haml similarity index 100% rename from app/views/blocks/index.html.haml rename to scripts/app/views/blocks/index.html.haml diff --git a/app/views/blocks/new.html.haml b/scripts/app/views/blocks/new.html.haml similarity index 100% rename from app/views/blocks/new.html.haml rename to scripts/app/views/blocks/new.html.haml diff --git a/app/views/blocks/releases/new.html.haml b/scripts/app/views/blocks/releases/new.html.haml similarity index 100% rename from app/views/blocks/releases/new.html.haml rename to scripts/app/views/blocks/releases/new.html.haml diff --git a/app/views/blocks/show.html.haml b/scripts/app/views/blocks/show.html.haml similarity index 100% rename from app/views/blocks/show.html.haml rename to scripts/app/views/blocks/show.html.haml diff --git a/app/views/cohorts/_cohort_edit_form.html.haml b/scripts/app/views/cohorts/_cohort_edit_form.html.haml similarity index 100% rename from app/views/cohorts/_cohort_edit_form.html.haml rename to scripts/app/views/cohorts/_cohort_edit_form.html.haml diff --git a/app/views/cohorts/_cohort_info.html.haml b/scripts/app/views/cohorts/_cohort_info.html.haml similarity index 100% rename from app/views/cohorts/_cohort_info.html.haml rename to scripts/app/views/cohorts/_cohort_info.html.haml diff --git a/app/views/cohorts/_completion_progress_donut.html.haml b/scripts/app/views/cohorts/_completion_progress_donut.html.haml similarity index 100% rename from app/views/cohorts/_completion_progress_donut.html.haml rename to scripts/app/views/cohorts/_completion_progress_donut.html.haml diff --git a/app/views/cohorts/_user_table.html.haml b/scripts/app/views/cohorts/_user_table.html.haml similarity index 100% rename from app/views/cohorts/_user_table.html.haml rename to scripts/app/views/cohorts/_user_table.html.haml diff --git a/app/views/cohorts/activity_dashboard.html.haml b/scripts/app/views/cohorts/activity_dashboard.html.haml similarity index 100% rename from app/views/cohorts/activity_dashboard.html.haml rename to scripts/app/views/cohorts/activity_dashboard.html.haml diff --git a/app/views/cohorts/blocks/content_files/_footer.html.haml b/scripts/app/views/cohorts/blocks/content_files/_footer.html.haml similarity index 100% rename from app/views/cohorts/blocks/content_files/_footer.html.haml rename to scripts/app/views/cohorts/blocks/content_files/_footer.html.haml diff --git a/app/views/cohorts/blocks/content_files/show.html.haml b/scripts/app/views/cohorts/blocks/content_files/show.html.haml similarity index 100% rename from app/views/cohorts/blocks/content_files/show.html.haml rename to scripts/app/views/cohorts/blocks/content_files/show.html.haml diff --git a/app/views/cohorts/cohort_releases/index.html.haml b/scripts/app/views/cohorts/cohort_releases/index.html.haml similarity index 100% rename from app/views/cohorts/cohort_releases/index.html.haml rename to scripts/app/views/cohorts/cohort_releases/index.html.haml diff --git a/app/views/cohorts/content.html.haml b/scripts/app/views/cohorts/content.html.haml similarity index 100% rename from app/views/cohorts/content.html.haml rename to scripts/app/views/cohorts/content.html.haml diff --git a/app/views/cohorts/content_files/checkpoint_submissions/show.html.haml b/scripts/app/views/cohorts/content_files/checkpoint_submissions/show.html.haml similarity index 100% rename from app/views/cohorts/content_files/checkpoint_submissions/show.html.haml rename to scripts/app/views/cohorts/content_files/checkpoint_submissions/show.html.haml diff --git a/app/views/cohorts/course_stats.html.haml b/scripts/app/views/cohorts/course_stats.html.haml similarity index 100% rename from app/views/cohorts/course_stats.html.haml rename to scripts/app/views/cohorts/course_stats.html.haml diff --git a/app/views/cohorts/edit.html.haml b/scripts/app/views/cohorts/edit.html.haml similarity index 100% rename from app/views/cohorts/edit.html.haml rename to scripts/app/views/cohorts/edit.html.haml diff --git a/app/views/cohorts/error.html.haml b/scripts/app/views/cohorts/error.html.haml similarity index 100% rename from app/views/cohorts/error.html.haml rename to scripts/app/views/cohorts/error.html.haml diff --git a/app/views/cohorts/feed.html.haml b/scripts/app/views/cohorts/feed.html.haml similarity index 100% rename from app/views/cohorts/feed.html.haml rename to scripts/app/views/cohorts/feed.html.haml diff --git a/app/views/cohorts/index.html.haml b/scripts/app/views/cohorts/index.html.haml similarity index 100% rename from app/views/cohorts/index.html.haml rename to scripts/app/views/cohorts/index.html.haml diff --git a/app/views/cohorts/new.html.haml b/scripts/app/views/cohorts/new.html.haml similarity index 100% rename from app/views/cohorts/new.html.haml rename to scripts/app/views/cohorts/new.html.haml diff --git a/app/views/cohorts/partnerup.html.haml b/scripts/app/views/cohorts/partnerup.html.haml similarity index 100% rename from app/views/cohorts/partnerup.html.haml rename to scripts/app/views/cohorts/partnerup.html.haml diff --git a/app/views/cohorts/setup.html.haml b/scripts/app/views/cohorts/setup.html.haml similarity index 100% rename from app/views/cohorts/setup.html.haml rename to scripts/app/views/cohorts/setup.html.haml diff --git a/app/views/cohorts/show.html.haml b/scripts/app/views/cohorts/show.html.haml similarity index 100% rename from app/views/cohorts/show.html.haml rename to scripts/app/views/cohorts/show.html.haml diff --git a/app/views/cohorts/standards/checkpoint_submissions/index.html.haml b/scripts/app/views/cohorts/standards/checkpoint_submissions/index.html.haml similarity index 100% rename from app/views/cohorts/standards/checkpoint_submissions/index.html.haml rename to scripts/app/views/cohorts/standards/checkpoint_submissions/index.html.haml diff --git a/app/views/cohorts/unit_progress.html.haml b/scripts/app/views/cohorts/unit_progress.html.haml similarity index 100% rename from app/views/cohorts/unit_progress.html.haml rename to scripts/app/views/cohorts/unit_progress.html.haml diff --git a/app/views/cohorts/users.html.haml b/scripts/app/views/cohorts/users.html.haml similarity index 100% rename from app/views/cohorts/users.html.haml rename to scripts/app/views/cohorts/users.html.haml diff --git a/app/views/cohorts/users/challenges/show.html.haml b/scripts/app/views/cohorts/users/challenges/show.html.haml similarity index 100% rename from app/views/cohorts/users/challenges/show.html.haml rename to scripts/app/views/cohorts/users/challenges/show.html.haml diff --git a/app/views/cohorts/users/mastery/index.html.haml b/scripts/app/views/cohorts/users/mastery/index.html.haml similarity index 100% rename from app/views/cohorts/users/mastery/index.html.haml rename to scripts/app/views/cohorts/users/mastery/index.html.haml diff --git a/app/views/cohorts/users/submissions_dashboard.html.haml b/scripts/app/views/cohorts/users/submissions_dashboard.html.haml similarity index 100% rename from app/views/cohorts/users/submissions_dashboard.html.haml rename to scripts/app/views/cohorts/users/submissions_dashboard.html.haml diff --git a/app/views/error_404.html.haml b/scripts/app/views/error_404.html.haml similarity index 100% rename from app/views/error_404.html.haml rename to scripts/app/views/error_404.html.haml diff --git a/app/views/error_500.html.haml b/scripts/app/views/error_500.html.haml similarity index 100% rename from app/views/error_500.html.haml rename to scripts/app/views/error_500.html.haml diff --git a/app/views/home/index.html.haml b/scripts/app/views/home/index.html.haml similarity index 100% rename from app/views/home/index.html.haml rename to scripts/app/views/home/index.html.haml diff --git a/app/views/layouts/_primary_navigation.html.haml b/scripts/app/views/layouts/_primary_navigation.html.haml similarity index 100% rename from app/views/layouts/_primary_navigation.html.haml rename to scripts/app/views/layouts/_primary_navigation.html.haml diff --git a/app/views/layouts/_secondary_navigation.html.haml b/scripts/app/views/layouts/_secondary_navigation.html.haml similarity index 100% rename from app/views/layouts/_secondary_navigation.html.haml rename to scripts/app/views/layouts/_secondary_navigation.html.haml diff --git a/app/views/layouts/application.html.haml b/scripts/app/views/layouts/application.html.haml similarity index 100% rename from app/views/layouts/application.html.haml rename to scripts/app/views/layouts/application.html.haml diff --git a/app/views/layouts/mailer.html.erb b/scripts/app/views/layouts/mailer.html.erb similarity index 100% rename from app/views/layouts/mailer.html.erb rename to scripts/app/views/layouts/mailer.html.erb diff --git a/app/views/layouts/mailer.text.erb b/scripts/app/views/layouts/mailer.text.erb similarity index 100% rename from app/views/layouts/mailer.text.erb rename to scripts/app/views/layouts/mailer.text.erb diff --git a/app/views/permalinks/permalink.html.haml b/scripts/app/views/permalinks/permalink.html.haml similarity index 100% rename from app/views/permalinks/permalink.html.haml rename to scripts/app/views/permalinks/permalink.html.haml diff --git a/app/views/shared/_content_file_js.html.haml b/scripts/app/views/shared/_content_file_js.html.haml similarity index 100% rename from app/views/shared/_content_file_js.html.haml rename to scripts/app/views/shared/_content_file_js.html.haml diff --git a/app/views/shared/_hopscotch_callbacks_js.html.haml b/scripts/app/views/shared/_hopscotch_callbacks_js.html.haml similarity index 100% rename from app/views/shared/_hopscotch_callbacks_js.html.haml rename to scripts/app/views/shared/_hopscotch_callbacks_js.html.haml diff --git a/app/views/shared/_intercom_js.html.haml b/scripts/app/views/shared/_intercom_js.html.haml similarity index 100% rename from app/views/shared/_intercom_js.html.haml rename to scripts/app/views/shared/_intercom_js.html.haml diff --git a/app/views/shared/_segment_js.html.haml b/scripts/app/views/shared/_segment_js.html.haml similarity index 100% rename from app/views/shared/_segment_js.html.haml rename to scripts/app/views/shared/_segment_js.html.haml diff --git a/app/views/user_mailer/send_file.html b/scripts/app/views/user_mailer/send_file.html similarity index 100% rename from app/views/user_mailer/send_file.html rename to scripts/app/views/user_mailer/send_file.html diff --git a/app/views/user_mailer/user_import_work.html.haml b/scripts/app/views/user_mailer/user_import_work.html.haml similarity index 100% rename from app/views/user_mailer/user_import_work.html.haml rename to scripts/app/views/user_mailer/user_import_work.html.haml diff --git a/app/views/users/edit.html.haml b/scripts/app/views/users/edit.html.haml similarity index 100% rename from app/views/users/edit.html.haml rename to scripts/app/views/users/edit.html.haml diff --git a/app/views/users/edit_user.html.haml b/scripts/app/views/users/edit_user.html.haml similarity index 100% rename from app/views/users/edit_user.html.haml rename to scripts/app/views/users/edit_user.html.haml diff --git a/app/views/users/index.html.haml b/scripts/app/views/users/index.html.haml similarity index 100% rename from app/views/users/index.html.haml rename to scripts/app/views/users/index.html.haml diff --git a/app/views/users/new.html.haml b/scripts/app/views/users/new.html.haml similarity index 100% rename from app/views/users/new.html.haml rename to scripts/app/views/users/new.html.haml diff --git a/babel.config.js b/scripts/babel.config.js similarity index 100% rename from babel.config.js rename to scripts/babel.config.js diff --git a/bin/bundle b/scripts/bin/bundle similarity index 100% rename from bin/bundle rename to scripts/bin/bundle diff --git a/bin/rails b/scripts/bin/rails similarity index 100% rename from bin/rails rename to scripts/bin/rails diff --git a/bin/rake b/scripts/bin/rake similarity index 100% rename from bin/rake rename to scripts/bin/rake diff --git a/bin/setup b/scripts/bin/setup similarity index 100% rename from bin/setup rename to scripts/bin/setup diff --git a/bin/update b/scripts/bin/update similarity index 100% rename from bin/update rename to scripts/bin/update diff --git a/bin/webpack b/scripts/bin/webpack similarity index 100% rename from bin/webpack rename to scripts/bin/webpack diff --git a/bin/webpack-dev-server b/scripts/bin/webpack-dev-server similarity index 100% rename from bin/webpack-dev-server rename to scripts/bin/webpack-dev-server diff --git a/bin/yarn b/scripts/bin/yarn similarity index 100% rename from bin/yarn rename to scripts/bin/yarn diff --git a/config.ru b/scripts/config.ru similarity index 100% rename from config.ru rename to scripts/config.ru diff --git a/config/application.rb b/scripts/config/application.rb similarity index 100% rename from config/application.rb rename to scripts/config/application.rb diff --git a/config/boot.rb b/scripts/config/boot.rb similarity index 100% rename from config/boot.rb rename to scripts/config/boot.rb diff --git a/config/database.yml b/scripts/config/database.yml similarity index 100% rename from config/database.yml rename to scripts/config/database.yml diff --git a/config/environment.rb b/scripts/config/environment.rb similarity index 100% rename from config/environment.rb rename to scripts/config/environment.rb diff --git a/config/environments/development.rb b/scripts/config/environments/development.rb similarity index 100% rename from config/environments/development.rb rename to scripts/config/environments/development.rb diff --git a/config/environments/production.rb b/scripts/config/environments/production.rb similarity index 100% rename from config/environments/production.rb rename to scripts/config/environments/production.rb diff --git a/config/environments/test.rb b/scripts/config/environments/test.rb similarity index 100% rename from config/environments/test.rb rename to scripts/config/environments/test.rb diff --git a/config/get-routes-audit.md b/scripts/config/get-routes-audit.md similarity index 100% rename from config/get-routes-audit.md rename to scripts/config/get-routes-audit.md diff --git a/config/honeybadger.yml b/scripts/config/honeybadger.yml similarity index 100% rename from config/honeybadger.yml rename to scripts/config/honeybadger.yml diff --git a/config/initializers/apitome.rb b/scripts/config/initializers/apitome.rb similarity index 100% rename from config/initializers/apitome.rb rename to scripts/config/initializers/apitome.rb diff --git a/config/initializers/application_controller_renderer.rb b/scripts/config/initializers/application_controller_renderer.rb similarity index 100% rename from config/initializers/application_controller_renderer.rb rename to scripts/config/initializers/application_controller_renderer.rb diff --git a/config/initializers/assets.rb b/scripts/config/initializers/assets.rb similarity index 100% rename from config/initializers/assets.rb rename to scripts/config/initializers/assets.rb diff --git a/config/initializers/auth_api.rb b/scripts/config/initializers/auth_api.rb similarity index 100% rename from config/initializers/auth_api.rb rename to scripts/config/initializers/auth_api.rb diff --git a/config/initializers/aws.rb b/scripts/config/initializers/aws.rb similarity index 100% rename from config/initializers/aws.rb rename to scripts/config/initializers/aws.rb diff --git a/config/initializers/backtrace_silencers.rb b/scripts/config/initializers/backtrace_silencers.rb similarity index 100% rename from config/initializers/backtrace_silencers.rb rename to scripts/config/initializers/backtrace_silencers.rb diff --git a/config/initializers/content_security_policy.rb b/scripts/config/initializers/content_security_policy.rb similarity index 100% rename from config/initializers/content_security_policy.rb rename to scripts/config/initializers/content_security_policy.rb diff --git a/config/initializers/cookies_serializer.rb b/scripts/config/initializers/cookies_serializer.rb similarity index 100% rename from config/initializers/cookies_serializer.rb rename to scripts/config/initializers/cookies_serializer.rb diff --git a/config/initializers/filter_parameter_logging.rb b/scripts/config/initializers/filter_parameter_logging.rb similarity index 100% rename from config/initializers/filter_parameter_logging.rb rename to scripts/config/initializers/filter_parameter_logging.rb diff --git a/config/initializers/inflections.rb b/scripts/config/initializers/inflections.rb similarity index 100% rename from config/initializers/inflections.rb rename to scripts/config/initializers/inflections.rb diff --git a/config/initializers/mime_types.rb b/scripts/config/initializers/mime_types.rb similarity index 100% rename from config/initializers/mime_types.rb rename to scripts/config/initializers/mime_types.rb diff --git a/config/initializers/new_framework_defaults_5_1.rb b/scripts/config/initializers/new_framework_defaults_5_1.rb similarity index 100% rename from config/initializers/new_framework_defaults_5_1.rb rename to scripts/config/initializers/new_framework_defaults_5_1.rb diff --git a/config/initializers/new_framework_defaults_5_2.rb b/scripts/config/initializers/new_framework_defaults_5_2.rb similarity index 100% rename from config/initializers/new_framework_defaults_5_2.rb rename to scripts/config/initializers/new_framework_defaults_5_2.rb diff --git a/config/initializers/octokit.rb b/scripts/config/initializers/octokit.rb similarity index 100% rename from config/initializers/octokit.rb rename to scripts/config/initializers/octokit.rb diff --git a/config/initializers/pagy.rb b/scripts/config/initializers/pagy.rb similarity index 100% rename from config/initializers/pagy.rb rename to scripts/config/initializers/pagy.rb diff --git a/config/initializers/pundit.rb b/scripts/config/initializers/pundit.rb similarity index 100% rename from config/initializers/pundit.rb rename to scripts/config/initializers/pundit.rb diff --git a/config/initializers/rack_attack.rb b/scripts/config/initializers/rack_attack.rb similarity index 100% rename from config/initializers/rack_attack.rb rename to scripts/config/initializers/rack_attack.rb diff --git a/config/initializers/segment.rb b/scripts/config/initializers/segment.rb similarity index 100% rename from config/initializers/segment.rb rename to scripts/config/initializers/segment.rb diff --git a/config/initializers/sidekiq.rb b/scripts/config/initializers/sidekiq.rb similarity index 100% rename from config/initializers/sidekiq.rb rename to scripts/config/initializers/sidekiq.rb diff --git a/config/initializers/wrap_parameters.rb b/scripts/config/initializers/wrap_parameters.rb similarity index 100% rename from config/initializers/wrap_parameters.rb rename to scripts/config/initializers/wrap_parameters.rb diff --git a/config/locales/en.yml b/scripts/config/locales/en.yml similarity index 100% rename from config/locales/en.yml rename to scripts/config/locales/en.yml diff --git a/config/puma.rb b/scripts/config/puma.rb similarity index 100% rename from config/puma.rb rename to scripts/config/puma.rb diff --git a/config/routes.rb b/scripts/config/routes.rb similarity index 100% rename from config/routes.rb rename to scripts/config/routes.rb diff --git a/config/secrets.yml b/scripts/config/secrets.yml similarity index 100% rename from config/secrets.yml rename to scripts/config/secrets.yml diff --git a/config/sidekiq.yml b/scripts/config/sidekiq.yml similarity index 100% rename from config/sidekiq.yml rename to scripts/config/sidekiq.yml diff --git a/config/spring.rb b/scripts/config/spring.rb similarity index 100% rename from config/spring.rb rename to scripts/config/spring.rb diff --git a/config/storage.yml b/scripts/config/storage.yml similarity index 100% rename from config/storage.yml rename to scripts/config/storage.yml diff --git a/config/webpack/development.js b/scripts/config/webpack/development.js similarity index 100% rename from config/webpack/development.js rename to scripts/config/webpack/development.js diff --git a/config/webpack/environment.js b/scripts/config/webpack/environment.js similarity index 100% rename from config/webpack/environment.js rename to scripts/config/webpack/environment.js diff --git a/config/webpack/loaders/typescript.js b/scripts/config/webpack/loaders/typescript.js similarity index 100% rename from config/webpack/loaders/typescript.js rename to scripts/config/webpack/loaders/typescript.js diff --git a/config/webpack/production.js b/scripts/config/webpack/production.js similarity index 100% rename from config/webpack/production.js rename to scripts/config/webpack/production.js diff --git a/config/webpack/test.js b/scripts/config/webpack/test.js similarity index 100% rename from config/webpack/test.js rename to scripts/config/webpack/test.js diff --git a/config/webpacker.yml b/scripts/config/webpacker.yml similarity index 100% rename from config/webpacker.yml rename to scripts/config/webpacker.yml diff --git a/db/drawio_schema_with_explanations.xml b/scripts/db/drawio_schema_with_explanations.xml similarity index 100% rename from db/drawio_schema_with_explanations.xml rename to scripts/db/drawio_schema_with_explanations.xml diff --git a/db/migrate/20171003191909_create_users.rb b/scripts/db/migrate/20171003191909_create_users.rb similarity index 100% rename from db/migrate/20171003191909_create_users.rb rename to scripts/db/migrate/20171003191909_create_users.rb diff --git a/db/migrate/20171005135550_add_admin_to_users.rb b/scripts/db/migrate/20171005135550_add_admin_to_users.rb similarity index 100% rename from db/migrate/20171005135550_add_admin_to_users.rb rename to scripts/db/migrate/20171005135550_add_admin_to_users.rb diff --git a/db/migrate/20171005140042_rename_users_profile_image_url.rb b/scripts/db/migrate/20171005140042_rename_users_profile_image_url.rb similarity index 100% rename from db/migrate/20171005140042_rename_users_profile_image_url.rb rename to scripts/db/migrate/20171005140042_rename_users_profile_image_url.rb diff --git a/db/migrate/20171006195948_create_blocks.rb b/scripts/db/migrate/20171006195948_create_blocks.rb similarity index 100% rename from db/migrate/20171006195948_create_blocks.rb rename to scripts/db/migrate/20171006195948_create_blocks.rb diff --git a/db/migrate/20171009194124_create_releases.rb b/scripts/db/migrate/20171009194124_create_releases.rb similarity index 100% rename from db/migrate/20171009194124_create_releases.rb rename to scripts/db/migrate/20171009194124_create_releases.rb diff --git a/db/migrate/20171011232501_add_sync_errors_to_block.rb b/scripts/db/migrate/20171011232501_add_sync_errors_to_block.rb similarity index 100% rename from db/migrate/20171011232501_add_sync_errors_to_block.rb rename to scripts/db/migrate/20171011232501_add_sync_errors_to_block.rb diff --git a/db/migrate/20171012200106_create_standards.rb b/scripts/db/migrate/20171012200106_create_standards.rb similarity index 100% rename from db/migrate/20171012200106_create_standards.rb rename to scripts/db/migrate/20171012200106_create_standards.rb diff --git a/db/migrate/20171016192627_create_cohorts.rb b/scripts/db/migrate/20171016192627_create_cohorts.rb similarity index 100% rename from db/migrate/20171016192627_create_cohorts.rb rename to scripts/db/migrate/20171016192627_create_cohorts.rb diff --git a/db/migrate/20171017205306_create_cohort_users.rb b/scripts/db/migrate/20171017205306_create_cohort_users.rb similarity index 100% rename from db/migrate/20171017205306_create_cohort_users.rb rename to scripts/db/migrate/20171017205306_create_cohort_users.rb diff --git a/db/migrate/20171017221501_remove_presence_indices_from_label_and_pretty_name_on_cohorts.rb b/scripts/db/migrate/20171017221501_remove_presence_indices_from_label_and_pretty_name_on_cohorts.rb similarity index 100% rename from db/migrate/20171017221501_remove_presence_indices_from_label_and_pretty_name_on_cohorts.rb rename to scripts/db/migrate/20171017221501_remove_presence_indices_from_label_and_pretty_name_on_cohorts.rb diff --git a/db/migrate/20171019165527_create_content_files.rb b/scripts/db/migrate/20171019165527_create_content_files.rb similarity index 100% rename from db/migrate/20171019165527_create_content_files.rb rename to scripts/db/migrate/20171019165527_create_content_files.rb diff --git a/db/migrate/20171019192005_create_cohort_releases.rb b/scripts/db/migrate/20171019192005_create_cohort_releases.rb similarity index 100% rename from db/migrate/20171019192005_create_cohort_releases.rb rename to scripts/db/migrate/20171019192005_create_cohort_releases.rb diff --git a/db/migrate/20171023211301_create_challenges.rb b/scripts/db/migrate/20171023211301_create_challenges.rb similarity index 100% rename from db/migrate/20171023211301_create_challenges.rb rename to scripts/db/migrate/20171023211301_create_challenges.rb diff --git a/db/migrate/20171024202210_create_checkpoint_submissions.rb b/scripts/db/migrate/20171024202210_create_checkpoint_submissions.rb similarity index 100% rename from db/migrate/20171024202210_create_checkpoint_submissions.rb rename to scripts/db/migrate/20171024202210_create_checkpoint_submissions.rb diff --git a/db/migrate/20171024203630_create_submitted_challenge_answers.rb b/scripts/db/migrate/20171024203630_create_submitted_challenge_answers.rb similarity index 100% rename from db/migrate/20171024203630_create_submitted_challenge_answers.rb rename to scripts/db/migrate/20171024203630_create_submitted_challenge_answers.rb diff --git a/db/migrate/20171025211916_create_performances.rb b/scripts/db/migrate/20171025211916_create_performances.rb similarity index 100% rename from db/migrate/20171025211916_create_performances.rb rename to scripts/db/migrate/20171025211916_create_performances.rb diff --git a/db/migrate/20171025223250_add_autoscore_to_content_files.rb b/scripts/db/migrate/20171025223250_add_autoscore_to_content_files.rb similarity index 100% rename from db/migrate/20171025223250_add_autoscore_to_content_files.rb rename to scripts/db/migrate/20171025223250_add_autoscore_to_content_files.rb diff --git a/db/migrate/20171026193413_add_title_to_content_files.rb b/scripts/db/migrate/20171026193413_add_title_to_content_files.rb similarity index 100% rename from db/migrate/20171026193413_add_title_to_content_files.rb rename to scripts/db/migrate/20171026193413_add_title_to_content_files.rb diff --git a/db/migrate/20171102144822_cohort_release_unique_index.rb b/scripts/db/migrate/20171102144822_cohort_release_unique_index.rb similarity index 100% rename from db/migrate/20171102144822_cohort_release_unique_index.rb rename to scripts/db/migrate/20171102144822_cohort_release_unique_index.rb diff --git a/db/migrate/20171102165314_add_unique_constraint_to_block_title.rb b/scripts/db/migrate/20171102165314_add_unique_constraint_to_block_title.rb similarity index 100% rename from db/migrate/20171102165314_add_unique_constraint_to_block_title.rb rename to scripts/db/migrate/20171102165314_add_unique_constraint_to_block_title.rb diff --git a/db/migrate/20171102171128_add_position_to_cohort_releases.rb b/scripts/db/migrate/20171102171128_add_position_to_cohort_releases.rb similarity index 100% rename from db/migrate/20171102171128_add_position_to_cohort_releases.rb rename to scripts/db/migrate/20171102171128_add_position_to_cohort_releases.rb diff --git a/db/migrate/20171127223701_create_activities.rb b/scripts/db/migrate/20171127223701_create_activities.rb similarity index 100% rename from db/migrate/20171127223701_create_activities.rb rename to scripts/db/migrate/20171127223701_create_activities.rb diff --git a/db/migrate/20171130183523_create_user_last_viewed_standard_path.rb b/scripts/db/migrate/20171130183523_create_user_last_viewed_standard_path.rb similarity index 100% rename from db/migrate/20171130183523_create_user_last_viewed_standard_path.rb rename to scripts/db/migrate/20171130183523_create_user_last_viewed_standard_path.rb diff --git a/db/migrate/20171130185636_add_uid_to_content_files.rb b/scripts/db/migrate/20171130185636_add_uid_to_content_files.rb similarity index 100% rename from db/migrate/20171130185636_add_uid_to_content_files.rb rename to scripts/db/migrate/20171130185636_add_uid_to_content_files.rb diff --git a/db/migrate/20171205211624_add_slack_data_to_user.rb b/scripts/db/migrate/20171205211624_add_slack_data_to_user.rb similarity index 100% rename from db/migrate/20171205211624_add_slack_data_to_user.rb rename to scripts/db/migrate/20171205211624_add_slack_data_to_user.rb diff --git a/db/migrate/20180104213519_add_standard_uid_to_performances.rb b/scripts/db/migrate/20180104213519_add_standard_uid_to_performances.rb similarity index 100% rename from db/migrate/20180104213519_add_standard_uid_to_performances.rb rename to scripts/db/migrate/20180104213519_add_standard_uid_to_performances.rb diff --git a/db/migrate/20180110174447_add_type_relative_display_name_to_content_files.rb b/scripts/db/migrate/20180110174447_add_type_relative_display_name_to_content_files.rb similarity index 100% rename from db/migrate/20180110174447_add_type_relative_display_name_to_content_files.rb rename to scripts/db/migrate/20180110174447_add_type_relative_display_name_to_content_files.rb diff --git a/db/migrate/20180110223429_add_auth_roles_to_user.rb b/scripts/db/migrate/20180110223429_add_auth_roles_to_user.rb similarity index 100% rename from db/migrate/20180110223429_add_auth_roles_to_user.rb rename to scripts/db/migrate/20180110223429_add_auth_roles_to_user.rb diff --git a/db/migrate/20180110224150_denormalize_submitted_challenge_answers.rb b/scripts/db/migrate/20180110224150_denormalize_submitted_challenge_answers.rb similarity index 100% rename from db/migrate/20180110224150_denormalize_submitted_challenge_answers.rb rename to scripts/db/migrate/20180110224150_denormalize_submitted_challenge_answers.rb diff --git a/db/migrate/20180110233303_add_roles_to_cohort_user.rb b/scripts/db/migrate/20180110233303_add_roles_to_cohort_user.rb similarity index 100% rename from db/migrate/20180110233303_add_roles_to_cohort_user.rb rename to scripts/db/migrate/20180110233303_add_roles_to_cohort_user.rb diff --git a/db/migrate/20180112234504_add_last_viewed_cohort_id_to_users.rb b/scripts/db/migrate/20180112234504_add_last_viewed_cohort_id_to_users.rb similarity index 100% rename from db/migrate/20180112234504_add_last_viewed_cohort_id_to_users.rb rename to scripts/db/migrate/20180112234504_add_last_viewed_cohort_id_to_users.rb diff --git a/db/migrate/20180116174519_add_github_user_name_to_user.rb b/scripts/db/migrate/20180116174519_add_github_user_name_to_user.rb similarity index 100% rename from db/migrate/20180116174519_add_github_user_name_to_user.rb rename to scripts/db/migrate/20180116174519_add_github_user_name_to_user.rb diff --git a/db/migrate/20180116181937_add_taught_in_learn_to_cohorts.rb b/scripts/db/migrate/20180116181937_add_taught_in_learn_to_cohorts.rb similarity index 100% rename from db/migrate/20180116181937_add_taught_in_learn_to_cohorts.rb rename to scripts/db/migrate/20180116181937_add_taught_in_learn_to_cohorts.rb diff --git a/db/migrate/20180116213927_remove_forge_admin_and_role.rb b/scripts/db/migrate/20180116213927_remove_forge_admin_and_role.rb similarity index 100% rename from db/migrate/20180116213927_remove_forge_admin_and_role.rb rename to scripts/db/migrate/20180116213927_remove_forge_admin_and_role.rb diff --git a/db/migrate/20180119184121_add_cohort_id_to_submitted_challenge_answer.rb b/scripts/db/migrate/20180119184121_add_cohort_id_to_submitted_challenge_answer.rb similarity index 100% rename from db/migrate/20180119184121_add_cohort_id_to_submitted_challenge_answer.rb rename to scripts/db/migrate/20180119184121_add_cohort_id_to_submitted_challenge_answer.rb diff --git a/db/migrate/20180124221642_denormalize_checkpoint_submissions.rb b/scripts/db/migrate/20180124221642_denormalize_checkpoint_submissions.rb similarity index 100% rename from db/migrate/20180124221642_denormalize_checkpoint_submissions.rb rename to scripts/db/migrate/20180124221642_denormalize_checkpoint_submissions.rb diff --git a/db/migrate/20180125212203_rename_taught_in_learn.rb b/scripts/db/migrate/20180125212203_rename_taught_in_learn.rb similarity index 100% rename from db/migrate/20180125212203_rename_taught_in_learn.rb rename to scripts/db/migrate/20180125212203_rename_taught_in_learn.rb diff --git a/db/migrate/20180125222044_change_learn_v2_default.rb b/scripts/db/migrate/20180125222044_change_learn_v2_default.rb similarity index 100% rename from db/migrate/20180125222044_change_learn_v2_default.rb rename to scripts/db/migrate/20180125222044_change_learn_v2_default.rb diff --git a/db/migrate/20180131220605_create_notifications.rb b/scripts/db/migrate/20180131220605_create_notifications.rb similarity index 100% rename from db/migrate/20180131220605_create_notifications.rb rename to scripts/db/migrate/20180131220605_create_notifications.rb diff --git a/db/migrate/20180202225951_add_github_sha_to_releases.rb b/scripts/db/migrate/20180202225951_add_github_sha_to_releases.rb similarity index 100% rename from db/migrate/20180202225951_add_github_sha_to_releases.rb rename to scripts/db/migrate/20180202225951_add_github_sha_to_releases.rb diff --git a/db/migrate/20180209175941_add_use_latest_release_to_cohort_releases.rb b/scripts/db/migrate/20180209175941_add_use_latest_release_to_cohort_releases.rb similarity index 100% rename from db/migrate/20180209175941_add_use_latest_release_to_cohort_releases.rb rename to scripts/db/migrate/20180209175941_add_use_latest_release_to_cohort_releases.rb diff --git a/db/migrate/20180313220132_add_docker_directory_path_to_challenges.rb b/scripts/db/migrate/20180313220132_add_docker_directory_path_to_challenges.rb similarity index 100% rename from db/migrate/20180313220132_add_docker_directory_path_to_challenges.rb rename to scripts/db/migrate/20180313220132_add_docker_directory_path_to_challenges.rb diff --git a/db/migrate/20180316222140_rename_cohort_title_to_name.rb b/scripts/db/migrate/20180316222140_rename_cohort_title_to_name.rb similarity index 100% rename from db/migrate/20180316222140_rename_cohort_title_to_name.rb rename to scripts/db/migrate/20180316222140_rename_cohort_title_to_name.rb diff --git a/db/migrate/20180320162849_add_deleted_at_to_cohorts.rb b/scripts/db/migrate/20180320162849_add_deleted_at_to_cohorts.rb similarity index 100% rename from db/migrate/20180320162849_add_deleted_at_to_cohorts.rb rename to scripts/db/migrate/20180320162849_add_deleted_at_to_cohorts.rb diff --git a/db/migrate/20180402212114_remove_pretty_name_from_cohort.rb b/scripts/db/migrate/20180402212114_remove_pretty_name_from_cohort.rb similarity index 100% rename from db/migrate/20180402212114_remove_pretty_name_from_cohort.rb rename to scripts/db/migrate/20180402212114_remove_pretty_name_from_cohort.rb diff --git a/db/migrate/20180412214504_add_used_by_application_to_cohorts.rb b/scripts/db/migrate/20180412214504_add_used_by_application_to_cohorts.rb similarity index 100% rename from db/migrate/20180412214504_add_used_by_application_to_cohorts.rb rename to scripts/db/migrate/20180412214504_add_used_by_application_to_cohorts.rb diff --git a/db/migrate/20180412223312_populate_used_by_application.rb b/scripts/db/migrate/20180412223312_populate_used_by_application.rb similarity index 100% rename from db/migrate/20180412223312_populate_used_by_application.rb rename to scripts/db/migrate/20180412223312_populate_used_by_application.rb diff --git a/db/migrate/20180504193033_add_feature_branch_columns_to_releases.rb b/scripts/db/migrate/20180504193033_add_feature_branch_columns_to_releases.rb similarity index 100% rename from db/migrate/20180504193033_add_feature_branch_columns_to_releases.rb rename to scripts/db/migrate/20180504193033_add_feature_branch_columns_to_releases.rb diff --git a/db/migrate/20180504200226_add_pending_release_id_to_cohort_releases.rb b/scripts/db/migrate/20180504200226_add_pending_release_id_to_cohort_releases.rb similarity index 100% rename from db/migrate/20180504200226_add_pending_release_id_to_cohort_releases.rb rename to scripts/db/migrate/20180504200226_add_pending_release_id_to_cohort_releases.rb diff --git a/db/migrate/20180813205413_add_readme_text_to_release.rb b/scripts/db/migrate/20180813205413_add_readme_text_to_release.rb similarity index 100% rename from db/migrate/20180813205413_add_readme_text_to_release.rb rename to scripts/db/migrate/20180813205413_add_readme_text_to_release.rb diff --git a/db/migrate/20180813213358_add_mode_to_cohort.rb b/scripts/db/migrate/20180813213358_add_mode_to_cohort.rb similarity index 100% rename from db/migrate/20180813213358_add_mode_to_cohort.rb rename to scripts/db/migrate/20180813213358_add_mode_to_cohort.rb diff --git a/db/migrate/20180813214453_populate_mode_cohorts.rb b/scripts/db/migrate/20180813214453_populate_mode_cohorts.rb similarity index 100% rename from db/migrate/20180813214453_populate_mode_cohorts.rb rename to scripts/db/migrate/20180813214453_populate_mode_cohorts.rb diff --git a/db/migrate/20180817181129_add_cohort_id_to_user_last_viewed_standard_path.rb b/scripts/db/migrate/20180817181129_add_cohort_id_to_user_last_viewed_standard_path.rb similarity index 100% rename from db/migrate/20180817181129_add_cohort_id_to_user_last_viewed_standard_path.rb rename to scripts/db/migrate/20180817181129_add_cohort_id_to_user_last_viewed_standard_path.rb diff --git a/db/migrate/20180824200753_create_lesson_visits.rb b/scripts/db/migrate/20180824200753_create_lesson_visits.rb similarity index 100% rename from db/migrate/20180824200753_create_lesson_visits.rb rename to scripts/db/migrate/20180824200753_create_lesson_visits.rb diff --git a/db/migrate/20181112215710_add_preferred_campus_to_users.rb b/scripts/db/migrate/20181112215710_add_preferred_campus_to_users.rb similarity index 100% rename from db/migrate/20181112215710_add_preferred_campus_to_users.rb rename to scripts/db/migrate/20181112215710_add_preferred_campus_to_users.rb diff --git a/db/migrate/20181114190340_create_job_results.rb b/scripts/db/migrate/20181114190340_create_job_results.rb similarity index 100% rename from db/migrate/20181114190340_create_job_results.rb rename to scripts/db/migrate/20181114190340_create_job_results.rb diff --git a/db/migrate/20181120221622_create_sections.rb b/scripts/db/migrate/20181120221622_create_sections.rb similarity index 100% rename from db/migrate/20181120221622_create_sections.rb rename to scripts/db/migrate/20181120221622_create_sections.rb diff --git a/db/migrate/20181121202104_add_section_id_to_cohort_releases.rb b/scripts/db/migrate/20181121202104_add_section_id_to_cohort_releases.rb similarity index 100% rename from db/migrate/20181121202104_add_section_id_to_cohort_releases.rb rename to scripts/db/migrate/20181121202104_add_section_id_to_cohort_releases.rb diff --git a/db/migrate/20181203215524_add_challenge_completion_to_performance.rb b/scripts/db/migrate/20181203215524_add_challenge_completion_to_performance.rb similarity index 100% rename from db/migrate/20181203215524_add_challenge_completion_to_performance.rb rename to scripts/db/migrate/20181203215524_add_challenge_completion_to_performance.rb diff --git a/db/migrate/20181213180900_add_show_tests_to_challenges.rb b/scripts/db/migrate/20181213180900_add_show_tests_to_challenges.rb similarity index 100% rename from db/migrate/20181213180900_add_show_tests_to_challenges.rb rename to scripts/db/migrate/20181213180900_add_show_tests_to_challenges.rb diff --git a/db/migrate/20181214175640_create_content_visibilities.rb b/scripts/db/migrate/20181214175640_create_content_visibilities.rb similarity index 100% rename from db/migrate/20181214175640_create_content_visibilities.rb rename to scripts/db/migrate/20181214175640_create_content_visibilities.rb diff --git a/db/migrate/20181220005015_add_default_visibility_to_content_files.rb b/scripts/db/migrate/20181220005015_add_default_visibility_to_content_files.rb similarity index 100% rename from db/migrate/20181220005015_add_default_visibility_to_content_files.rb rename to scripts/db/migrate/20181220005015_add_default_visibility_to_content_files.rb diff --git a/db/migrate/20190410171829_create_pairings.rb b/scripts/db/migrate/20190410171829_create_pairings.rb similarity index 100% rename from db/migrate/20190410171829_create_pairings.rb rename to scripts/db/migrate/20190410171829_create_pairings.rb diff --git a/db/migrate/20190423171448_add_data_path_to_challenge.rb b/scripts/db/migrate/20190423171448_add_data_path_to_challenge.rb similarity index 100% rename from db/migrate/20190423171448_add_data_path_to_challenge.rb rename to scripts/db/migrate/20190423171448_add_data_path_to_challenge.rb diff --git a/db/migrate/20190510193410_add_last_setup_visit_to_cohort_users.rb b/scripts/db/migrate/20190510193410_add_last_setup_visit_to_cohort_users.rb similarity index 100% rename from db/migrate/20190510193410_add_last_setup_visit_to_cohort_users.rb rename to scripts/db/migrate/20190510193410_add_last_setup_visit_to_cohort_users.rb diff --git a/db/migrate/20190520223958_add_max_attempts_to_content_files.rb b/scripts/db/migrate/20190520223958_add_max_attempts_to_content_files.rb similarity index 100% rename from db/migrate/20190520223958_add_max_attempts_to_content_files.rb rename to scripts/db/migrate/20190520223958_add_max_attempts_to_content_files.rb diff --git a/db/migrate/20190529170059_add_allowed_attempts_to_challenge.rb b/scripts/db/migrate/20190529170059_add_allowed_attempts_to_challenge.rb similarity index 100% rename from db/migrate/20190529170059_add_allowed_attempts_to_challenge.rb rename to scripts/db/migrate/20190529170059_add_allowed_attempts_to_challenge.rb diff --git a/db/migrate/20190624222000_remove_max_attempts_from_challenges.rb b/scripts/db/migrate/20190624222000_remove_max_attempts_from_challenges.rb similarity index 100% rename from db/migrate/20190624222000_remove_max_attempts_from_challenges.rb rename to scripts/db/migrate/20190624222000_remove_max_attempts_from_challenges.rb diff --git a/db/migrate/20190625173725_add_points_and_success_criteria_to_challenges.rb b/scripts/db/migrate/20190625173725_add_points_and_success_criteria_to_challenges.rb similarity index 100% rename from db/migrate/20190625173725_add_points_and_success_criteria_to_challenges.rb rename to scripts/db/migrate/20190625173725_add_points_and_success_criteria_to_challenges.rb diff --git a/db/migrate/20190709170851_drop_learn_version_columns_on_cohorts.rb b/scripts/db/migrate/20190709170851_drop_learn_version_columns_on_cohorts.rb similarity index 100% rename from db/migrate/20190709170851_drop_learn_version_columns_on_cohorts.rb rename to scripts/db/migrate/20190709170851_drop_learn_version_columns_on_cohorts.rb diff --git a/db/migrate/20190829203949_add_settings_to_cohorts.rb b/scripts/db/migrate/20190829203949_add_settings_to_cohorts.rb similarity index 100% rename from db/migrate/20190829203949_add_settings_to_cohorts.rb rename to scripts/db/migrate/20190829203949_add_settings_to_cohorts.rb diff --git a/db/migrate/20190910161219_add_points_to_submitted_challenge_answer.rb b/scripts/db/migrate/20190910161219_add_points_to_submitted_challenge_answer.rb similarity index 100% rename from db/migrate/20190910161219_add_points_to_submitted_challenge_answer.rb rename to scripts/db/migrate/20190910161219_add_points_to_submitted_challenge_answer.rb diff --git a/db/migrate/20190910161303_add_points_to_checkpoint_submissions.rb b/scripts/db/migrate/20190910161303_add_points_to_checkpoint_submissions.rb similarity index 100% rename from db/migrate/20190910161303_add_points_to_checkpoint_submissions.rb rename to scripts/db/migrate/20190910161303_add_points_to_checkpoint_submissions.rb diff --git a/db/migrate/20190919173956_change_success_criteria_to_rubric_on_challenges.rb b/scripts/db/migrate/20190919173956_change_success_criteria_to_rubric_on_challenges.rb similarity index 100% rename from db/migrate/20190919173956_change_success_criteria_to_rubric_on_challenges.rb rename to scripts/db/migrate/20190919173956_change_success_criteria_to_rubric_on_challenges.rb diff --git a/db/migrate/20191001214559_add_topics_to_challenges.rb b/scripts/db/migrate/20191001214559_add_topics_to_challenges.rb similarity index 100% rename from db/migrate/20191001214559_add_topics_to_challenges.rb rename to scripts/db/migrate/20191001214559_add_topics_to_challenges.rb diff --git a/db/migrate/20191001221324_add_perf_data_to_submissions.rb b/scripts/db/migrate/20191001221324_add_perf_data_to_submissions.rb similarity index 100% rename from db/migrate/20191001221324_add_perf_data_to_submissions.rb rename to scripts/db/migrate/20191001221324_add_perf_data_to_submissions.rb diff --git a/db/migrate/20191008211403_add_release_id_to_challenges.rb b/scripts/db/migrate/20191008211403_add_release_id_to_challenges.rb similarity index 100% rename from db/migrate/20191008211403_add_release_id_to_challenges.rb rename to scripts/db/migrate/20191008211403_add_release_id_to_challenges.rb diff --git a/db/migrate/20191009214248_add_user_id_to_release.rb b/scripts/db/migrate/20191009214248_add_user_id_to_release.rb similarity index 100% rename from db/migrate/20191009214248_add_user_id_to_release.rb rename to scripts/db/migrate/20191009214248_add_user_id_to_release.rb diff --git a/db/migrate/20191024200444_add_visibility_type_to_content_visibilities.rb b/scripts/db/migrate/20191024200444_add_visibility_type_to_content_visibilities.rb similarity index 100% rename from db/migrate/20191024200444_add_visibility_type_to_content_visibilities.rb rename to scripts/db/migrate/20191024200444_add_visibility_type_to_content_visibilities.rb diff --git a/db/migrate/20191031212058_add_api_token_to_user.rb b/scripts/db/migrate/20191031212058_add_api_token_to_user.rb similarity index 100% rename from db/migrate/20191031212058_add_api_token_to_user.rb rename to scripts/db/migrate/20191031212058_add_api_token_to_user.rb diff --git a/db/migrate/20191108211234_add_preview_to_releases.rb b/scripts/db/migrate/20191108211234_add_preview_to_releases.rb similarity index 100% rename from db/migrate/20191108211234_add_preview_to_releases.rb rename to scripts/db/migrate/20191108211234_add_preview_to_releases.rb diff --git a/db/migrate/20191114211938_create_api_interactions.rb b/scripts/db/migrate/20191114211938_create_api_interactions.rb similarity index 100% rename from db/migrate/20191114211938_create_api_interactions.rb rename to scripts/db/migrate/20191114211938_create_api_interactions.rb diff --git a/db/migrate/20191119225902_add_sandbox_boolean_to_cohorts.rb b/scripts/db/migrate/20191119225902_add_sandbox_boolean_to_cohorts.rb similarity index 100% rename from db/migrate/20191119225902_add_sandbox_boolean_to_cohorts.rb rename to scripts/db/migrate/20191119225902_add_sandbox_boolean_to_cohorts.rb diff --git a/db/migrate/20191206230913_add_sync_warnings.rb b/scripts/db/migrate/20191206230913_add_sync_warnings.rb similarity index 100% rename from db/migrate/20191206230913_add_sync_warnings.rb rename to scripts/db/migrate/20191206230913_add_sync_warnings.rb diff --git a/db/migrate/20191209165026_add_metadata_to_api_interactions.rb b/scripts/db/migrate/20191209165026_add_metadata_to_api_interactions.rb similarity index 100% rename from db/migrate/20191209165026_add_metadata_to_api_interactions.rb rename to scripts/db/migrate/20191209165026_add_metadata_to_api_interactions.rb diff --git a/db/migrate/20191218232717_add_assign_partial_credig_to_challenges.rb b/scripts/db/migrate/20191218232717_add_assign_partial_credig_to_challenges.rb similarity index 100% rename from db/migrate/20191218232717_add_assign_partial_credig_to_challenges.rb rename to scripts/db/migrate/20191218232717_add_assign_partial_credig_to_challenges.rb diff --git a/db/migrate/20200108184027_add_end_time_to_checkpoint_submissions.rb b/scripts/db/migrate/20200108184027_add_end_time_to_checkpoint_submissions.rb similarity index 100% rename from db/migrate/20200108184027_add_end_time_to_checkpoint_submissions.rb rename to scripts/db/migrate/20200108184027_add_end_time_to_checkpoint_submissions.rb diff --git a/db/migrate/20200110201253_change_cohort_default_percentage.rb b/scripts/db/migrate/20200110201253_change_cohort_default_percentage.rb similarity index 100% rename from db/migrate/20200110201253_change_cohort_default_percentage.rb rename to scripts/db/migrate/20200110201253_change_cohort_default_percentage.rb diff --git a/db/migrate/20200213201649_add_time_limit_to_content_file.rb b/scripts/db/migrate/20200213201649_add_time_limit_to_content_file.rb similarity index 100% rename from db/migrate/20200213201649_add_time_limit_to_content_file.rb rename to scripts/db/migrate/20200213201649_add_time_limit_to_content_file.rb diff --git a/db/migrate/20200310222235_add_submitted_at_to_checkpoint_submissions.rb b/scripts/db/migrate/20200310222235_add_submitted_at_to_checkpoint_submissions.rb similarity index 100% rename from db/migrate/20200310222235_add_submitted_at_to_checkpoint_submissions.rb rename to scripts/db/migrate/20200310222235_add_submitted_at_to_checkpoint_submissions.rb diff --git a/db/migrate/20200408212051_add_allow_paired_submissions_to_cohort.rb b/scripts/db/migrate/20200408212051_add_allow_paired_submissions_to_cohort.rb similarity index 100% rename from db/migrate/20200408212051_add_allow_paired_submissions_to_cohort.rb rename to scripts/db/migrate/20200408212051_add_allow_paired_submissions_to_cohort.rb diff --git a/db/migrate/20200416155234_add_pair_submission_ids_to_checkpoint_submissinos.rb b/scripts/db/migrate/20200416155234_add_pair_submission_ids_to_checkpoint_submissinos.rb similarity index 100% rename from db/migrate/20200416155234_add_pair_submission_ids_to_checkpoint_submissinos.rb rename to scripts/db/migrate/20200416155234_add_pair_submission_ids_to_checkpoint_submissinos.rb diff --git a/db/migrate/20200430165251_add_external_to_challenge.rb b/scripts/db/migrate/20200430165251_add_external_to_challenge.rb similarity index 100% rename from db/migrate/20200430165251_add_external_to_challenge.rb rename to scripts/db/migrate/20200430165251_add_external_to_challenge.rb diff --git a/db/migrate/20200702155846_add_cache_to_blocks.rb b/scripts/db/migrate/20200702155846_add_cache_to_blocks.rb similarity index 100% rename from db/migrate/20200702155846_add_cache_to_blocks.rb rename to scripts/db/migrate/20200702155846_add_cache_to_blocks.rb diff --git a/db/migrate/20200707164932_add_archived_at_to_blocks.rb b/scripts/db/migrate/20200707164932_add_archived_at_to_blocks.rb similarity index 100% rename from db/migrate/20200707164932_add_archived_at_to_blocks.rb rename to scripts/db/migrate/20200707164932_add_archived_at_to_blocks.rb diff --git a/db/migrate/20200720213420_add_block_location_details_to_blocks.rb b/scripts/db/migrate/20200720213420_add_block_location_details_to_blocks.rb similarity index 100% rename from db/migrate/20200720213420_add_block_location_details_to_blocks.rb rename to scripts/db/migrate/20200720213420_add_block_location_details_to_blocks.rb diff --git a/db/migrate/20200724195251_add_whitelabeled_to_cohorts.rb b/scripts/db/migrate/20200724195251_add_whitelabeled_to_cohorts.rb similarity index 100% rename from db/migrate/20200724195251_add_whitelabeled_to_cohorts.rb rename to scripts/db/migrate/20200724195251_add_whitelabeled_to_cohorts.rb diff --git a/db/migrate/20200729214247_remove_block_uniqueness_pg_columns.rb b/scripts/db/migrate/20200729214247_remove_block_uniqueness_pg_columns.rb similarity index 100% rename from db/migrate/20200729214247_remove_block_uniqueness_pg_columns.rb rename to scripts/db/migrate/20200729214247_remove_block_uniqueness_pg_columns.rb diff --git a/db/migrate/20200811215713_add_student_import_status_to_cohorts.rb b/scripts/db/migrate/20200811215713_add_student_import_status_to_cohorts.rb similarity index 100% rename from db/migrate/20200811215713_add_student_import_status_to_cohorts.rb rename to scripts/db/migrate/20200811215713_add_student_import_status_to_cohorts.rb diff --git a/db/migrate/20200818172419_addd_docker_directory_zip_to_challenges.rb b/scripts/db/migrate/20200818172419_addd_docker_directory_zip_to_challenges.rb similarity index 100% rename from db/migrate/20200818172419_addd_docker_directory_zip_to_challenges.rb rename to scripts/db/migrate/20200818172419_addd_docker_directory_zip_to_challenges.rb diff --git a/db/migrate/20200828165130_default_block_org_tog_school.rb b/scripts/db/migrate/20200828165130_default_block_org_tog_school.rb similarity index 100% rename from db/migrate/20200828165130_default_block_org_tog_school.rb rename to scripts/db/migrate/20200828165130_default_block_org_tog_school.rb diff --git a/db/migrate/20200925230149_remove_null_constraints_from_users.rb b/scripts/db/migrate/20200925230149_remove_null_constraints_from_users.rb similarity index 100% rename from db/migrate/20200925230149_remove_null_constraints_from_users.rb rename to scripts/db/migrate/20200925230149_remove_null_constraints_from_users.rb diff --git a/db/migrate/20200929004708_remove_null_email_from_users.rb b/scripts/db/migrate/20200929004708_remove_null_email_from_users.rb similarity index 100% rename from db/migrate/20200929004708_remove_null_email_from_users.rb rename to scripts/db/migrate/20200929004708_remove_null_email_from_users.rb diff --git a/db/schema.rb b/scripts/db/schema.rb similarity index 100% rename from db/schema.rb rename to scripts/db/schema.rb diff --git a/db/seeds.rb b/scripts/db/seeds.rb similarity index 100% rename from db/seeds.rb rename to scripts/db/seeds.rb diff --git a/scripts/delete-blocks b/scripts/delete-blocks new file mode 100644 index 0000000..3e3bb40 --- /dev/null +++ b/scripts/delete-blocks @@ -0,0 +1,11 @@ + + +delete from submitted_challenge_answers where true; +delete from challenges where true; +delete from content_files where true; +delete from job_results where true; +delete from sections where true; +delete from standards where true; +delete from cohort_releases where true; +delete from releases where true; +delete from blocks where true; diff --git a/doc/api.md b/scripts/doc/api.md similarity index 100% rename from doc/api.md rename to scripts/doc/api.md diff --git a/doc/api/cohorts/creating_a_new_cohort.json b/scripts/doc/api/cohorts/creating_a_new_cohort.json similarity index 100% rename from doc/api/cohorts/creating_a_new_cohort.json rename to scripts/doc/api/cohorts/creating_a_new_cohort.json diff --git a/doc/api/cohorts/viewing_curriculum_details.json b/scripts/doc/api/cohorts/viewing_curriculum_details.json similarity index 100% rename from doc/api/cohorts/viewing_curriculum_details.json rename to scripts/doc/api/cohorts/viewing_curriculum_details.json diff --git a/doc/api/content/update_content_visibility.json b/scripts/doc/api/content/update_content_visibility.json similarity index 100% rename from doc/api/content/update_content_visibility.json rename to scripts/doc/api/content/update_content_visibility.json diff --git a/doc/api/enrollments/creating_a_user_and_their_enrollment.json b/scripts/doc/api/enrollments/creating_a_user_and_their_enrollment.json similarity index 100% rename from doc/api/enrollments/creating_a_user_and_their_enrollment.json rename to scripts/doc/api/enrollments/creating_a_user_and_their_enrollment.json diff --git a/doc/api/enrollments/unenrolling_a_user_from_a_cohort.json b/scripts/doc/api/enrollments/unenrolling_a_user_from_a_cohort.json similarity index 100% rename from doc/api/enrollments/unenrolling_a_user_from_a_cohort.json rename to scripts/doc/api/enrollments/unenrolling_a_user_from_a_cohort.json diff --git a/doc/api/enrollments/updating_a_user's_enrollment_roles.json b/scripts/doc/api/enrollments/updating_a_user's_enrollment_roles.json similarity index 100% rename from doc/api/enrollments/updating_a_user's_enrollment_roles.json rename to scripts/doc/api/enrollments/updating_a_user's_enrollment_roles.json diff --git a/doc/api/enrollments/viewing_cohort_enrollments.json b/scripts/doc/api/enrollments/viewing_cohort_enrollments.json similarity index 100% rename from doc/api/enrollments/viewing_cohort_enrollments.json rename to scripts/doc/api/enrollments/viewing_cohort_enrollments.json diff --git a/doc/api/index.json b/scripts/doc/api/index.json similarity index 100% rename from doc/api/index.json rename to scripts/doc/api/index.json diff --git a/doc/api/units/update_unit_visibility.json b/scripts/doc/api/units/update_unit_visibility.json similarity index 100% rename from doc/api/units/update_unit_visibility.json rename to scripts/doc/api/units/update_unit_visibility.json diff --git a/doc/api/users/regenerate_user_token.json b/scripts/doc/api/users/regenerate_user_token.json similarity index 100% rename from doc/api/users/regenerate_user_token.json rename to scripts/doc/api/users/regenerate_user_token.json diff --git a/entrypoint-server.sh b/scripts/entrypoint-server.sh similarity index 100% rename from entrypoint-server.sh rename to scripts/entrypoint-server.sh diff --git a/gems/block-parser/Dockerfile b/scripts/gems/block-parser/Dockerfile similarity index 100% rename from gems/block-parser/Dockerfile rename to scripts/gems/block-parser/Dockerfile diff --git a/gems/block-parser/Gemfile b/scripts/gems/block-parser/Gemfile similarity index 100% rename from gems/block-parser/Gemfile rename to scripts/gems/block-parser/Gemfile diff --git a/gems/block-parser/Gemfile.lock b/scripts/gems/block-parser/Gemfile.lock similarity index 100% rename from gems/block-parser/Gemfile.lock rename to scripts/gems/block-parser/Gemfile.lock diff --git a/gems/block-parser/README.md b/scripts/gems/block-parser/README.md similarity index 100% rename from gems/block-parser/README.md rename to scripts/gems/block-parser/README.md diff --git a/gems/block-parser/Rakefile b/scripts/gems/block-parser/Rakefile similarity index 100% rename from gems/block-parser/Rakefile rename to scripts/gems/block-parser/Rakefile diff --git a/gems/block-parser/bin/console b/scripts/gems/block-parser/bin/console similarity index 100% rename from gems/block-parser/bin/console rename to scripts/gems/block-parser/bin/console diff --git a/gems/block-parser/bin/parse_block b/scripts/gems/block-parser/bin/parse_block similarity index 100% rename from gems/block-parser/bin/parse_block rename to scripts/gems/block-parser/bin/parse_block diff --git a/gems/block-parser/bin/setup b/scripts/gems/block-parser/bin/setup similarity index 100% rename from gems/block-parser/bin/setup rename to scripts/gems/block-parser/bin/setup diff --git a/gems/block-parser/block_parser-0.1.0.gem b/scripts/gems/block-parser/block_parser-0.1.0.gem similarity index 100% rename from gems/block-parser/block_parser-0.1.0.gem rename to scripts/gems/block-parser/block_parser-0.1.0.gem diff --git a/gems/block-parser/block_parser.gemspec b/scripts/gems/block-parser/block_parser.gemspec similarity index 100% rename from gems/block-parser/block_parser.gemspec rename to scripts/gems/block-parser/block_parser.gemspec diff --git a/gems/block-parser/build b/scripts/gems/block-parser/build similarity index 100% rename from gems/block-parser/build rename to scripts/gems/block-parser/build diff --git a/gems/block-parser/lib/block_parser.rb b/scripts/gems/block-parser/lib/block_parser.rb similarity index 100% rename from gems/block-parser/lib/block_parser.rb rename to scripts/gems/block-parser/lib/block_parser.rb diff --git a/gems/block-parser/lib/block_parser/build_challenge.rb b/scripts/gems/block-parser/lib/block_parser/build_challenge.rb similarity index 100% rename from gems/block-parser/lib/block_parser/build_challenge.rb rename to scripts/gems/block-parser/lib/block_parser/build_challenge.rb diff --git a/gems/block-parser/lib/block_parser/build_html.rb b/scripts/gems/block-parser/lib/block_parser/build_html.rb similarity index 100% rename from gems/block-parser/lib/block_parser/build_html.rb rename to scripts/gems/block-parser/lib/block_parser/build_html.rb diff --git a/gems/block-parser/lib/block_parser/build_html_from_md_json.rb b/scripts/gems/block-parser/lib/block_parser/build_html_from_md_json.rb similarity index 100% rename from gems/block-parser/lib/block_parser/build_html_from_md_json.rb rename to scripts/gems/block-parser/lib/block_parser/build_html_from_md_json.rb diff --git a/gems/block-parser/lib/block_parser/build_image_link.rb b/scripts/gems/block-parser/lib/block_parser/build_image_link.rb similarity index 100% rename from gems/block-parser/lib/block_parser/build_image_link.rb rename to scripts/gems/block-parser/lib/block_parser/build_image_link.rb diff --git a/gems/block-parser/lib/block_parser/build_link.rb b/scripts/gems/block-parser/lib/block_parser/build_link.rb similarity index 100% rename from gems/block-parser/lib/block_parser/build_link.rb rename to scripts/gems/block-parser/lib/block_parser/build_link.rb diff --git a/gems/block-parser/lib/block_parser/build_title_from_filename_and_html.rb b/scripts/gems/block-parser/lib/block_parser/build_title_from_filename_and_html.rb similarity index 100% rename from gems/block-parser/lib/block_parser/build_title_from_filename_and_html.rb rename to scripts/gems/block-parser/lib/block_parser/build_title_from_filename_and_html.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/challenge.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/challenge.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/challenge.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/challenge.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/checkbox_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/code_snippet_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/custom_snippet_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/local_snippet_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/multiple_choice_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/number_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/number_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/number_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/number_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/paragraph_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/poll_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/poll_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/poll_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/poll_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/project_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/project_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/project_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/project_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/short_answer_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb b/scripts/gems/block-parser/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb similarity index 100% rename from gems/block-parser/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb rename to scripts/gems/block-parser/lib/block_parser/challenge_validators/testable_project_challenge_validator.rb diff --git a/gems/block-parser/lib/block_parser/convert_md_to_json.rb b/scripts/gems/block-parser/lib/block_parser/convert_md_to_json.rb similarity index 100% rename from gems/block-parser/lib/block_parser/convert_md_to_json.rb rename to scripts/gems/block-parser/lib/block_parser/convert_md_to_json.rb diff --git a/gems/block-parser/lib/block_parser/parse_directory.rb b/scripts/gems/block-parser/lib/block_parser/parse_directory.rb similarity index 100% rename from gems/block-parser/lib/block_parser/parse_directory.rb rename to scripts/gems/block-parser/lib/block_parser/parse_directory.rb diff --git a/gems/block-parser/lib/block_parser/parse_markdown_file.rb b/scripts/gems/block-parser/lib/block_parser/parse_markdown_file.rb similarity index 100% rename from gems/block-parser/lib/block_parser/parse_markdown_file.rb rename to scripts/gems/block-parser/lib/block_parser/parse_markdown_file.rb diff --git a/gems/block-parser/lib/block_parser/parse_standards.rb b/scripts/gems/block-parser/lib/block_parser/parse_standards.rb similarity index 100% rename from gems/block-parser/lib/block_parser/parse_standards.rb rename to scripts/gems/block-parser/lib/block_parser/parse_standards.rb diff --git a/gems/block-parser/lib/block_parser/version.rb b/scripts/gems/block-parser/lib/block_parser/version.rb similarity index 100% rename from gems/block-parser/lib/block_parser/version.rb rename to scripts/gems/block-parser/lib/block_parser/version.rb diff --git a/gems/block-parser/pkg/block_parser-0.1.0.gem b/scripts/gems/block-parser/pkg/block_parser-0.1.0.gem similarity index 100% rename from gems/block-parser/pkg/block_parser-0.1.0.gem rename to scripts/gems/block-parser/pkg/block_parser-0.1.0.gem diff --git a/gems/block-parser/spec/build_challenge_spec.rb b/scripts/gems/block-parser/spec/build_challenge_spec.rb similarity index 100% rename from gems/block-parser/spec/build_challenge_spec.rb rename to scripts/gems/block-parser/spec/build_challenge_spec.rb diff --git a/gems/block-parser/spec/build_html_from_md_json_spec.rb b/scripts/gems/block-parser/spec/build_html_from_md_json_spec.rb similarity index 100% rename from gems/block-parser/spec/build_html_from_md_json_spec.rb rename to scripts/gems/block-parser/spec/build_html_from_md_json_spec.rb diff --git a/gems/block-parser/spec/build_html_spec.rb b/scripts/gems/block-parser/spec/build_html_spec.rb similarity index 100% rename from gems/block-parser/spec/build_html_spec.rb rename to scripts/gems/block-parser/spec/build_html_spec.rb diff --git a/gems/block-parser/spec/build_image_link_spec.rb b/scripts/gems/block-parser/spec/build_image_link_spec.rb similarity index 100% rename from gems/block-parser/spec/build_image_link_spec.rb rename to scripts/gems/block-parser/spec/build_image_link_spec.rb diff --git a/gems/block-parser/spec/build_link_spec.rb b/scripts/gems/block-parser/spec/build_link_spec.rb similarity index 100% rename from gems/block-parser/spec/build_link_spec.rb rename to scripts/gems/block-parser/spec/build_link_spec.rb diff --git a/gems/block-parser/spec/build_title_from_filename_and_html_spec.rb b/scripts/gems/block-parser/spec/build_title_from_filename_and_html_spec.rb similarity index 100% rename from gems/block-parser/spec/build_title_from_filename_and_html_spec.rb rename to scripts/gems/block-parser/spec/build_title_from_filename_and_html_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/challenge_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/challenge_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/challenge_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/challenge_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/challenge_validator_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/challenge_validator_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/challenge_validator_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/challenge_validator_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/checkbox_challenge_validator_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/checkbox_challenge_validator_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/checkbox_challenge_validator_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/checkbox_challenge_validator_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/code_snippet_challenge_validator_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/custom_snippet_challenge_validator_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/multiple_choice_challenge_validator_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/number_challenge_validator_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/number_challenge_validator_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/number_challenge_validator_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/number_challenge_validator_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/project_challenge_validator_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/project_challenge_validator_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/project_challenge_validator_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/project_challenge_validator_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/short_answer_challenge_validator_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/short_answer_challenge_validator_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/short_answer_challenge_validator_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/short_answer_challenge_validator_spec.rb diff --git a/gems/block-parser/spec/challenge_validators/testable_project_challenge_validator_spec.rb b/scripts/gems/block-parser/spec/challenge_validators/testable_project_challenge_validator_spec.rb similarity index 100% rename from gems/block-parser/spec/challenge_validators/testable_project_challenge_validator_spec.rb rename to scripts/gems/block-parser/spec/challenge_validators/testable_project_challenge_validator_spec.rb diff --git a/gems/block-parser/spec/convert_md_to_json_spec.rb b/scripts/gems/block-parser/spec/convert_md_to_json_spec.rb similarity index 100% rename from gems/block-parser/spec/convert_md_to_json_spec.rb rename to scripts/gems/block-parser/spec/convert_md_to_json_spec.rb diff --git a/gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md b/scripts/gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md similarity index 100% rename from gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md rename to scripts/gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/bad-challenges.md diff --git a/gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml b/scripts/gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml similarity index 100% rename from gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml rename to scripts/gems/block-parser/spec/fixtures/checkpoint-with-bad-challenge-tag/config.yaml diff --git a/gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml b/scripts/gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml similarity index 100% rename from gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml rename to scripts/gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/config.yaml diff --git a/gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md b/scripts/gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md similarity index 100% rename from gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md rename to scripts/gems/block-parser/spec/fixtures/checkpoint-without-challenges-block-repo/no-challenges.md diff --git a/gems/block-parser/spec/fixtures/invalid-config-test-block-repo/config.yaml b/scripts/gems/block-parser/spec/fixtures/invalid-config-test-block-repo/config.yaml similarity index 100% rename from gems/block-parser/spec/fixtures/invalid-config-test-block-repo/config.yaml rename to scripts/gems/block-parser/spec/fixtures/invalid-config-test-block-repo/config.yaml diff --git a/gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/config.yaml b/scripts/gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/config.yaml similarity index 100% rename from gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/config.yaml rename to scripts/gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/config.yaml diff --git a/gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/resources.md b/scripts/gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/resources.md similarity index 100% rename from gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/resources.md rename to scripts/gems/block-parser/spec/fixtures/no-lessons-nor-checkpoints/resources.md diff --git a/gems/block-parser/spec/fixtures/no-standards-config-test-block-repo/config.yaml b/scripts/gems/block-parser/spec/fixtures/no-standards-config-test-block-repo/config.yaml similarity index 100% rename from gems/block-parser/spec/fixtures/no-standards-config-test-block-repo/config.yaml rename to scripts/gems/block-parser/spec/fixtures/no-standards-config-test-block-repo/config.yaml diff --git a/gems/block-parser/spec/fixtures/sample-iframe.md b/scripts/gems/block-parser/spec/fixtures/sample-iframe.md similarity index 100% rename from gems/block-parser/spec/fixtures/sample-iframe.md rename to scripts/gems/block-parser/spec/fixtures/sample-iframe.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo-yml/config.yml b/scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/config.yml similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo-yml/config.yml rename to scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/config.yml diff --git a/gems/block-parser/spec/fixtures/test-block-repo-yml/dummy.pdf b/scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/dummy.pdf similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo-yml/dummy.pdf rename to scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/dummy.pdf diff --git a/gems/block-parser/spec/fixtures/test-block-repo-yml/folder/sibling.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/folder/sibling.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo-yml/folder/sibling.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/folder/sibling.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo-yml/folder/target.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/folder/target.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo-yml/folder/target.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/folder/target.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png b/scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png rename to scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/images/galvanize-logo.png diff --git a/gems/block-parser/spec/fixtures/test-block-repo-yml/images/register_klass.gif b/scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/images/register_klass.gif similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo-yml/images/register_klass.gif rename to scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/images/register_klass.gif diff --git a/gems/block-parser/spec/fixtures/test-block-repo-yml/lessoN.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/lessoN.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo-yml/lessoN.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/lessoN.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo-yml/markdown-smoketest.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/markdown-smoketest.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo-yml/markdown-smoketest.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/markdown-smoketest.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo-yml/target.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/target.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo-yml/target.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo-yml/target.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo/README.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo/README.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/README.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/README.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo/config.yaml b/scripts/gems/block-parser/spec/fixtures/test-block-repo/config.yaml similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/config.yaml rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/config.yaml diff --git a/gems/block-parser/spec/fixtures/test-block-repo/folder/sibling.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo/folder/sibling.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/folder/sibling.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/folder/sibling.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo/folder/target.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo/folder/target.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/folder/target.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/folder/target.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo/images/galvanize-logo.png b/scripts/gems/block-parser/spec/fixtures/test-block-repo/images/galvanize-logo.png similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/images/galvanize-logo.png rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/images/galvanize-logo.png diff --git a/gems/block-parser/spec/fixtures/test-block-repo/images/register_klass.gif b/scripts/gems/block-parser/spec/fixtures/test-block-repo/images/register_klass.gif similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/images/register_klass.gif rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/images/register_klass.gif diff --git a/gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.ipynb b/scripts/gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.ipynb similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.ipynb rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.ipynb diff --git a/gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/ipynb-test.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo/lessoN.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo/lessoN.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/lessoN.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/lessoN.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo/markdown-smoketest.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo/markdown-smoketest.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/markdown-smoketest.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/markdown-smoketest.md diff --git a/gems/block-parser/spec/fixtures/test-block-repo/target.md b/scripts/gems/block-parser/spec/fixtures/test-block-repo/target.md similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-repo/target.md rename to scripts/gems/block-parser/spec/fixtures/test-block-repo/target.md diff --git a/gems/block-parser/spec/fixtures/test-block-two-configs/config.yaml b/scripts/gems/block-parser/spec/fixtures/test-block-two-configs/config.yaml similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-two-configs/config.yaml rename to scripts/gems/block-parser/spec/fixtures/test-block-two-configs/config.yaml diff --git a/gems/block-parser/spec/fixtures/test-block-two-configs/config.yml b/scripts/gems/block-parser/spec/fixtures/test-block-two-configs/config.yml similarity index 100% rename from gems/block-parser/spec/fixtures/test-block-two-configs/config.yml rename to scripts/gems/block-parser/spec/fixtures/test-block-two-configs/config.yml diff --git a/gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile b/scripts/gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile similarity index 100% rename from gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile rename to scripts/gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/Dockerfile diff --git a/gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh b/scripts/gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh similarity index 100% rename from gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh rename to scripts/gems/block-parser/spec/fixtures/valid-custom-snippet-challenge-directory/test.sh diff --git a/gems/block-parser/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml b/scripts/gems/block-parser/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml similarity index 100% rename from gems/block-parser/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml rename to scripts/gems/block-parser/spec/fixtures/valid-yaml-array-test-block-repo/config.yaml diff --git a/gems/block-parser/spec/parse_directory_spec.rb b/scripts/gems/block-parser/spec/parse_directory_spec.rb similarity index 100% rename from gems/block-parser/spec/parse_directory_spec.rb rename to scripts/gems/block-parser/spec/parse_directory_spec.rb diff --git a/gems/block-parser/spec/parse_markdown_file_spec.rb b/scripts/gems/block-parser/spec/parse_markdown_file_spec.rb similarity index 100% rename from gems/block-parser/spec/parse_markdown_file_spec.rb rename to scripts/gems/block-parser/spec/parse_markdown_file_spec.rb diff --git a/gems/block-parser/spec/parse_standards_spec.rb b/scripts/gems/block-parser/spec/parse_standards_spec.rb similarity index 100% rename from gems/block-parser/spec/parse_standards_spec.rb rename to scripts/gems/block-parser/spec/parse_standards_spec.rb diff --git a/gems/block-parser/spec/spec_helper.rb b/scripts/gems/block-parser/spec/spec_helper.rb similarity index 100% rename from gems/block-parser/spec/spec_helper.rb rename to scripts/gems/block-parser/spec/spec_helper.rb diff --git a/gems/block-parser/spec/support/freeloader.rb b/scripts/gems/block-parser/spec/support/freeloader.rb similarity index 100% rename from gems/block-parser/spec/support/freeloader.rb rename to scripts/gems/block-parser/spec/support/freeloader.rb diff --git a/gems/block-parser/spec/support/mocktokit.rb b/scripts/gems/block-parser/spec/support/mocktokit.rb similarity index 100% rename from gems/block-parser/spec/support/mocktokit.rb rename to scripts/gems/block-parser/spec/support/mocktokit.rb diff --git a/gems/block-parser/spec/tmp/.keep b/scripts/gems/block-parser/spec/tmp/.keep similarity index 100% rename from gems/block-parser/spec/tmp/.keep rename to scripts/gems/block-parser/spec/tmp/.keep diff --git a/gems/block-parser/validate-block b/scripts/gems/block-parser/validate-block similarity index 100% rename from gems/block-parser/validate-block rename to scripts/gems/block-parser/validate-block diff --git a/lib/tasks/challenge_release_ids.rake b/scripts/lib/tasks/challenge_release_ids.rake similarity index 100% rename from lib/tasks/challenge_release_ids.rake rename to scripts/lib/tasks/challenge_release_ids.rake diff --git a/lib/tasks/checkpoint_resubmission.rake b/scripts/lib/tasks/checkpoint_resubmission.rake similarity index 100% rename from lib/tasks/checkpoint_resubmission.rake rename to scripts/lib/tasks/checkpoint_resubmission.rake diff --git a/lib/tasks/checkpoint_submission_points.rake b/scripts/lib/tasks/checkpoint_submission_points.rake similarity index 100% rename from lib/tasks/checkpoint_submission_points.rake rename to scripts/lib/tasks/checkpoint_submission_points.rake diff --git a/lib/tasks/content_visibility_update.rake b/scripts/lib/tasks/content_visibility_update.rake similarity index 100% rename from lib/tasks/content_visibility_update.rake rename to scripts/lib/tasks/content_visibility_update.rake diff --git a/lib/tasks/course_progress.rake b/scripts/lib/tasks/course_progress.rake similarity index 100% rename from lib/tasks/course_progress.rake rename to scripts/lib/tasks/course_progress.rake diff --git a/lib/tasks/course_yaml_backfill.rake b/scripts/lib/tasks/course_yaml_backfill.rake similarity index 100% rename from lib/tasks/course_yaml_backfill.rake rename to scripts/lib/tasks/course_yaml_backfill.rake diff --git a/lib/tasks/grade_checkpoints.rake b/scripts/lib/tasks/grade_checkpoints.rake similarity index 100% rename from lib/tasks/grade_checkpoints.rake rename to scripts/lib/tasks/grade_checkpoints.rake diff --git a/lib/tasks/object_space_count.rake b/scripts/lib/tasks/object_space_count.rake similarity index 100% rename from lib/tasks/object_space_count.rake rename to scripts/lib/tasks/object_space_count.rake diff --git a/lib/tasks/prep_notifier.rake b/scripts/lib/tasks/prep_notifier.rake similarity index 100% rename from lib/tasks/prep_notifier.rake rename to scripts/lib/tasks/prep_notifier.rake diff --git a/lib/tasks/set_block_caches.rake b/scripts/lib/tasks/set_block_caches.rake similarity index 100% rename from lib/tasks/set_block_caches.rake rename to scripts/lib/tasks/set_block_caches.rake diff --git a/lib/tasks/spec.rake b/scripts/lib/tasks/spec.rake similarity index 100% rename from lib/tasks/spec.rake rename to scripts/lib/tasks/spec.rake diff --git a/lib/tasks/ts_routes.rake b/scripts/lib/tasks/ts_routes.rake similarity index 100% rename from lib/tasks/ts_routes.rake rename to scripts/lib/tasks/ts_routes.rake diff --git a/lintspec.sh b/scripts/lintspec.sh similarity index 100% rename from lintspec.sh rename to scripts/lintspec.sh diff --git a/package.json b/scripts/package.json similarity index 100% rename from package.json rename to scripts/package.json diff --git a/postcss.config.js b/scripts/postcss.config.js similarity index 100% rename from postcss.config.js rename to scripts/postcss.config.js diff --git a/public/404.html b/scripts/public/404.html similarity index 100% rename from public/404.html rename to scripts/public/404.html diff --git a/public/422.html b/scripts/public/422.html similarity index 100% rename from public/422.html rename to scripts/public/422.html diff --git a/public/500.html b/scripts/public/500.html similarity index 100% rename from public/500.html rename to scripts/public/500.html diff --git a/public/apple-touch-icon-152x152.png b/scripts/public/apple-touch-icon-152x152.png similarity index 100% rename from public/apple-touch-icon-152x152.png rename to scripts/public/apple-touch-icon-152x152.png diff --git a/public/apple-touch-icon-precomposed-152x152.png b/scripts/public/apple-touch-icon-precomposed-152x152.png similarity index 100% rename from public/apple-touch-icon-precomposed-152x152.png rename to scripts/public/apple-touch-icon-precomposed-152x152.png diff --git a/public/apple-touch-icon-precomposed.png b/scripts/public/apple-touch-icon-precomposed.png similarity index 100% rename from public/apple-touch-icon-precomposed.png rename to scripts/public/apple-touch-icon-precomposed.png diff --git a/public/apple-touch-icon.png b/scripts/public/apple-touch-icon.png similarity index 100% rename from public/apple-touch-icon.png rename to scripts/public/apple-touch-icon.png diff --git a/public/assets/images/hopscotch-sprite-green.png b/scripts/public/assets/images/hopscotch-sprite-green.png similarity index 100% rename from public/assets/images/hopscotch-sprite-green.png rename to scripts/public/assets/images/hopscotch-sprite-green.png diff --git a/public/assets/images/hopscotch-sprite-orange.png b/scripts/public/assets/images/hopscotch-sprite-orange.png similarity index 100% rename from public/assets/images/hopscotch-sprite-orange.png rename to scripts/public/assets/images/hopscotch-sprite-orange.png diff --git a/public/assets/images/jupyter-logo.png b/scripts/public/assets/images/jupyter-logo.png similarity index 100% rename from public/assets/images/jupyter-logo.png rename to scripts/public/assets/images/jupyter-logo.png diff --git a/public/assets/images/svg/checkpoint-is-rejected.svg b/scripts/public/assets/images/svg/checkpoint-is-rejected.svg similarity index 100% rename from public/assets/images/svg/checkpoint-is-rejected.svg rename to scripts/public/assets/images/svg/checkpoint-is-rejected.svg diff --git a/public/assets/images/svg/checkpoint-is-scored.svg b/scripts/public/assets/images/svg/checkpoint-is-scored.svg similarity index 100% rename from public/assets/images/svg/checkpoint-is-scored.svg rename to scripts/public/assets/images/svg/checkpoint-is-scored.svg diff --git a/public/assets/images/svg/checkpoint-is-submitted.svg b/scripts/public/assets/images/svg/checkpoint-is-submitted.svg similarity index 100% rename from public/assets/images/svg/checkpoint-is-submitted.svg rename to scripts/public/assets/images/svg/checkpoint-is-submitted.svg diff --git a/public/assets/images/svg/github-icon.svg b/scripts/public/assets/images/svg/github-icon.svg similarity index 100% rename from public/assets/images/svg/github-icon.svg rename to scripts/public/assets/images/svg/github-icon.svg diff --git a/public/assets/images/svg/gitlab-icon-rgb.svg b/scripts/public/assets/images/svg/gitlab-icon-rgb.svg similarity index 100% rename from public/assets/images/svg/gitlab-icon-rgb.svg rename to scripts/public/assets/images/svg/gitlab-icon-rgb.svg diff --git a/public/assets/images/svg/octicon-git-branch.svg b/scripts/public/assets/images/svg/octicon-git-branch.svg similarity index 100% rename from public/assets/images/svg/octicon-git-branch.svg rename to scripts/public/assets/images/svg/octicon-git-branch.svg diff --git a/public/assets/images/svg/redpriority_high-24px.svg b/scripts/public/assets/images/svg/redpriority_high-24px.svg similarity index 100% rename from public/assets/images/svg/redpriority_high-24px.svg rename to scripts/public/assets/images/svg/redpriority_high-24px.svg diff --git a/public/assets/images/svg/svg-link_off-24px.svg b/scripts/public/assets/images/svg/svg-link_off-24px.svg similarity index 100% rename from public/assets/images/svg/svg-link_off-24px.svg rename to scripts/public/assets/images/svg/svg-link_off-24px.svg diff --git a/public/assets/images/svg/svg-sprite-action-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-action-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-action-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-action-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-alert-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-alert-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-alert-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-alert-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-av-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-av-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-av-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-av-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-content-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-content-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-content-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-content-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-custom-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-custom-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-custom-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-custom-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-custom_material-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-custom_material-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-custom_material-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-custom_material-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-device-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-device-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-device-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-device-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-file-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-file-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-file-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-file-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-hardware-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-hardware-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-hardware-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-hardware-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-navigation-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-navigation-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-navigation-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-navigation-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-notification-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-notification-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-notification-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-notification-symbol.svg diff --git a/public/assets/images/svg/svg-sprite-social-symbol.svg b/scripts/public/assets/images/svg/svg-sprite-social-symbol.svg similarity index 100% rename from public/assets/images/svg/svg-sprite-social-symbol.svg rename to scripts/public/assets/images/svg/svg-sprite-social-symbol.svg diff --git a/public/favicon.ico b/scripts/public/favicon.ico similarity index 100% rename from public/favicon.ico rename to scripts/public/favicon.ico diff --git a/public/javascripts/apitome/application.js b/scripts/public/javascripts/apitome/application.js similarity index 100% rename from public/javascripts/apitome/application.js rename to scripts/public/javascripts/apitome/application.js diff --git a/public/robots.txt b/scripts/public/robots.txt similarity index 100% rename from public/robots.txt rename to scripts/public/robots.txt diff --git a/public/sandbox/chai.js b/scripts/public/sandbox/chai.js similarity index 100% rename from public/sandbox/chai.js rename to scripts/public/sandbox/chai.js diff --git a/public/sandbox/challenge-worker.js b/scripts/public/sandbox/challenge-worker.js similarity index 100% rename from public/sandbox/challenge-worker.js rename to scripts/public/sandbox/challenge-worker.js diff --git a/public/sandbox/jasmine/boot.js b/scripts/public/sandbox/jasmine/boot.js similarity index 100% rename from public/sandbox/jasmine/boot.js rename to scripts/public/sandbox/jasmine/boot.js diff --git a/public/sandbox/jasmine/jasmine.js b/scripts/public/sandbox/jasmine/jasmine.js similarity index 100% rename from public/sandbox/jasmine/jasmine.js rename to scripts/public/sandbox/jasmine/jasmine.js diff --git a/public/sandbox/mocha/boot.js b/scripts/public/sandbox/mocha/boot.js similarity index 100% rename from public/sandbox/mocha/boot.js rename to scripts/public/sandbox/mocha/boot.js diff --git a/public/sandbox/mocha/mocha.js b/scripts/public/sandbox/mocha/mocha.js similarity index 100% rename from public/sandbox/mocha/mocha.js rename to scripts/public/sandbox/mocha/mocha.js diff --git a/public/sandbox/mocha/test.html b/scripts/public/sandbox/mocha/test.html similarity index 100% rename from public/sandbox/mocha/test.html rename to scripts/public/sandbox/mocha/test.html diff --git a/public/sandbox/sandbox.html b/scripts/public/sandbox/sandbox.html similarity index 100% rename from public/sandbox/sandbox.html rename to scripts/public/sandbox/sandbox.html diff --git a/public/sandbox/stacktrace.js b/scripts/public/sandbox/stacktrace.js similarity index 100% rename from public/sandbox/stacktrace.js rename to scripts/public/sandbox/stacktrace.js diff --git a/public/sandbox/worker.js b/scripts/public/sandbox/worker.js similarity index 100% rename from public/sandbox/worker.js rename to scripts/public/sandbox/worker.js diff --git a/public/stylesheets/apitome/application.css b/scripts/public/stylesheets/apitome/application.css similarity index 100% rename from public/stylesheets/apitome/application.css rename to scripts/public/stylesheets/apitome/application.css diff --git a/requirements.txt b/scripts/requirements.txt similarity index 100% rename from requirements.txt rename to scripts/requirements.txt diff --git a/scripts/sh/cohort_curriculum.sh b/scripts/scripts/sh/cohort_curriculum.sh similarity index 100% rename from scripts/sh/cohort_curriculum.sh rename to scripts/scripts/sh/cohort_curriculum.sh diff --git a/scripts/sh/content_file_mark_hidden.sh b/scripts/scripts/sh/content_file_mark_hidden.sh similarity index 100% rename from scripts/sh/content_file_mark_hidden.sh rename to scripts/scripts/sh/content_file_mark_hidden.sh diff --git a/scripts/sh/content_file_mark_visible.sh b/scripts/scripts/sh/content_file_mark_visible.sh similarity index 100% rename from scripts/sh/content_file_mark_visible.sh rename to scripts/scripts/sh/content_file_mark_visible.sh diff --git a/scripts/sh/unit_mark_hidden.sh b/scripts/scripts/sh/unit_mark_hidden.sh similarity index 100% rename from scripts/sh/unit_mark_hidden.sh rename to scripts/scripts/sh/unit_mark_hidden.sh diff --git a/scripts/sh/unit_mark_visible.sh b/scripts/scripts/sh/unit_mark_visible.sh similarity index 100% rename from scripts/sh/unit_mark_visible.sh rename to scripts/scripts/sh/unit_mark_visible.sh diff --git a/scripts/sql/checkpoint_answers.sql b/scripts/scripts/sql/checkpoint_answers.sql similarity index 100% rename from scripts/sql/checkpoint_answers.sql rename to scripts/scripts/sql/checkpoint_answers.sql diff --git a/scripts/sql/cohort_challenges_with_answers.sql b/scripts/scripts/sql/cohort_challenges_with_answers.sql similarity index 100% rename from scripts/sql/cohort_challenges_with_answers.sql rename to scripts/scripts/sql/cohort_challenges_with_answers.sql diff --git a/scripts/sql/cohort_prune_for_testing.sql b/scripts/scripts/sql/cohort_prune_for_testing.sql similarity index 100% rename from scripts/sql/cohort_prune_for_testing.sql rename to scripts/scripts/sql/cohort_prune_for_testing.sql diff --git a/scripts/sql/percent_metrics.sql b/scripts/scripts/sql/percent_metrics.sql similarity index 100% rename from scripts/sql/percent_metrics.sql rename to scripts/scripts/sql/percent_metrics.sql diff --git a/scripts/sql/scrub_cohort_data.sql b/scripts/scripts/sql/scrub_cohort_data.sql similarity index 100% rename from scripts/sql/scrub_cohort_data.sql rename to scripts/scripts/sql/scrub_cohort_data.sql diff --git a/scripts/serviceentry.yaml b/scripts/serviceentry.yaml new file mode 100644 index 0000000..9b4e97c --- /dev/null +++ b/scripts/serviceentry.yaml @@ -0,0 +1,15 @@ +apiVersion: networking.istio.io/v1beta1 +kind: ServiceEntry +metadata: + name: gitlab-external-se + namespace: galvanize-learn +spec: + hosts: + - code.il2.dsop.io + - google.com + location: MESH_EXTERNAL + ports: + - name: https + number: 443 + protocol: TLS + resolution: DNS diff --git a/scripts/spec/component_props/activity_feed_item_component_props_spec.rb b/scripts/spec/component_props/activity_feed_item_component_props_spec.rb new file mode 100644 index 0000000..44fd65a --- /dev/null +++ b/scripts/spec/component_props/activity_feed_item_component_props_spec.rb @@ -0,0 +1,154 @@ +require "spec_helper" + +describe ActivityFeedItemComponentProps do + let(:activity) { create(:activity, :comment) } + subject { described_class.execute(activity) } + + context "when a content file has been viewed" do + let(:content_file) { create(:content_file) } + let(:activity) { create(:activity, name: Activity::NAMES[:content_file_viewed], subject: content_file) } + + it "returns a hash of the activity" do + expect(subject).to eq( + id: activity.id, + creator: activity.creator.full_name, + message: "viewed #{content_file.standard.release.block.title} - #{content_file.standard.title} - #{content_file.title}.", + created_at: activity.created_at, + label_class: "-default", + user_photo: activity.creator.profile_image, + user_initials: activity.creator.initials + ) + end + end + + context "when no creator is set (system-generated event)" do + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: SubmittedChallengeAnswer::STATUSES[:correct]) } + let(:activity) { create(:activity, creator: nil, name: Activity::NAMES[:submitted_challenge_answer_evaluated], subject: submitted_challenge_answer) } + + it "returns a hash of the activity with the name from the subject" do + expect(subject[:creator]).to eq(submitted_challenge_answer.user.full_name) + end + end + + context "when a checkpoint submission has been evaluated" do + let!(:user) { create(:user) } + let!(:content_file) { create(:content_file, :checkpoint, autoscore: true) } + let!(:challenge) { create(:challenge, content_file: content_file) } + let!(:checkpoint_submission) { create_checkpoint_submission(create(:user), challenge) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, challenge: challenge, checkpoint_submission: checkpoint_submission) } + let(:activity) { create(:activity, creator: user, name: Activity::NAMES[:checkpoint_submission_evaluated], subject: checkpoint_submission) } + let!(:performance) { create(:performance, user: user, standard: content_file.standard, score: 1, checkpoint_submission: checkpoint_submission) } + + it "returns a hash of the activity" do + expect(subject).to include( + id: activity.id, + creator: activity.creator.full_name, + message: "scored a 1 on #{content_file.standard.release.block.title} - #{content_file.standard.title} - #{content_file.title}.", + created_at: activity.created_at, + label_class: "-score-1" + ) + end + end + + context "when a checkpoint submission has been created" do + context "when the checkpoint is set to auto-score" do + let!(:user) { create(:user) } + let!(:content_file) { create(:content_file, :checkpoint, autoscore: true) } + let!(:challenge) { create(:challenge, content_file: content_file) } + let!(:checkpoint_submission) { create_checkpoint_submission(create(:user), challenge) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, challenge: challenge, checkpoint_submission: checkpoint_submission) } + let(:activity) { create(:activity, creator: user, name: Activity::NAMES[:checkpoint_submission_created], subject: checkpoint_submission) } + + it "returns a hash of the activity" do + expect(subject).to include( + id: activity.id, + creator: activity.creator.full_name, + message: "submitted #{content_file.standard.release.block.title} - #{content_file.standard.title} - #{content_file.title}, awaiting automatic evaluation.", + created_at: activity.created_at, + label_class: "-primary" + ) + end + end + + context "when the checkpoint is set to manual scoring" do + let!(:user) { create(:user) } + let!(:content_file) { create(:content_file, :checkpoint, autoscore: false) } + let!(:challenge) { create(:challenge, content_file: content_file) } + let!(:checkpoint_submission) { create_checkpoint_submission(create(:user), challenge) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, challenge: challenge, checkpoint_submission: checkpoint_submission) } + let(:activity) { create(:activity, creator: user, name: Activity::NAMES[:checkpoint_submission_created], subject: checkpoint_submission) } + + it "returns a hash of the activity" do + expect(subject).to include( + id: activity.id, + creator: activity.creator.full_name, + message: "submitted #{content_file.standard.release.block.title} - #{content_file.standard.title} - #{content_file.title}, awaiting manual scoring.", + created_at: activity.created_at, + label_class: "-warning" + ) + end + end + end + + context "when a challenge has been answered" do + context "when the answer is correct" do + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: SubmittedChallengeAnswer::STATUSES[:correct]) } + let(:activity) { create(:activity, name: Activity::NAMES[:submitted_challenge_answer_created], subject: submitted_challenge_answer) } + + it "returns a hash of the activity" do + expect(subject).to include( + id: activity.id, + creator: activity.creator.full_name, + message: "answered #{submitted_challenge_answer.challenge.title} - #{submitted_challenge_answer.challenge.content_file.standard.release.block.title} - #{submitted_challenge_answer.challenge.content_file.standard.title} - #{submitted_challenge_answer.challenge.content_file.title} correctly.", + created_at: activity.created_at, + label_class: "-success" + ) + end + end + + context "when the answer is incorrect" do + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: SubmittedChallengeAnswer::STATUSES[:incorrect]) } + let(:activity) { create(:activity, name: Activity::NAMES[:submitted_challenge_answer_created], subject: submitted_challenge_answer) } + + it "returns a hash of the activity" do + expect(subject).to include( + id: activity.id, + creator: activity.creator.full_name, + message: "answered #{submitted_challenge_answer.challenge.title} - #{submitted_challenge_answer.challenge.content_file.standard.release.block.title} - #{submitted_challenge_answer.challenge.content_file.standard.title} - #{submitted_challenge_answer.challenge.content_file.title} incorrectly.", + created_at: activity.created_at, + label_class: "-danger" + ) + end + end + + context "when the answer is processing" do + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: SubmittedChallengeAnswer::STATUSES[:processing]) } + let(:activity) { create(:activity, name: Activity::NAMES[:submitted_challenge_answer_created], subject: submitted_challenge_answer) } + + it "returns a hash of the activity" do + expect(subject).to include( + id: activity.id, + creator: activity.creator.full_name, + message: "answered #{submitted_challenge_answer.challenge.title} - #{submitted_challenge_answer.challenge.content_file.standard.release.block.title} - #{submitted_challenge_answer.challenge.content_file.standard.title} - #{submitted_challenge_answer.challenge.content_file.title}, awaiting automatic evaluation.", + created_at: activity.created_at, + label_class: "-primary" + ) + end + end + + context "when the answer is not processing" do + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: SubmittedChallengeAnswer::STATUSES[:timeout]) } + let(:activity) { create(:activity, name: Activity::NAMES[:submitted_challenge_answer_created], subject: submitted_challenge_answer) } + + it "returns a hash of the activity" do + expect(subject).to include( + id: activity.id, + creator: activity.creator.full_name, + message: "answered #{submitted_challenge_answer.challenge.title} - #{submitted_challenge_answer.challenge.content_file.standard.release.block.title} - #{submitted_challenge_answer.challenge.content_file.standard.title} - #{submitted_challenge_answer.challenge.content_file.title} but ran into an error: timeout.", + created_at: activity.created_at, + label_class: "-warning" + ) + end + end + end +end diff --git a/scripts/spec/component_props/notifications_component_props_spec.rb b/scripts/spec/component_props/notifications_component_props_spec.rb new file mode 100644 index 0000000..d3096ff --- /dev/null +++ b/scripts/spec/component_props/notifications_component_props_spec.rb @@ -0,0 +1,40 @@ +require "spec_helper" + +describe NotificationsComponentProps do + let(:user) { create(:user) } + subject { described_class.execute(user.id) } + + context "when there is a notification" do + let!(:notification) { create(:notification, user: user) } + + it "returns a hash of the notification" do + expect(subject[:notifications].first).to include( + id: notification.id, + tagline: notification.tagline, + title: notification.title, + url: notification_path(notification), + description: notification.description, + read_at: nil + ) + end + end + + context "when there are read and unread notifications" do + let!(:older_read_notification) { create(:notification, :read, user: user, created_at: DateTime.yesterday) } + let!(:newer_read_notification) { create(:notification, :read, user: user) } + let!(:older_unread_notification) { create(:notification, user: user, created_at: DateTime.yesterday) } + let!(:newer_unread_notification) { create(:notification, user: user) } + + it "returns a list of all notifications, sorted with unread notifications first, then newest notifications first" do + expect(subject[:notifications].map { |n| n[:id] }).to eq([newer_unread_notification.id, older_unread_notification.id, newer_read_notification.id, older_read_notification.id]) + end + end + + context "when there are more than 100 notifications for the user" do + before { 100.times { create(:notification, user: user) } } + + it "truncates prettyUnreadCount" do + expect(subject[:prettyUnreadCount]).to eq("99+") + end + end +end diff --git a/scripts/spec/component_props/standard_card_component_props_spec.rb b/scripts/spec/component_props/standard_card_component_props_spec.rb new file mode 100644 index 0000000..1ad96d7 --- /dev/null +++ b/scripts/spec/component_props/standard_card_component_props_spec.rb @@ -0,0 +1,29 @@ +require "spec_helper" + +describe StandardCardComponentProps do + let(:standard) { create(:standard) } + + context "when no performance is provided" do + subject { described_class.new(standard: standard) } + + it "returns a presenter containing a standard presenter" do + expect(StandardPresenter::ForCheckpointSubmission).to receive(:new).with(standard: standard, latest_performance: nil).and_call_original + expect(subject.standard.id).to eq(standard.id) + end + end + + context "when a performance is provided" do + let!(:checkpoint_performance) { create(:performance, standard: standard, checkpoint_submission_id: create_checkpoint_submission(create(:user), create(:challenge, content_file: create(:content_file, :checkpoint))).id, score: 3) } + let!(:latest_performance) { create(:performance, standard: standard, score: 2) } + subject { described_class.new(standard: standard, checkpoint_performance: checkpoint_performance, latest_performance: latest_performance) } + + it "returns a presenter containing a standard presenter and a performance presenter" do + expect(StandardPresenter::ForCheckpointSubmission).to receive(:new).with(standard: standard, latest_performance: latest_performance).and_call_original + expect(PerformancePresenter).to receive(:new).with(checkpoint_performance).and_call_original + + expect(subject.standard.id).to eq(standard.id) + expect(subject.standard.current_score).to eq(latest_performance.score) + expect(subject.checkpoint_performance.id).to eq(checkpoint_performance.id) + end + end +end diff --git a/scripts/spec/controllers/api/v1/blocks/releases_controller_spec.rb b/scripts/spec/controllers/api/v1/blocks/releases_controller_spec.rb new file mode 100644 index 0000000..51a9897 --- /dev/null +++ b/scripts/spec/controllers/api/v1/blocks/releases_controller_spec.rb @@ -0,0 +1,45 @@ +require "spec_helper" + +describe Api::V1::Blocks::ReleasesController do + let(:json_response) { JSON.parse(response.body) } + + let!(:block) { create(:block) } + + describe "POST #create" do + context "as a user with blocks_manager rights" do + let(:admin) { create(:user, :admin, roles: [User::ROLES[:blocks_manager]]) } + before do + request.headers["X-LEARN-API-TOKEN"] = "Bearer #{admin.api_token}" + end + + it "enqueues a new CreateReleaseJob and yields the pending release id" do + expect(CreateReleaseJob).to receive(:perform_later) + + post :create, format: :json, params: { block_id: block.id, release: { notes: "Hello, world!" } } + + expect(response.status).to eq 200 + expect(json_response["release_id"]).to eq Release.last.id + end + + it "allows publishing without release notes" do + expect(CreateReleaseJob).to receive(:perform_later) + + post :create, format: :json, params: { block_id: block.id } + + expect(response.status).to eq 200 + expect(json_response["release_id"]).to eq Release.last.id + expect(Release.last.notes).to eq "" + end + + it "yields 400 when the release has no id" do + allow_any_instance_of(Release).to receive(:id).and_return(nil) + expect(CreateReleaseJob).to_not receive(:perform_later) + + post :create, format: :json, params: { block_id: block.id } + + expect(response.status).to eq 400 + expect(json_response["release_id"]).to eq nil + end + end + end +end diff --git a/scripts/spec/controllers/api/v1/blocks_controller_spec.rb b/scripts/spec/controllers/api/v1/blocks_controller_spec.rb new file mode 100644 index 0000000..1b757d4 --- /dev/null +++ b/scripts/spec/controllers/api/v1/blocks_controller_spec.rb @@ -0,0 +1,122 @@ +require "spec_helper" + +describe Api::V1::BlocksController do + let(:json_response) { JSON.parse(response.body) } + describe "GET index" do + context "when Authorization header matches a user with blocks_manager role" do + let(:admin) { create(:user, :admin, roles: [User::ROLES[:blocks_manager]]) } + let!(:block_1) { create(:block, title: "A", repo_name: "nice-repo") } + let!(:block_2) { create(:block, title: "B", repo_name: "Learning-material") } + + let!(:release) { create(:release, block: block_1) } + + before do + request.headers["X-LEARN-API-TOKEN"] = "Bearer #{admin.api_token}" + end + + it "returns blocks as json" do + get :index, format: :json + + expect(json_response["blocks"].length).to eq 2 + + first_block = json_response["blocks"][0] + expect(first_block["id"]).to eq block_1.id + expect(first_block["org"]).to eq block_1.org + expect(first_block["origin"]).to eq block_1.origin + expect(first_block["repo_name"]).to eq block_1.repo_name + expect(first_block["title"]).to eq block_1.title + expect(first_block["sync_errors"]).to eq [] + expect(first_block["cohorts_using"]).to eq [] + + second_block = json_response["blocks"][1] + expect(second_block["id"]).to eq block_2.id + expect(first_block["org"]).to eq block_1.org + expect(first_block["origin"]).to eq block_1.origin + expect(second_block["repo_name"]).to eq block_2.repo_name + expect(second_block["title"]).to eq block_2.title + expect(second_block["sync_errors"]).to eq [] + expect(second_block["cohorts_using"]).to eq [] + end + + it "selects a block by repo_name if the repo_name, org, origin param specifies one" do + get :index, format: :json, params: { repo_name: "learning-material", origin: "github.com", org: "gSchool"} + + expect(json_response["blocks"].length).to eq 1 + + second_block = json_response["blocks"][0] + expect(second_block["id"]).to eq block_2.id + expect(second_block["repo_name"]).to eq block_2.repo_name + expect(second_block["title"]).to eq block_2.title + expect(second_block["sync_errors"]).to eq [] + expect(second_block["cohorts_using"]).to eq [] + end + + it "selects a block by repo_name if the repo_name param specifies one" do + get :index, format: :json, params: { repo_name: "learning-material"} + + expect(json_response["blocks"].length).to eq 1 + + second_block = json_response["blocks"][0] + expect(second_block["id"]).to eq block_2.id + expect(second_block["repo_name"]).to eq block_2.repo_name + expect(second_block["title"]).to eq block_2.title + expect(second_block["sync_errors"]).to eq [] + expect(second_block["cohorts_using"]).to eq [] + end + + it "returns an empty array if the repo_name param finds no exact match" do + get :index, format: :json, params: { repo_name: "nothin-bröther", org: "any", origin: "thing" } + + expect(json_response["blocks"].length).to eq 0 + end + end + end + + describe "POST create" do + context "when Authorization header matches a user with blocks_manager role" do + let(:admin) { create(:user, :admin, roles: [User::ROLES[:blocks_manager]]) } + before do + request.headers["X-LEARN-API-TOKEN"] = "Bearer #{admin.api_token}" + end + + it "creates a block when given a unique repo_name" do + post :create, format: :json, params: {block: { org: "test", origin: "test", repo_name: "Unique", title: "Unique" }} + expect(response.status).to eq 200 + expect(json_response["blocks"].length).to eq 1 + expect(Block.all.length).to eq 1 + expect(Block.last.repo_name).to eq "Unique" + end + + it "creates a block when only given a repo_name" do + post :create, format: :json, params: {block: { org: "test", origin: "test", repo_name: "Uniqueness" }} + expect(response.status).to eq 200 + expect(json_response["blocks"].length).to eq 1 + expect(Block.all.length).to eq 1 + expect(Block.last.title).to eq "Uniqueness" + expect(Block.last.repo_name).to eq "Uniqueness" + end + + it "returns an error when the block already exists by the repo name" do + create(:block, repo_name: "exists", title: "exists") + post :create, format: :json, params: {block: { title: "exists", repo_name: "exists" }} + expect(response.status).to eq 400 + expect(json_response["errors"]["title"]).to include "Title has already been taken" + expect(Block.all.length).to eq 1 + end + + it "returns an error when blank repo name given" do + post :create, format: :json, params: {block: { repo_name: nil }} + expect(response.status).to eq 400 + expect(json_response["errors"]["title"]).to eq "errors: Title can't be blank, Repo name can't be blank" + expect(Block.all.length).to eq 0 + end + + it "returns an error when repo_name is missing from params" do + post :create, format: :json, params: {block: {}} + expect(response.status).to eq 400 + expect(json_response["errors"]["title"]).to eq "A parameter is missing to fulfill your request" + expect(Block.all.length).to eq 0 + end + end + end +end diff --git a/scripts/spec/controllers/api/v1/cohorts/blocks/content_files_controller_spec.rb b/scripts/spec/controllers/api/v1/cohorts/blocks/content_files_controller_spec.rb new file mode 100644 index 0000000..3171dd0 --- /dev/null +++ b/scripts/spec/controllers/api/v1/cohorts/blocks/content_files_controller_spec.rb @@ -0,0 +1,338 @@ +require "integration_helper" + +resource "Content" do + context "when an admin" do + let(:user) { create(:user, :admin, api_token: "token") } + let(:content_file) { create(:content_file) } + let(:cohort) { create(:cohort_release, release: content_file.standard.release).cohort } + + header "Content-Type", "application/json" + header "Accept", "application/json" + let(:api_token) { "Bearer #{user.api_token}" } + header "X-LEARN-API-TOKEN", :api_token + + describe "updating visibility" do + explanation <<-MARKDOWN.strip_heredoc + Hide or show an individual Lesson or Checkpoint. This is equivalent to clicking the “eye” icon on the cohort visibility page. If you set the content to visible but the parent unit is hidden, the Lesson or Checkpoint will not be shown. + MARKDOWN + + parameter ":cohort_id", "[Path Parameter]\nThe cohort ID. Matches the ID from the URL when viewing any cohort in Learn.", required: true, example: "1500" + parameter ":block_id", "[Path Parameter]\nThe block ID. The id that the content is attached to.", required: true, example: "1500" + parameter ":id", "[Path Parameter]\nThe UID of the content, as returned from GET /api/v1/cohorts/:cohort_id/curriculum.", required: true, example: "d092d0ebf9d991629c75f3dde79aab1e" + parameter :visible, "[Body Parameter]\nThe boolean that sets the visibility of the content.", required: true, example: "true = visible\nfalse = hidden" + + let(:cohort_id) { cohort.id } + let(:block_id) { content_file.standard.release.block_id } + let(:id) { content_file.uid } + + context "when hiding the content file" do + let(:visible) { false } + + patch "/api/v1/cohorts/:cohort_id/blocks/:block_id/content_files/:id" do + example "Update Content Visibility" do + expect { do_request }.to change { ContentVisibility.count }.by(1) + expect(response_json["status"]).to eq("ok") + expect(status).to eq 200 + end + end + end + + context "when revealing the content file" do + let(:visible) { true } + + patch "/api/v1/cohorts/:cohort_id/blocks/:block_id/content_files/:id" do + example "To reveal the ContentFile", document:false do + create(:content_visibility, cohort_id: cohort.id, content_uid: content_file.uid, content_type: "ContentFile", visibility_type: "hidden") + expect { do_request }.to change { ContentVisibility.count }.by(-1) + expect(response_json["status"]).to eq("ok") + expect(status).to eq 200 + end + end + end + end + end + + describe Api::V1::Cohorts::Blocks::ContentFilesController do + let(:json_response) { ActiveSupport::HashWithIndifferentAccess.new(JSON.parse(response.body)) } + let!(:cohort) { create(:cohort) } + let!(:release) { create(:release, branch_name: "master") } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let!(:standard) { create(:standard, uid: "xyz789", release: release) } + let!(:checkpoint) { create(:content_file, uid: "abc123", path: "01-instructor.md", position: 1, content_file_type: "checkpoint", standard: standard, max_checkpoint_submissions: 1, time_limit: 1) } + let!(:challenge) { create(:challenge, points: 2, content_file: checkpoint) } + let!(:user) { create(:cohort_user, cohort: cohort).user } + + describe "POST #take_assessmet" do + context "when the content file is not a checkpoint" do + let!(:lesson) { create(:content_file, uid: "abc123", path: "01-instructor.md", position: 1, content_file_type: "lesson", standard: standard) } + + example "yields a 403", document:false do + post :take_assessment, params: { cohort_id: cohort.id, block_id: release.block_id, id: lesson.id }, format: :json, session: { user_uid: user.uid } + expect(response.code).to eq '403' + end + end + + context "when a student of the cohort has no previous assessment started" do + example "creates a started checkpoint submission", document:false do + expect(ChallengeWithSubmittedChallengeAnswersPresenter).to receive(:new).and_call_original + post :take_assessment, params: { cohort_id: cohort.id, block_id: release.block_id, id: checkpoint.id }, format: :json, session: { user_uid: user.uid } + expect(CheckpointSubmission.count).to eq 1 + checkpoint_submission = CheckpointSubmission.last + expect(checkpoint_submission.cohort_id).to eq cohort.id + expect(checkpoint_submission.content_file_uid).to eq "abc123" + expect(checkpoint_submission.state).to eq "started" + expect(checkpoint_submission.user_id).to eq user.id + + expect(json_response[:content_file_html]).to eq checkpoint.html + expect(json_response[:submission_id]).to eq checkpoint_submission.id + expect(json_response[:challenges_with_answers].length).to eq 1 + expect(json_response[:limit_reached]).to eq false + end + end + + context "when a new release has been created/attached and it has a time limit" do + let(:new_release) { create(:release, branch_name: "master", notes: "?", block: checkpoint.standard.release.block) } + let(:new_standard) { create(:standard, uid: "xyz789", release: new_release) } + let!(:new_cf) { create(:content_file, uid: "abc123", path: "01-instructor.md", position: 1, content_file_type: "checkpoint", standard: new_standard, max_checkpoint_submissions: 1, time_limit: 10) } + + xit "gives the new time_limit" do # TODO: No idea why this wont work... + post :take_assessment, params: { cohort_id: cohort.id, block_id: release.block_id, id: checkpoint.id }, format: :json, session: { user_uid: user.uid } + expect(json_response[:time_limit]).to eq 1 + + CohortRelease.last.update(release: new_release) + + post :take_assessment, params: { cohort_id: cohort.id, block_id: release.block_id, id: checkpoint.id }, format: :json, session: { user_uid: user.uid } + expect(json_response[:time_limit]).to eq 10 + end + end + + context "when a student has a previous submission" do + example "yields the submission id if the submission has been started", document:false do + expect(ChallengeWithSubmittedChallengeAnswersPresenter).to receive(:new).and_call_original + existing_submission = CheckpointSubmission.create!( + cohort_id: cohort.id, + content_file_uid: checkpoint.uid, + content_file_block_id: checkpoint.standard.release.block_id, + user_id: user.id, + state: CheckpointSubmission::STATES[:started] + ) + post :take_assessment, params: { cohort_id: cohort.id, block_id: release.block_id, id: checkpoint.id }, format: :json, session: { user_uid: user.uid } + expect(CheckpointSubmission.count).to eq 1 + checkpoint_submission = CheckpointSubmission.last + expect(checkpoint_submission.id).to eq existing_submission.id + + expect(json_response[:content_file_html]).to eq checkpoint.html + expect(json_response[:submission_id]).to eq existing_submission.id + expect(json_response[:challenges_with_answers].length).to eq 1 + expect(json_response[:limit_reached]).to eq false + end + + example "prevents a new submission from being created if the max submission limit has been reached for done checkpoints", document:false do + expect(ChallengeWithSubmittedChallengeAnswersPresenter).to receive(:new).and_call_original + existing_submission = CheckpointSubmission.create!( + cohort_id: cohort.id, + content_file_uid: checkpoint.uid, + content_file_block_id: checkpoint.standard.release.block_id, + user_id: user.id, + state: CheckpointSubmission::STATES[:needs_review] + ) + post :take_assessment, params: { cohort_id: cohort.id, block_id: release.block_id, id: checkpoint.id }, format: :json, session: { user_uid: user.uid } + expect(CheckpointSubmission.count).to eq 1 + checkpoint_submission = CheckpointSubmission.last + expect(checkpoint_submission.id).to eq existing_submission.id + + expect(json_response[:content_file_html]).to eq checkpoint.html + expect(json_response[:submission_id]).to eq nil + expect(json_response[:challenges_with_answers].length).to eq 1 + expect(json_response[:limit_reached]).to eq true + end + end + end + + describe "#student_scores" do + let!(:challenge_2) { create(:challenge, points: 3, content_file: checkpoint, challenge_type: "number", position: 2) } + + let(:student_1) { create(:user, last_viewed_cohort_id: cohort.id, last_name: "Later-Done") } + let!(:s1_enrollment) { create(:cohort_user, :student, cohort: cohort, user: student_1) } + let!(:s1_cs1) { create(:checkpoint_submission, + cohort: cohort, + user: student_1, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block_id, + state: CheckpointSubmission::STATES[:done], + created_at: 1.day.ago, + submitted_at: 12.hours.ago + ) } + let!(:s1_sca1) { create(:submitted_challenge_answer, challenge: challenge, points: 1, cohort: cohort, checkpoint_submission_id: s1_cs1.id, user: student_1) } + let!(:s1_sca2) { create(:submitted_challenge_answer, challenge: challenge_2, points: 1, cohort: cohort, checkpoint_submission_id: s1_cs1.id, user: student_1) } + + # expect to see s1_cs2 for student_1 this as it was created more recently than s1_cs1 + let!(:s1_cs2) { create(:checkpoint_submission, + cohort: cohort, + user: student_1, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block_id, + state: CheckpointSubmission::STATES[:done], + created_at: 5.hours.ago, + submitted_at: 2.hours.ago, + total_points: 5, + correct_points: 5 + ) } + let!(:s1_sca3) { create(:submitted_challenge_answer, challenge: challenge, points: 2, cohort: cohort, checkpoint_submission_id: s1_cs2.id, user: student_1) } + let!(:s1_sca4) { create(:submitted_challenge_answer, challenge: challenge_2, points: 3, cohort: cohort, checkpoint_submission_id: s1_cs2.id, user: student_1) } + + let(:student_2) { create(:user, last_viewed_cohort_id: cohort.id, last_name: "Suggestim") } + let!(:s2_enrollment) { create(:cohort_user, :student, cohort: cohort, user: student_2) } + + # s2_cs should yield suggested scores as it is needs_review and all submissions have been scored + let!(:s2_cs) { create(:checkpoint_submission, + cohort: cohort, + user: student_2, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block_id, + created_at: 4.hours.ago, + submitted_at: 2.hours.ago + ) } + let!(:s2_sca1) { create(:submitted_challenge_answer, challenge: challenge, points: 1, cohort: cohort, checkpoint_submission_id: s2_cs.id, user: student_2) } + let!(:s2_sca2) { create(:submitted_challenge_answer, challenge: challenge_2, points: 2, cohort: cohort, checkpoint_submission_id: s2_cs.id, user: student_2) } + + let(:student_3) { create(:user, last_viewed_cohort_id: cohort.id, last_name: "Starteo") } + let!(:s3_enrollment) { create(:cohort_user, :student, cohort: cohort, user: student_3) } + let!(:s3_cs) { create(:checkpoint_submission, + cohort: cohort, + user: student_3, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block_id, + created_at: 4.hours.ago, + state: CheckpointSubmission::STATES[:started], + ) } + + let(:student_4) { create(:user, last_viewed_cohort_id: cohort.id, last_name: "Done-and-Pending") } + let!(:s4_enrollment) { create(:cohort_user, :student, cohort: cohort, user: student_4) } + let!(:s4_cs_done) { create(:checkpoint_submission, + cohort: cohort, + user: student_4, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block_id, + state: CheckpointSubmission::STATES[:done], + created_at: 1.day.ago, + submitted_at: 12.hours.ago, + total_points: 5, + correct_points: 3 + ) } + let!(:s4_sca1) { create(:submitted_challenge_answer, challenge: challenge, points: 1, cohort: cohort, checkpoint_submission_id: s4_cs_done.id, user: student_4) } + let!(:s4_sca2) { create(:submitted_challenge_answer, challenge: challenge_2, points: 2, cohort: cohort, checkpoint_submission_id: s4_cs_done.id, user: student_4) } + + let!(:s4_cs_pending) { create(:checkpoint_submission, + cohort: cohort, + user: student_4, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block_id, + state: CheckpointSubmission::STATES[:needs_review], + created_at: 5.hours.ago + ) } + let!(:s4_sca3) { create(:submitted_challenge_answer, challenge: challenge, points: 2, cohort: cohort, checkpoint_submission_id: s4_cs_pending.id, user: student_4) } + let!(:s4_sca4) { create(:submitted_challenge_answer, challenge: challenge_2, points: 3, cohort: cohort, checkpoint_submission_id: s4_cs_pending.id, user: student_4) } + + let(:student_5) { create(:user, last_viewed_cohort_id: cohort.id, last_name: "Needs-Review-No-Points") } + let!(:s5_enrollment) { create(:cohort_user, :student, cohort: cohort, user: student_5) } + let!(:s5_cs) { create(:checkpoint_submission, + cohort: cohort, + user: student_5, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block_id, + state: CheckpointSubmission::STATES[:needs_review], + created_at: 1.day.ago, + submitted_at: 12.hours.ago + ) } + let!(:s5_sca1) { create(:submitted_challenge_answer, challenge: challenge, points: 1, cohort: cohort, checkpoint_submission_id: s5_cs.id, user: student_4) } + let!(:s1_sca2) { create(:submitted_challenge_answer, challenge: challenge_2, points: nil, cohort: cohort, checkpoint_submission_id: s5_cs.id, user: student_5) } + + # instrucor should not appear + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + + let(:json_response) { JSON.parse(response.body) } + + context "several students with checkpoints in differents states" do + it "yields full_name, initials, avatar, sign-in state, and submission details" do + get :student_scores, params: { cohort_id: cohort.id, block_id: release.block_id, id: checkpoint.id }, format: :json, session: { user_uid: instructor.uid } + expect(response.code).to eq '200' + + expect(json_response.length).to eq 6 # user, student_1, 2, 3, 4, 5 + user_resp = json_response.find {|s| s["email"] == user.email } + expect(user_resp["initials"]).to eq user.initials + expect(user_resp["first_name"]).to eq user.first_name + expect(user_resp["last_name"]).to eq user.last_name + expect(user_resp["profile_image"]).to eq user.profile_image + expect(user_resp["has_signed_in"]).to eq false + expect(user_resp["submissions"]).to eq ({}) + + student_1_resp = json_response.find {|s| s["email"] == student_1.email } + expect(student_1_resp["initials"]).to eq student_1.initials + expect(student_1_resp["first_name"]).to eq student_1.first_name + expect(student_1_resp["last_name"]).to eq student_1.last_name + expect(student_1_resp["profile_image"]).to eq student_1.profile_image + expect(student_1_resp["has_signed_in"]).to eq true + expect(student_1_resp["submissions"]["needs_review_id"]).to eq nil + expect(student_1_resp["submissions"]["done_id"]).to eq s1_cs2.id + expect(student_1_resp["submissions"]["started_id"]).to eq nil + expect(student_1_resp["submissions"]["score"]).to eq 100 + expect(student_1_resp["submissions"]["state"]).to eq s1_cs2.state + expect(student_1_resp["submissions"]["created_at"][0..9]).to eq s1_cs2.created_at.to_s[0..9] + + student_2_resp = json_response.find {|s| s["email"] == student_2.email } + expect(student_2_resp["initials"]).to eq student_2.initials + expect(student_2_resp["first_name"]).to eq student_2.first_name + expect(student_2_resp["last_name"]).to eq student_2.last_name + expect(student_2_resp["profile_image"]).to eq student_2.profile_image + expect(student_2_resp["has_signed_in"]).to eq true + expect(student_2_resp["submissions"]["needs_review_id"]).to eq s2_cs.id + expect(student_2_resp["submissions"]["done_id"]).to eq nil + expect(student_2_resp["submissions"]["started_id"]).to eq nil + expect(student_2_resp["submissions"]["score"]).to eq 60 + expect(student_2_resp["submissions"]["state"]).to eq s2_cs.state + expect(student_2_resp["submissions"]["created_at"][0..9]).to eq s2_cs.created_at.to_s[0..9] + + student_3_resp = json_response.find {|s| s["email"] == student_3.email } + expect(student_3_resp["initials"]).to eq student_3.initials + expect(student_3_resp["first_name"]).to eq student_3.first_name + expect(student_3_resp["last_name"]).to eq student_3.last_name + expect(student_3_resp["profile_image"]).to eq student_3.profile_image + expect(student_3_resp["has_signed_in"]).to eq true + expect(student_3_resp["submissions"]["needs_review_id"]).to eq nil + expect(student_3_resp["submissions"]["done_id"]).to eq nil + expect(student_3_resp["submissions"]["started_id"]).to eq s3_cs.id + expect(student_3_resp["submissions"]["score"]).to eq nil + expect(student_3_resp["submissions"]["state"]).to eq s3_cs.state + expect(student_3_resp["submissions"]["created_at"][0..9]).to eq s3_cs.created_at.to_s[0..9] + + student_4_resp = json_response.find {|s| s["email"] == student_4.email } + expect(student_4_resp["initials"]).to eq student_4.initials + expect(student_4_resp["first_name"]).to eq student_4.first_name + expect(student_4_resp["last_name"]).to eq student_4.last_name + expect(student_4_resp["profile_image"]).to eq student_4.profile_image + expect(student_4_resp["has_signed_in"]).to eq true + expect(student_4_resp["submissions"]["needs_review_id"]).to eq s4_cs_pending.id + expect(student_4_resp["submissions"]["done_id"]).to eq s4_cs_done.id + expect(student_4_resp["submissions"]["started_id"]).to eq nil + expect(student_4_resp["submissions"]["score"]).to eq 60 # matches done state score + expect(student_4_resp["submissions"]["state"]).to eq "needs_review_and_done" + expect(student_4_resp["submissions"]["created_at"][0..9]).to eq s4_cs_pending.created_at.to_s[0..9] + + student_5_resp = json_response.find {|s| s["email"] == student_5.email } + expect(student_5_resp["initials"]).to eq student_5.initials + expect(student_5_resp["first_name"]).to eq student_5.first_name + expect(student_5_resp["last_name"]).to eq student_5.last_name + expect(student_5_resp["profile_image"]).to eq student_5.profile_image + expect(student_5_resp["has_signed_in"]).to eq true + expect(student_5_resp["submissions"]["needs_review_id"]).to eq s5_cs.id + expect(student_5_resp["submissions"]["done_id"]).to eq nil + expect(student_5_resp["submissions"]["started_id"]).to eq nil + expect(student_5_resp["submissions"]["score"]).to eq nil # score cannot be suggested, sca has nil points + expect(student_5_resp["submissions"]["state"]).to eq s5_cs.state + expect(student_5_resp["submissions"]["created_at"][0..9]).to eq s5_cs.created_at.to_s[0..9] + end + end + end + end +end diff --git a/scripts/spec/controllers/api/v1/cohorts/blocks/units_controller_spec.rb b/scripts/spec/controllers/api/v1/cohorts/blocks/units_controller_spec.rb new file mode 100644 index 0000000..a2c7b5f --- /dev/null +++ b/scripts/spec/controllers/api/v1/cohorts/blocks/units_controller_spec.rb @@ -0,0 +1,59 @@ +require "integration_helper" + +resource "Units" do + context "when an admin" do + let(:user) { create(:user, :admin, api_token: "token") } + let(:content_file) { create(:content_file) } + let(:cohort) { create(:cohort_release, release: content_file.standard.release).cohort } + let(:unit) { content_file.standard } + + header "Content-Type", "application/json" + header "Accept", "application/json" + let(:api_token) { "Bearer #{user.api_token}" } + header "X-LEARN-API-TOKEN", :api_token + + describe "updating visibility" do + + let(:cohort_id) { cohort.id } + let(:block_id) { content_file.standard.release.block_id } + let(:id) { unit.uid } + + context "when hiding the unit" do + explanation <<-MARKDOWN.strip_heredoc + Hide or show an entire unit. This is equivalent to clicking the “eye” icon on the cohort settings visibility page. If you set a Unit to visible, any children content that are also visible will be shown. + MARKDOWN + + parameter ":cohort_id", "[Path Parameter]\nThe cohort ID. Matches the ID from the URL when viewing any cohort in Learn.", required: true, example: "1500" + parameter ":block_id", "[Path Parameter]\nThe block ID. The id that the unit is attached to.", required: true, example: "1500" + parameter ":id", "[Path Parameter]\nThe UID of the unit, as returned from GET /api/v1/cohorts/:cohort_id/curriculum.", required: true, example: "d092d0ebf9d991629c75f3dde79aab1e" + parameter :visible, "[Body Parameter]\nThe boolean that sets the visibility of the unit.", required: true, example: "true = visible\nfalse = hidden" + + let(:visible) { false } + + patch "/api/v1/cohorts/:cohort_id/blocks/:block_id/units/:id" do + example "Update Unit Visibility" do + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + expect { do_request }.to change { ContentVisibility.count }.by(1) + expect(response_json["status"]).to eq("ok") + expect(status).to eq 200 + end + end + end + + context "when revealing the unit" do + parameter :visible, "The boolean that toggles the visibility of the unit", required: false + let(:visible) { true } + + patch "/api/v1/cohorts/:cohort_id/blocks/:block_id/units/:id" do + example "To reveal the Unit", document:false do + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + create(:content_visibility, cohort_id: cohort.id, content_uid: unit.uid, content_type: "Standard", visibility_type: "hidden") + expect { do_request }.to change { ContentVisibility.count }.by(-1) + expect(response_json["status"]).to eq("ok") + expect(status).to eq 200 + end + end + end + end + end +end diff --git a/scripts/spec/controllers/api/v1/cohorts/cohorts_controller_spec.rb b/scripts/spec/controllers/api/v1/cohorts/cohorts_controller_spec.rb new file mode 100644 index 0000000..10411d6 --- /dev/null +++ b/scripts/spec/controllers/api/v1/cohorts/cohorts_controller_spec.rb @@ -0,0 +1,165 @@ +require "integration_helper" + +resource "Cohorts" do + context "As an auth product admin user (not a forge admin) create cohort" do + ActiveJob::Base.queue_adapter = :test + let(:user) { create(:user, :product_admin, api_token: "token") } + + header "Content-Type", "application/json" + header "Accept", "application/json" + + describe "a admin can create a cohort" do + parameter :name, "[Body Parameter]\nThe name of the Cohort.", required: true, example: "Web Development with scripting language ArnoldC" + parameter :product_type, "[Body Parameter]\nThe type or structure of the cohort.", required: true, example: "Workshop" + parameter :label, "[Body Parameter]\nThe label of this cohort.", required: false, example: "17-01-WD-GT" + parameter :campus_name, "[Body Parameter]\nThe campus of this cohort.", required: false, example: "Seattle-Pioneer Square" + parameter :opt_out_for_marketing, "[Body Parameter]\nThe boolean submitted for marketing preference.", required: false, example: "false" + parameter :starts_on, "[Body Parameter]\nThe start date of the cohort.", required: true, example: "2020-10-10" + parameter :ends_on, "[Body Parameter]\nThe end date of the cohort.", required: false, example: "2021-01-10" + + let(:name) { "Web Development with scripting language ArnoldC" } + let(:product_type) { "Workshop" } + let(:label) { "17-01-WD-GT" } + let(:campus_name) { "Seattle-Pioneer Square" } + let(:opt_out_for_marketing) { true } + let(:starts_on) { "2020-10-10" } + let(:ends_on) { "2021-01-10" } + + post "/api/v1/cohorts" do + example "Creating a new cohort" do + explanation <<-MARKDOWN.strip_heredoc + Allow users to easily create a cohort. + MARKDOWN + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + # Commented out since AuthApi has been removed + # dbl = double(AuthApi::Client) + # expect(AuthResolverService).to receive(:resolve) + # expect(SecureRandom).to receive(:hex).and_return("4b58555dbbc93c9016") + # expect(dbl).to receive(:create_product).with( + # { + # product: { + # uid: "4b58555dbbc93c9016", + # name: name, + # product_type: product_type, + # label: label, + # campus_name: campus_name, + # opt_out_for_marketing: true, + # starts_on: starts_on, + # ends_on: ends_on + # } + # } + # ).and_return(OpenStruct.new({uid: "4b58555dbbc93c9016"})) + # + # allow(AuthApi::Client).to receive(:new).and_return(dbl) + expect { + do_request + }.to have_enqueued_job(CreateApiInteractionJob) + + expect(status).to eq 200 + #expect(response_json["uid"]).to eq("4b58555dbbc93c9016") + + end + context "when cohorts aren't created" do + let(:name) { "" } + let(:product_type) { "" } + let(:starts_on) { "" } + let(:opt_out_for_marketing) {"hampster"} + example "required params are missing", document:false do + explanation <<-MARKDOWN.strip_heredoc + Responds with a 400 when Required Params: name, product_type, or starts_on are missing. opt_out_for_marketing must be true or false. + MARKDOWN + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + expect { + do_request + }.to have_enqueued_job(CreateApiInteractionJob) + + expect(status).to eq 400 + expect(response_json["error"]).to eq("Validation Error: name can't be blank, starts_on can't be blank, product_type can't be blank, opt_out_for_marketing must be true or false") + end + end + context "responds with a 401 when for unauthorized call" do + example "auth API fails", document:false do + explanation <<-MARKDOWN.strip_heredoc + Responds with a 400 when auth API request fails. + MARKDOWN + header "X-LEARN-API-TOKEN", "Bearer 'wrong'" + # Commented out since AuthApi has been removed + # dbl = double(AuthApi::Client) + # expect(dbl).to receive(:create_product).and_raise(AuthApi::RequestFailed, "{}") + # allow(AuthApi::Client).to receive(:new).and_return(dbl) + # expect { + do_request + # }.to have_enqueued_job(CreateApiInteractionJob) + + # puts "RESPONSE_JSON = " + response_json.to_s + expect(status).to eq 401 + + expect(response_json["errors"]["code"]).to eq("unauthorized") + end + end + end + end + end + + context "when an admin" do + ActiveJob::Base.queue_adapter = :test + + let(:user) { create(:user, :admin, api_token: "token") } + let!(:cohort) { create(:cohort) } + let!(:release) { create(:release, branch_name: "master") } + let!(:section) { create(:section, title: "Week 3") } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release, section_id: section.id) } + let!(:standard) { create(:standard, uid: "xyz789", title: "Mocking in Java", description: "Mocking in Java", release: release) } + let!(:content_file) { create(:content_file, uid: "abc123", title: "LESSON PLAN: Introduction to Mocking in Java", path: "01-instructor.md", position: 1, content_file_type: "lesson", standard: standard) } + let!(:hidden_content_file) { create(:content_file, uid: "bcd456", title: "Java Mocking Exit Ticket", position: 2, content_file_type: "lesson", standard: standard, path: "03-exit-ticket.md") } + let!(:content_visibility) { create(:content_visibility, content_uid: "bcd456", content_type: "ContentFile", visibility_type: "hidden", cohort_id: cohort.id) } + let!(:resync_job_result) { create(:resync_job_result, edges: { cohort_id: cohort.id }, data: { course_config_url: 'https://github.com/gSchool/learn-course-files/blob/master/test/test.yaml' }) } + + header "Content-Type", "application/json" + header "Accept", "application/json" + + parameter ":cohort_id", "[Path Parameter]\nThe ID of the cohort. Matches the ID from the URL when viewing any cohort in Learn", required: true, example: "1500" + + describe "fetching a cohort's curriculum" do + let!(:cohort_id) { cohort.id } + get "/api/v1/cohorts/:cohort_id/curriculum" do + example "Viewing Curriculum Details" do + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + release.block.update(repo_name: "demo-mocking-java") + + expect { + do_request + }.to have_enqueued_job(CreateApiInteractionJob) + + expect(status).to eq 200 + expect(response_json["cohort_id"]).to eq(cohort.id) + expect(response_json["cohort_title"]).to eq(cohort.name) + expect(response_json).to include_partial_json(<<-JSON) + { + "course_yaml_url": "https://github.com/gSchool/learn-course-files/blob/master/test/test.yaml" + } + JSON + unit = response_json["sections"][0]["units"][0] + expect(unit["uid"]).to eq "xyz789" + expect(unit["title"]).to eq "Mocking in Java" + expect(unit["description"]).to eq "Mocking in Java" + expect(unit["visible"]).to eq true + expect(unit["block_id"]).to eq release.block_id + expect(unit["api_endpoint"]).to include "/api/v1/cohorts/#{cohort_id}/blocks/#{release.block_id}/units/#{unit['uid']}" + cf = unit["content_files"][0] + expect(cf["uid"]).to eq content_file.uid + expect(cf["title"]).to eq content_file.title + expect(cf["type"]).to eq content_file.content_file_type + expect(cf["path"]).to eq "/cohorts/#{cohort.id}/blocks/#{release.block_id}/content_files/#{content_file.path}" + expect(cf["visible"]).to eq true + expect(cf["git_url"]).to eq "#{release.block.git_url}/blob/#{release.branch_name}/#{content_file.path}" + expect(cf["api_endpoint"]).to include "/api/v1/cohorts/#{cohort_id}/blocks/#{release.block_id}/content_files/#{content_file.uid}" + hidden_cf = unit["content_files"][1] + expect(hidden_cf["uid"]).to eq hidden_content_file.uid + expect(hidden_cf["visible"]).to eq false + end + end + end + end +end + diff --git a/scripts/spec/controllers/api/v1/cohorts/users_controller_spec.rb b/scripts/spec/controllers/api/v1/cohorts/users_controller_spec.rb new file mode 100644 index 0000000..ac2970a --- /dev/null +++ b/scripts/spec/controllers/api/v1/cohorts/users_controller_spec.rb @@ -0,0 +1,148 @@ +require "integration_helper" + +resource "Enrollments" do + let(:user) { create(:user, :admin, api_token: "token") } + let(:student_to_enroll) { create(:user) } + let!(:cohort) { create(:cohort) } + let!(:enrolled_student) { create(:user, first_name: "Eric", last_name: "Ericson", email: "eric@example.com") } + let!(:cohort_user) { create(:cohort_user, created_at: 3.days.ago, cohort: cohort, user: enrolled_student) } + + context "when an admin" do + ActiveJob::Base.queue_adapter = :test + + header "Content-Type", "application/json" + header "Accept", "application/json" + + describe "fetching a cohort's enrolled users" do + parameter ":cohort_id", "[Path Parameter]\nThe ID of the cohort. Matches the ID from the URL when viewing any cohort in Learn", required: true, example: "1500" + + let!(:cohort_id) { cohort.id } + let!(:instructor_user) { create(:user, first_name: "Bob", last_name: "Bobberson", email: "bob@example.com") } + let!(:cohort_instructor) { create(:cohort_user, :instructor, created_at: 2.days.ago, cohort: cohort, user: instructor_user) } + let!(:second_user) { create(:user, first_name: "Cindy", last_name: "Louwho", email: "cindy@example.com") } + let!(:cohort_second_user) { create(:cohort_user, created_at: 1.day.ago, cohort: cohort, user: second_user) } + get "/api/v1/cohorts/:cohort_id/users" do + example "Viewing Cohort Enrollments" do + explanation <<-MARKDOWN.strip_heredoc + Lists users currently enrolled in the cohort, including instructors and onboarders. Roles are specific to that cohort, and do not include any global user roles. + MARKDOWN + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + expect { + do_request + }.to have_enqueued_job(CreateApiInteractionJob) + + expect(status).to eq 200 + expect(response_json.length).to eq 3 + + expect(response_json[0]["id"]).to eq(second_user.id) + expect(response_json[0]["uid"]).to eq(second_user.uid) + expect(response_json[0]["first_name"]).to eq(second_user.first_name) + expect(response_json[0]["last_name"]).to eq(second_user.last_name) + expect(response_json[0]["email"]).to eq(second_user.email) + expect(response_json[0]["roles"]).to eq(second_user.cohort_users.last.roles) + + expect(response_json[1]["id"]).to eq(instructor_user.id) + expect(response_json[1]["uid"]).to eq(instructor_user.uid) + expect(response_json[1]["first_name"]).to eq(instructor_user.first_name) + expect(response_json[1]["last_name"]).to eq(instructor_user.last_name) + expect(response_json[1]["email"]).to eq(instructor_user.email) + expect(response_json[1]["roles"]).to eq(instructor_user.cohort_users.last.roles) + + expect(response_json[2]["id"]).to eq(enrolled_student.id) + expect(response_json[2]["uid"]).to eq(enrolled_student.uid) + expect(response_json[2]["first_name"]).to eq(enrolled_student.first_name) + expect(response_json[2]["last_name"]).to eq(enrolled_student.last_name) + expect(response_json[2]["email"]).to eq(enrolled_student.email) + expect(response_json[2]["roles"]).to eq([]) + end + end + end + + describe "Creating an enrollment" do + + parameter ":cohort_id", "[Path Parameter]\nThe ID of the cohort. Matches the ID from the URL when viewing any cohort in Learn", required: true, example: "1500" + parameter :email, "[Body Parameter]\nThe email of the user to enroll. This field is used to find existing users.", required: true, example: "jamie@example.com" + + let(:cohort_id) { cohort.id } + let(:first_name) { "Jamie" } + let(:last_name) { "Wittman" } + let(:email) { "jamie@example.com" } + + post "/api/v1/cohorts/:cohort_id/users" do + example "Creating a user and their enrollment" do + explanation <<-MARKDOWN.strip_heredoc + Invite new users or enroll existing users to a cohort. A user is added only if they do not exist. Enrolling a user who is already enrolled changes nothing about their enrollment- their roles will not be modified to match the body parameters, and instead must be changed via a PATCH. + MARKDOWN + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + + expect { + do_request + }.to have_enqueued_job(CreateApiInteractionJob) + + new_user = User.find_by(email: email) + # New user only has an email field and no uid or name info + # expect(new_user.first_name).to eq(first_name) + # expect(new_user.last_name).to eq(last_name) + expect(new_user.email).to eq(email) + + expect(status).to eq 200 + + expect(response_json["status"]).to eq("ok") + end + + it "finds the user by email in the system" do + existing_user = create(:user, email: email, first_name: first_name, last_name: last_name) + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + + expect { + do_request + }.to have_enqueued_job(CreateApiInteractionJob) + + existing_user = User.find_by(email: email) + expect(existing_user.first_name).to eq(first_name) + expect(existing_user.last_name).to eq(last_name) + expect(existing_user.email).to eq(email) + + expect(status).to eq 200 + + expect(response_json["status"]).to eq("ok") + end + + it "returns an error if the user exists in the cohort" do + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + existing_user = create(:user, email: email, first_name: first_name, last_name: last_name) + create(:cohort_user, user: existing_user, cohort: cohort) + + expect { + do_request + }.to have_enqueued_job(CreateApiInteractionJob) + + existing_user = User.find_by(email: email) + expect(existing_user.first_name).to eq(first_name) + expect(existing_user.last_name).to eq(last_name) + expect(existing_user.email).to eq(email) + + expect(status).to eq 400 + + expect(response_json["error"]).to eq("User is already in the cohort.") + end + + context "malformed body" do + let(:first_name) { "" } + let(:last_name) { "" } + let(:email) { "" } + + it "responds with a 400 when an email parameter is missing" do + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + expect { + do_request + }.to have_enqueued_job(CreateApiInteractionJob) + + expect(status).to eq 400 + expect(response_json["error"]).to eq("Validation Error: email can't be blank") + end + end + end + end + end +end diff --git a/scripts/spec/controllers/api/v1/content_files_controller_spec.rb b/scripts/spec/controllers/api/v1/content_files_controller_spec.rb new file mode 100644 index 0000000..50105c8 --- /dev/null +++ b/scripts/spec/controllers/api/v1/content_files_controller_spec.rb @@ -0,0 +1,83 @@ +require "spec_helper" + +describe Api::V1::ContentFilesController do + describe "GET #show" do + context "when signed in as an admin" do + let(:admin) { create(:user, :admin) } + + before do + sign_in(admin) + end + + it "returns the content file as json" do + cohort, block, content_file = create_content_file + + get :show, params: { id: content_file.id }, format: :json, session: {user_uid: admin.uid} + + expect( + JSON.parse(response.body) + ).to eq(JSON.parse(content_file.to_json)) + end + end + + context "when signed in as an instructor" do + let(:instructor) { create(:user) } + + before do + sign_in(instructor) + end + + it "returns the content file as json" do + cohort, block, content_file = create_content_file + + create(:cohort_user, :instructor, user: instructor, cohort: cohort) + + get :show, params: { id: content_file.id }, format: :json, session: {user_uid: instructor.uid} + + expect( + JSON.parse(response.body) + ).to eq(JSON.parse(content_file.to_json)) + end + end + + context "when signed in as an student" do + let(:student) { create(:user) } + + before do + sign_in(student) + end + + it "returns the content file as json" do + cohort, block, content_file = create_content_file + + create(:cohort_user, user: student, cohort: cohort) + + get :show, params: { id: content_file.id }, format: :json, session: {user_uid: student.uid} + + expect( + response.status + ).to eq(401) + end + end + + context "when signed in as an user but not part of cohort" do + let(:bad_student) { create(:user) } + + before do + sign_in(bad_student) + end + + it "is not accessable" do + cohort, block, content_file = create_content_file + + create(:cohort_user, user: bad_student) + + get :show, params: { id: content_file.id }, format: :json, session: {user_uid: bad_student.uid} + + expect( + response.status + ).to eq(401) + end + end + end +end diff --git a/scripts/spec/controllers/api/v1/users_controller_spec.rb b/scripts/spec/controllers/api/v1/users_controller_spec.rb new file mode 100644 index 0000000..b706241 --- /dev/null +++ b/scripts/spec/controllers/api/v1/users_controller_spec.rb @@ -0,0 +1,162 @@ +require "integration_helper" + +resource "Users" do + context "when an a admin/teacher with an api_token" do + describe "regenerate_token" do + let(:user) { create(:user, :admin, api_token: "token") } + + header "Content-Type", "application/json" + header "Accept", "application/json" + + describe "endpoints" do + get "/api/v1/users/regenerate_token" do + example "Regenerate User Token" do + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + do_request + + expect(status).to eq 200 + end + end + end + end + + describe "learn_cli_credentials" do + describe "with correct HTTP method" do + let(:user) { create(:user, :admin, api_token: "token") } + + header "Content-Type", "application/json" + header "Accept", "application/json" + + get "/api/v1/users/learn_cli_credentials" do + example "Access CLI credentials", document:false do + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + do_request + + expect(status).to eq 200 + end + end + end + + describe "with incorrect HTTP method" do + let(:user) { create(:user, :admin, api_token: "token") } + + header "Content-Type", "application/json" + header "Accept", "application/json" + + post "/api/v1/users/learn_cli_credentials" do + example "CLI credentials with incorrect method", document:false do + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + do_request + + expect(status).to eq 405 + end + end + end + end + + describe "learn_cli_metadata" do + + # # Commenting this section out since the learn cli metadata is an undocumented interface + # describe "with correct HTTP method" do + # + # let(:user) { create(:user, :admin, api_token: "token") } + # + # header "Content-Type", "application/json" + # header "Accept", "application/json" + # + # # Can't seem to pass params to this post operation correctly + # post "/api/v1/users/learn_cli_metadata", format: :json do + # parameter ":time_to_compress", "[Path Parameter]\nThe time taken to compress the files", required: true, scope: :cli_benchmark + # parameter ":time_to_build_on_learn", "[Path Parameter]\nThe time taken to build the files on learn", required: true, scope: :cli_benchmark + # parameter ":time_to_upload_to_s3", "[Path Parameter]\nThe time taken to upload to AWS S3", required: true, scope: :cli_benchmark + # parameter ":master_release_and_build", "[Path Parameter]\nThe master build and release version number", required: true, scope: :cli_benchmark + # parameter ":total_cmd_time", "[Path Parameter]\nThe total time taken for the command", required: true, scope: :cli_benchmark + # parameter ":command_name", "[Path Parameter]\nThe command executed", required: true, scope: :cli_benchmark + # + # let(:time_to_compress) { 0 } + # let(:time_to_build_on_learn) { 0 } + # let(:time_to_upload_to_s3) { 0 } + # let(:master_release_and_build) { 0 } + # let(:total_cmd_time) { 0 } + # let(:command_name) { "p1learn" } + # + # let(:cli_benchmark) { + # time_to_compress: "0", + # time_to_build_on_learn: "0", + # time_to_upload_to_s3: "0", + # master_release_and_build: "0", + # total_cmd_time: "0", + # command_name: "p1learn" + # } + # } + # + # # example "Creating an order" do + # # byebug + # # expect(params).to eq({ cli_benchmark: { + # # time_to_compress: 0 + # # }}) + # # end + # + # # example "Access CLI metadata", document:false do + # # header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + # # do_request + # # + # # expect(status).to eq 200 + # # end + # end + # end + + describe "with incorrect HTTP method" do + let(:user) { create(:user, :admin, api_token: "token") } + + header "Content-Type", "application/json" + header "Accept", "application/json" + + get "/api/v1/users/learn_cli_metadata" do + example "CLI metadata with incorrect method", document:false do + header "X-LEARN-API-TOKEN", "Bearer #{user.api_token}" + do_request + + expect(status).to eq 405 + end + end + end + end + end + + context "with wrong or no credentials" do + describe "regenerate_token" do + let(:user) { create(:user, :admin) } + + header "Content-Type", "application/json" + header "Accept", "application/json" + + describe "endpoints" do + get "/api/v1/users/regenerate_token" do + example "regenerate token with bad bearer token", document:false do + header "X-LEARN-API-TOKEN", "Bearer incorrect_token" + do_request + + expect(status).to eq 401 + end + end + end + end + + describe "learn_cli_credentials" do + let(:user) { create(:user, :admin) } + + header "Content-Type", "application/json" + header "Accept", "application/json" + + get "/api/v1/users/learn_cli_credentials" do + example "CLI credentials with bad bearer token", document:false do + header "X-LEARN-API-TOKEN", "Bearer incorrect_token" + do_request + + expect(status).to eq 401 + end + end + end + end +end diff --git a/scripts/spec/controllers/application_controller_spec.rb b/scripts/spec/controllers/application_controller_spec.rb new file mode 100644 index 0000000..9caf5d5 --- /dev/null +++ b/scripts/spec/controllers/application_controller_spec.rb @@ -0,0 +1,26 @@ +require "spec_helper" + +describe ApplicationController do + describe "exception handling" do + controller do + skip_before_action :require_signed_in_user + + def index + raise "some error message" + end + end + + it "renders a 500 page when an uncaught exception is thrown" do + get :index + + expect(response).to render_template("error_500") + end + + it "notifies honey badger" do + allow(Rails).to receive(:env).and_return(double(development?: false)) + expect(Honeybadger).to receive(:notify) + + get :index + end + end +end diff --git a/scripts/spec/controllers/blocks/releases_controller_spec.rb b/scripts/spec/controllers/blocks/releases_controller_spec.rb new file mode 100644 index 0000000..8e47b18 --- /dev/null +++ b/scripts/spec/controllers/blocks/releases_controller_spec.rb @@ -0,0 +1,56 @@ +require "spec_helper" + +describe Blocks::ReleasesController do + context "when signed in as a blocks manager" do + let!(:block) { create(:block) } + let!(:blocks_manager) { create(:user, :blocks_manager) } + + before do + sign_in(blocks_manager) + end + + describe "GET #index" do + let!(:release_1) { create(:release, block: block) } + let!(:release_2) { create(:release, block: block) } + let!(:release_3) { create(:release) } + let(:cohort) { create(:cohort) } + + before do + create(:cohort_release, cohort: cohort, release: release_1) + create(:cohort_user, :instructor, cohort: cohort, user: blocks_manager) + end + + it "returns all releases for a given block" do + expect(BlockPresenter::ForCohortReleases).to receive(:releases_for_cohort).and_call_original + get :index, params: { block_id: block.id, cohort_id: cohort.id }, format: :json + + releases_json = JSON.parse(response.body)["releases"] + + expect(releases_json.length).to eq(2) + expect(releases_json.first["release_id"]).to eq(release_2.id) + expect(releases_json.last["release_id"]).to eq(release_1.id) + end + end + + describe "GET #new" do + it "returns locals with a new Release" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:release]).to be_a_new(Release) + end + get :new, params: { block_id: block.id } + end + end + + describe "POST #create" do + it "enqueues a new CreateReleaseJob and redirects to block show" do + expect(CreateReleaseJob).to receive(:perform_later) + + post :create, params: { block_id: block.id, release: { notes: "Hello, world!" } } + + expect(flash[:notice]).to eq "Release creation queued." + expect(response).to redirect_to block_url(block) + end + end + end +end diff --git a/scripts/spec/controllers/blocks_controller_spec.rb b/scripts/spec/controllers/blocks_controller_spec.rb new file mode 100644 index 0000000..2447410 --- /dev/null +++ b/scripts/spec/controllers/blocks_controller_spec.rb @@ -0,0 +1,54 @@ +require "spec_helper" + +describe BlocksController do + context "when signed in as a blocks manager" do + before do + sign_in(create(:user, :blocks_manager)) + end + + describe "GET #index" do + let!(:block_one) { create(:block, title: "last") } + let!(:block_two) { create(:block, title: "first") } + let!(:block_archived) { create(:block, title: "archie", archived_at: 1.day.ago) } + + it "returns locals with a list of all Blocks" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:blocks].length).to eq 2 + expect(args[:locals][:blocks].first.id).to eq(block_two.id) + expect(args[:locals][:blocks].last.id).to eq(block_one.id) + end + + get :index + end + end + + describe "GET #show" do + let!(:block) { create(:block) } + let!(:release) { create(:release, block: block) } + let!(:feature_release) { create(:release, block: block, branch_name: "feature") } + + it "returns locals with the specific block and its releases" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:block]).to eq(block) + expect(args[:locals][:releases][0].release_id).to eq(release.id) + expect(args[:locals][:releases]).to_not include(feature_release) + end + + get :show, params: { id: block.id } + end + end + + describe "GET #new" do + it "returns locals with a new Block" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:block]).to be_a_new(Block) + end + + get :new + end + end + end +end diff --git a/scripts/spec/controllers/cohorts/blocks/content_files_controller_spec.rb b/scripts/spec/controllers/cohorts/blocks/content_files_controller_spec.rb new file mode 100644 index 0000000..d6e6eff --- /dev/null +++ b/scripts/spec/controllers/cohorts/blocks/content_files_controller_spec.rb @@ -0,0 +1,234 @@ +require "spec_helper" + +describe Cohorts::Blocks::ContentFilesController do + let(:user) { create(:user) } + let(:cohort) { create(:cohort) } + let(:release) { create(:release) } + let(:standard) { create(:standard, release: release) } + let(:content_file) { create(:content_file, standard: standard) } + let!(:lesson_visit_1) do + create( + :lesson_visit, + user: user, + block_id: release.block_id, + cohort_id: cohort.id, + standard_uid: standard.uid, + content_file_uid: "123" + ) + end + let!(:lesson_visit_2) do + create( + :lesson_visit, + user: user, + block_id: release.block_id, + cohort_id: cohort.id, + standard_uid: standard.uid, + content_file_uid: "abc" + ) + end + before { create(:cohort_release, cohort: cohort, release: release) } + + context "when signed in as an admin" do + let(:user) { create(:user, :admin) } + + before do + sign_in(user) + end + + describe "GET #show" do + it "returns locals with the current content file" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:is_enrolled_student]).to eq(false) + expect(args[:locals][:is_instructor_or_admin]).to eq(true) + expect(args[:locals][:content_file][:id]).to eq(content_file.id) + expect(args[:locals][:content_file][:html]).to eq(content_file.html) + expect(args[:locals][:content_file][:is_checkpoint]).to eq(content_file.checkpoint?) + expect(args[:locals][:content_file][:is_resource]).to eq(content_file.resource?) + expect(args[:locals][:content_file][:title]).to eq(content_file.title) + expect(args[:locals][:content_file][:type]).to eq(content_file.content_file_type) + expect(args[:locals][:cohort_mode]).to eq(cohort.mode) + expect(args[:locals][:cohort_path]).to eq(cohort_path(cohort)) + expect(args[:locals][:content_file_presenter]).to be_a(ContentFilePresenter::ForShow) + expect(args[:locals][:content_files_submissions]).to be_a(Array) + expect(args[:locals][:visited_lesson_uids]).to include("123", "abc", content_file.uid) + expect(args[:locals][:mastery_score]).to eq(nil) + expect(args[:locals][:content_file_footer_presenter]).to be_a(ContentFilePresenter::ForFooter) + end + + expect(ContentFileVisitJob).to receive(:perform_later).with( + user.id, + release.block_id, + cohort.id, + content_file.id, + content_file.standard.uid + ) + expect(StandardSubmissionsService).to receive(:new).and_call_original + expect(ContentFilePresenter::ForFooter).to receive(:new).with( + cohort: cohort, content_file: content_file + ).and_call_original + + get :show, params: { cohort_id: cohort.id, block_id: release.block_id, path: content_file.path } + end + end + + context "when cohort is not found" do + let(:content_file) { create(:content_file) } + + subject { get :show, params: { cohort_id: 0, block_id: content_file.standard.release.block_id, path: content_file.path } } + + it "renders error_404" do + expect(subject).to render_template("error_404") + end + end + + context "when content_file is not found" do + subject { get :show, params: { cohort_id: create(:cohort).id, block_id: create(:block).id, path: "blah" } } + + it "renders error_404" do + expect(subject).to render_template("error_404") + end + end + end + + context "when signed in as an instructor" do + before do + create(:cohort_user, :instructor, cohort: cohort, user: user) + sign_in(user) + end + + describe "GET #show" do + xit "TODO replace with new checkpoint flow; returns locals with the current content file" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:is_enrolled_student]).to eq(false) + expect(args[:locals][:is_instructor_or_admin]).to eq(true) + expect(args[:locals][:content_file_id]).to eq(content_file.id) + expect(args[:locals][:content_file_html]).to eq(content_file.html) + expect(args[:locals][:content_file_is_checkpoint]).to eq(content_file.checkpoint?) + expect(args[:locals][:content_file_is_resource]).to eq(content_file.resource?) + expect(args[:locals][:content_file_title]).to eq(content_file.title) + expect(args[:locals][:content_file_type]).to eq(content_file.content_file_type) + expect(args[:locals][:cohort_mode]).to eq(cohort.mode) + expect(args[:locals][:cohort_path]).to eq(cohort_path(cohort)) + expect(args[:locals][:content_file_presenter]).to be_a(ContentFilePresenter::ForShow) + expect(args[:locals][:content_files_submissions]).to be_a(Array) + expect(args[:locals][:visited_lesson_uids]).to include("123", "abc", content_file.uid) + expect(args[:locals][:block_title]).to eq(release.block.title) + expect(args[:locals][:mastery_score]).to eq(nil) + expect(args[:locals][:content_file_footer_presenter]).to be_a(ContentFilePresenter::ForFooter) + end + + expect(ContentFilePresenter::ForShow).to receive(:new).with(content_file, cohort, user).and_call_original + expect(StandardSubmissionsService).to receive(:new).and_call_original + expect(ContentFilePresenter::ForFooter).to receive(:new).with( + cohort: cohort, content_file: content_file + ).and_call_original + + get :show, params: { cohort_id: cohort.id, block_id: release.block_id, path: content_file.path } + end + end + end + + context "when signed in as an student" do + before do + create(:cohort_user, :student, cohort: cohort, user: user) + sign_in(user) + end + + describe "GET #show" do + it "returns locals with the current content file" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:is_enrolled_student]).to eq(true) + expect(args[:locals][:is_instructor_or_admin]).to eq(false) + expect(args[:locals][:content_file][:id]).to eq(content_file.id) + expect(args[:locals][:content_file][:html]).to eq(content_file.html) + expect(args[:locals][:content_file][:is_checkpoint]).to eq(content_file.checkpoint?) + expect(args[:locals][:content_file][:is_resource]).to eq(content_file.resource?) + expect(args[:locals][:content_file][:title]).to eq(content_file.title) + expect(args[:locals][:content_file][:type]).to eq(content_file.content_file_type) + expect(args[:locals][:cohort_mode]).to eq(cohort.mode) + expect(args[:locals][:cohort_path]).to eq(cohort_path(cohort)) + expect(args[:locals][:content_file_presenter]).to be_a(ContentFilePresenter::ForShow) + expect(args[:locals][:content_files_submissions]).to be_a(Array) + expect(args[:locals][:visited_lesson_uids]).to include("123", "abc", content_file.uid) + expect(args[:locals][:mastery_score]).to eq(nil) + expect(args[:locals][:content_file_footer_presenter]).to be_a(ContentFilePresenter::ForFooter) + end + + expect(ContentFilePresenter::ForShow).to receive(:new).with(content_file, cohort, user).and_call_original + expect(StandardSubmissionsService).to receive(:new).and_call_original + expect(ContentFilePresenter::ForFooter).to receive(:new).with( + cohort: cohort, content_file: content_file + ).and_call_original + + get :show, params: { cohort_id: cohort.id, block_id: release.block_id, path: content_file.path } + end + + it "raises a pundit unauthorized error if the content is not visible" do + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file.uid) + get :show, params: { cohort_id: cohort.id, block_id: release.block_id, path: content_file.path } + expect(flash[:alert]).to eq "You do not have permission to access that page." + end + + it "raises a pundit unauthorized error if the content's standard is not visible" do + create(:content_visibility, cohort_id: cohort.id, content_type: "Standard", content_uid: content_file.standard.uid) + get :show, params: { cohort_id: cohort.id, block_id: release.block_id, path: content_file.path } + expect(flash[:alert]).to eq "You do not have permission to access that page." + end + end + + describe "checkpoint attempts" do + let(:checkpoint) { create(:content_file, :checkpoint, max_checkpoint_submissions: 4, standard: standard) } + + xit "TODO replace with new checkpoint flow; renders the number of checkpoint attempts as a local" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:max_checkpoint_submissions]).to eq(4) + expect(args[:locals][:checkpoint_submissions_count]).to eq(0) + end + get :show, params: { cohort_id: cohort.id, block_id: release.block_id, path: checkpoint.path } + end + + context "when there are submissions for a student" do + context "when the checkpoint is only started" do + xit "TODO replace with new checkpoint flow; does not reduce the number of submissions left" do + create(:checkpoint_submission, state: CheckpointSubmission::STATES[:started], user: user, content_file_uid: checkpoint.uid, content_file_block_id: standard.release.block_id) + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:max_checkpoint_submissions]).to eq(4) + expect(args[:locals][:checkpoint_submissions_count]).to eq(0) + end + get :show, params: { cohort_id: cohort.id, block_id: release.block_id, path: checkpoint.path } + end + end + + xit "TODO replace with new checkpoint flow; renders student attempts in the locals" do + create(:checkpoint_submission, user: user, content_file_uid: checkpoint.uid, content_file_block_id: standard.release.block_id) + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:max_checkpoint_submissions]).to eq(4) + expect(args[:locals][:checkpoint_submissions_count]).to eq(1) + end + get :show, params: { cohort_id: cohort.id, block_id: release.block_id, path: checkpoint.path } + end + end + end + + describe "POST #track_activity_event" do + it "tracks the activity" do + expect do + post :track_activity_event, params: { cohort_id: cohort.id, block_id: release.block_id, path: content_file.path, event: Activity::NAMES[:video_started], content: "123456" }, format: :json + end.to change { Activity.count }.from(0).to(1) + + expect(Activity.last.name).to eq(Activity::NAMES[:video_started]) + expect(Activity.last.content).to eq("123456") + expect(Activity.last.creator).to eq(user) + expect(Activity.last.creator).to eq(user) + expect(Activity.last.cohort).to eq(cohort) + expect(Activity.last.subject).to eq(content_file) + end + end + end +end diff --git a/scripts/spec/controllers/cohorts/checkpoint_submissions/activities_controller_spec.rb b/scripts/spec/controllers/cohorts/checkpoint_submissions/activities_controller_spec.rb new file mode 100644 index 0000000..8592907 --- /dev/null +++ b/scripts/spec/controllers/cohorts/checkpoint_submissions/activities_controller_spec.rb @@ -0,0 +1,113 @@ +require "spec_helper" + +describe Cohorts::CheckpointSubmissions::ActivitiesController do + let(:cohort) { create(:cohort) } + let(:block) { create(:block) } + let(:release) { create(:release, block: block) } + let(:standard) { create(:standard, release: release) } + let(:content_file) { create(:content_file, :checkpoint, standard: standard) } + let(:challenge) { create(:challenge, content_file: content_file) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let(:checkpoint_submission) { create_checkpoint_submission(student, challenge, cohort: cohort) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, status: :ungraded, checkpoint_submission: checkpoint_submission) } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:response_json) { JSON.parse(response.body, symbolize_names: true) } + + context "when an a student comments" do + before { sign_in(student) } + + it "should allow the activity to be persisted as a comment type" do + expect do + post :create, params: { cohort_id: cohort.id, checkpoint_submission_id: checkpoint_submission.id, activity: { content: "postin' me a comment" } } + end.to change { Activity.count }.from(0).to(1) + activity = Activity.last + expect(activity.subject_id).to eq(checkpoint_submission.id) + expect(activity.subject_type).to eq("CheckpointSubmission") + expect(activity.name).to eq(Activity::NAMES[:comment_created]) + expect(activity.content).to eq("postin' me a comment") + expect(activity.creator).to eq(student) + end + + it "sends a Slack message to the instructor to notify them that a comment has been given for the students submission" do + instructor.update(slack_username: "@duffus", slack_username_verified_at: Time.now) + + expect(SlackStudentJob).to receive(:perform_later) do |message, username| + expect(username).to eq(instructor.slack_username) + expect(message).to include(content_file.title) + expect(message).to include(cohort_checkpoint_submission_url(cohort, checkpoint_submission)) + end + + post :create, params: { cohort_id: cohort.id, checkpoint_submission_id: checkpoint_submission.id, activity: { notify: "true", content: "postin' me a comment" } } + end + + it "notifies the instructor" do + expect do + post :create, params: { cohort_id: cohort.id, checkpoint_submission_id: checkpoint_submission.id, activity: { notify: "true", content: "postin' me a comment" } } + end.to change { Notification.count }.from(0).to(1) + + new_notification = Notification.last + expect(new_notification.user_id).to eq(instructor.id) + expect(new_notification.tagline).to eq("Comment Left") + expect(new_notification.title).to eq("#{student.full_name} commented on #{content_file.standard.release.block.title} - #{content_file.standard.title} - Checkpoint") + expect(new_notification.url).to eq(cohort_checkpoint_submission_url(cohort, checkpoint_submission)) + expect(new_notification.description).to include("postin' me a comment") + end + + it "prevents a student from commenting on another student's challenge submission" do + sign_in(create(:cohort_user, :student, cohort: cohort).user) + post :create, params: { cohort_id: cohort.id, checkpoint_submission_id: checkpoint_submission.id, activity: { content: "postin' me a comment" } } + expect(response.status).to eq(302) + end + end + + context "when an instructor comments" do + before do + sign_in(instructor) + end + + it "creates a new activity for the submitted challenge answer" do + post :create, params: { cohort_id: cohort.id, checkpoint_submission_id: checkpoint_submission.id, activity: { content: "postin' me a comment" } } + activity = Activity.last + expect(activity.subject_id).to eq(checkpoint_submission.id) + expect(activity.content).to eq("postin' me a comment") + expect(activity.creator).to eq instructor + end + + it "sends a Slack message to the student to notify them that a comment has been given for their submission" do + student.update(slack_username: "@duffus", slack_username_verified_at: Time.now) + + expect(SlackStudentJob).to receive(:perform_later) do |message, username| + expect(username).to eq(student.slack_username) + expect(message).to include(content_file.title) + expect(message).to include(cohort_checkpoint_submission_url(cohort, checkpoint_submission)) + end + + post :create, params: { cohort_id: cohort.id, checkpoint_submission_id: checkpoint_submission.id, activity: { notify: "true", content: "postin' me a comment" } } + end + + context "when the notify param is true" do + it "notifies the student" do + expect do + post :create, params: { cohort_id: cohort.id, checkpoint_submission_id: checkpoint_submission.id, activity: { notify: "true", content: "postin' me a comment" } } + end.to change { Notification.count }.from(0).to(1) + + new_notification = Notification.last + expect(new_notification.user_id).to eq(student.id) + expect(new_notification.tagline).to eq("Comment Left") + expect(new_notification.title).to eq("#{instructor.full_name} commented on #{content_file.standard.release.block.title} - #{content_file.standard.title} - Checkpoint") + expect(new_notification.url).to eq(cohort_checkpoint_submission_url(cohort, checkpoint_submission)) + expect(new_notification.description).to include("postin' me a comment") + end + end + + # in the case of rejecting a checkpoint submission, we send a separate notification to the student that includes both the comment and the rejection. + context "when the notify param is false" do + it "does not notify the student" do + expect do + post :create, params: { cohort_id: cohort.id, checkpoint_submission_id: checkpoint_submission.id, activity: { notify: "false", content: "postin' me a comment" } } + end.to_not(change { Notification.count }) + end + end + end +end diff --git a/scripts/spec/controllers/cohorts/cohort_releases_controller_spec.rb b/scripts/spec/controllers/cohorts/cohort_releases_controller_spec.rb new file mode 100644 index 0000000..7747fbc --- /dev/null +++ b/scripts/spec/controllers/cohorts/cohort_releases_controller_spec.rb @@ -0,0 +1,162 @@ +require "spec_helper" + +describe Cohorts::CohortReleasesController do + let(:cohort) { create(:cohort) } + let(:release) { create(:release, block: create(:block, title: "a")) } + + before { sign_in(user) } + + describe "POST #create" do + context "when authorized" do + let(:user) { create(:cohort_user, :instructor, cohort: cohort).user } + + it "should create a new cohort release" do + expect { post :create, params: { cohort_id: cohort.id, cohort_release: { release_id: release.id } } }.to( + change { CohortRelease.where(cohort_id: cohort.id).count }.from(0).to(1) + ) + + expect(response).to redirect_to(setup_cohort_path(cohort)) + expect(flash[:success]).to eq("Block attached.") + end + end + + context "when unauthorized" do + let(:user) { create(:user) } + + it "should return an error" do + post :create, params: { cohort_id: cohort.id, cohort_release: { release_id: release.id } } + + expect(response.status).to eq(302) + expect(flash[:alert]).to eq("You do not have permission to access that page.") + end + end + end + + describe "PATCH #update" do + context "when authorized" do + let(:user) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:old_release) { create(:release, branch_name: "master", github_sha: "old") } + let!(:cohort_release) { create(:cohort_release, release: old_release, cohort: cohort, pending_release_id: create(:release, github_sha: "somebranchstuff", branch_name: "notmaster")) } + let!(:new_release) { create(:release, block: cohort_release.release.block, branch_name: "master", github_sha: "new") } + + it "should update the cohort release" do + expect { patch :update, params: { cohort_id: cohort.id, id: cohort_release.id, cohort_release: { release_id: new_release.id } } }.to( + change { cohort_release.reload.release_id }.from(old_release.id).to(new_release.id) + ) + + expect(response).to redirect_to(setup_cohort_path(cohort)) + expect(flash[:success]).to eq("Block release updated.") + expect(cohort_release.reload.pending_release_id).to eq nil + end + + it "should update the cohort release via json format" do + expect { patch :update, format: :json, params: { cohort_id: cohort.id, id: cohort_release.id, cohort_release: { release_id: new_release.id } } }.to( + change { cohort_release.reload.release_id }.from(old_release.id).to(new_release.id) + ) + expect(JSON.parse(response.body)).to eq(JSON.parse({ release: CohortReleasePresenter::ForCohortSetup.new(cohort_release: cohort_release) }.to_json)) + end + + context "when switching to use auto updates" do + it "should set the release id to latest" do + expect { + patch :update, as: :json, params: { cohort_id: cohort.id, id: cohort_release.id, cohort_release: { use_latest_release: true } } + }.to( + change { cohort_release.reload.release_id }.from(old_release.id).to(new_release.id) + ) + expect(cohort_release.use_latest_release).to be true + end + end + end + end + + describe "PATCH #switch_to_branch" do + let(:user) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:block) { create(:block) } + let!(:cohort_release) { create(:cohort_release, release: create(:release, block: block), cohort: cohort) } + let(:response_json) { JSON.parse(response.body).deep_symbolize_keys } + + subject { patch :switch_to_branch, params: { cohort_id: cohort.id, id: cohort_release.id, block_id: block.id, branch_name: "test-branch" } } + + context "when the branch release is already in use" do + let(:release) { create(:release, block: block, branch_name: "test-branch") } + + context "by another cohort" do + it "should allow the job to process" do + cohort_release = create(:cohort_release, pending_release_id: release.id, cohort: create(:cohort)) + allow_any_instance_of(described_class).to receive(:branch_sha).and_return("asdf1234") + subject + + expect(response_json[:flash_info][:type]).to eq("warning") + expect(response_json[:flash_info][:msg]).to eq("Publishing branch 'test-branch'") + expect(response_json[:flash_info][:title]).to eq(block.repo_name.titleize) + end + end + + context "but is pending for current cohort" do + it "should allow the job to process" do + create(:cohort_release, pending_release_id: release.id, cohort: cohort) + create(:release, block_id: block.id, branch_name: "test-branch", github_sha: "different", sync_errors: ["badthing"]) + allow_any_instance_of(described_class).to receive(:branch_sha).and_return("asdf1234") + expect(SwitchToBranchJob).to receive(:perform_later) + subject + + expect(response_json[:flash_info][:type]).to eq("warning") + expect(response_json[:flash_info][:msg]).to eq("Publishing branch 'test-branch'") + expect(response_json[:flash_info][:title]).to eq(block.repo_name.titleize) + expect(release.reload.sync_errors).to eq [] + end + end + + context "when the release existed at the commit sha already and was unnattached" do + it "should attach the existing release to the current cohort without re-syncing and notify the user that nothing changed" do + release = create(:release, block_id: block.id, branch_name: "test-branch", github_sha: "asdf1234", state: Release::STATES[:success]) + create(:standard, release: release) + cohort_release.update(release: release) + allow_any_instance_of(described_class).to receive(:branch_sha).and_return("asdf1234") + expect(SwitchToBranchJob).to_not receive(:perform_later) + subject + + expect(response_json[:flash_info][:type]).to eq("success") + expect(response_json[:flash_info][:msg]).to eq("Nothing to publish, block is already up-to-date with the latest commit on 'test-branch'.") + expect(response_json[:flash_info][:title]).to eq(block.repo_name.titleize) + expect(cohort_release.reload.release.id).to eq release.id + end + + it "should attach the existing release to the current cohort without re-syncing and notify the user that it attachd the branch release" do + release = create(:release, block_id: block.id, branch_name: "test-branch", github_sha: "asdf1234", state: Release::STATES[:success]) + create(:standard, release: release) + allow_any_instance_of(described_class).to receive(:branch_sha).and_return("asdf1234") + expect(SwitchToBranchJob).to_not receive(:perform_later) + subject + + expect(response_json[:flash_info][:type]).to eq("success") + expect(response_json[:flash_info][:msg]).to eq("Attached existing and previously synced branch 'test-branch'") + expect(response_json[:flash_info][:title]).to eq(block.repo_name.titleize) + expect(cohort_release.reload.release.id).to eq release.id + end + end + end + + context "when the branch release doesn't exist on github" do + it "should return a json error" do + allow_any_instance_of(DownloadGithubRepositoryService).to receive(:branch_sha).and_return(nil) + allow_any_instance_of(DownloadGitlabRepositoryService).to receive(:branch_sha).and_return(nil) + subject + + expect(response_json[:flash_info][:type]).to eq("danger") + expect(response_json[:flash_info][:msg]).to include("Error. The provided branch 'test-branch' does not exist") + expect(response_json[:flash_info][:title]).to eq(block.repo_name.titleize) + end + end + + it "should update the cohort release with a pending_release_id and trigger a switch to branch job" do + allow_any_instance_of(described_class).to receive(:branch_sha).and_return("asdf1234") + expect(SwitchToBranchJob).to receive(:perform_later) + subject + + expect(response_json[:flash_info][:type]).to eq("warning") + expect(response_json[:flash_info][:msg]).to eq("Publishing branch 'test-branch'") + expect(response_json[:flash_info][:title]).to eq(block.repo_name.titleize) + end + end +end diff --git a/scripts/spec/controllers/cohorts/content_files/checkpoint_submissions_controller_spec.rb b/scripts/spec/controllers/cohorts/content_files/checkpoint_submissions_controller_spec.rb new file mode 100644 index 0000000..665abc3 --- /dev/null +++ b/scripts/spec/controllers/cohorts/content_files/checkpoint_submissions_controller_spec.rb @@ -0,0 +1,492 @@ +require "spec_helper" + +describe Cohorts::ContentFiles::CheckpointSubmissionsController do + let(:cohort) { create(:cohort) } + let(:release) { create(:release) } + let(:standard_1) { create(:standard, release: release, position: 1) } + let(:content_file_lesson) { create(:content_file, :lesson, standard: standard_1) } + let(:content_file) { create(:content_file, :checkpoint, standard: standard_1) } + let(:student_user) { create(:cohort_user, :student, cohort: cohort).user } + let(:first_challenge) { create(:challenge, content_file: content_file, position: 1, points: 5) } + let(:second_challenge) { create(:challenge, content_file: content_file, position: 2, points: 5) } + let(:checkpoint_submission) { create_checkpoint_submission(student_user, first_challenge, cohort: cohort) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, challenge: first_challenge, user: student_user, checkpoint_submission: checkpoint_submission) } + let!(:activity) { create(:activity, :checkpoint_submission_comment, subject: checkpoint_submission, cohort: cohort) } + let!(:excluded_activity) { create(:activity, :checkpoint_resubmitted, subject: checkpoint_submission, cohort: cohort) } + + before do + create(:cohort_release, cohort: cohort, release: release, position: 1) + sign_in(user) + end + + describe "GET #show" do + let(:user) { create(:user) } + let(:action) { get :show, params: { cohort_id: cohort.id, content_file_id: content_file.id, id: checkpoint_submission.id } } + + context "user is not authorized" do + before { create(:cohort_user, :student, user: user, cohort: cohort) } + + it "call out for auth" do + action + + expect(response).to redirect_to(root_path) + end + end + + context "viewing a checkpoint that is in started state" do + before do + create(:cohort_user, :instructor, user: user, cohort: cohort) + checkpoint_submission.update(state: CheckpointSubmission::STATES[:started]) + end + + it "call out for auth" do + action + + expect(controller).to set_flash[:alert].to(/Cannot view a Checkpoint that has not yet been submitted./) + expect(response).to redirect_to(cohorts_path(cohort)) + end + end + + context "when user is signed in as an instructor" do + let(:user) { create(:cohort_user, :instructor, cohort: cohort).user } + + context "when there is a next checkpoint submission, but there is a more recent checkpoint submission for that student" do + let!(:first_student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:last_student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:latest_done_submission) { create_checkpoint_submission(last_student, first_challenge, cohort: cohort, created_at: 1.day.from_now, state: 'done') } + let!(:started_checkpoint_submission) { create_checkpoint_submission(first_student, first_challenge, cohort: cohort, created_at: 1.days.ago, state: 'started') } + let!(:previous_checkpoint_submission) { create_checkpoint_submission(first_student, first_challenge, cohort: cohort, created_at: 2.days.ago, state: 'needs_review') } + let!(:orphaned_checkpoint_submission) { create_checkpoint_submission(first_student, first_challenge, cohort: cohort, created_at: 5.days.ago, state: 'needs_review') } + + before do + allow(controller).to receive(:render).and_call_original + checkpoint_submission.update(state: CheckpointSubmission::STATES[:needs_review], created_at: 1.minute.ago) + previous_checkpoint_submission.update(state: CheckpointSubmission::STATES[:needs_review]) + create(:submitted_challenge_answer, challenge: first_challenge, checkpoint_submission: latest_done_submission, user: last_student) + create(:submitted_challenge_answer, challenge: first_challenge, checkpoint_submission: previous_checkpoint_submission, user: first_student) + create(:submitted_challenge_answer, challenge: first_challenge, checkpoint_submission: orphaned_checkpoint_submission, user: first_student) + create(:submitted_challenge_answer, challenge: first_challenge, checkpoint_submission: started_checkpoint_submission, user: first_student) + end + + it "should link to the latest ungraded submission" do + action + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:next_ungraded_submission_url]).to eq(Rails.application.routes.url_helpers.cohort_checkpoint_submission_path( + cohort.id, previous_checkpoint_submission.id + )) + end + end + end + + context "when checkpoint submission has pair submission ids" do + let!(:paired_student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:pair_checkpoint_submission) { create_checkpoint_submission(paired_student, first_challenge, cohort: cohort, pair_submission_ids: [checkpoint_submission.id]) } + + before do + allow(controller).to receive(:render).and_call_original + cohort.update(mode: 'Percentage') + create(:submitted_challenge_answer, challenge: first_challenge, checkpoint_submission: pair_checkpoint_submission, user: paired_student) + checkpoint_submission.update(pair_submission_ids: [pair_checkpoint_submission.id]) + end + + it "should yield paired checkpoint submissions" do + action + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:pair_submissions].length).to eq(1) + expect(args[:locals][:pair_submissions][0].class).to eq(CheckpointSubmissionPresenter) + expect(args[:locals][:pair_submissions][0].id).to eq(pair_checkpoint_submission.id) + end + end + + context "pair has later checkpoint submission as an individual" do + let!(:later_checkpoint_submission) { create_checkpoint_submission(paired_student, first_challenge, cohort: cohort, created_at: Time.now + 1.day) } + it "should not yield a checkpoint submission" do + action + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:pair_submissions].length).to eq(0) + end + end + end + end + + context "when generating links to navigate up and down the user's submissions" do + before do + allow(controller).to receive(:render).and_call_original + end + + context "next_challenge_path is set" do + it "to the next challenge if it is in the block" do + later_content_file = create(:content_file, :lesson, standard: create(:standard, release: release, position: 2)) # standard in block, later position + later_challenge = create(:challenge, content_file: later_content_file, position: 0) + create(:submitted_challenge_answer, cohort: cohort, challenge: later_challenge, user: student_user) + create(:cohort_release, cohort: cohort, release: create(:release), position: 2) + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:next_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student_user.id}/challenges/#{later_challenge.id}") + end + end + + it "to the next challenge if it is in the block" do + later_content_file = create(:content_file, :lesson, standard: create(:standard, release: release, position: 2)) # standard in block, later position + later_challenge = create(:challenge, content_file: later_content_file, position: 0) + create(:submitted_challenge_answer, cohort: cohort, challenge: later_challenge, user: student_user) + create(:cohort_release, cohort: cohort, release: create(:release), position: 2) + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:next_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student_user.id}/challenges/#{later_challenge.id}") + end + end + + it "to the next checkpoint if it is in the block and the submission has a checkpoint submission id" do + later_content_file = create(:content_file, :checkpoint, standard: create(:standard, release: release, position: 2)) # standard in block, later position + later_challenge = create(:challenge, content_file: later_content_file, position: 0) + later_checkpoint_submission = create(:checkpoint_submission, cohort_id: cohort.id, user_id: student_user.id, content_file_block_id: release.block_id, content_file_uid: "abc123") + create(:submitted_challenge_answer, checkpoint_submission_id: later_checkpoint_submission.id, cohort: cohort, challenge: later_challenge, user: student_user) + create(:cohort_release, cohort: cohort, release: create(:release), position: 2) + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:next_challenge_path]).to eq("/cohorts/#{cohort.id}/checkpoint_submissions/#{later_checkpoint_submission.id}") + end + end + + it "to the next challenge if it is in a later block" do + later_release = create(:release) + create(:cohort_release, cohort: cohort, release: later_release, position: 2) + later_content_file = create(:content_file, :lesson, standard: create(:standard, release: later_release, position: 0)) # standard out of block, same position + later_challenge = create(:challenge, content_file: later_content_file, position: 0) + create(:submitted_challenge_answer, cohort: cohort, challenge: later_challenge, user: student_user) + create(:cohort_release, cohort: cohort, release: create(:release), position: 2) + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:next_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student_user.id}/challenges/#{later_challenge.id}") + end + end + + it "to the next checkpoint if it is in a later block and the submission has a checkpoint submission id" do + later_release = create(:release) + create(:cohort_release, cohort: cohort, release: later_release, position: 3) + later_content_file = create(:content_file, :lesson, standard: create(:standard, release: later_release, position: 0)) # standard out of block, same position + later_challenge = create(:challenge, content_file: later_content_file, position: 0) + later_checkpoint_submission = create(:checkpoint_submission, cohort_id: cohort.id, user_id: student_user.id, content_file_block_id: later_release.block_id, content_file_uid: "abc123") + create(:submitted_challenge_answer, checkpoint_submission_id: later_checkpoint_submission.id, cohort: cohort, challenge: later_challenge, user: student_user) + create(:cohort_release, cohort: cohort, release: create(:release), position: 2) + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:next_challenge_path]).to eq("/cohorts/#{cohort.id}/checkpoint_submissions/#{later_checkpoint_submission.id}") + end + end + end + + context "previous_challenge_path is set" do + it "to the previous challenge if it is in the block" do + earlier_challenge = create(:challenge, content_file: content_file_lesson, position: 0) + create(:submitted_challenge_answer, cohort: cohort, challenge: earlier_challenge, user: student_user) + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:previous_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student_user.id}/challenges/#{earlier_challenge.id}") + end + end + + it "to the previous checkpoint if it is in the block and the submission has a checkpoint submission id" do + earlier_challenge = create(:challenge, content_file: content_file_lesson, position: 0) # note that actual content file type doesn't matter, only if there is a checkpoint submission id + previous_checkpoint_submission = create(:checkpoint_submission, cohort_id: cohort.id, user_id: student_user.id, content_file_block_id: release.block_id, content_file_uid: "abc123") + create(:submitted_challenge_answer, checkpoint_submission_id: previous_checkpoint_submission.id, cohort: cohort, challenge: earlier_challenge, user: student_user) + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:previous_challenge_path]).to eq("/cohorts/#{cohort.id}/checkpoint_submissions/#{previous_checkpoint_submission.id}") + end + end + + it "to the previous challenge if it is in an earlier block" do + earlier_release = create(:release) + standard_0 = create(:standard, release: release, position: 0) + create(:cohort_release, cohort: cohort, release: earlier_release, position: 0) + earlier_content_file = create(:content_file, :lesson, standard: standard_0) + earlier_challenge = create(:challenge, content_file: earlier_content_file, position: 0) + create(:submitted_challenge_answer, cohort: cohort, challenge: earlier_challenge, user: student_user) + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:previous_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student_user.id}/challenges/#{earlier_challenge.id}") + end + end + + it "to the previous checkpoint if it is in an earlier block and the submission has a checkpoint submission id" do + earlier_release = create(:release) + standard_0 = create(:standard, release: release, position: 0) + create(:cohort_release, cohort: cohort, release: earlier_release, position: 0) + earlier_content_file = create(:content_file, :checkpoint, standard: standard_0) + earlier_challenge = create(:challenge, content_file: earlier_content_file, position: 0) + previous_checkpoint_submission = create(:checkpoint_submission, cohort_id: cohort.id, user_id: student_user.id, content_file_block_id: release.block_id, content_file_uid: "abc123") + create(:submitted_challenge_answer, checkpoint_submission_id: previous_checkpoint_submission.id, cohort: cohort, challenge: earlier_challenge, user: student_user) + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:previous_challenge_path]).to eq("/cohorts/#{cohort.id}/checkpoint_submissions/#{previous_checkpoint_submission.id}") + end + end + end + end + + context "when there is a submitted challenge answer for the checkpoint submission" do + before do + allow(controller).to receive(:render).and_call_original + end + + it "returns the checkpoint submission info" do + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:current_checkpoint_submission].title).to eq(content_file.title) + expect(args[:locals][:challenges][0].can_view_status).to eq(true) + expect(args[:locals][:loaded_student]).to have_attributes(full_name: student_user.full_name, id: student_user.id, initials: student_user.initials) + expect(args[:locals][:current_user_id]).to eq(user.id) + expect(args[:locals][:resubmit_url]).to eq(content_file_path(cohort, content_file.standard.release.block, content_file.path)) + expect(args[:locals][:activities].as_json).to eq([ActivityPresenter.new(activity)].as_json) + end + end + + it "passes the challenge to the view" do + double = double(ChallengeWithSubmittedChallengeAnswersPresenter) + + expect(ChallengeWithSubmittedChallengeAnswersPresenter).to receive(:new).and_return(double) + + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:challenges]).to eq([double]) + end + end + + context "when there are multiple challenges" do + before do + create(:submitted_challenge_answer, cohort: cohort, challenge: second_challenge, user: student_user, checkpoint_submission: checkpoint_submission) + end + + it "should order the challenge presenters by the assigned challenge order" do + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:challenges].map(&:id)).to eq([first_challenge.id, second_challenge.id]) + end + end + end + + context "when there is a more recent submitted challenge answer for the challenge" do + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, challenge: first_challenge, user: student_user, checkpoint_submission: checkpoint_submission) } + + it "returns the submitted challenge answer associated with the checkpoint submission" do + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:challenges][0].submitted_challenge_answer_presenters[0].submitted_challenge_answer_id).to eq(submitted_challenge_answer.id) + end + end + end + + context "when there are standards attached to the challenge" do + it "passes the standards associated to the curriculum to the view" do + double = double(StandardCardComponentProps, checkpoint_performance: nil) + + expect(StandardCardComponentProps).to receive(:new).with( + standard: content_file.standard, + latest_performance: nil, + checkpoint_performance: nil + ).and_return(double) + + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:standard_card_props]).to eq(double) + end + end + + context "and there is a performance attached to the standard" do + let!(:performance) { create(:performance, user_id: student_user.id, standard: content_file.standard, cohort: cohort) } + + it "passes the standard presenter to the view" do + expect(StandardPresenter::ForCheckpointSubmission).to receive(:new).with( + standard: content_file.standard, + latest_performance: performance + ).and_call_original + + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:standard_card_props]).to be_a(StandardCardComponentProps) + expect(args[:locals][:standard_card_props].standard.current_score).to eq(performance.score) + end + end + end + end + + context "when there is another checkpoint submission by another user" do + before do + user = create(:cohort_user, :student, cohort: cohort).user + create(:submitted_challenge_answer, cohort: cohort, challenge: second_challenge, user: user, checkpoint_submission: create_checkpoint_submission(user, second_challenge, cohort: cohort)) + end + + it "returns the checkpoint submission state" do + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:current_checkpoint_submission].state).to eq(CheckpointSubmission::STATES[:needs_review]) + end + end + end + end + end + + context "when user is signed in as a student" do + before do + sign_in(student_user) + allow(controller).to receive(:render).and_call_original + end + + context "when the checkpoint submission needs review" do + it "does not allow the challenge status to be viewed" do + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:challenges][0].can_view_status).to eq(false) + end + end + + it "does not allow the challenge explanation to be viewed" do + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:challenges][0].can_view_explanation).to eq(false) + end + end + end + + context "when the checkpoint submission is done" do + let!(:checkpoint_submission) do + create_checkpoint_submission( + user, + second_challenge, + cohort: cohort, + grader_id: create(:cohort_user, :instructor, cohort: cohort).user, + state: CheckpointSubmission::STATES[:done] + ) + end + + it "allows the challenge status to be viewed" do + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:challenges][0].can_view_status).to eq(true) + end + end + + it "allows the challenge explanation to be viewed" do + action + + expect(controller).to have_received(:render) do |args| + expect(args[:locals][:challenges][0].can_view_explanation).to eq(true) + end + end + end + end + end + + describe "PATCH #update" do + let(:user) { create(:user) } + let!(:instructor) { create(:cohort_user, :instructor, user: user, cohort: cohort).user } + let(:update_params) { { cohort_id: cohort.id, content_file_id: content_file.id, id: checkpoint_submission.id, checkpoint_submission: checkpoint_submission_params } } + let(:action) { patch :update, params: update_params } + let(:checkpoint_submission_params) { { state: CheckpointSubmission::STATES[:rejected] } } + + context "with valid params" do + context "when cohort is in percentage mode" do + it "creates bad data in the sense that we need a performance" do + cohort.update(mode: Cohort::MODES[:percentage]) + action + expect(checkpoint_submission.reload.state).to eq(CheckpointSubmission::STATES[:rejected]) + perf = Performance.last + expect(perf.checkpoint_submission_id).to eq(checkpoint_submission.id) + expect(perf.score).to eq(0) + end + end + + it "updates the requested checkpoint submission" do + action + + expect(checkpoint_submission.reload.state).to eq(CheckpointSubmission::STATES[:rejected]) + expect(checkpoint_submission.grader_id).to eq(instructor.id) + end + + it "notifies the user" do + expect(NotificationService).to receive(:checkpoint_submission_graded).with( + user: student_user, + grader: instructor, + cohort: cohort, + content_file: content_file, + checkpoint_submission: checkpoint_submission + ) + + action + end + + it "sends a slack message to the student when rejecting a checkpoint submission" do + student_user.update(slack_username: "@test", slack_username_verified_at: Time.now) + expect(SlackStudentJob).to receive(:perform_later) do |message, username| + expect(username).to eq(student_user.slack_username) + expect(message).to include(instructor.full_name) + expect(message).to include("rejected") + expect(message).to include(content_file.standard.title) + expect(message).to include(content_file.title) + expect(message).to include(cohort_checkpoint_submission_path(cohort.id, checkpoint_submission.id)) + end + + action + end + + context "when marking a checkpoint submission as done" do + let(:checkpoint_submission_params) { { state: CheckpointSubmission::STATES[:done] } } + + it "sends a slack message to the student" do + student_user.update(slack_username: "@test", slack_username_verified_at: Time.now) + expect(SlackStudentJob).to receive(:perform_later) do |message, username| + expect(username).to eq(student_user.slack_username) + expect(message).to include(instructor.full_name) + expect(message).to include("finished scoring") + expect(message).to include(content_file.standard.title) + expect(message).to include(content_file.title) + expect(message).to include(cohort_checkpoint_submission_path(cohort.id, checkpoint_submission.id)) + end + + action + end + end + + context "when marking a checkponit as retry" do + let(:checkpoint_submission_params) { { state: CheckpointSubmission::STATES[:retry] } } + + it "allows a user to resubmit a checkpoint again by marking all submitted challenge answers as retry" do + action + + expect(submitted_challenge_answer.reload.status).to eq(SubmittedChallengeAnswer::STATUSES[:correct]) + end + end + end + + context "when receiving a json request" do + before { update_params[:format] = :json } + + it "should return json checkpoint submission" do + action + rsp = JSON.parse(response.body).symbolize_keys + expect(rsp[:current_checkpoint_submission]).to_not eq nil + end + end + end +end diff --git a/scripts/spec/controllers/cohorts/content_files/submitted_challenge_answers_controller_spec.rb b/scripts/spec/controllers/cohorts/content_files/submitted_challenge_answers_controller_spec.rb new file mode 100644 index 0000000..e837028 --- /dev/null +++ b/scripts/spec/controllers/cohorts/content_files/submitted_challenge_answers_controller_spec.rb @@ -0,0 +1,857 @@ +require "spec_helper" + +describe Cohorts::ContentFiles::SubmittedChallengeAnswersController do + let(:cohort) { create(:cohort) } + let(:user) { create(:user) } + let!(:enrollment) { create(:cohort_user, :student, cohort: cohort, user: user) } + let(:release) { create(:cohort_release, cohort: cohort).release } + let(:standard) { create(:standard, release: release) } + let(:content_file) { create(:content_file, standard: standard, max_checkpoint_submissions: 1) } + let!(:challenge) { create(:challenge, content_file: content_file, explanation: "Thanks for submitting!", question: "huh?", challenge_type: Challenge::TYPES[:project]) } + let(:response_json) { JSON.parse(response.body, symbolize_names: true) } + + before { sign_in(user) } + + describe "#update" do + context "an instructor manually grades a student's submission" do + let(:challenge) { create(:challenge, content_file: content_file, points: 3) } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, status: :ungraded) } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:response_json) { JSON.parse(response.body, symbolize_names: true) } + + before { sign_in(instructor) } + + it "updates the status and sets the manual grader id" do + expect(NotificationService).to_not receive(:checkpoint_challenge_graded) + patch :update, params: { cohort_id: cohort.id, content_file_id: submitted_challenge_answer.challenge.content_file_id, id: submitted_challenge_answer.id, submitted_challenge_answer: { status: "correct", points: 2 } } + submitted_challenge_answer.reload + expect(submitted_challenge_answer.status).to eq("correct") + expect(submitted_challenge_answer.points).to eq(2) + expect(submitted_challenge_answer.manual_grader_id).to eq(instructor.id) + end + + context "the submitted challenge answer belongs to a checkpoint submission" do + let!(:checkpoint_submission) { create_checkpoint_submission(student, challenge, {state: 'done', total_points: 0, correct_points: 0}) } + it "sets checkpoint correct_points and total_points and sends a notification to the student when the checkpoint is done" do + submitted_challenge_answer.update(checkpoint_submission_id: checkpoint_submission.id) + expect(NotificationService).to receive(:checkpoint_challenge_graded).with( + user: student, + cohort: cohort, + content_file: content_file, + checkpoint_submission: checkpoint_submission, + challenge: submitted_challenge_answer.challenge + ) + + patch :update, params: { cohort_id: cohort.id, content_file_id: submitted_challenge_answer.challenge.content_file_id, id: submitted_challenge_answer.id, submitted_challenge_answer: { status: "correct", points: 2 } } + expect(checkpoint_submission.reload.total_points).to eq 3 + expect(checkpoint_submission.correct_points).to eq 2 + end + end + end + + context "cohort is not related to user making request" do + let(:cohort) { create(:cohort) } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, status: :ungraded) } + before { sign_in(create(:user)) } + it "raises an active record error when the cohort exists but is not scoped to the user" do + expect(controller).to_not receive(:authorize) + expect( + patch(:update, params: { cohort_id: cohort.id, content_file_id: submitted_challenge_answer.challenge.content_file_id, id: submitted_challenge_answer.id, submitted_challenge_answer: { status: "correct" } }) + ).to render_template("error_404") + end + end + end + + describe "PATCH cancel" do + context "authorized users" do + context "when the submitted challenge answer is processing" do + let!(:challenge) { create(:challenge, content_file_id: content_file.id, explanation: "Thanks for submitting!", question: "huh?", challenge_type: Challenge::TYPES[:code_snippet]) } + let(:response_json) { JSON.parse(response.body, symbolize_names: true) } + + it "cancels the submission" do + submitted_challenge_answer = create(:submitted_challenge_answer, cohort: cohort, user: user, challenge: challenge, status: "processing") + + patch :cancel, params: { cohort_id: cohort.id, content_file_id: content_file.id, id: submitted_challenge_answer.id } + + submitted_challenge_answer.reload + expect(response_json[:submittedChallengeAnswer]).to include( + answer: submitted_challenge_answer.answer, + status: "canceled", + challenge_id: submitted_challenge_answer.challenge_id, + test_results: "Tests canceled by student.", + last_graded_at: nil, + grader_label: nil, + student_comment_url: routes.url_helpers.cohort_user_challenge_path(cohort, submitted_challenge_answer.user, submitted_challenge_answer.challenge), + submitted_challenge_answer_url: cohort_content_file_submitted_challenge_answer_path(cohort, content_file, submitted_challenge_answer), + increment_hints_shown_submitted_challenge_answer_url: increment_hints_shown_cohort_content_file_submitted_challenge_answer_path(cohort, content_file, submitted_challenge_answer), + cancel_url: nil, + challenge_explanation: nil, + hints: [], + incorrect_attempts: 0, + permissions: { update: false }, + created_at: JSON.parse(submitted_challenge_answer.to_json)["created_at"], + submitted_challenge_answer_id: submitted_challenge_answer.id + ) + end + end + end + + context "when submitted challenge answer is not processing" do + it "doesn't allow a user to cancel" do + submitted_challenge_answer = create(:submitted_challenge_answer, cohort: cohort, user: user, challenge: challenge, status: "incorrect") + + patch :cancel, params: { cohort_id: cohort.id, content_file_id: content_file.id, id: submitted_challenge_answer.id } + + expect(response_json[:submittedChallengeAnswer]).to include( + answer: submitted_challenge_answer.answer, + status: "incorrect", + challenge_id: submitted_challenge_answer.challenge_id, + test_results: submitted_challenge_answer.test_results, + last_graded_at: JSON.parse(submitted_challenge_answer.to_json)["graded_at"], + grader_label: "Autograded", + student_comment_url: routes.url_helpers.cohort_user_challenge_path(cohort, submitted_challenge_answer.user, submitted_challenge_answer.challenge), + submitted_challenge_answer_url: cohort_content_file_submitted_challenge_answer_path(cohort, content_file, submitted_challenge_answer), + increment_hints_shown_submitted_challenge_answer_url: increment_hints_shown_cohort_content_file_submitted_challenge_answer_path(cohort, content_file, submitted_challenge_answer), + cancel_url: nil, + challenge_explanation: nil, + hints: [], + incorrect_attempts: 1, + permissions: { update: false }, + created_at: JSON.parse(submitted_challenge_answer.to_json)["created_at"], + submitted_challenge_answer_id: submitted_challenge_answer.id + ) + end + end + end + + describe "POST create" do + context "authorized users" do + + context "challenge is code-snippet" do + let!(:challenge) { create(:challenge, content_file_id: content_file.id, explanation: "Thanks for submitting!", question: "huh?", challenge_type: Challenge::TYPES[:code_snippet]) } + let(:response_json) { JSON.parse(response.body, symbolize_names: true) } + + it "runs the EvaluateCodeSnippetJob job and puts a cancel url in the challenge hash" do + expect(EvaluateCodeSnippetJob).to receive(:perform_later) + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "Blue", challenge_id: challenge.id } + + expect(Activity.count).to eq(1) + expect(Activity.last.name).to eq(Activity::NAMES[:submitted_challenge_answer_created_code_snippet]) + expect(Activity.last.cohort).to eq(cohort) + expect(Activity.last.creator).to eq(user) + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(response_json[:submittedChallengeAnswer]).to include( + answer: submitted_challenge_answer.answer, + status: submitted_challenge_answer.status, + challenge_id: submitted_challenge_answer.challenge_id, + test_results: "Tests Running…", + last_graded_at: nil, + grader_label: nil, + challenge_explanation: nil, + hints: [], + incorrect_attempts: 0, + permissions: { update: false }, + created_at: JSON.parse(submitted_challenge_answer.to_json)["created_at"], + submitted_challenge_answer_id: submitted_challenge_answer.id + ) + end + end + + context "challenge is custom-snippet" do + let!(:challenge) { create(:challenge, content_file_id: content_file.id, explanation: "Thanks for submitting!", question: "huh?", challenge_type: Challenge::TYPES[:custom_snippet]) } + let(:response_json) { JSON.parse(response.body, symbolize_names: true) } + + it "runs the EvaluateCustomSnippetJob job and puts a cancel url in the challenge hash" do + expect(EvaluateCustomSnippetJob).to receive(:perform_later) + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "Blue", challenge_id: challenge.id } + + expect(Activity.count).to eq(1) + expect(Activity.last.name).to eq(Activity::NAMES[:submitted_challenge_answer_created_custom_snippet]) + expect(Activity.last.cohort).to eq(cohort) + expect(Activity.last.creator).to eq(user) + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(response_json[:submittedChallengeAnswer]).to include( + answer: submitted_challenge_answer.answer, + status: submitted_challenge_answer.status, + challenge_id: submitted_challenge_answer.challenge_id, + test_results: "Tests Running…", + last_graded_at: nil, + grader_label: nil, + challenge_explanation: nil, + hints: [], + incorrect_attempts: 0, + permissions: { update: false }, + created_at: JSON.parse(submitted_challenge_answer.to_json)["created_at"], + submitted_challenge_answer_id: submitted_challenge_answer.id + ) + end + + context "when the answer is nil" do + it "sets the answer to empty string" do + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: nil, challenge_id: challenge.id } + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(response_json[:submittedChallengeAnswer][:answer]).to eq("") + end + end + end + + context "challenge is a project" do + context "with an answer" do + context "not gradable" do + let!(:challenge) { create(:challenge, content_file_id: content_file.id, explanation: nil, question: "huh?", challenge_type: Challenge::TYPES[:project], upstream_repo_path: nil) } + + it "returns json representing the user's answer without kicking off a job" do + expect(EvaluateProjectJob).to_not receive(:perform_later) + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "www.github.com/UserName/RepoName/", challenge_id: challenge.id } + + expect(Activity.count).to eq(1) + expect(Activity.last.name).to eq(Activity::NAMES[:submitted_challenge_answer_created_project]) + expect(Activity.last.cohort).to eq(cohort) + expect(Activity.last.creator).to eq(user) + submitted_challenge_answer = SubmittedChallengeAnswer.last + + expect(response_json[:submittedChallengeAnswer]).to include( + answer: submitted_challenge_answer.answer, + challenge_id: submitted_challenge_answer.challenge_id, + status: submitted_challenge_answer.status, + test_results: "", + last_graded_at: nil, + grader_label: nil, + cancel_url: nil, + challenge_explanation: nil, + hints: [], + incorrect_attempts: 0, + permissions: { update: false }, + created_at: JSON.parse(submitted_challenge_answer.to_json)["created_at"], + submitted_challenge_answer_id: submitted_challenge_answer.id + ) + end + end + end + end + + context "challenge is testable project" do + let!(:challenge) do + create(:challenge, content_file_id: content_file.id, explanation: "Thanks for submitting!", + question: "huh?", challenge_type: Challenge::TYPES[:testable_project], + upstream_repo_path: "www.github.com/Org/Repo", validate_fork: validate_fork) + end + + context "when the challenge validates forks" do + let(:validate_fork) { true } + + context "and a valid fork submitted" do + let(:repo_info) { OpenStruct.new(fork: true, parent: parent_repo) } + let(:parent_repo) { OpenStruct.new(full_name: "Org/Repo") } + + it "kicks off EvaluateProjectJob" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository).with("UserName/Repo").and_return(repo_info) + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with("UserName/Repo", "master").and_return(true) + + expect(EvaluateProjectJob).to receive(:perform_later) + expect_any_instance_of(described_class).to receive(:validate_fork).and_call_original + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "www.github.com/UserName/Repo/", challenge_id: challenge.id } + submitted_challenge_answer = SubmittedChallengeAnswer.last + + expect(response_json[:submittedChallengeAnswer]).to include( + answer: submitted_challenge_answer.answer, + challenge_id: submitted_challenge_answer.challenge_id, + status: submitted_challenge_answer.status, + test_results: "Tests Running…", + last_graded_at: nil, + grader_label: nil, + challenge_explanation: nil, + hints: [], + incorrect_attempts: 0, + permissions: { update: false }, + created_at: JSON.parse(submitted_challenge_answer.to_json)["created_at"], + submitted_challenge_answer_id: submitted_challenge_answer.id + ) + end + end + + context "and an invalid fork submitted" do + context "and a non-github url submitted" do + it "updates submitted_challenge_answer status to invalid_fork and does not kick off job" do + expect(EvaluateProjectJob).to_not receive(:perform_later) + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "google.com", challenge_id: challenge.id } + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(submitted_challenge_answer.status).to eq("invalid_fork") + expect(submitted_challenge_answer.test_results).to eq("Unexpected URL. Must contain github.com") + end + end + + context "repo cannot be found" do + it "updates submitted_challenge_answer status to invalid_fork and does not kick off job" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository).with("UserName/Repo").and_raise(Octokit::NotFound) + expect(EvaluateProjectJob).to_not receive(:perform_later) + expect_any_instance_of(described_class).to receive(:validate_fork).and_call_original + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "www.github.com/UserName/Repo/", challenge_id: challenge.id } + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(submitted_challenge_answer.status).to eq("invalid_fork") + expect(submitted_challenge_answer.test_results).to eq("Unexpected URL. A repository was not found for that submission url.") + end + end + + context "branch cannot be found" do + it "updates submitted_challenge_answer status to invalid_fork and does not kick off job" do + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with("UserName/Repo", "branchname").and_raise(Octokit::NotFound) + expect(EvaluateProjectJob).to_not receive(:perform_later) + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "www.github.com/UserName/Repo/tree/branchname", challenge_id: challenge.id } + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(submitted_challenge_answer.status).to eq("invalid_fork") + expect(submitted_challenge_answer.test_results).to eq("Unexpected URL. The submission branch does not exist on GitHub.") + end + end + + context "repo is not a fork" do + let(:repo_info) { OpenStruct.new(fork: false, parent: parent_repo) } + let(:parent_repo) { OpenStruct.new(full_name: "Org/Repo") } + it "updates submitted_challenge_answer status to invalid_fork and does not kick off job" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository).with("UserName/Repo").and_return(repo_info) + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with("UserName/Repo", "validbranchname").and_return(true) + expect(EvaluateProjectJob).to_not receive(:perform_later) + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "www.github.com/UserName/Repo/tree/validbranchname", challenge_id: challenge.id } + + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(submitted_challenge_answer.status).to eq("invalid_fork") + expect(submitted_challenge_answer.test_results).to eq("Unexpected URL. The submission url is not a fork.") + end + end + + context "repo names do not match" do + let(:repo_info) { OpenStruct.new(fork: true, parent: parent_repo) } + let(:parent_repo) { OpenStruct.new(full_name: "Org/DifferentRepo") } + it "updates submitted_challenge_answer status to invalid_fork and does not kick off job" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository).with("UserName/Repo").and_return(repo_info) + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with("UserName/Repo", "validbranchname").and_return(true) + expect(EvaluateProjectJob).to_not receive(:perform_later) + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "www.github.com/UserName/Repo/tree/validbranchname", challenge_id: challenge.id } + + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(submitted_challenge_answer.status).to eq("invalid_fork") + expect(submitted_challenge_answer.test_results).to eq("Unexpected URL. Submission should be a fork of www.github.com/Org/Repo") + end + end + + context "folders do not match" do + let!(:challenge) do + create(:challenge, + content_file_id: content_file.id, + explanation: "Thanks for submitting!", + question: "huh?", + challenge_type: Challenge::TYPES[:testable_project], + upstream_repo_path: "www.github.com/Org/Repo/tree/master/folder/subfolder", + validate_fork: true) + end + let(:repo_info) { OpenStruct.new(fork: true, parent: parent_repo) } + let(:parent_repo) { OpenStruct.new(full_name: "Org/Repo") } + + it "updates submitted_challenge_answer status to invalid_fork and does not kick off job" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository).with("UserName/Repo").and_return(repo_info) + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with("UserName/Repo", "master").and_return(true) + expect(EvaluateProjectJob).to_not receive(:perform_later) + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "www.github.com/UserName/Repo/tree/master/folder", challenge_id: challenge.id } + + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(submitted_challenge_answer.status).to eq("invalid_fork") + expect(submitted_challenge_answer.test_results).to eq("Unexpected URL. Submission URL must point to folder: folder/subfolder") + end + end + end + + context "without an answer" do + let!(:challenge) do + create(:challenge, + content_file_id: content_file.id, explanation: "Thanks for submitting!", + question: "huh?", challenge_type: Challenge::TYPES[:testable_project], validate_fork: true) + end + it "does not run EvaluateProjectJob" do + expect(EvaluateProjectJob).to_not receive(:perform_later) + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "www.github.com/UserName/RepoName/", challenge_id: challenge.id } + end + end + end + + context "when the challenge does not validate forks" do + let(:validate_fork) { false } + + context "when submission is not a fork" do + let(:repo_info) { OpenStruct.new(fork: false, parent: parent_repo) } + let(:parent_repo) { OpenStruct.new(full_name: "Org/Repo") } + + it "kicks off EvaluateProjectJob" do + allow_any_instance_of(Mocktokit::Client).to receive(:repository).with("UserName/Repo").and_return(repo_info) + allow_any_instance_of(Mocktokit::Client).to receive(:branch).with("UserName/Repo", "validbranchname").and_return(true) + expect(EvaluateProjectJob).to receive(:perform_later) + expect_any_instance_of(described_class).to_not receive(:validate_fork) + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "www.github.com/UserName/Repo/tree/validbranchname", challenge_id: challenge.id } + + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(response_json[:submittedChallengeAnswer]).to include( + answer: submitted_challenge_answer.answer, + challenge_id: submitted_challenge_answer.challenge_id, + status: submitted_challenge_answer.status, + test_results: "Tests Running…", + last_graded_at: nil, + grader_label: nil, + challenge_explanation: nil, + hints: [], + incorrect_attempts: 0, + permissions: { update: false }, + created_at: JSON.parse(submitted_challenge_answer.to_json)["created_at"], + submitted_challenge_answer_id: submitted_challenge_answer.id + ) + end + end + end + end + + context "challenge is not code-snippet" do + it "returns json representing the user's answer" do + expect(EvaluateCodeSnippetJob).to_not receive(:perform_later) + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "Blue", challenge_id: challenge.id } + + submitted_challenge_answer = SubmittedChallengeAnswer.last + + expect(response_json[:submittedChallengeAnswer]).to include( + last_graded_at: nil, + status: submitted_challenge_answer.status, + answer: submitted_challenge_answer.answer, + grader_label: nil, + challenge_id: submitted_challenge_answer.challenge_id, + test_results: "", + cancel_url: nil, + challenge_explanation: "Thanks for submitting!", + incorrect_attempts: 0, + hints: [], + permissions: { update: false }, + created_at: JSON.parse(submitted_challenge_answer.to_json)["created_at"], + submitted_challenge_answer_id: submitted_challenge_answer.id + ) + end + + it "saves the answer the user gave" do + expect(EvaluateCodeSnippetJob).to_not receive(:perform_later) + + expect do + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "Blue", challenge_id: challenge.id } + end.to change { SubmittedChallengeAnswer.count }.by(1) + + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(submitted_challenge_answer.user_id).to eq(user.id) + expect(submitted_challenge_answer.challenge_id).to eq(challenge.id) + expect(submitted_challenge_answer.challenge.content_file_id).to eq(content_file.id) + expect(submitted_challenge_answer.answer).to eq("Blue") + expect(submitted_challenge_answer.status).to eq("ungraded") + end + + context "when the challenge type is not code snipper or project" do + let(:challenge) { create(:challenge, content_file: content_file, challenge_type: Challenge::TYPES[:short_answer], answer: "Blue") } + it "creates a submitted_challenge_answer.created activity" do + expect(EvaluateCodeSnippetJob).to_not receive(:perform_later) + + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: "Blue", challenge_id: challenge.id } + + expect(Activity.count).to eq(1) + expect(Activity.last.name).to eq(Activity::NAMES[:submitted_challenge_answer_created]) + expect(Activity.last.cohort).to eq(cohort) + expect(Activity.last.creator).to eq(user) + end + end + end + + context "challenge is a checkbox and has multi points assigned" do + let!(:challenge) do + create(:challenge, + content_file_id: content_file.id, explanation: "Thanks for submitting!", + question: "huh?", challenge_type: Challenge::TYPES[:checkbox], answer: ['this', 'that'], points: 10, partial_credit: true) + end + + context "when its a checkpoint" do + let(:content_file) { create(:content_file, :checkpoint, standard: standard) } + + it "assigns partial credit" do + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: ['this', 'that', 'and another'], challenge_id: challenge.id } + expect(SubmittedChallengeAnswer.last.points).to eq(5) + end + end + + context "when its a lesson" do + it "assigns no partial credit" do + post :create, params: { cohort_id: cohort.id, content_file_id: content_file.id, answer: ['this', 'that', 'and another'], challenge_id: challenge.id } + expect(SubmittedChallengeAnswer.last.points).to eq(0) + end + end + end + end + end + + describe "GET checkpoint_submission_results" do + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:release) { create(:release) } + let!(:standard) { create(:standard, release: release) } + let(:challenge_1) { create(:challenge, content_file: content_file, answer: "foo", challenge_type: Challenge::TYPES[:short_answer], points: 3) } + let(:challenge_2) { create(:challenge, content_file: content_file, answer: "bah", challenge_type: Challenge::TYPES[:short_answer], points: 5) } + + let(:batch_payload) do + { + cohort_id: cohort.id, + content_file_id: content_file.id, + submitted_challenge_answers: [ + { challenge_id: challenge_1.id, answer: "foo" }, + { challenge_id: challenge_2.id, answer: "bar" } + ], + submit: true + } + end + let(:batch_response) { JSON.parse(response.body, symbolize_names: true) } + let(:batch_action) { post :batch_create, params: batch_payload } + + context "when the checkpoint is not autoscored" do + let(:content_file) { create(:content_file, :checkpoint, standard: standard) } + + before do + create(:cohort_release, release: content_file.standard.release, cohort: cohort) + sign_in(student) + batch_action + end + + it "has a json response of just the url of the checkpoint show" do + submission = CheckpointSubmissionFinder.latest_for_checkpoint_content_file_for_user_in_cohort( + cohort_id: cohort.id, + content_file: content_file, + user_id: student.id, + ) + + get :checkpoint_submission_results, params: { cohort_id: cohort.id, content_file_id: content_file.id } + resp = JSON.parse(response.body, symbolize_names: true) + + expect(resp[:url]).to eq(cohort_checkpoint_submission_path(cohort, submission)) + expect(resp[:mastery_score]).to be(nil) + expect(resp[:correct_challenges]).to be(nil) + expect(resp[:total_challenges]).to be(nil) + end + end + + context "when the checkpoint is autoscored" do + let(:content_file) { create(:content_file, :checkpoint, standard: standard, autoscore: true) } + before do + create(:cohort_release, release: content_file.standard.release, cohort: cohort) + sign_in(student) + end + + context "when in mastery mode" do + before do + batch_action + end + + it "returns the performance score of the checkpoint" do + submission = CheckpointSubmissionFinder.latest_for_checkpoint_content_file_for_user_in_cohort( + cohort_id: cohort.id, + content_file: content_file, + user_id: student.id + ) + + get :checkpoint_submission_results, params: { cohort_id: cohort.id, content_file_id: content_file.id } + resp = JSON.parse(response.body, symbolize_names: true) + + expect(resp[:url]).to eq(cohort_checkpoint_submission_path(cohort, submission)) + expect(resp[:mastery_score]).to be(2) + expect(resp[:correct_challenges]).to be(1) + expect(resp[:total_challenges]).to be(2) + end + end + + context "when in percentage mode" do + before do + cohort.update!(mode: Cohort::MODES[:percentage]) + batch_action + end + + it "returns the number correct out of the total" do + submission = CheckpointSubmissionFinder.latest_for_checkpoint_content_file_for_user_in_cohort( + cohort_id: cohort.id, + content_file: content_file, + user_id: student.id + ) + + expect(submission.total_points).to eq 8 + expect(submission.correct_points).to eq 3 + + get :checkpoint_submission_results, params: { cohort_id: cohort.id, content_file_id: content_file.id } + resp = JSON.parse(response.body, symbolize_names: true) + + expect(resp[:url]).to eq(cohort_checkpoint_submission_path(cohort, submission)) + expect(resp[:mastery_score]).to be(2) + expect(resp[:correct_challenges]).to be(1) + expect(resp[:total_challenges]).to be(2) + expect(resp[:total_points]).to be(8) + expect(resp[:correct_points]).to be(3) + end + end + + context "when any of the submitted challenge answers have a failure status (SubmittedChallengeAnswer::FAILURE_STATUSES)" do + before do + batch_action + end + + it "updates the scas with a checkpoint_submission_id of nil, destroys latest checkpoint submission, and returns errors as json" do + submission = CheckpointSubmissionFinder.latest_for_checkpoint_content_file_for_user_in_cohort( + cohort_id: cohort.id, + content_file: content_file, + user_id: student.id + ) + scas = submission.submitted_challenge_answers + scas.first.update(status: SubmittedChallengeAnswer::FAILURE_STATUSES[:repo_failed]) + + expect(scas.pluck(:checkpoint_submission_id)).to eq([submission.id, submission.id]) + + get :checkpoint_submission_results, params: { cohort_id: cohort.id, content_file_id: content_file.id } + resp = JSON.parse(response.body, symbolize_names: true) + + expect(CheckpointSubmission.find_by(id: submission.id)).to eq(nil) + expect(resp[:errors]).to eq([{ challenge_num: scas.first.challenge.position, challenge_err: "Invalid Repo" }]) + expect(scas.pluck(:checkpoint_submission_id)).to eq([]) + end + end + end + + context "when submitting as pairs" do + let!(:pair_bud) { create(:cohort_user, :student, cohort: cohort).user } + let!(:pair_pal) { create(:cohort_user, :student, cohort: cohort).user } + let!(:pair_chum) { create(:cohort_user, :student, cohort: cohort).user } + let(:batch_payload_with_pairs) do + { + cohort_id: cohort.id, + content_file_id: content_file.id, + submitted_challenge_answers: [ + { challenge_id: challenge_1.id, answer: "foo" }, + { challenge_id: challenge_2.id, answer: "bar" } + ], + pairs: {pair_bud.id => pair_bud, pair_pal.id => pair_pal, pair_chum.id => pair_chum, student.id => student}, + submit: true + } + end + let(:batch_action_with_pairs) { post :batch_create, params: batch_payload_with_pairs } + + before do + sign_in(student) + end + + it "creates pair submissions and assigns pair ids" do + expect { + batch_action_with_pairs + }.to change{ CheckpointSubmission.count }.by 4 + end + + context "a pair has reached the checkpoint submission limit" do + it "does not create one for the student at their limit" do + create(:checkpoint_submission, + cohort_id: cohort.id, + user_id: pair_bud.id, + content_file_block_id: content_file.standard.release.block_id, + content_file_uid: content_file.uid, + state: CheckpointSubmission::STATES[:needs_review] + ) + expect { + batch_action_with_pairs + }.to change{ CheckpointSubmission.count }.by 3 + end + end + context "one pair has a started checkpoint submission" do + it "finds a previous started submission" do + create(:checkpoint_submission, + cohort_id: cohort.id, + user_id: pair_chum.id, + content_file_block_id: content_file.standard.release.block_id, + content_file_uid: content_file.uid, + state: CheckpointSubmission::STATES[:started] + ) + expect { + batch_action_with_pairs + }.to change{ CheckpointSubmission.count }.by 3 + end + end + end + end + + describe "PATCH update" do + context "when the current user is authorized" do + context "when attempting to update the status" do + let(:cohort) { create(:cohort) } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, status: :ungraded) } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:action) { patch :update, params: { cohort_id: cohort.id, content_file_id: submitted_challenge_answer.challenge.content_file_id, id: submitted_challenge_answer.id, submitted_challenge_answer: { status: "correct" } } } + + before { sign_in(instructor) } + + it "updates the status and sets the manual grader_id" do + action + + expect(submitted_challenge_answer.reload.status).to eq("correct") + expect(submitted_challenge_answer.reload.manual_grader_id).to eq(instructor.id) + end + end + end + end + + describe "POST #batch_create" do + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:release) { create(:release) } + let!(:standard) { create(:standard, release: release) } + let!(:content_file) { create(:content_file, :checkpoint, standard: standard, max_checkpoint_submissions: 2) } + let!(:challenge_1) { create(:challenge, content_file: content_file, challenge_type: Challenge::TYPES[:code_snippet]) } + let!(:challenge_2) { create(:challenge, content_file: content_file) } + + before do + create(:cohort_release, release: content_file.standard.release, cohort: cohort) + sign_in(student) + end + + context "when a checkpoint is submitted" do + let!(:payload) do + { + cohort_id: cohort.id, + content_file_id: content_file.id, + submitted_challenge_answers: [ + { challenge_id: challenge_1.id, answer: "foo" }, + { challenge_id: challenge_2.id, answer: "bar" } + ], + submit: true + } + end + let(:response_json) { JSON.parse(response.body, symbolize_names: true) } + + let(:action) { post :batch_create, params: payload } + + it "creates the submitted challenge answers with an associated checkpoint submission" do + started_checkpoint_submission = create(:checkpoint_submission, + cohort_id: cohort.id, + user_id: student.id, + content_file_block_id: content_file.standard.release.block_id, + content_file_uid: content_file.uid, + state: CheckpointSubmission::STATES[:started]) + action + expect(CheckpointSubmission.count).to eq(1) + expect(SubmittedChallengeAnswer.count).to eq(2) + expect(Activity.count).to eq(1) + expect(Activity.last.name).to eq("checkpoint_submission.created") + expect(Activity.last.cohort).to eq(cohort) + expect(Activity.last.creator).to eq(student) + expect(SubmittedChallengeAnswer.pluck(:challenge_id)).to match_array([challenge_1.id, challenge_2.id]) + expect(SubmittedChallengeAnswer.pluck(:answer)).to match_array(["foo", "bar"]) + expect((SubmittedChallengeAnswer.pluck(:checkpoint_submission_id)).uniq).to eq([started_checkpoint_submission.id]) + started_checkpoint_submission.reload + expect(started_checkpoint_submission.cohort_id).to eq(cohort.id) + expect(started_checkpoint_submission.state).to eq(CheckpointSubmission::STATES[:needs_review]) + end + + it "returns the submission date and submitted status as true" do + expect do + action + end.to change { CheckpointSubmission.count }.from(0).to(1) + + expect(Time.parse(response_json[:submittedDate])).to be_within(1.second).of Time.now + expect(response_json[:checkpointSubmission][:updated_at]).to eq(CheckpointSubmission.last.updated_at.as_json) + expect(response_json[:checkpointSubmission][:state]).to eq("needs_review") + expect(response.status).to eq(200) + end + + it "creates a EvaluateCodeSnippetJob" do + expect(EvaluateCodeSnippetJob).to receive(:perform_later) + action + end + + it "always attempts_autoscore" do + checkpoint_submission = create_checkpoint_submission(student, challenge_1, cohort: cohort) + expect(CheckpointSubmission).to receive(:create!).with( + cohort_id: cohort.id.to_s, + state: CheckpointSubmission::STATES[:needs_review], + user_id: student.id, + content_file_block_id: challenge_1.content_file.standard.release.block_id, + content_file_uid: challenge_1.content_file.uid + ).and_return(checkpoint_submission) + action + end + + context "when an answer is nil" do + let(:payload) do + { + cohort_id: cohort.id, + content_file_id: content_file.id, + submitted_challenge_answers: [ + { challenge_id: challenge_1.id, answer: nil }, + { challenge_id: challenge_2.id, answer: nil } + ], + submit: true + } + end + + it "sets the answer to empty string" do + action + expect(SubmittedChallengeAnswer.pluck(:answer)).to match_array(["", ""]) + end + end + + context "previous checkpoint submitted challenge answers exist for the challenges on the checkpoint and belong to the student" do + let(:existing_checkpoint_submission) { create_checkpoint_submission(student, challenge_1, cohort: cohort) } + let!(:previous_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, checkpoint_submission: existing_checkpoint_submission, challenge: challenge_1) } + + it "creates a resubmission activity, not a submission activity" do + action + expect(Activity.last.name).to eq(Activity::NAMES[:checkpoint_submission_resubmitted]) + expect(Activity.last.cohort).to eq(cohort) + expect(Activity.last.creator).to eq(student) + end + end + + context "when a student does not have any more attempts" do + it "does not create a checkpoint submission" do + existing_checkpoint_1 = create_checkpoint_submission(student, challenge_1, cohort: cohort) + existing_checkpoint_2 = create_checkpoint_submission(student, challenge_1, cohort: cohort) + + action + expect(CheckpointSubmission.count).to eq(2) + expect(JSON.parse(response.body)).to eq({"message" => "Sorry, you have already submitted this checkpoint the max amount of times."}) + end + end + + context "when a submitted challenge answer already exists" do + it "uses the existing submitted challenge answer when it has the same answer, cohort, challenge_id, user, and a nil checkpoint_submission" do + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1, answer: 'foo') + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, answer: 'bar') + action + expect(SubmittedChallengeAnswer.count).to eq(2) + end + + it "does not use the existing submitted challenge answer when it has a different answer" do + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1, answer: 'notFoo') + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, answer: 'notBar') + action + expect(SubmittedChallengeAnswer.count).to eq(4) + end + + it "does not use the existing submitted challenge answer when it has a checkpoint attached" do + existing_checkpoint = create_checkpoint_submission(student, challenge_1, cohort: cohort) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1, answer: 'foo', checkpoint_submission_id: existing_checkpoint.id) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, answer: 'bar', checkpoint_submission_id: existing_checkpoint.id) + action + expect(SubmittedChallengeAnswer.count).to eq(4) + end + end + end + end +end diff --git a/scripts/spec/controllers/cohorts/content_files_controller_spec.rb b/scripts/spec/controllers/cohorts/content_files_controller_spec.rb new file mode 100644 index 0000000..23bb2f1 --- /dev/null +++ b/scripts/spec/controllers/cohorts/content_files_controller_spec.rb @@ -0,0 +1,407 @@ +require "spec_helper" + +describe Cohorts::ContentFilesController do + before do + sign_in(create(:user, :admin)) + end + + let!(:cohort) { create(:cohort) } + let!(:release) { create(:release) } + let!(:standard) { create(:standard, release: release) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let!(:content_file) { create(:content_file, standard: standard, position: 0) } + let!(:resource_content_file) { create(:content_file, standard: standard, position: 1, content_file_type: ContentFile::TYPES[:resource]) } + + describe "#visibility" do + it "creates a hide visibility" do + expect(ContentVisibility.count).to eq(0) + + put :visibility, params: { cohort_id: cohort.id, id: content_file.id, visibility_type: "hidden" } + + expect(ContentVisibility.count).to eq(1) + expect(ContentVisibility.first.hidden?).to eq(true) + end + + it "removes a hide visibility" do + expect(ContentVisibility.count).to eq(0) + + put :visibility, params: { cohort_id: cohort.id, id: content_file.id, visibility_type: "hidden" } + delete :visibility, params: { cohort_id: cohort.id, id: content_file.id, visibility_type: "hidden" } + + expect(ContentVisibility.count).to eq(0) + end + + context "resource" do + it "creates a hide visibility" do + expect(ContentVisibility.count).to eq(0) + + put :visibility, params: { cohort_id: cohort.id, id: resource_content_file.id, visibility_type: "hidden" } + + expect(ContentVisibility.count).to eq(1) + expect(ContentVisibility.first.hidden?).to eq(true) + end + + it "removes a hide visibility" do + expect(ContentVisibility.count).to eq(0) + + put :visibility, params: { cohort_id: cohort.id, id: resource_content_file.id, visibility_type: "hidden" } + delete :visibility, params: { cohort_id: cohort.id, id: resource_content_file.id, visibility_type: "hidden" } + + expect(ContentVisibility.count).to eq(0) + end + end + end + + describe "csv downloads" do + let!(:checkpoint_content_file) { create(:content_file, :checkpoint, standard: standard, position: 2) } + let!(:challenge_1) { create(:challenge, points: 2, content_file: checkpoint_content_file, challenge_type: "short-answer", position: 1, topics: ["python", "arbitty"]) } + let!(:challenge_2) { create(:challenge, points: 3, content_file: checkpoint_content_file, challenge_type: "number", position: 2, topics: ["python"]) } + + let(:student_1) { create(:user, last_viewed_cohort_id: cohort.id, last_name: "A") } + let!(:s1_enrollment) { create(:cohort_user, :student, cohort: cohort, user: student_1) } + let!(:s1_cs1) { create(:checkpoint_submission, + cohort: cohort, + user: student_1, + content_file_uid: checkpoint_content_file.uid, + content_file_block_id: release.block_id, + state: CheckpointSubmission::STATES[:done], + created_at: 1.day.ago, + submitted_at: 12.hours.ago + ) } + let!(:s1_sca1) { create(:submitted_challenge_answer, challenge: challenge_1, points: 1, cohort: cohort, checkpoint_submission_id: s1_cs1.id, user: student_1) } + let!(:s1_sca2) { create(:submitted_challenge_answer, challenge: challenge_2, points: 1, cohort: cohort, checkpoint_submission_id: s1_cs1.id, user: student_1) } + + let!(:s1_cs2) { create(:checkpoint_submission, + cohort: cohort, + user: student_1, + content_file_uid: checkpoint_content_file.uid, + content_file_block_id: release.block_id, + state: CheckpointSubmission::STATES[:done], + created_at: 5.hours.ago, + submitted_at: 2.hours.ago + ) } + let!(:s1_sca3) { create(:submitted_challenge_answer, challenge: challenge_1, points: 2, cohort: cohort, checkpoint_submission_id: s1_cs2.id, user: student_1) } + let!(:s1_sca4) { create(:submitted_challenge_answer, challenge: challenge_2, points: 3, cohort: cohort, checkpoint_submission_id: s1_cs2.id, user: student_1) } + + let(:student_2) { create(:user, last_viewed_cohort_id: cohort.id, last_name: "B") } + let!(:s2_enrollment) { create(:cohort_user, :student, cohort: cohort, user: student_2) } + let!(:s2_cs) { create(:checkpoint_submission, + cohort: cohort, + user: student_2, + content_file_uid: checkpoint_content_file.uid, + content_file_block_id: release.block_id, + created_at: 4.hours.ago, + submitted_at: 2.hours.ago + ) } + let!(:s2_sca1) { create(:submitted_challenge_answer, challenge: challenge_1, points: 0, cohort: cohort, checkpoint_submission_id: s2_cs.id, user: student_2) } + let!(:s2_sca2) { create(:submitted_challenge_answer, challenge: challenge_2, points: 0, cohort: cohort, checkpoint_submission_id: s2_cs.id, user: student_2) } + + let(:student_3) { create(:user, last_name: "0") } + let!(:s3_enrollment) { create(:cohort_user, :student, cohort: cohort, user: student_3) } + let!(:s3_cs) { create(:checkpoint_submission, + cohort: cohort, + user: student_3, + content_file_uid: checkpoint_content_file.uid, + content_file_block_id: release.block_id, + created_at: 4.hours.ago, + state: CheckpointSubmission::STATES[:started], + ) } + + describe "#student_summary_csv" do + context "no students" do + it "returns no content when no data is fetched" do + SubmittedChallengeAnswer.destroy_all + CheckpointSubmission.destroy_all + CohortUser.destroy_all + get :student_summary_csv, params: { cohort_id: cohort.id, id: checkpoint_content_file.id } + expect(response.code).to eq "204" + end + end + + context "students with work" do + + it "redirects when in html format" do + get :student_summary_csv, params: { cohort_id: cohort.id, id: checkpoint_content_file.id } + expect(response.code).to eq "302" + end + + it "yields a csv when in csv format" do + get :student_summary_csv, params: { cohort_id: cohort.id, id: checkpoint_content_file.id, format: "csv" } + expect(response.code).to eq "200" + expect(response.headers["Content-Type"]).to eq "text/csv; charset=utf-8" + expect(response.body).to_not eq nil + csv_contents = CSV.parse(response.body) + header = csv_contents[0] + expect(header[0]).to eq "first_name" + expect(header[1]).to eq "last_name" + expect(header[2]).to eq "email" + expect(header[3]).to eq "has_logged_in" + expect(header[4]).to eq "attempt_number" + expect(header[5]).to eq "checkpoint_submission_id" + expect(header[6]).to eq "submission_state" + expect(header[7]).to eq "attempt_started" + expect(header[8]).to eq "attempt_submitted" + expect(header[9]).to eq "points_earned" + expect(header[10]).to eq "total_points" + expect(header[11]).to eq "score_percentage" + expect(header[12]).to eq "topic:python_pts_earned" + expect(header[13]).to eq "topic:python_pts_avail" + expect(header[14]).to eq "topic:python_percent" + expect(header[15]).to eq "topic:arbitty_pts_earned" + expect(header[16]).to eq "topic:arbitty_pts_avail" + expect(header[17]).to eq "topic:arbitty_percent" + + student_a = csv_contents[1] + expect(student_a[0]).to eq student_1.first_name + expect(student_a[1]).to eq "A" + expect(student_a[2]).to eq student_1.email + expect(student_a[3]).to eq "y" + expect(student_a[4]).to eq "1" + expect(student_a[5]).to eq s1_cs1.id.to_s + expect(student_a[6]).to eq s1_cs1.state + expect(student_a[7][0..15]).to eq s1_cs1.created_at.to_s[0..15] + expect(student_a[8][0..15]).to eq s1_cs1.submitted_at.to_s[0..15] + expect(student_a[9]).to eq "2" + expect(student_a[10]).to eq "5" + expect(student_a[11]).to eq "40" + expect(student_a[12]).to eq "2" + expect(student_a[13]).to eq "5" + expect(student_a[14]).to eq "40" + expect(student_a[15]).to eq "1" + expect(student_a[16]).to eq "2" + expect(student_a[17]).to eq "50" + + + student_a_next = csv_contents[2] + expect(student_a_next[0]).to eq student_1.first_name + expect(student_a_next[1]).to eq "A" + expect(student_a_next[2]).to eq student_1.email + expect(student_a_next[3]).to eq "y" + expect(student_a_next[4]).to eq "2" + expect(student_a_next[5]).to eq s1_cs2.id.to_s + expect(student_a_next[6]).to eq s1_cs2.state + expect(student_a_next[7][0..15]).to eq s1_cs2.created_at.to_s[0..15] + expect(student_a_next[8][0..15]).to eq s1_cs2.submitted_at.to_s[0..15] + expect(student_a_next[9]).to eq "5" + expect(student_a_next[10]).to eq "5" + expect(student_a_next[11]).to eq "100" + expect(student_a_next[12]).to eq "5" + expect(student_a_next[13]).to eq "5" + expect(student_a_next[14]).to eq "100" + expect(student_a_next[15]).to eq "2" + expect(student_a_next[16]).to eq "2" + expect(student_a_next[17]).to eq "100" + + student_b = csv_contents[3] + expect(student_b[0]).to eq student_2.first_name + expect(student_b[1]).to eq "B" + expect(student_b[2]).to eq student_2.email + expect(student_b[3]).to eq "y" + expect(student_b[4]).to eq "1" + expect(student_b[5]).to eq s2_cs.id.to_s + expect(student_b[6]).to eq s2_cs.state + expect(student_b[7][0..15]).to eq s2_cs.created_at.to_s[0..15] + expect(student_b[8][0..15]).to eq s2_cs.submitted_at.to_s[0..15] + expect(student_b[9]).to eq "0" + expect(student_b[10]).to eq "5" + expect(student_b[11]).to eq "0" + expect(student_b[12]).to eq "0" + expect(student_b[13]).to eq "5" + expect(student_b[14]).to eq "0" + expect(student_b[15]).to eq "0" + expect(student_b[16]).to eq "2" + expect(student_b[17]).to eq "0" + + student_0 = csv_contents[4] + expect(student_0[0]).to eq student_3.first_name + expect(student_0[1]).to eq "0" + expect(student_0[2]).to eq student_3.email + expect(student_0[3]).to eq "n" + expect(student_0[4]).to eq "1" + expect(student_0[5]).to eq s3_cs.id.to_s + expect(student_0[6]).to eq s3_cs.state + expect(student_0[7][0..15]).to eq s3_cs.created_at.to_s[0..15] + expect(student_0[8]).to eq nil + expect(student_0[9]).to eq nil + expect(student_0[10]).to eq nil + expect(student_0[11]).to eq nil + expect(student_0[12]).to eq nil + expect(student_0[13]).to eq "5" + expect(student_0[14]).to eq nil + expect(student_0[15]).to eq nil + expect(student_0[16]).to eq "2" + expect(student_0[17]).to eq nil + end + end + end + + describe "#challenge_data_csv" do + context "no students" do + it "returns no content when no data is fetched" do + SubmittedChallengeAnswer.destroy_all + CheckpointSubmission.destroy_all + CohortUser.destroy_all + get :challenge_data_csv, params: { cohort_id: cohort.id, id: checkpoint_content_file.id } + expect(response.code).to eq "204" + end + end + + context "students with work" do + it "redirects when in html format" do + get :challenge_data_csv, params: { cohort_id: cohort.id, id: checkpoint_content_file.id } + expect(response.code).to eq "302" + end + + it "yields a csv when in csv format" do + get :challenge_data_csv, params: { cohort_id: cohort.id, id: checkpoint_content_file.id, format: "csv" } + expect(response.code).to eq "200" + expect(response.headers["Content-Type"]).to eq "text/csv; charset=utf-8" + expect(response.body).to_not eq nil + csv_contents = CSV.parse(response.body) + expect(csv_contents.length).to eq 7 + + header = csv_contents[0] + expect(header[0]).to eq "first_name" + expect(header[1]).to eq "last_name" + expect(header[2]).to eq "email" + expect(header[3]).to eq "challenge_type" + expect(header[4]).to eq "position" + expect(header[5]).to eq "title" + expect(header[6]).to eq "topics" + expect(header[7]).to eq "question" + expect(header[8]).to eq "correct_answer" + expect(header[9]).to eq "student_answer" + expect(header[10]).to eq "possible_points" + expect(header[11]).to eq "student_points" + expect(header[12]).to eq "submitted_at" + expect(header[13]).to eq "content_file_uid" + expect(header[14]).to eq "checkpoint_state" + expect(header[15]).to eq "checkpoint_id" + expect(header[16]).to eq "challenge_uid" + expect(header[17]).to eq "attempt_number" + + student_a_ch1_cs1 = csv_contents[1] + expect(student_a_ch1_cs1[0]).to eq student_1.first_name + expect(student_a_ch1_cs1[1]).to eq "A" + expect(student_a_ch1_cs1[2]).to eq student_1.email + expect(student_a_ch1_cs1[3]).to eq challenge_1.challenge_type + expect(student_a_ch1_cs1[4]).to eq challenge_1.position.to_s + expect(student_a_ch1_cs1[5]).to eq challenge_1.title + expect(student_a_ch1_cs1[6]).to eq "{python,arbitty}" + expect(student_a_ch1_cs1[7]).to eq challenge_1.question + expect(student_a_ch1_cs1[8]).to eq challenge_1.answer + expect(student_a_ch1_cs1[9]).to eq s1_sca1.answer + expect(student_a_ch1_cs1[10]).to eq challenge_1.points.to_s + expect(student_a_ch1_cs1[11]).to eq s1_sca1.points.to_s + expect(student_a_ch1_cs1[12][0..15]).to eq s1_sca1.created_at.to_s[0..15] + expect(student_a_ch1_cs1[13]).to eq checkpoint_content_file.uid + expect(student_a_ch1_cs1[14]).to eq s1_cs1.state + expect(student_a_ch1_cs1[15]).to eq s1_cs1.id.to_s + expect(student_a_ch1_cs1[16]).to eq challenge_1.uid + expect(student_a_ch1_cs1[17]).to eq "1" + + student_a_ch1_cs2 = csv_contents[2] + expect(student_a_ch1_cs2[0]).to eq student_1.first_name + expect(student_a_ch1_cs2[1]).to eq "A" + expect(student_a_ch1_cs2[2]).to eq student_1.email + expect(student_a_ch1_cs2[3]).to eq challenge_1.challenge_type + expect(student_a_ch1_cs2[4]).to eq challenge_1.position.to_s + expect(student_a_ch1_cs2[5]).to eq challenge_1.title + expect(student_a_ch1_cs2[6]).to eq "{python,arbitty}" + expect(student_a_ch1_cs2[7]).to eq challenge_1.question + expect(student_a_ch1_cs2[8]).to eq challenge_1.answer + expect(student_a_ch1_cs2[9]).to eq s1_sca3.answer + expect(student_a_ch1_cs2[10]).to eq challenge_1.points.to_s + expect(student_a_ch1_cs2[11]).to eq s1_sca3.points.to_s + expect(student_a_ch1_cs2[12][0..15]).to eq s1_sca3.created_at.to_s[0..15] + expect(student_a_ch1_cs2[13]).to eq checkpoint_content_file.uid + expect(student_a_ch1_cs2[14]).to eq s1_cs2.state + expect(student_a_ch1_cs2[15]).to eq s1_cs2.id.to_s + expect(student_a_ch1_cs2[16]).to eq challenge_1.uid + expect(student_a_ch1_cs2[17]).to eq "2" + + student_a_ch2_cs1 = csv_contents[3] + expect(student_a_ch2_cs1[0]).to eq student_1.first_name + expect(student_a_ch2_cs1[1]).to eq "A" + expect(student_a_ch2_cs1[2]).to eq student_1.email + expect(student_a_ch2_cs1[3]).to eq challenge_2.challenge_type + expect(student_a_ch2_cs1[4]).to eq challenge_2.position.to_s + expect(student_a_ch2_cs1[5]).to eq challenge_2.title + expect(student_a_ch2_cs1[6]).to eq "{python}" + expect(student_a_ch2_cs1[7]).to eq challenge_2.question + expect(student_a_ch2_cs1[8]).to eq challenge_2.answer + expect(student_a_ch2_cs1[9]).to eq s1_sca2.answer + expect(student_a_ch2_cs1[10]).to eq challenge_2.points.to_s + expect(student_a_ch2_cs1[11]).to eq s1_sca2.points.to_s + expect(student_a_ch2_cs1[12][0..15]).to eq s1_sca2.created_at.to_s[0..15] + expect(student_a_ch2_cs1[13]).to eq checkpoint_content_file.uid + expect(student_a_ch2_cs1[14]).to eq s1_cs1.state + expect(student_a_ch2_cs1[15]).to eq s1_cs1.id.to_s + expect(student_a_ch2_cs1[16]).to eq challenge_2.uid + expect(student_a_ch2_cs1[17]).to eq "1" + + student_a_ch2_cs2 = csv_contents[4] + expect(student_a_ch2_cs2[0]).to eq student_1.first_name + expect(student_a_ch2_cs2[1]).to eq "A" + expect(student_a_ch2_cs2[2]).to eq student_1.email + expect(student_a_ch2_cs2[3]).to eq challenge_2.challenge_type + expect(student_a_ch2_cs2[4]).to eq challenge_2.position.to_s + expect(student_a_ch2_cs2[5]).to eq challenge_2.title + expect(student_a_ch2_cs2[6]).to eq "{python}" + expect(student_a_ch2_cs2[7]).to eq challenge_2.question + expect(student_a_ch2_cs2[8]).to eq challenge_2.answer + expect(student_a_ch2_cs2[9]).to eq s1_sca4.answer + expect(student_a_ch2_cs2[10]).to eq challenge_2.points.to_s + expect(student_a_ch2_cs2[11]).to eq s1_sca4.points.to_s + expect(student_a_ch2_cs2[12][0..15]).to eq s1_sca4.created_at.to_s[0..15] + expect(student_a_ch2_cs2[13]).to eq checkpoint_content_file.uid + expect(student_a_ch2_cs2[14]).to eq s1_cs2.state + expect(student_a_ch2_cs2[15]).to eq s1_cs2.id.to_s + expect(student_a_ch2_cs2[16]).to eq challenge_2.uid + expect(student_a_ch2_cs2[17]).to eq "2" + + student_b_ch1 = csv_contents[5] + expect(student_b_ch1[0]).to eq student_2.first_name + expect(student_b_ch1[1]).to eq "B" + expect(student_b_ch1[2]).to eq student_2.email + expect(student_b_ch1[3]).to eq challenge_1.challenge_type + expect(student_b_ch1[4]).to eq challenge_1.position.to_s + expect(student_b_ch1[5]).to eq challenge_1.title + expect(student_b_ch1[6]).to eq "{python,arbitty}" + expect(student_b_ch1[7]).to eq challenge_1.question + expect(student_b_ch1[8]).to eq challenge_1.answer + expect(student_b_ch1[9]).to eq s2_sca1.answer + expect(student_b_ch1[10]).to eq challenge_1.points.to_s + expect(student_b_ch1[11]).to eq s2_sca1.points.to_s + expect(student_b_ch1[12][0..15]).to eq s2_sca1.created_at.to_s[0..15] + expect(student_b_ch1[13]).to eq checkpoint_content_file.uid + expect(student_b_ch1[14]).to eq s2_cs.state + expect(student_b_ch1[15]).to eq s2_cs.id.to_s + expect(student_b_ch1[16]).to eq challenge_1.uid + expect(student_b_ch1[17]).to eq "1" + + student_b_ch2 = csv_contents[6] + expect(student_b_ch2[0]).to eq student_2.first_name + expect(student_b_ch2[1]).to eq "B" + expect(student_b_ch2[2]).to eq student_2.email + expect(student_b_ch2[3]).to eq challenge_2.challenge_type + expect(student_b_ch2[4]).to eq challenge_2.position.to_s + expect(student_b_ch2[5]).to eq challenge_2.title + expect(student_b_ch2[6]).to eq "{python}" + expect(student_b_ch2[7]).to eq challenge_2.question + expect(student_b_ch2[8]).to eq challenge_2.answer + expect(student_b_ch2[9]).to eq s2_sca2.answer + expect(student_b_ch2[10]).to eq challenge_2.points.to_s + expect(student_b_ch2[11]).to eq s2_sca2.points.to_s + expect(student_b_ch2[12][0..15]).to eq s2_sca2.created_at.to_s[0..15] + expect(student_b_ch2[13]).to eq checkpoint_content_file.uid + expect(student_b_ch2[14]).to eq s2_cs.state + expect(student_b_ch2[15]).to eq s2_cs.id.to_s + expect(student_b_ch2[16]).to eq challenge_2.uid + expect(student_b_ch2[17]).to eq "1" + end + end + end + end + +end diff --git a/scripts/spec/controllers/cohorts/pairings_controller_spec.rb b/scripts/spec/controllers/cohorts/pairings_controller_spec.rb new file mode 100644 index 0000000..1ca5fda --- /dev/null +++ b/scripts/spec/controllers/cohorts/pairings_controller_spec.rb @@ -0,0 +1,61 @@ +require "spec_helper" + +describe Cohorts::PairingsController do + + let!(:cohort) { create(:cohort) } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + + before do + sign_in(instructor) + end + + describe "GET #index" do + let!(:pairing1) { create(:pairing, cohort_id: cohort.id) } + let!(:pairing2) { create(:pairing, cohort_id: cohort.id, created_at: 1.day.ago) } + let!(:pairing_other_cohort) { create(:pairing, cohort_id: create(:cohort).id) } + + it "lists all pairings for the cohort" do + get :index, params: { cohort_id: cohort.id } + + results = JSON.parse(response.body) + expect(results.length).to eq(2) + expect(results[0]["id"]).to eq(pairing1.id) + expect(results[1]["id"]).to eq(pairing2.id) + end + end + + describe "POST #create" do + it "creates a pairing" do + expect(Pairing.count).to eq(0) + + post :create, params: {cohort_id: cohort.id, pairing: {title: "Title Yo", size: 99, groups: [["uid","uid"], ["uid","uid"]].to_json} } + + expect(Pairing.count).to eq(1) + expect(Pairing.first.title).to eq("Title Yo") + expect(Pairing.first.size).to eq(99) + expect(JSON.parse(Pairing.first.groups).size).to eq(2) + end + end + + describe "PATCH #update" do + it "updates a pairing" do + pairing = create(:pairing, cohort_id: cohort.id) + + patch :update, params: {cohort_id: cohort.id, id: pairing.id, pairing: {title: "Title Yo", size: 99, groups: [["uid","uid"], ["uid","uid"]].to_json} } + + expect(Pairing.first.title).to eq("Title Yo") + expect(Pairing.first.size).to eq(99) + expect(JSON.parse(Pairing.first.groups).size).to eq(2) + end + end + + describe "DELETE #delete" do + it "deletes a pairing" do + pairing = create(:pairing, cohort_id: cohort.id) + + delete :destroy, params: {cohort_id: cohort.id, id: pairing.id } + + expect(Pairing.count).to eq(0) + end + end +end diff --git a/scripts/spec/controllers/cohorts/standards_controller_spec.rb b/scripts/spec/controllers/cohorts/standards_controller_spec.rb new file mode 100644 index 0000000..2156c05 --- /dev/null +++ b/scripts/spec/controllers/cohorts/standards_controller_spec.rb @@ -0,0 +1,116 @@ +require "spec_helper" + +describe Cohorts::StandardsController do + before do + sign_in(create(:user, :admin)) + end + + describe "#show" do + let!(:cohort) { create(:cohort) } + let!(:release) { create(:release) } + let!(:standard) { create(:standard, release: release) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + + let!(:content_file_0) { create(:content_file, standard: standard, title: "Fizz", position: 0) } + let!(:challenge_0_0) { create(:challenge, content_file: content_file_0, position: 0, title: "Fizz Tastic") } + let!(:challenge_0_1) { create(:challenge, content_file: content_file_0, position: 1, title: "Fizzlin") } + + let!(:content_file_1) { create(:content_file, standard: standard, title: "Buzz", position: 1) } + let!(:challenge_1_0) { create(:challenge, content_file: content_file_1, position: 0, title: "Buzz Nonedrin") } + let!(:challenge_1_1) { create(:challenge, content_file: content_file_1, position: 1, title: "Buzzelzebub") } + + let!(:content_file_checkpoint) { create(:content_file, :checkpoint, standard: standard, title: "Bazz", position: 2) } + let!(:challenge_in_checkpoint) { create(:challenge, content_file: content_file_checkpoint, position: 0, title: "Bazz Yetu") } + + describe "content file information" do + it "yields lesson and challenge data" do + get :show, params: { cohort_id: cohort.id, id: standard.id } + results = JSON.parse(response.body) + expect(results.length).to eq 3 + first_content_file = results[0].deep_symbolize_keys + expect(first_content_file[:id]).to eq content_file_0.id + expect(first_content_file[:title]).to eq content_file_0.title + expect(first_content_file[:content_file_type]).to eq content_file_0.content_file_type + expect(first_content_file[:challenges].length).to eq 2 + expect(first_content_file[:challenges][0][:title]).to eq challenge_0_0.title + expect(first_content_file[:challenges][0][:id]).to eq challenge_0_0.id + expect(first_content_file[:challenges][1][:title]).to eq challenge_0_1.title + expect(first_content_file[:challenges][1][:id]).to eq challenge_0_1.id + + second_content_file = results[1].deep_symbolize_keys + expect(second_content_file[:id]).to eq content_file_1.id + expect(second_content_file[:title]).to eq content_file_1.title + expect(second_content_file[:content_file_type]).to eq content_file_1.content_file_type + expect(second_content_file[:challenges].length).to eq 2 + expect(second_content_file[:challenges][0][:title]).to eq challenge_1_0.title + expect(second_content_file[:challenges][0][:id]).to eq challenge_1_0.id + expect(second_content_file[:challenges][1][:title]).to eq challenge_1_1.title + expect(second_content_file[:challenges][1][:id]).to eq challenge_1_1.id + + third_content_file = results[2].deep_symbolize_keys + expect(third_content_file[:id]).to eq content_file_checkpoint.id + expect(third_content_file[:title]).to eq content_file_checkpoint.title + expect(third_content_file[:content_file_type]).to eq content_file_checkpoint.content_file_type + expect(third_content_file[:challenges].length).to eq 1 + expect(third_content_file[:challenges][0][:title]).to eq challenge_in_checkpoint.title + expect(third_content_file[:challenges][0][:id]).to eq challenge_in_checkpoint.id + end + end + + describe "student submission information" do + let!(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user } + let!(:student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user } + + let(:incorrect) { SubmittedChallengeAnswer::STATUSES[:incorrect] } + let(:correct) { SubmittedChallengeAnswer::STATUSES[:correct] } + let(:ungraded) { SubmittedChallengeAnswer::STATUSES[:ungraded] } + let(:failed) { SubmittedChallengeAnswer::STATUSES[:failed] } + + let!(:student_1_challenge_0_0_old_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_0, created_at: 1.day.ago, status: incorrect, cohort: cohort) } + let!(:student_1_challenge_0_0_latest_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_0, status: correct, points: 1, cohort: cohort) } + let!(:student_2_challenge_0_0_sca) { create(:submitted_challenge_answer, user: student_2, challenge: challenge_0_0, status: failed, cohort: cohort) } + + let!(:student_1_challenge_0_1_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_1, status: ungraded, cohort: cohort) } + + # no 0_1 submission for student 2, test absence of submission still creates empty object + + it "yields latest student submission information for each challenge" do + get :show, params: { cohort_id: cohort.id, id: standard.id } + results = JSON.parse(response.body) + first_content_file = results[0].deep_symbolize_keys + first_challenges = first_content_file[:challenges] + expect(first_challenges[0][:student_submissions][student_1.id.to_s.to_sym][:status]).to eq correct + expect(first_challenges[0][:student_submissions][student_1.id.to_s.to_sym][:correct_points]).to eq 1 + expect(first_challenges[0][:student_submissions][student_2.id.to_s.to_sym][:status]).to eq failed + + expect(first_challenges[1][:student_submissions][student_1.id.to_s.to_sym][:status]).to eq ungraded + expect(first_challenges[1][:student_submissions][student_2.id.to_s.to_sym][:status]).to eq "n/a" + end + end + + describe "#visibility" do + let!(:cohort) { create(:cohort) } + let!(:release) { create(:release) } + let!(:standard) { create(:standard, release: release) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + + it "creates a hide visibility" do + expect(ContentVisibility.count).to eq(0) + + put :visibility, params: { cohort_id: cohort.id, id: standard.id, visibility_type: "hidden" } + + expect(ContentVisibility.count).to eq(1) + expect(ContentVisibility.first.hidden?).to eq(true) + end + + it "removes a hide visibility" do + expect(ContentVisibility.count).to eq(0) + + put :visibility, params: { cohort_id: cohort.id, id: standard.id, visibility_type: "hidden" } + delete :visibility, params: { cohort_id: cohort.id, id: standard.id, visibility_type: "hidden" } + + expect(ContentVisibility.count).to eq(0) + end + end + end +end diff --git a/scripts/spec/controllers/cohorts/submitted_challenge_answers/activities_controller_spec.rb b/scripts/spec/controllers/cohorts/submitted_challenge_answers/activities_controller_spec.rb new file mode 100644 index 0000000..ace349b --- /dev/null +++ b/scripts/spec/controllers/cohorts/submitted_challenge_answers/activities_controller_spec.rb @@ -0,0 +1,99 @@ +require "spec_helper" + +describe Cohorts::SubmittedChallengeAnswers::ActivitiesController do + let(:cohort) { create(:cohort) } + let(:block) { create(:block) } + let(:release) { create(:release, block: block) } + let(:standard) { create(:standard, release: release) } + let(:content_file) { create(:content_file, standard: standard) } + let(:challenge) { create(:challenge, content_file: content_file) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, status: :ungraded) } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:response_json) { JSON.parse(response.body, symbolize_names: true) } + + describe "POST #create" do + subject { post :create, params: { cohort_id: cohort.id, submitted_challenge_answer_id: submitted_challenge_answer.id, activity: { content: "postin' me a comment" } } } + + context "when an a student comments" do + before { sign_in(student) } + + it "should allow the activity to be persisted as a comment type" do + expect { subject }.to change { Activity.count }.from(0).to(1) + activity = Activity.last + expect(activity.subject_id).to eq submitted_challenge_answer.id + expect(activity.subject_type).to eq "SubmittedChallengeAnswer" + expect(activity.name).to eq Activity::NAMES[:comment_created] + expect(activity.content).to eq "postin' me a comment" + expect(activity.creator).to eq student + end + + it "sends a Slack message to the instructor to notify them that a comment has been given for the students submission" do + instructor.update(slack_username: "@duffus", slack_username_verified_at: Time.now) + + expect(SlackStudentJob).to receive(:perform_later) do |message, username| + expect(username).to eq(instructor.slack_username) + expect(message).to include(submitted_challenge_answer.challenge.title) + expect(message).to include(cohort_user_challenge_url(cohort, student, submitted_challenge_answer.challenge)) + end + + subject + end + + it "sends a notification to the instructor to notify them that a comment has been given for the students submission" do + expect_any_instance_of(User).to receive(:notify) do |_, args| + expect(args[:tagline]).to eq("Comment Left") + expect(args[:title]).to eq("#{student.full_name} commented on #{block.title} - #{standard.title} - #{content_file.title} - #{challenge.title}") + expect(args[:url]).to eq(cohort_user_challenge_url(cohort, submitted_challenge_answer.user, submitted_challenge_answer.challenge)) + expect(args[:description]).to eq("\"postin' me a comment\"") + end + + subject + end + + it "prevents a student from commenting on another student's challenge submission" do + sign_in(create(:cohort_user, :student, cohort: cohort).user) + subject + expect(response.status).to eq(302) + end + end + + context "when an instructor comments" do + before do + sign_in(instructor) + end + + it "creates a new activity for the submitted challenge answer" do + subject + activity = Activity.last + expect(activity.subject_id).to eq(submitted_challenge_answer.id) + expect(activity.content).to eq("postin' me a comment") + expect(activity.creator).to eq instructor + end + + it "sends a notification to the student to notify them that a comment has been given for their submission" do + expect_any_instance_of(User).to receive(:notify) do |_, args| + expect(args[:tagline]).to eq("Comment Left") + expect(args[:title]).to eq("#{instructor.full_name} commented on #{block.title} - #{standard.title} - #{content_file.title} - #{challenge.title}") + expect(args[:url]).to eq(cohort_user_challenge_url(cohort, submitted_challenge_answer.user, submitted_challenge_answer.challenge)) + expect(args[:description]).to eq("\"postin' me a comment\"") + end + + subject + end + + it "sends a Slack message to the student to notify them that a comment has been given for their submission" do + student.update(slack_username: "@duffus", slack_username_verified_at: Time.now) + + expect(SlackStudentJob).to receive(:perform_later) do |message, username| + expect(username).to eq(student.slack_username) + expect(message).to include(submitted_challenge_answer.challenge.title) + expect(message).to include(cohort_user_challenge_url(cohort, student, submitted_challenge_answer.challenge)) + end + + subject + end + end + end +end diff --git a/scripts/spec/controllers/cohorts/users/challenges_controller_spec.rb b/scripts/spec/controllers/cohorts/users/challenges_controller_spec.rb new file mode 100644 index 0000000..7ed9202 --- /dev/null +++ b/scripts/spec/controllers/cohorts/users/challenges_controller_spec.rb @@ -0,0 +1,207 @@ +require "spec_helper" + +describe Cohorts::Users::ChallengesController do + let(:cohort) { create(:cohort) } + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:block) { create(:block) } + let(:release) { create(:release, block: block) } + let!(:cohort_release) { create(:cohort_release, release: release, cohort: cohort, position: 1) } + let(:standard) { create(:standard, release: release, position: 1) } + let(:content_file) { create(:content_file, standard: standard, position: 1) } + let(:challenge_1) { create(:challenge, content_file: content_file, position: 1) } + + let!(:submitted_challenge_answer_1) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1) } + let!(:submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1) } + let!(:submitted_challenge_answer_3) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1) } + + before { allow(controller).to receive(:render).and_call_original } + + describe "#show" do + context "when generating links to navigate up and down the user's submissions" do + before do + allow(controller).to receive(:render).and_call_original + sign_in(instructor) + end + + context "previous_challenge_path is set" do + it "to match a previous challenge if it is in the content file" do + previous_challenge = create(:challenge, content_file: content_file, position: 0) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: previous_challenge) + new_release = create(:release, block: block) + create(:cohort_release, release: new_release, cohort: cohort, position: 1) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:previous_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student.id}/challenges/#{previous_challenge.id}") + end + + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + end + + it "to match a previous challenge if the closest previous challenge is earlier in the standard" do + previous_content_file = create(:content_file, standard: standard, position: 0) + previous_challenge = create(:challenge, content_file: previous_content_file, position: 0) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: previous_challenge) + new_release = create(:release, block: block) + create(:cohort_release, release: new_release, cohort: cohort, position: 1) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:previous_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student.id}/challenges/#{previous_challenge.id}") + end + + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + end + + it "to match a previous challenge if the closest previous challenge is in an earlier standard" do + previous_standard = create(:standard, release: release, position: 0) + previous_content_file = create(:content_file, standard: previous_standard, position: 1) + previous_challenge = create(:challenge, content_file: previous_content_file, position: 1) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: previous_challenge) + new_release = create(:release, block: block) + create(:cohort_release, release: new_release, cohort: cohort, position: 1) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:previous_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student.id}/challenges/#{previous_challenge.id}") + end + + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + end + + it "to match a previous challenge if the closest previous challenge is in an earlier block" do + previous_block = create(:block) + previous_release = create(:release, block: previous_block) + create(:cohort_release, cohort: cohort, release: previous_release, position: 0) + previous_standard = create(:standard, release: previous_release, position: 1) + previous_content_file = create(:content_file, standard: previous_standard, position: 1) + previous_challenge = create(:challenge, content_file: previous_content_file, position: 1) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: previous_challenge) + new_release = create(:release, block: block) + create(:cohort_release, release: new_release, cohort: cohort, position: 1) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:previous_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student.id}/challenges/#{previous_challenge.id}") + end + + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + end + end + + context "next_challenge_path is set" do + it "to match an upcoming challenge if it is in the content file" do + next_challenge = create(:challenge, content_file: content_file, position: 2) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: next_challenge) + new_release = create(:release, block: block) + create(:cohort_release, release: new_release, cohort: cohort, position: 1) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:next_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student.id}/challenges/#{next_challenge.id}") + end + + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + end + + it "to match an upcoming challenge if it is in the standard" do + next_content_file = create(:content_file, standard: standard, position: 2) + next_challenge = create(:challenge, content_file: next_content_file, position: 0) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: next_challenge) + new_release = create(:release, block: block) + create(:cohort_release, release: new_release, cohort: cohort, position: 1) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:next_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student.id}/challenges/#{next_challenge.id}") + end + + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + end + + it "to match an upcoming challenge if it is in a later standard in block" do + next_standard = create(:standard, release: release, position: 2) + next_content_file = create(:content_file, standard: next_standard, position: 1) + next_challenge = create(:challenge, content_file: next_content_file, position: 1) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: next_challenge) + new_release = create(:release, block: block) + create(:cohort_release, release: new_release, cohort: cohort, position: 1) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:next_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student.id}/challenges/#{next_challenge.id}") + end + + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + end + + it "to match an upcoming challenge if it is in a later block" do + next_block = create(:block) + next_release = create(:release, block: next_block) + create(:cohort_release, cohort: cohort, release: next_release, position: 2) + next_standard = create(:standard, release: next_release, position: 1) + next_content_file = create(:content_file, standard: next_standard, position: 1) + next_challenge = create(:challenge, content_file: next_content_file, position: 1) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: next_challenge) + new_release = create(:release, block: block) + create(:cohort_release, release: new_release, cohort: cohort, position: 1) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:next_challenge_path]).to eq("/cohorts/#{cohort.id}/users/#{student.id}/challenges/#{next_challenge.id}") + end + + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + end + end + end + + context "when a student tries to view another student's work" do + let(:other_student) { create(:cohort_user, :student, cohort: cohort).user } + before { sign_in(student) } + + it "redirects not authorized users" do + get :show, params: { user_id: other_student.id, cohort_id: cohort.id, id: challenge_1.id } + + expect(response).to redirect_to(root_path) + end + end + + context "when a user is not related to the cohort" do + let(:user_not_related) { create(:user) } + before { sign_in(user_not_related) } + + it "returns a 404" do + get :show, params: { user_id: user_not_related.id, cohort_id: cohort.id, id: challenge_1.id } + + expect(response.status).to eq 404 + end + end + + context "for a signed in student of the cohort" do + before { sign_in(student) } + + it "passes the challenge to the view with submitted challenge answers ordered by most recent" do + double = double(ChallengeWithSubmittedChallengeAnswersPresenter) + submitted_challenge_answers = [submitted_challenge_answer_3, submitted_challenge_answer_2, submitted_challenge_answer_1] + + expect(ChallengeWithSubmittedChallengeAnswersPresenter).to receive(:new).with( + cohort, challenge_1, submitted_challenge_answers, false, is_checkpoint: false, user_context: student + ).and_return(double) + + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + + expect(controller).to have_received(:render) do |options| + expect(options[:locals][:challenge]).to eq(double) + end + end + + context "when there are comments" do + let!(:activity_yesterday) { create(:activity, :comment, subject: submitted_challenge_answer_1, created_at: Date.yesterday) } + let!(:activity_today) { create(:activity, :comment, subject: submitted_challenge_answer_1) } + let!(:excluded_activity) { create(:activity, name: Activity::NAMES[:submitted_challenge_answer_created], subject: submitted_challenge_answer_1) } + + it "passes the comments to the view" do + get :show, params: { user_id: student.id, cohort_id: cohort.id, id: challenge_1.id } + + expect(controller).to have_received(:render) do |options| + expect(options[:locals][:activities].map(&:id)).to eq([activity_yesterday.id, activity_today.id]) + end + end + end + end + end +end diff --git a/scripts/spec/controllers/cohorts/users/performances_controller_spec.rb b/scripts/spec/controllers/cohorts/users/performances_controller_spec.rb new file mode 100644 index 0000000..ff1412c --- /dev/null +++ b/scripts/spec/controllers/cohorts/users/performances_controller_spec.rb @@ -0,0 +1,69 @@ +require "spec_helper" + +describe Cohorts::Users::PerformancesController do + context "#create" do + let!(:cohort) { create(:cohort) } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + + let(:standard) { create(:standard) } + let(:cohort_release) { create(:cohort_release, release: standard.release, cohort: cohort) } + + let!(:content_file) { create(:content_file, :checkpoint, standard: standard) } + let!(:challenge) { create(:challenge, content_file: content_file) } + let(:checkpoint_submission) { create_checkpoint_submission(student, challenge, cohort: cohort) } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student) } + + before { sign_in(instructor) } + + context "with permitted params" do + context "when no checkpoint_submission_id is provided" do + it "generates a performance without a checkpoint submission" do + expect do + post :create, params: { cohort_id: cohort.id, user_id: student.id, performance: { standard_ids: [standard.id], score: 3, checkpoint_submission_id: nil } } + end.to change { Performance.all.count }.by 1 + expect(Performance.first.checkpoint_submission_id).to eq(nil) + end + end + + context "when checkpoint_submission_id is provided" do + it "generates a performance with a checkpoint submission" do + expect do + post :create, params: { cohort_id: cohort.id, user_id: student.id, performance: { standard_ids: [standard.id], score: 2, checkpoint_submission_id: checkpoint_submission.id } } + end.to change { Performance.all.count }.by 1 + expect(Performance.first.checkpoint_submission_id).to eq(checkpoint_submission.id) + end + end + + it "generates a performance even if one already exists" do + post :create, params: { cohort_id: cohort.id, user_id: student.id, performance: { standard_ids: [standard.id], score: 3 } } + expect(Performance.count).to eq(1) + + post :create, params: { cohort_id: cohort.id, user_id: student.id, performance: { standard_ids: [standard.id], score: 2 } } + expect(Performance.count).to eq(2) + expect(Performance.first.standard_id).to eq(standard.id) + expect(Performance.first.score).to eq(3) + expect(Performance.last.standard_id).to eq(standard.id) + expect(Performance.last.score).to eq(2) + end + end + + context "when a student attempts to give themself a score" do + before { sign_in(student) } + + it "it creates no performances and yields a 403 when not authorized" do + post :create, params: { cohort_id: cohort.id, user_id: student.id, performance: { standard_ids: [standard.id], score: 3, checkpoint_submission_id: checkpoint_submission.id } }, format: :json + expect(Performance.count).to eq(0) + expect(response.status).to eq(403) + end + end + + context "when missing required params" do + it "returns a status 500" do + post :create, params: { cohort_id: cohort.id, user_id: student.id, standard_ids: [standard.id], score: 3, checkpoint_submission_id: checkpoint_submission.id }, format: :json + expect(response.status).to eq(500) + expect(JSON.parse(response.body)["errors"]).to eq([{ "message" => "500 param is missing or the value is empty: performance" }]) + end + end + end +end diff --git a/scripts/spec/controllers/cohorts/users_controller_spec.rb b/scripts/spec/controllers/cohorts/users_controller_spec.rb new file mode 100644 index 0000000..f35ebb6 --- /dev/null +++ b/scripts/spec/controllers/cohorts/users_controller_spec.rb @@ -0,0 +1,137 @@ +require "spec_helper" + +describe Cohorts::UsersController do + let(:cohort) { create(:cohort) } + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let(:block) { create(:block) } + let(:release) { create(:release, block: block) } + let!(:cohort_release) { create(:cohort_release, release: release, cohort: cohort) } + let(:standard) { create(:standard, release: release, position: 0) } + let(:content_file) { create(:content_file, standard: standard) } + let(:challenge_1) { create(:challenge, content_file: content_file) } + + let!(:submitted_challenge_answer_1) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1) } + let!(:submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1) } + let!(:submitted_challenge_answer_3) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1) } + + before { allow(controller).to receive(:render).and_call_original } + + describe "#submissions_dashboard" do + context "when a student views their own work" do + before { sign_in(student) } + + it "passes show_student_avatar to the view" do + get :submissions_dashboard, params: { cohort_id: cohort.id, id: student.id } + + expect(controller).to have_received(:render) do |options| + expect(options[:locals][:submission_data][:blocks].size).to eq(1) + expect(options[:locals][:cohort_id]).to eq(cohort.id) + expect(options[:locals][:students]).to eq([{ + id: student.id, + avatar: student.profile_image, + name: student.full_name, + initials: student.initials, + challenges_path: submissions_dashboard_cohort_user_path(cohort.id, student.id) + }]) + end + end + + context "when a student tries to view another student's work" do + let(:other_student) { create(:cohort_user, :student, cohort: cohort).user } + + it "redirects not authorized users" do + get :submissions_dashboard, params: { cohort_id: cohort.id, id: other_student.id } + + expect(response).to redirect_to(root_path) + end + end + end + + context "when an instructor views a student" do + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + before { sign_in(instructor) } + + it "passes show_student_avatar to the view" do + get :submissions_dashboard, params: { cohort_id: cohort.id, id: student.id } + + expect(controller).to have_received(:render) do |options| + expect(options[:locals][:submission_data][:blocks].size).to eq(1) + expect(options[:locals][:cohort_id]).to eq(cohort.id) + expect(options[:locals][:students]).to eq([{ + id: student.id, + avatar: student.profile_image, + name: student.full_name, + initials: student.initials, + challenges_path: submissions_dashboard_cohort_user_path(cohort.id, student.id) + }]) + end + end + end + end + + describe "#curriculum_progress" do + # student needs a checkpoint in two different standards across two different checkpoints + + let(:cohort_2) { create(:cohort, mode: 'Percentage') } + let!(:enrollment_2) { create(:cohort_user, :student, cohort: cohort_2, user: student) } + let!(:cohort_release_2) { create(:cohort_release, release: release, cohort: cohort_2) } + + let(:checkpoint_standard_1) { create(:standard, release: release, position: 1) } + let(:checkpoint_standard_2) { create(:standard, release: release, position: 2) } + + let!(:content_file_first_checkpoint) { create(:content_file, :checkpoint, standard: checkpoint_standard_1, autoscore: true) } + let!(:challenge_first_checkpoint) { create(:challenge, content_file: content_file_first_checkpoint, points: 2) } + let!(:first_checkpoint_submission) { + create_checkpoint_submission( + student, + challenge_first_checkpoint, + cohort: cohort, + content_file_block_id: release.block_id, + state: CheckpointSubmission::STATES[:done], + total_points: 2, + correct_points: 2, + ) + } + let!(:first_sca) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_first_checkpoint, checkpoint_submission_id: first_checkpoint_submission.id, points: 2) } + let!(:first_performance) { create(:performance, cohort: cohort, standard: checkpoint_standard_1, user: student, score: 1, checkpoint_submission_id: first_checkpoint_submission) } + + let!(:content_file_second_checkpoint) { create(:content_file, :checkpoint, standard: checkpoint_standard_2, autoscore: true) } + let!(:challenge_second_checkpoint) { create(:challenge, content_file: content_file_second_checkpoint, points: 2) } + let!(:second_checkpoint_submission) { + create_checkpoint_submission( + student, + challenge_second_checkpoint, + cohort: cohort_2, + content_file_block_id: release.block_id, + state: CheckpointSubmission::STATES[:done], + total_points: 2, + correct_points: 1, + ) + } + let!(:second_sca) { + create(:submitted_challenge_answer, cohort: cohort_2, user: student, challenge: challenge_second_checkpoint, checkpoint_submission_id: second_checkpoint_submission.id, points: 1) + } + let!(:second_performance) { create(:performance, cohort: cohort_2, standard: checkpoint_standard_2, user: student, score: 1, checkpoint_submission_id: second_checkpoint_submission) } + let(:results) { JSON.parse(response.body) } + + before { sign_in(student) } + context "when the current user has progress completion across two cohorts with the same curriculum" do + it "does not share content progress across cohorts for the average" do + get :curriculum_progress, params: { cohort_id: cohort_2.id, id: student.id } + + expect(results["checkpoint_average"]).to eq 50 + expect(results["user_has_checkpoints"]).to eq true + expect(results["cohort_uses_checkpoints"]).to eq true + expect(results["topic_averages"]).to eq nil + + progress = JSON.parse(results["progress"]) + + expect(progress["percent_completed"]).to eq 33 + expect(progress["completed_standards"].length).to eq 1 + expect(progress["completed_standards"].map{|s| s["id"]}).to_not include standard.id # from cohort 1 + expect(progress["completed_standards"][0]["id"]).to eq checkpoint_standard_2.id + expect(progress["completed_standards"][0]["uid"]).to eq checkpoint_standard_2.uid + end + end + end +end diff --git a/scripts/spec/controllers/cohorts_controller_spec.rb b/scripts/spec/controllers/cohorts_controller_spec.rb new file mode 100644 index 0000000..c7bdfcb --- /dev/null +++ b/scripts/spec/controllers/cohorts_controller_spec.rb @@ -0,0 +1,618 @@ +require "spec_helper" + +describe CohortsController do + let!(:cohort) { create(:cohort) } + let!(:release) { create(:release) } + let!(:standard) { create(:standard, release: release) } + let!(:student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + + context "when signed in as an admin" do + let(:admin) { create(:user, :admin) } + + before do + sign_in(admin) + end + + describe "GET #new" do + it "cohort should be new" do + expect(Cohort).to receive(:new).and_return(Cohort.new) + get :new + end + end + + describe "POST #create" do + + it "creates cohort" do + cohort_params = attributes_for(:cohort, product_type: "test", name: "Ruby", starts_on: Time.zone.now) + # cohort_params = { product_type: "test", name: "Ruby", starts_on: Time.zone.now } + expect { post :create, params: { cohort: cohort_params } }.to change(Cohort, :count).by(1) + end + + it 'redirects to the setup path' do + post :create, params: { cohort: attributes_for(:cohort, product_type: "class", name: "Intro to CSS") } + + cohort = Cohort.last + expect(response).to redirect_to(setup_cohort_path(cohort)) + end + + end + + describe "GET #edit" do + let(:current_cohort) { create(:cohort) } + + it "cohort can be edited" do + # get :edit, params: { cohort: { id: current_cohort.id } } + # expect(response).to have_http_status(:success) # 200 + end + end + + describe "PATCH #update" do + let!(:current_cohort) { create(:cohort) } + + before do + allow(Cohort).to receive(:find).and_return(current_cohort) + allow(current_cohort).to receive(:update).and_return(true) + end + + it "updates the cohort" do + patch :update_cohort, params: { + id: current_cohort.id, + cohort: { name: "New Name" } + } + expect(current_cohort).to have_received(:update) + end + + context "when the update succeeds" do + it "redirects to the cohort page" do + patch :update_cohort, params: { + id: current_cohort.id, + cohort: { name: "New Name" } + } + expect(response).to redirect_to(setup_cohort_path(current_cohort)) + end + end + + context "when the update fails" do + before do + allow(current_cohort).to receive(:update).and_return(false) + end + + it "renders the edit page again" do + patch :update_cohort, params: { + id: current_cohort.id, + cohort: { name: "New Name" } + } + expect(response).to render_template(:edit) + end + end + end + + describe "GET #index" do + it "returns locals with a list of all cohorts" do + # Commenting out since we don't use auth anymore + # allow(Rails.application.secrets).to receive(:auth_url).and_return("http://auth.galvanize.com") + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:all_other_cohorts]).to eq([cohort]) + # expect(args[:locals][:new_auth_product_url]).to eq("http://auth.galvanize.com/admin/products/new") + end + + get :index + end + + context "when the admin has enrollments or staffings in cohorts" do + let!(:enrolled_cohort) { create(:cohort_user, :student, user: admin).cohort } + let!(:staffed_cohort) { create(:cohort_user, :instructor, user: admin).cohort } + + it "assigns them separately from other cohorts" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:all_other_cohorts]).to eq([cohort]) + expect(args[:locals][:own_cohorts]).to match_array([enrolled_cohort, staffed_cohort]) + end + + get :index + end + end + + context "when there are multiple cohorts with users" do + let!(:cohort_1) { create(:cohort) } + let!(:cohort_2) { create(:cohort) } + + before do + create(:cohort_user, :instructor, cohort: cohort_1) + create(:cohort_user, :student, cohort: cohort_1) + + create(:cohort_user, :student, cohort: cohort_2) + end + + it "yields the counts of users for each cohort" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:cohort_id_counts]).to include(cohort_1.id => 2, cohort_2.id => 1) + end + + get :index + end + end + end + + describe "PATCH #update" do + context "with valid params" do + it "updates the cohorts mode" do + cohort = create(:cohort) + expect(cohort.reload.mode).to eq(Cohort::MODES[:mastery]) + + patch :update, params: { id: cohort.id, cohort: { mode: Cohort::MODES[:percentage] } } + + expect(cohort.reload.mode).to eq(Cohort::MODES[:percentage]) + end + end + + context "with invalid params" do + it "does not update the cohorts mode" do + cohort = create(:cohort) + expect(cohort.reload.mode).to eq(Cohort::MODES[:mastery]) + + patch :update, params: { id: cohort.id, cohort: { mode: "whatever" } } + + expect(cohort.reload.mode).to eq(Cohort::MODES[:mastery]) + end + end + end + + describe "GET #setup" do + let!(:unassociated_release) { create(:release) } + let!(:failed_job_result) { create(:resync_job_result, edges: { cohort_id: cohort.id }, status: 'failure' ) } + let!(:success_job_result) { create(:resync_job_result, edges: { cohort_id: cohort.id }, created_at: 1.day.ago) } + + context "when in a sandbox" do + let(:sandbox) { create(:cohort, sandbox: true) } + let(:instructor) { create(:cohort_user, :instructor, cohort: sandbox).user } + + it "should redirect to the cohort#show page" do + sign_in(instructor) + + expect( + get :setup, params: { id: sandbox.id } + ).to redirect_to(content_cohort_path(sandbox)) + end + end + + it "returns locals with the current cohort details" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:cohort]).to eq(cohort) + expect(args[:locals][:cohort_releases].first).to be_a(CohortReleasePresenter::ForCohortSetup) + expect(args[:locals][:cohort_releases].first.id).to eq(cohort_release.id) + expect(args[:locals][:unattached_block_release_options]).to eq([unassociated_release]) + expect(args[:locals][:resync_job]).to eq(failed_job_result) + expect(args[:locals][:successful_resync_job]).to eq(success_job_result) + end + + get :setup, params: { id: cohort.id } + end + end + + describe "GET #users" do + context "when in a sandbox" do + let(:sandbox) { create(:cohort, sandbox: true) } + let(:instructor) { create(:cohort_user, :instructor, cohort: sandbox).user } + + it "should redirect to the cohort#show page" do + sign_in(instructor) + + expect( + get :users, params: { id: sandbox.id } + ).to redirect_to(content_cohort_path(sandbox)) + end + end + end + + describe "GET #partnerup" do + context "when in a sandbox" do + let(:sandbox) { create(:cohort, sandbox: true) } + let(:instructor) { create(:cohort_user, :instructor, cohort: sandbox).user } + + it "should redirect to the cohort#show page" do + sign_in(instructor) + + expect( + get :partnerup, params: { id: sandbox.id } + ).to redirect_to(content_cohort_path(sandbox)) + end + end + context "when over 500 students, redirect" do + let(:cohort) { create(:cohort) } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + before do + 501.times do + create(:cohort_user, :student, cohort: cohort) + end + end + it "should redirect to the cohort#show page" do + sign_in(instructor) + + expect( + get :partnerup, params: { id: cohort.id } + ).to redirect_to(content_cohort_path(cohort)) + end + end + end + end + + context "as a student" do + before do + sign_in(student) + cohort_release.update(position: 1) + end + + describe "#show" do + let(:release_2) { create(:cohort_release, cohort: cohort, position: 0).release } + let!(:standard) { create(:standard, release: release) } + let!(:standard_2) { create(:standard, release: release_2) } + let!(:content_file) { create(:content_file, standard: standard) } + let!(:content_file_2) { create(:content_file, standard: standard_2) } + let(:action) { get :show, params: { id: cohort.id } } + + before { create(:content_file, standard: standard) } + + it "sets last_viewed_cohort_id on user" do + action + + expect(student.reload.last_viewed_cohort_id).to eq(cohort.id) + end + + it "does not show hidden standards" do + create(:content_visibility, cohort_id: cohort.id, content_type: "Standard", content_uid: standard_2.uid) + + allow(controller).to receive(:render) + + # TODO: Why is render being called twice? + expect(controller).to receive(:render) do |args| + expect(args[:locals][:cohort_releases].length).to eq(CohortRelease.where(cohort: cohort).length) + + standard_ids = args[:locals][:cohort_releases].flat_map(&:standard_presenters).map(&:id) + expect(standard_ids).to_not include(standard_2.id) + end + action + end + end + end + + describe "GET #activity_dashboard" do + context "when in a sandbox" do + let(:sandbox) { create(:cohort, sandbox: true) } + let(:instructor) { create(:cohort_user, :instructor, cohort: sandbox).user } + + it "should redirect to the cohort#show page for the found cohort" do + sign_in(instructor) + + expect( + get :activity_dashboard, params: { id: sandbox.id } + ).to redirect_to(cohort_path(sandbox)) + end + end + + context "as instructor with json response" do + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Virginia", last_name: "Wolf")).user } + before do + sign_in(instructor) + end + + it "uses the ActivityAggregatorService to build a response" do + expect(ActivityAggregatorService).to receive(:new).and_call_original + expect_any_instance_of(ActivityAggregatorService).to receive(:execute) + get :activity_dashboard, params: { id: cohort.id }, format: :json + end + end + end + + describe "GET #submissions" do + let(:standard) { create(:standard, release: release) } + let!(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, profile_image: "www.example.com", first_name: "Able")).user } + let!(:student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, profile_image: "www.other.com", last_name: "Bella")).user } + + let(:standard_1) { create(:standard, release: release, position: 1) } + let(:standard_2) { create(:standard, release: release, position: 2) } + let!(:standard_3) { create(:standard, release: release, position: 3) } + + let(:content_file_1) { create(:content_file, standard: standard_1) } + let!(:challenge_1) { create(:challenge, content_file: content_file_1, position: 1) } + let(:content_file_2) { create(:content_file, standard: standard_2) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2, position: 1) } + + context "when in a sandbox" do + let(:sandbox) { create(:cohort, sandbox: true) } + let(:instructor) { create(:cohort_user, :instructor, cohort: sandbox).user } + + it "should redirect to the cohort#show page" do + sign_in(instructor) + + expect( + get :unit_progress, params: { id: sandbox.id } + ).to redirect_to(cohort_path(sandbox)) + end + end + + context "for a signed in instructor of the cohort" do + before do + allow(controller).to receive(:render) + + instructor = create(:cohort_user, :instructor, cohort: cohort).user + sign_in(instructor) + end + + it "assigns the cohort" do + expect(controller).to receive(:render) do |args| + expect(args[:locals][:cohort_id]).to eq(cohort.id) + end + + get :unit_progress, params: { id: cohort.id } + end + + it "returns an array serialized students" do + expect(controller).to receive(:render) do |args| + expect(args[:locals][:students]).to eq( + [ + { + id: student.id, + avatar: student.profile_image, + name: student.full_name, + initials: student.initials, + cohort_reg_date: student.cohort_users.where(cohort_id: cohort.id).first&.created_at, + challenges_path: submissions_dashboard_cohort_user_path(cohort, student) + }, + { + id: student_1.id, + avatar: student_1.profile_image, + name: student_1.full_name, + initials: student_1.initials, + cohort_reg_date: student_1.cohort_users.where(cohort_id: cohort.id).first&.created_at, + challenges_path: submissions_dashboard_cohort_user_path(cohort, student_1) + }, + { + id: student_2.id, + avatar: student_2.profile_image, + name: student_2.full_name, + initials: student_2.initials, + cohort_reg_date: student_2.cohort_users.where(cohort_id: cohort.id).first&.created_at, + challenges_path: submissions_dashboard_cohort_user_path(cohort, student_2) + } + ] + ) + end + + get :unit_progress, params: { id: cohort.id } + end + + it "sets the blocks with standards" do + expect(controller).to receive(:render) do |args| + expect(args[:locals][:blocks]).to eq( + [{ id: release.block.id, + title: release.block.title, + standards: [{ id: standard_1.id, + title: standard_1.title, + student_performances: { student.id.to_sym => {}, student_2.id.to_sym => {}, student_3.id.to_sym => {} } }, + { id: standard_2.id, + title: standard_2.title, + loaded: false, + open: false, + content_files: [], + student_performances: { student.id.to_sym => {}, student_2.id.to_sym => {}, student_3.id.to_sym => {} } }, + { id: standard_3.id, + title: standard_3.title, + loaded: false, + open: false, + content_files: [], + student_performances: { student.id.to_sym => {}, student_2.id.to_sym => {}, student_3.id.to_sym => {} } }, + { id: standard_4.id, + title: standard_4.title, + loaded: false, + open: false, + content_files: [], + student_performances: { student.id.to_sym => {}, student_2.id.to_sym => {}, student_3.id.to_sym => {} } }] }] + ) + end + + get :unit_progress, params: { id: cohort.id } + end + + it "does not yield standards that are hidden" do + create(:content_visibility, cohort: cohort, content_type: "Standard", content_uid: standard_2.uid) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:blocks]).to eq( + [{ id: release.block.id, + title: release.block.title, + standards: [{ id: standard_1.id, + title: standard_1.title, + student_performances: { student.id.to_sym => {}, student_2.id.to_sym => {}, student_3.id.to_sym => {} } }, + { id: standard_3.id, + title: standard_3.title, + loaded: false, + open: false, + content_files: [], + student_performances: { student.id.to_sym => {}, student_2.id.to_sym => {}, student_3.id.to_sym => {} } }, + { id: standard_4.id, + title: standard_4.title, + loaded: false, + open: false, + content_files: [], + student_performances: { student.id.to_sym => {}, student_2.id.to_sym => {}, student_3.id.to_sym => {} } }] }] + ) + end + + get :unit_progress, params: { id: cohort.id } + end + + it "sets student performances and state on checkpoint submissions for standards" do + create(:content_file, :checkpoint, uid: "abc", standard: standard) + checkpoint_submission_1 = create(:checkpoint_submission, content_file_uid: "abc", content_file_block_id: release.block.id, user_id: student.id) + create(:performance, user: student, score: 3, checkpoint_submission: checkpoint_submission_1, standard_uid: standard.uid, standard: standard) + + checkpoint_content_file_2 = create(:content_file, :checkpoint, uid: "abc", standard: standard_3) + checkpoint_submission_2 = create(:checkpoint_submission, content_file_uid: "123", content_file_block_id: release.block.id, user_id: student_1.id, created_at: 1.day.ago) + create(:performance, user: student_1, score: 3, checkpoint_submission: checkpoint_submission_2, standard_uid: standard_3.uid, standard: standard_3) + + ungraded_challenge = create(:challenge, content_file: checkpoint_content_file_2) + checkpoint_submission_ungraded = create(:checkpoint_submission, content_file_uid: "123", content_file_block_id: release.block.id, user_id: student_1.id) + create(:submitted_challenge_answer, challenge: ungraded_challenge, checkpoint_submission: checkpoint_submission_ungraded, user: student_1) + + expect(controller).to receive(:render) do |args| + # expect(args[:locals][:blocks][0][:standards][0][:student_performances][student.id]).to eq( + # score: 3, checkpoint_submission_id: checkpoint_submission_1.id, checkpoint_created_at: checkpoint_submission_1.created_at, checkpoint_state: checkpoint_submission_1.state + # ) + standard_student_performances = args[:locals][:blocks][0][:standards][0][:student_performances][student.id] + expect(standard_student_performances[:score]).to eq(3) + expect(standard_student_performances[:checkpoint_submission_id]).to eq(checkpoint_submission_1.id) + expect(standard_student_performances[:checkpoint_created_at].to_s[0..10]).to eq(checkpoint_submission_1.created_at.to_s[0..10]) + expect(standard_student_performances[:checkpoint_state]).to eq(checkpoint_submission_1.state) + + expect(args[:locals][:blocks][0][:standards][1][:student_performances][student.id]).to eq(student_id: student.id) + + standard_3_student_1_performances = args[:locals][:blocks][0][:standards][3][:student_performances][student_1.id] + expect(standard_3_student_1_performances[:score]).to eq(3) + expect(standard_3_student_1_performances[:checkpoint_submission_id]).to eq(checkpoint_submission_2.id) + expect(standard_3_student_1_performances[:checkpoint_created_at].to_s[1..10]).to eq(checkpoint_submission_2.created_at.to_s[1..10]) + expect(standard_3_student_1_performances[:checkpoint_state]).to eq(checkpoint_submission_2.state) + expect(standard_3_student_1_performances[:ungraded_submission_id]).to eq(checkpoint_submission_ungraded.id) + expect(standard_3_student_1_performances[:ungraded_submission_state]).to eq(checkpoint_submission_ungraded.state) + end + get :unit_progress, params: { id: cohort.id } + end + end + end + + describe "GET #feed" do + let!(:cohort) { create(:cohort) } + + before { sign_in(create(:user, :admin)) } + + it "returns locals with the current_cohort" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:current_cohort]).to eq(cohort) + end + + get :feed, params: { id: cohort.id } + end + + context "when there are existing Activities for the cohort" do + before { 100.times { create(:activity, :comment, cohort: cohort) } } + + it "returns the most recent 50 activities" do + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:activities].map { |activity| activity[:id] }).to eq(Activity.where(cohort: cohort).order(created_at: :desc).first(50).pluck(:id)) + end + + get :feed, params: { id: cohort.id } + end + end + end + + describe "GET #course_stats" do + let!(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, profile_image: "avatar_1")).user } + let!(:student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, profile_image: "avatar_2")).user } + let!(:student_3) { create(:cohort_user, :student, cohort: cohort, user: create(:user, profile_image: "avatar_3")).user } + + context "when in a sandbox" do + let(:sandbox) { create(:cohort, sandbox: true) } + let(:instructor) { create(:cohort_user, :instructor, cohort: sandbox).user } + + it "should redirect to the cohort#show page" do + sign_in(instructor) + + expect( + get :course_stats, params: { id: sandbox.id } + ).to redirect_to(cohort_path(sandbox)) + end + end + + context "instructor of cohort" do + it "returns a collection of presenters for each student" do + sign_in(create(:cohort_user, :instructor, cohort: cohort).user) + + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:students].map(&:full_name)).to match_array( + [student_1.full_name, student_2.full_name, student_3.full_name, student.full_name] + ) + expect(args[:locals][:students].map(&:avatar)).to match_array( + [student_1.profile_image, student_2.profile_image, student_3.profile_image, student.profile_image] + ) + expect(args[:locals][:students].map(&:performances_url)).to match_array( + [ + submissions_dashboard_cohort_user_path(cohort, student_1), + submissions_dashboard_cohort_user_path(cohort, student_2), + submissions_dashboard_cohort_user_path(cohort, student_3), + submissions_dashboard_cohort_user_path(cohort, student) + ] + ) + end + + get :course_stats, params: { id: cohort.id } + end + end + context "admin" do + it "returns a collection of presenters for each student" do + sign_in(create(:user, :admin)) + + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:students].map(&:full_name)).to match_array( + [student_1.full_name, student_2.full_name, student_3.full_name, student.full_name] + ) + expect(args[:locals][:students].map(&:avatar)).to match_array( + [student_1.profile_image, student_2.profile_image, student_3.profile_image, student.profile_image] + ) + expect(args[:locals][:students].map(&:performances_url)).to match_array( + [ + submissions_dashboard_cohort_user_path(cohort, student_1), + submissions_dashboard_cohort_user_path(cohort, student_2), + submissions_dashboard_cohort_user_path(cohort, student_3), + submissions_dashboard_cohort_user_path(cohort, student) + ] + ) + end + + get :course_stats, params: { id: cohort.id } + end + end + + context "instructor of other cohort" do + it "raises pundit error" do + sign_in(create(:cohort_user, :instructor).user) + get :course_stats, params: { id: cohort.id } + expect(response).to redirect_to(root_url) + end + + context "over 200 students in the course" do + before do + 197.times do + create(:cohort_user, :student, cohort: cohort, user: create(:user)) + end + end + it "should yield no students" do + sign_in(create(:cohort_user, :instructor, cohort: cohort).user) + allow(controller).to receive(:render) + expect(controller).to receive(:render) do |args| + expect(args[:locals][:students]).to eq nil + end + + get :course_stats, params: { id: cohort.id } + end + end + end + + context "student" do + it "raises pundit error" do + sign_in(create(:cohort_user, :student, cohort: cohort).user) + get :course_stats, params: { id: cohort.id } + expect(response).to redirect_to(root_url) + end + end + end +end diff --git a/scripts/spec/controllers/home_controller_spec.rb b/scripts/spec/controllers/home_controller_spec.rb new file mode 100644 index 0000000..4e95273 --- /dev/null +++ b/scripts/spec/controllers/home_controller_spec.rb @@ -0,0 +1,116 @@ +require "spec_helper" + +describe HomeController do + describe "GET #index" do + context "when signed in as a user without access to cohorts" do + render_views + + before do + sign_in(create(:user)) + end + + it "renders the no Cohorts page" do + get :index + + expect(response.body).to include("You do not have access to any Cohorts yet.") + end + end + + context "when signed in as a user with access to a cohort" do + let(:cohort_user) { create(:cohort_user, :student) } + + before do + create(:cohort_user, :student, user: cohort_user.user) + sign_in(cohort_user.user) + end + + context "when signed in with a requested path" do + it "redirects the user to the requested path" do + get :index, session: { requested_path: submissions_dashboard_cohort_user_path(cohort_user.cohort, cohort_user.user) } + + expect(response).to redirect_to(submissions_dashboard_cohort_user_path(cohort_user.cohort, cohort_user.user)) + expect(session[:requested_path]).to eq(nil) + end + end + + it "redirects to the last created cohort" do + last_created_cohort = create(:cohort_user, :student, user: cohort_user.user).cohort + get :index + + expect(response).to redirect_to(cohort_path(last_created_cohort)) + end + end + + context "when signed in as an admin without any cohort relationships" do + let!(:user) { create(:user, :admin) } + + before { sign_in(user) } + + it "redirects to the cohorts list" do + get :index + + expect(response).to redirect_to(cohorts_path) + end + end + + context "when the user has a last viewed cohort id" do + let(:user) { create(:user) } + let(:cohort) { create(:cohort) } + + before do + user.update(last_viewed_cohort_id: cohort.id) + sign_in(user) + end + + context "when the user is a student for the given cohort" do + before { create(:cohort_user, :student, user: user, cohort: cohort) } + + it "redirects to the last viewed cohort path" do + get :index + + expect(response).to redirect_to(cohort_path(cohort)) + end + end + + context "when the user is an instructor for the given cohort" do + before { create(:cohort_user, :instructor, user: user, cohort: cohort) } + + it "redirects to the last viewed cohort path" do + get :index + + expect(response).to redirect_to(cohort_path(cohort)) + end + end + + context "when the user is an admin" do + before { user.update(roles: [User::ROLES[:admin]]) } + + it "redirects to the last viewed cohort path" do + get :index + + expect(response).to redirect_to(cohort_path(cohort)) + end + end + + context "when the user doesn't have access to the cohort" do + context "but has access to a different cohort" do + let(:other_cohort) { create(:cohort) } + + before { create(:cohort_user, :student, user: user, cohort: other_cohort) } + + it "redirects to the other cohort path" do + get :index + + expect(response).to redirect_to(cohort_path(other_cohort)) + end + end + + context "and doesn't have access to any other cohort" do + it "renders the no Cohorts page" do + expect(get(:index)).to render_template(:index) + end + end + end + end + end +end diff --git a/scripts/spec/controllers/notifications_controller_spec.rb b/scripts/spec/controllers/notifications_controller_spec.rb new file mode 100644 index 0000000..232fc12 --- /dev/null +++ b/scripts/spec/controllers/notifications_controller_spec.rb @@ -0,0 +1,67 @@ +require "spec_helper" + +describe NotificationsController do + let!(:user) { create(:user) } + before { sign_in(user) } + + describe "GET #index" do + context "when there are notifications for multiple users" do + let!(:included_notification) { create(:notification, user: user) } + let!(:excluded_notification) { create(:notification) } + + it "returns locals with a list of all notifications for the user" do + get :index + + response_json = JSON.parse(response.body) + expect(response_json["notifications"].map { |n| n["id"] }).to eq([included_notification.id]) + end + end + end + + describe "GET #show" do + context "when the notification has not been read" do + let!(:unread_notification) { create(:notification, user: user) } + + it "marks it the notification as read and redirects to the notification URL" do + get :show, params: { id: unread_notification.id } + + expect(response).to redirect_to(unread_notification.url) + expect(unread_notification.reload.read_at).to_not be_nil + end + end + + context "when the notification has been read" do + let!(:read_notification) { create(:notification, :read, user: user) } + + it "does not update :read_at and redirects to the notification URL" do + expect do + get :show, params: { id: read_notification.id } + end.to_not(change { read_notification.reload.read_at }) + end + end + end + + describe "POST #mark_all_read" do + context "when there is an existing read notification" do + let!(:read_notification) { create(:notification, :read, user: user) } + + it "does not update read_at" do + expect do + post :mark_all_read + end.to_not(change { read_notification.reload.read_at }) + end + end + + context "when there are unread notifications for the user" do + let!(:unread_notification_1) { create(:notification, user: user) } + let!(:unread_notification_2) { create(:notification, user: user) } + + it "marks the notifications read" do + post :mark_all_read + + expect(unread_notification_1.reload.read_at).to_not eq(nil) + expect(unread_notification_2.reload.read_at).to_not eq(nil) + end + end + end +end diff --git a/scripts/spec/controllers/permalinks_controller_spec.rb b/scripts/spec/controllers/permalinks_controller_spec.rb new file mode 100644 index 0000000..056d3b7 --- /dev/null +++ b/scripts/spec/controllers/permalinks_controller_spec.rb @@ -0,0 +1,179 @@ +require "spec_helper" + +describe PermalinksController do + let(:cohort) { create(:cohort) } + let(:user) { create(:cohort_user, cohort: cohort).user } + let!(:block) { create(:block, repo_name: "this.repo-name") } + let!(:release) { create(:release, block: block) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let!(:standard) { create(:standard, release: release) } + let!(:content_file) { create(:content_file, standard: standard, path: "this_path") } + + describe "GET #permalink" do + + context "signed in user" do + before { sign_in(user) } + + context "with a single cohort" do + context "when the referrer cohort exists without the content" do + let!(:unattached_cohort) { create(:cohort) } + # we need a full set of content to test past the simple case where the block is unattached + let!(:unattached_cohort_user) { create(:cohort_user, cohort: unattached_cohort, user: user) } + let!(:release_without_path) { create(:release, block_id: release.block_id) } + let!(:cohort_release) { create(:cohort_release, cohort: unattached_cohort, release: release_without_path) } + let!(:standard_without_path) { create(:standard, release: release_without_path) } + let!(:content_file_without_path) { create(:content_file, standard: standard_without_path, path: "not_this_path") } + it "finds the cohort it exists in and renders the redirect page" do + allow(controller).to receive(:render) + request.headers.merge( {referer: "#{root_url}/cohorts/#{unattached_cohort.id}/curriculum" } ) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:all_content_file_paths].length).to eq 1 + expect(args[:locals][:all_content_file_paths].first).to eq({name: cohort.name, path: content_file_path(cohort, block.id, "this_path")}) + end + get(:permalink, params: { repo_name: release.block.repo_name, path: content_file.path }) + end + end + + context "when the cohort has the content" do + context "when referer is from outside the app" do + it "find the cohort it exists in and redirect you" do + request.headers.merge( {referer: "http://example.com"} ) + + expect( + get(:permalink, params: { repo_name: release.block.repo_name, path: content_file.path }) + ).to redirect_to content_file_path(cohort, release.block_id, content_file.path) + end + end + + context "when referer is from inside the app and has no cohort_id" do + it "find the cohort it exists in and redirect you" do + request.headers.merge( {referer: "#{root_url}/blocks" } ) + expect( + get(:permalink, params: { repo_name: release.block.repo_name, path: content_file.path }) + ).to redirect_to content_file_path(cohort, release.block_id, content_file.path) + end + end + end + + context "when the content does not exist in the users cohort" do + context "when referer is from outside the app" do + it "should 404" do + request.headers.merge( {referer: "http://example.com"} ) + + expect( + get(:permalink, params: { repo_name: "block", path: "new_path" }) + ).to render_template("error_404") + end + end + + context "when referer is from inside the app and has no cohort_id" do + it "should 404 when no cohort contains matching content" do + request.headers.merge( {referer: "#{root_url}/blocks" } ) + expect( + get(:permalink, params: { repo_name: "block", path: "new_path" }) + ).to render_template("error_404") + end + end + end + + context "when referer is from inside the app and has a cohort_id" do + describe "with a valid repo name" do + it "should redirect the user to that content" do + request.headers.merge( {referer: "#{root_url}/cohorts/#{cohort.id}/curriculum" } ) + get :permalink, params: { repo_name: release.block.repo_name, path: content_file.path } + expect( + response + ).to redirect_to content_file_path(cohort, release.block_id, content_file.path) + end + end + + describe "with an invalid repo name" do + it "should 404 when the block cannot be found" do + request.headers.merge( {referer: "#{root_url}/cohorts/#{cohort.id}/curriculum" } ) + expect( + get(:permalink, params: { repo_name: "block", path: "new_path" }) + ).to render_template("error_404") + end + end + end + end + + context "with multiple cohorts" do + let(:another_cohort) { create(:cohort, name: "zanzabar") } + let!(:another_release) { create(:release, block_id: release.block_id)} + let!(:another_standard) { create(:standard, release: another_release) } + let!(:another_content_file) { create(:content_file, standard: another_standard, path: "this_path") } + + before do + create(:cohort_release, cohort: another_cohort, release: another_release) + create(:cohort_user, cohort: another_cohort, user: user) + allow(controller).to receive(:render) + end + + context "when the referrer cohort exists without the content" do + let(:unattached_cohort) { create(:cohort) } + it "renders locals with the available cohort options" do + request.headers.merge( {referer: "#{root_url}/cohorts/#{unattached_cohort.id}/curriculum" } ) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:all_content_file_paths].length).to eq 2 + expect(args[:locals][:all_content_file_paths].first).to eq({name: cohort.name, path: content_file_path(cohort, block.id, "this_path")}) + expect(args[:locals][:all_content_file_paths].last).to eq({name: another_cohort.name, path: content_file_path(another_cohort, block.id, "this_path")}) + end + + get(:permalink, params: { repo_name: another_release.block.repo_name, path: another_content_file.path }) + end + end + + context "when the referrer cohort exists with the content" do + let(:unattached_cohort) { create(:cohort) } + it "finds the cohort it exists in and renders the redirect page" do + request.headers.merge( {referer: "#{root_url}/cohorts/#{another_cohort.id}/curriculum" } ) + + expect( + get(:permalink, params: { repo_name: another_release.block.repo_name, path: another_content_file.path }) + ).to redirect_to content_file_path(another_cohort, release.block_id, content_file.path) + end + end + + context "when a cohort has the content" do + context "when referer is from outside the app" do + it "renders locals with the available cohort options" do + request.headers.merge( {referer: "http://example.com"} ) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:all_content_file_paths].length).to eq 2 + expect(args[:locals][:all_content_file_paths].first).to eq({name: cohort.name, path: content_file_path(cohort, block.id, "this_path")}) + expect(args[:locals][:all_content_file_paths].last).to eq({name: another_cohort.name, path: content_file_path(another_cohort, block.id, "this_path")}) + end + + get(:permalink, params: { repo_name: another_release.block.repo_name, path: another_content_file.path }) + end + end + + context "when referer is from inside the app and has no cohort_id" do + it "renders locals with the available cohort options" do + request.headers.merge( {referer: "#{root_url}/blocks" } ) + + expect(controller).to receive(:render) do |args| + expect(args[:locals][:all_content_file_paths].length).to eq 2 + expect(args[:locals][:all_content_file_paths].first).to eq({name: cohort.name, path: content_file_path(cohort, block.id, "this_path")}) + expect(args[:locals][:all_content_file_paths].last).to eq({name: another_cohort.name, path: content_file_path(another_cohort, block.id, "this_path")}) + end + + get(:permalink, params: { repo_name: another_release.block.repo_name, path: another_content_file.path }) + end + end + end + end + end + + context "user is not a learn user" do + it "should 404" do + get :permalink, params: { repo_name: release.block.repo_name, path: content_file.path } + expect(response.code).to eq "302" + end + end + end +end diff --git a/scripts/spec/controllers/sessions_controller_spec.rb b/scripts/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 0000000..4dab6eb --- /dev/null +++ b/scripts/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,70 @@ +require "spec_helper" + +describe SessionsController do + describe "GET #new" do + + context "when signing in as a user already authenticated" do + + before do + existing_user = create(:user) + sign_in(existing_user) + session[:user_uid] = existing_user.uid + end + + it "redirects to root" do + get :new + expect(response).to redirect_to '/' + end + + it "logs out a user" do + get :destroy + expect(session[:user_uid]).to be_nil + expect(response).to redirect_to '/' + end + + end + + context "when signing a new user already authenticated" do + + before do + payload = { + exp: Time.now.to_i + 18000000, + iat: Time.now.to_i, + auth_time: Time.now.to_i, + jti: "e4600f88-1b42-4e01-ac01-8646d3cc4df6", + iss: "https://login.dsop.io/auth/realms/baby-yoda", + aud: "il4_191f836b-ec50-4819-ba10-1afaa5b99600_mission-widow", + sub: "2cba6ea8-8451-4952-80d6-c7de379d5b7e", + typ: "ID", + given_name: "Jane", + family_name: "Doe", + email: "janedoe@test.com" + } + + token = JWT.encode(payload, nil, 'none') + request.headers["Authorization"] = "Bearer #{token}" + end + + it "redirects to root" do + get :new + expect(session[:user_uid]).to eq("2cba6ea8-8451-4952-80d6-c7de379d5b7e") + expect(response).to redirect_to '/' + end + + it "logs out a user" do + get :destroy + expect(session[:user_uid]).to be_nil + expect(response).to redirect_to '/' + end + + it "works in production" do + Rails.env = 'production' + get :new + expect(session[:user_uid]).to eq("2cba6ea8-8451-4952-80d6-c7de379d5b7e") + expect(response).to redirect_to '/' + end + + end + + end +end diff --git a/scripts/spec/controllers/users_controller_spec.rb b/scripts/spec/controllers/users_controller_spec.rb new file mode 100644 index 0000000..42adb57 --- /dev/null +++ b/scripts/spec/controllers/users_controller_spec.rb @@ -0,0 +1,187 @@ +require "spec_helper" + +describe UsersController do + + let(:student_to_enroll) { create(:user) } + let(:cohort) { create(:cohort) } + let(:enrolled_student) { create(:user, first_name: "Eric", last_name: "Ericson", email: "eric@example.com") } + let(:cohort_user) { create(:cohort_user, created_at: 3.days.ago, cohort: cohort, user: enrolled_student) } + # let(:user) { create(:user, :admin, api_token: "token") } + + describe "GET #index" do + context "when signed in as an admin" do + # let(:new_student) { create(:user, first_name: "Inigo", last_name: "Montoya", email: "inigo@example.com") } + let(:admin) { create(:user, :admin) } + + before do + sign_in(admin) + end + + it "populates an array of users" do + get :index + + expect(assigns(:users)).to eq([enrolled_student, admin]) + + expect(response).to be_successful + expect(response).to render_template(:index) + end + end + end + + describe "GET #edit_user" do + context "edits a user" do + let(:admin) { create(:user, :admin) } + + before do + sign_in(admin) + end + + it "edits user" do + get :edit_user, params: { id: admin.id } + + expect(response).to be_successful + expect(response).to render_template(:edit_user) + end + end + end + + describe "PATCH #update_user" do + let(:admin) { create(:user, :admin) } + + before do + sign_in(admin) + end + + context "when the update succeeds" do + it "updates the user and redirects to user path" do + allow(User).to receive(:find_by).and_return(enrolled_student) + allow(enrolled_student).to receive(:update).and_return(true) + + patch :update_user, params: { + id: enrolled_student.id + } + + expect(enrolled_student).to have_received(:update) + expect(response).to redirect_to(users_path) + end + end + + context "when the update fails" do + before do + allow(User).to receive(:find_by).and_return(enrolled_student) + allow(enrolled_student).to receive(:update).and_return(false) + end + + it "renders the edit page again" do + patch :update_user, params: { + id: enrolled_student.id + } + expect(enrolled_student).to have_received(:update) + expect(response).to render_template(:edit) + end + end + end + + describe "GET #new" do + context "when signed in as an admin" do + let(:admin) { create(:user, :admin) } + + before do + sign_in(admin) + end + + it "creates new users for a cohort from a list of users" do + get :new, params: { cohort_id: cohort.id } + + expect(response).to render_template(:new) + end + end + end + + describe "GET #edit" do + context "edits a user for a given cohort" do + let(:admin) { create(:user, :admin) } + + before do + sign_in(admin) + end + + it "edits users for a cohort" do + get :edit, params: { cohort_id: cohort.id, id: admin.id } + + expect(response).to be_successful + expect(response).to render_template(:edit) + end + end + end + + describe "PATCH #update" do + let(:cohort_user) { create(:cohort_user, cohort: cohort, user: enrolled_student) } + + before do + sign_in(create(:user, :admin, :product_admin)) + allow(CohortUser).to receive(:find_by).and_return(cohort_user) + allow(cohort_user).to receive(:update).and_return(true) + end + + it "updates the cohort user" do + patch :update, params: { + cohort_id: cohort.id, + id: enrolled_student.id + } + expect(cohort_user).to have_received(:update) + end + + context "when the update succeeds" do + it "redirects to the user cohorts page" do + patch :update, params: { + cohort_id: cohort.id, + id: enrolled_student.id + } + expect(response).to redirect_to(users_cohort_path(cohort)) + end + end + + context "when the update fails" do + before do + allow(cohort_user).to receive(:update).and_return(false) + end + + it "renders the edit page again" do + patch :update, params: { + cohort_id: cohort.id, + id: enrolled_student.id + } + expect(response).to render_template(:edit) + end + end + end + + describe "DELETE #destroy" do + let(:cohort_user) { create(:cohort_user, cohort: cohort, user: enrolled_student) } + + before do + sign_in(create(:user, :admin, :product_admin)) + allow(CohortUser).to receive(:find_by).and_return(cohort_user) + allow(cohort_user).to receive(:destroy).and_return(true) + end + + it "deletes the cohort user" do + delete :destroy, params: { + cohort_id: cohort.id, + id: enrolled_student.id + } + expect(cohort_user).to have_received(:destroy) + end + + context "when the delete succeeds" do + it "redirects to the user cohorts page" do + delete :destroy, params: { + cohort_id: cohort.id, + id: enrolled_student.id + } + expect(response).to redirect_to(users_cohort_path(cohort)) + end + end + end +end diff --git a/scripts/spec/controllers/webhooks/assessments_service/submitted_challenge_answers_controller_spec.rb b/scripts/spec/controllers/webhooks/assessments_service/submitted_challenge_answers_controller_spec.rb new file mode 100644 index 0000000..78aa7e7 --- /dev/null +++ b/scripts/spec/controllers/webhooks/assessments_service/submitted_challenge_answers_controller_spec.rb @@ -0,0 +1,151 @@ +require "spec_helper" + +describe Webhooks::AssessmentsService::SubmittedChallengeAnswersController do + let(:challenge) { create(:challenge, content_file: create(:content_file, :checkpoint), challenge_type: Challenge::TYPES[:code_snippet], points: 2) } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: SubmittedChallengeAnswer::STATUSES[:processing], challenge: challenge) } + + describe "#update" do + context "when token is invalid" do + it "should return a 401 unauthorized code" do + patch :update, params: { id: submitted_challenge_answer.id } + expect(response.status).to eq(401) + end + end + + context "when token is provided" do + let(:cohort) { create(:cohort) } + + it "creates an Activity" do + expect do + patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: "✓ 2 passing, 1 failing (112ms)\n\n1", status: "correct", cohort_id: cohort.id } + end.to change { Activity.count }.from(0).to(1) + + new_activity = Activity.last + expect(new_activity.name).to eq(Activity::NAMES[:submitted_challenge_answer_evaluated]) + expect(new_activity.subject).to eq(submitted_challenge_answer) + expect(new_activity.creator).to eq(nil) + expect(new_activity.cohort).to eq(cohort) + end + + context "when status_only param is specified" do + it "only updates the status of the submitted challenge answer" do + expect do + patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, status_only: true, status: "building", cohort_id: cohort.id } + end.to_not change { Activity.count } + expect(submitted_challenge_answer.reload.status).to eq("building") + end + end + + context "when the unit tests failed" do + let(:test_results) { "✓ 2 passing, 1 failing (112ms)" } + + it "updates submitted challenge answer status to incorrect" do + run_at = DateTime.current - 4.seconds + response = patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: test_results, status: "incorrect", assessment_ran_at: run_at} + expect(submitted_challenge_answer.reload.status).to eq("incorrect") + expect(submitted_challenge_answer.reload.test_results).to eq(test_results) + expect(submitted_challenge_answer.reload.assessment_ran_at.to_i).to eq(run_at.to_i) + expect(response.status).to eq(200) + end + end + + context "when js failed" do + let(:test_results) { "SuperbadError: Undefined constant 'passing';(112ms)" } + + it "updates submitted challenge answer status to incorrect and gives 0 points" do + response = patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: test_results, status: "incorrect" } + submitted_challenge_answer.reload + expect(submitted_challenge_answer.status).to eq("incorrect") + expect(submitted_challenge_answer.points).to eq(0) + expect(submitted_challenge_answer.test_results).to eq(test_results) + end + end + + context "when the unit tests passed" do + let(:test_results) { "✓ 3 passing (112ms)" } + + it "updates submitted challenge answer status to correct and gives challenge points" do + response = patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: test_results, status: "correct" } + submitted_challenge_answer.reload + expect(submitted_challenge_answer.status).to eq("correct") + expect(submitted_challenge_answer.points).to eq(challenge.points) + expect(submitted_challenge_answer.test_results).to eq(test_results) + end + end + + context "When submitted challenge answer was canceled" do + let(:test_results) { "✓ 3 passing (112ms)" } + + before do + submitted_challenge_answer.update(status: SubmittedChallengeAnswer::STATUSES[:canceled], test_results: "Tests canceled by student.") + end + + it "does not update the submitted challenge answer" do + response = patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: test_results, status: "correct" } + expect(submitted_challenge_answer.reload.status).to eq("canceled") + expect(submitted_challenge_answer.reload.test_results).to eq("Tests canceled by student.") + expect(response.status).to eq(200) + end + end + + context "when project challenge was missing files" do + let(:test_results) { "Build failed, Dockerfile & test.sh not found at upstream: https://github.com/gSchool/js-native-arrays" } + + it "updates submitted challenge answer status to missing_file" do + response = patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: test_results, status: "missing_file" } + expect(submitted_challenge_answer.reload.status).to eq("missing_file") + expect(submitted_challenge_answer.reload.test_results).to eq(test_results) + expect(response.status).to eq(200) + end + end + + context "when challenge isn't a code snippet or project type" do + let(:test_results) { "✓ 3 passing (112ms)" } + before do + submitted_challenge_answer.update(status: SubmittedChallengeAnswer::STATUSES[:ungraded], challenge: create(:challenge, challenge_type: Challenge::TYPES[:multiple_choice])) + end + + it "does not update the submitted challenge answer" do + response = patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: test_results, status: "correct" } + + expect(submitted_challenge_answer.reload.status).to eq("ungraded") + expect(submitted_challenge_answer.reload.test_results).to eq("") + expect(response.status).to eq(200) + end + + it "notifies honeybadger" do + expect(Honeybadger).to receive(:notify) + patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: test_results, status: "correct" } + end + end + + context "when submitted challenge answer can't be found" do + let(:test_results) { "✓ 3 passing (112ms)" } + + it "returns a 404" do + submitted_challenge_answer.destroy + patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: test_results, status: "correct" } + expect(response.status).to eq(404) + end + end + + context "when submitted challenge answer is part of a checkpoint submission" do + it "attempts autoscoring the checkpoint submission" do + checkpoint_submission = create_checkpoint_submission(submitted_challenge_answer.user, submitted_challenge_answer.challenge, cohort: cohort) + submitted_challenge_answer.update(checkpoint_submission_id: checkpoint_submission.id) + + patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: "you done good", status: "correct" } + end + + it "does not create an Activity" do + checkpoint_submission = create_checkpoint_submission(submitted_challenge_answer.user, submitted_challenge_answer.challenge, cohort: cohort) + submitted_challenge_answer.update(checkpoint_submission_id: checkpoint_submission.id) + + expect do + patch :update, params: { id: submitted_challenge_answer.id, token: Rails.application.secrets.assessments_callback_token, results: "you done good", status: "correct", cohort_id: cohort.id } + end.to_not(change { Activity.count }) + end + end + end + end +end diff --git a/scripts/spec/exporters/performance_exporter_spec.rb b/scripts/spec/exporters/performance_exporter_spec.rb new file mode 100644 index 0000000..f113705 --- /dev/null +++ b/scripts/spec/exporters/performance_exporter_spec.rb @@ -0,0 +1,44 @@ +require "spec_helper" + +describe PerformanceExporter do + describe "#to_csv" do + it "returns csv mastery data for cohort" do + cohort = create(:cohort) + student = create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user + diff_student = create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Zabe", last_name: "Zabbot")).user + + block_1 = create(:block) + block_2 = create(:block) + + release_1 = create(:release, block: block_1) + release_2 = create(:release, block: block_2) + + standard_1 = create(:standard, release: release_1, description: "standard 1", uid: "1234") + standard_2 = create(:standard, release: release_2, description: "standard 2", uid: "5678") + + cohort_release_1 = create(:cohort_release, cohort: cohort, release: release_1) + cohort_release_2 = create(:cohort_release, cohort: cohort, release: release_2) + + create(:performance, cohort: cohort, standard: standard_1, user: student, score: 1) + create(:performance, cohort: cohort, standard: standard_2, user: student, score: 2) + create(:performance, cohort: cohort, standard: standard_1, user: diff_student, score: 3) + + release_3 = create(:release, block: block_1) + release_4 = create(:release, block: block_2) + + standard_3 = create(:standard, release: release_3, description: "standard 3", uid: "1234") + standard_4 = create(:standard, release: release_4, description: "standard 4", uid: "5678") + + cohort_release_1.update(release: release_3) + cohort_release_2.update(release: release_4) + + data = described_class.new(cohort).to_csv + + expect(data).to eq([ + "Cohort Title,Cohort Label,Block Title,Standard,#{student.full_name},#{diff_student.full_name}", + "#{cohort.name},#{cohort.label},#{standard_3.release.block.title},#{standard_3.description},1,3", + "#{cohort.name},#{cohort.label},#{standard_4.release.block.title},#{standard_4.description},2,0" + ].join("\n") + "\n") + end + end +end diff --git a/scripts/spec/factories.rb b/scripts/spec/factories.rb new file mode 100644 index 0000000..c99384c --- /dev/null +++ b/scripts/spec/factories.rb @@ -0,0 +1,246 @@ +FactoryBot.define do + factory :pairing do + title { "PAIR" } + size { 2 } + groups { "{}" } + end + + factory :content_visibility do + cohort_id { nil } + content_type { "Standard" } + content_uid { "MyString" } + visibility_type { "hidden" } + end + + factory :user do + sequence(:uid) { SecureRandom.hex } + sequence(:email) { |n| "#{[first_name, last_name].join(' ').gsub(/\s+/, '.').downcase}#{n}@gmail.com" } + first_name { "Secret" } + last_name { "Spy" } + + trait :admin do + roles { [User::ROLES[:admin]] } + end + + trait :product_admin do + roles { [User::ROLES[:admin], User::ROLES[:product_admin]] } + end + + trait :blocks_manager do + roles { [User::ROLES[:blocks_manager]] } + end + end + + factory :block do + sequence(:title) { "block-#{SecureRandom.hex}" } + sync_errors { [] } + org { "gSchool" } + origin { "github.com" } + sequence(:repo_name) { "react-block-#{SecureRandom.hex}" } + end + + factory :release do + block + notes { "Note: notes" } + branch_name { "master" } + github_sha { SecureRandom.hex } + + trait :pending do + state { Release::STATES[:pending] } + end + end + + factory :activity do + cohort + association :creator, factory: :user + + trait :comment do + association :subject, factory: :submitted_challenge_answer + name { Activity::NAMES[:comment_created] } + content { "This is a comment on a submitted challenge answer." } + end + + trait :checkpoint_resubmitted do + name { Activity::NAMES[:checkpoint_submission_resubmitted] } + end + + trait :checkpoint_submission_comment do + association :subject, factory: :checkpoint_submission + name { Activity::NAMES[:comment_created] } + content { "This is a comment on a checkpoint submission." } + end + end + + factory :standard do + release + uid { SecureRandom.hex } + title { "Some Standard" } + description { "This is a sample standard." } + success_criteria { ["Success 1", "Success 2"] } + end + + factory :performance do + cohort + standard + user + association :updator, factory: :user + score { 1 } + end + + factory :notification do + tagline { "Notification tagline" } + title { "Notification title" } + url { "http://www.google.com" } + description { "Notification description" } + user + + trait :read do + read_at { DateTime.now } + end + end + + factory :content_file do + standard + lesson + sequence(:path) { "#{SecureRandom.hex}.md" } + sequence(:position) { |n| n } + html { "

    this is some html

    check out this paragraph

    " } + title { SecureRandom.hex } + uid { SecureRandom.hex } + + trait :checkpoint do + content_file_type { ContentFile::TYPES[:checkpoint] } + end + + trait :lesson do + content_file_type { ContentFile::TYPES[:lesson] } + end + + trait :resource do + content_file_type { ContentFile::TYPES[:resource] } + end + end + + factory :challenge do + content_file + uid { SecureRandom.hex } + challenge_type { Challenge::TYPES[:project] } + title { "do some work" } + explanation { "

    Daddy wants you take a load of shine up to a man near Harlan!

    " } + release_id { nil } + topics {} + sequence(:position) { |n| n } + + trait :multiple_choice do + challenge_type { Challenge::TYPES[:multiple_choice] } + title { "People Skills" } + answer { '`Peter Gr"unde`' } + end + + trait :local_snippet do + challenge_type { Challenge::TYPES[:local_snippet] } + title { "Local JS Test" } + answer { '`foo()`' } + end + + trait :sql do + challenge_type { Challenge::TYPES[:code_snippet] } + language { "sql" } + title { "Sql Test" } + data_path { '/test.sql' } + tests { "SELECT Foo FROM Bar" } + end + end + + factory :submitted_challenge_answer do + challenge + user + cohort + status { SubmittedChallengeAnswer::STATUSES[:correct] } + answer { "some answer" } + end + + factory :cohort do + uid { SecureRandom.hex } + product_type { "Some Product Type" } + name { "Some Really Long Unwieldy Title" } + sequence(:label) { |n| "00-#{n}-WD-DP" } + mode { "Mastery" } + + trait :enterprise do + product_type { "Enterprise" } + end + + trait :deleted do + deleted_at { DateTime.current } + end + end + + factory :checkpoint_submission do + cohort + user + content_file_uid { SecureRandom.hex } + state { CheckpointSubmission::STATES[:needs_review] } + total_points { 1 } + correct_points { 1 } + end + + factory :cohort_release do + sequence(:position) { |n| n } + cohort + release + end + + factory :cohort_user do + cohort + user + + trait :student do + roles { [] } + end + + trait :instructor do + roles { [CohortUser::ROLES[:instructor]] } + end + end + + factory :user_last_viewed_standard_path do + user + block { create(:block) } + standard_uid { create(:standard).uid } + content_file_path { create(:content_file).path } + end + + factory :lesson_visit do + user + block { create(:block) } + standard_uid { create(:standard).uid } + content_file_path { create(:content_file).path } + content_file_uid { create(:content_file).uid } + end + + factory :section do + title { "Test Section" } + cohort { create(:cohort) } + sequence(:position) { |n| n } + end + + factory :resync_job_result do + type { "ResyncJobResult" } + status { "success" } + edges { { cohort_id: create(:cohort).id } } + data { ( { course_config_url: 'https://github.com/gSchool/test-block/config.yml' } ) } + end + + # Cannot for the life of me figure out why this is breaking so many things + # factory :api_interaction do + # user + # method "GET" + # path "/api/v1/pineapple" + # ip "666.666.666" + # duration 500 + # response_code 200 + # action_name "neat_action" + # controller_name "NeatController" + # end +end diff --git a/scripts/spec/features/blocks/management_feature_spec.rb b/scripts/spec/features/blocks/management_feature_spec.rb new file mode 100644 index 0000000..3a840c1 --- /dev/null +++ b/scripts/spec/features/blocks/management_feature_spec.rb @@ -0,0 +1,76 @@ +require "features_helper" + +describe "Blocks management" do + let!(:blocks_manager) { create(:user, :blocks_manager) } + + before do + sign_in(blocks_manager) + end + + it "allows creation of blocks", js: true do + visit(root_path) + + within(".primary-navigation") do + all(".navigation-dropdown")[0].hover + click_on "Blocks" + end + + find(".button-solid-primary").click + + fill_in "titleInputText", with: "React" + click_on "Create Block" + + expect(page).to have_content("Not a valid repo URL") + + fill_in "urlInputText", with: "https://github.com/gSchool/react-block" + click_on "Create Block" + + expect(page).to have_content "New release" + expect(page).to have_content "Creating a new release will publish from the latest SHA on github." + end + + context "given a block has been created" do + let!(:block) { create(:block) } + + it "allows a release to be created", js: true do + visit(blocks_path) + + click_on block.title + + click_on "New Release" + + fill_in "Notes", with: "Another exciting block release." + + click_on "Create Release" + + expect(page).to have_content("Release creation queued.") + end + + context "given a release has been created", js: true do + let!(:release) { create(:release, block: block, notes: "Hello world", github_sha: "expected-sha") } + let!(:cohort_release) { create(:cohort_release, release: release) } + + it "lists the release in the block details" do + visit(block_path(block)) + + within("#release-#{release.id}") do + expect(page).to have_content(release.label) + expect(page).to have_content(release.notes) + expect(page).to have_link "1" + end + end + end + end + + context "given a block has sync_errors", js: true do + let!(:block) { create(:block, sync_errors: ["foo error"]) } + + it "displays the sync errors" do + visit(block_path(block)) + + within(".table-warning") do + expect(page).to have_content("foo error") + end + end + end +end diff --git a/scripts/spec/features/cohorts/activity_dashboard_spec.rb b/scripts/spec/features/cohorts/activity_dashboard_spec.rb new file mode 100644 index 0000000..7f04d1a --- /dev/null +++ b/scripts/spec/features/cohorts/activity_dashboard_spec.rb @@ -0,0 +1,50 @@ +# TODO this spec is causing the entire suite to hang without a db cleanup, causing issues with other tests +# +# require "features_helper" +# +# describe "Enangement dashboard", js: true do +# let(:cohort) { create(:cohort, starts_on: DateTime.new(2018, 1, 1), ends_on: DateTime.new(2018, 2, 15)) } +# let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } +# let!(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, profile_image: "http://www.s3.com/test.com")).user } +# let!(:student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Bob", last_name: "Cook", profile_image: "http://www.s3.com/test-2.com")).user } +# let!(:block) { create(:block) } +# let!(:release) { create(:release, block: block) } +# let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } +# let!(:standard) { create(:standard, release: release, position: 1) } +# let!(:checkpoint_standard) { create(:standard, release: release, position: 2, title: "CSS") } +# let!(:content_file) { create(:content_file, :lesson, standard: standard, position: 1, path: "title1.md") } +# let!(:checkpoint_content_file) { create(:content_file, :checkpoint, standard: checkpoint_standard, position: 3, path: "check.md") } +# let!(:challenge) { create(:challenge, content_file: content_file, position: 1) } +# let!(:checkpoint_challenge) { create(:challenge, content_file: checkpoint_content_file, position: 1) } +# +# let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student_1, challenge: challenge) } +# let!(:checkpoint_submission) { create_checkpoint_submission(student_2, checkpoint_challenge, cohort: cohort) } +# let!(:checkpoint_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student_2, challenge: checkpoint_challenge, checkpoint_submission: checkpoint_submission) } +# +# let!(:comment_activity) { create(:activity, :comment, creator: student_1, subject: submitted_challenge_answer, cohort: cohort, created_at: DateTime.new(2020, 2, 5)) } +# let!(:checkpoint_activity) { create(:activity, creator: student_2, name: "checkpoint_submission.created", subject: checkpoint_submission, cohort: cohort, created_at: DateTime.new(2020, 2, 6)) } +# before do +# sign_in(instructor) +# end +# +# describe "Submissions Dashboard" do +# let(:path) { activity_dashboard_cohort_path(cohort) } +# +# context "when there are activities" do +# it "displays students in the cohort with lines indicating activity on each day" do +# Timecop.freeze(DateTime.new(2018, 2, 8)) do +# visit path +# expect(page).to have_content student_1.full_name +# expect(page).to have_content student_2.full_name +# within("#student-#{student_1.id}") do +# expect(find("polyline")[:points]).to start_with("0,40 20,40 60,40 100,40 140,40 180,40 220,40") +# end +# +# within("#student-#{student_2.id}") do +# expect(find("polyline")[:points]).to start_with("0,40 20,40 60,40 100,40 140,40 180,40 220,40") +# end +# end +# end +# end +# end +# end diff --git a/scripts/spec/features/cohorts/checkpoint_submissions_spec.rb b/scripts/spec/features/cohorts/checkpoint_submissions_spec.rb new file mode 100644 index 0000000..592f6f9 --- /dev/null +++ b/scripts/spec/features/cohorts/checkpoint_submissions_spec.rb @@ -0,0 +1,181 @@ +require "features_helper" + +describe "Cohort Checkpoint Submission", js: true do + let(:cohort) { create(:cohort, mode: Cohort::MODES[:percentage]) } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, profile_image: "http://www.s3.com/test.com")).user } + let!(:block) { create(:block) } + let!(:release) { create(:release, block: block) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let!(:standard) { create(:standard, release: release, position: 1) } + let!(:content_file) { create(:content_file, :lesson, standard: standard, position: 1, path: "title1.md") } + let!(:checkpoint_content_file) { create(:content_file, :checkpoint, standard: standard, position: 2, path: "check.md", max_checkpoint_submissions: max_checkpoint_submissions) } + let!(:checkpoint_challenge_1) { create(:challenge, content_file: checkpoint_content_file) } + let!(:checkpoint_challenge_2) { create(:challenge, content_file: checkpoint_content_file) } + let!(:checkpoint_challenge_3) { create(:challenge, content_file: checkpoint_content_file) } + + let!(:checkpoint_submission) do + create_checkpoint_submission(student, + checkpoint_challenge_1, # Doesn't matter which challenge we provide here + cohort_id: cohort.id, + state: CheckpointSubmission::STATES[:needs_review]) + end + + def submit_answer(challenge, status, points, user) + create(:submitted_challenge_answer, + cohort: cohort, + user: user, + challenge: challenge, + checkpoint_submission: checkpoint_submission, + points: points, + status: SubmittedChallengeAnswer::STATUSES[status], + created_at: 1.day.ago) + end + + let!(:path) { cohort_checkpoint_submission_path(cohort, checkpoint_submission) } + + describe "Instructor viewing their own submission" do + let(:max_checkpoint_submissions) { nil } + before do + checkpoint_submission.update(user_id: instructor.id) + submit_answer(checkpoint_challenge_1, :correct, 1, instructor) + submit_answer(checkpoint_challenge_2, :incorrect, 0, instructor) + sign_in(instructor) + visit path + end + + it "displays percent score details with a grade button" do + within(".standards") do + expect(page).to have_css(".percentage-score", text: "1/2 pts") + expect(page).to have_css(".percentage-score", text: "50%") + expect(page).to have_selector(".standardcard", text: "Submit Score") + expect(page).to have_selector(".button-wrapper") + end + within(".standardcard") do + find(".button-solid-primary").click + end + expect(page).to have_current_path(cohort_checkpoint_submission_path(cohort, checkpoint_submission)) + + end + end + + describe "Percentage score cards" do + let(:max_checkpoint_submissions) { nil } + before do + submit_answer(checkpoint_challenge_1, :correct, 1, student) + submit_answer(checkpoint_challenge_2, :incorrect, 0, student) + sign_in(instructor) + end + + context "not all graded" do + before do + submit_answer(checkpoint_challenge_3, :ungraded, nil, student) + visit path + end + + it "displays an ungraded count" do + within(".standards") do + expect(page).to have_css(".percentage-score", text: "1 unscored") + end + end + end + + context "all graded" do + before do + submit_answer(checkpoint_challenge_3, :correct, 1, student) + visit path + end + + it "displays the answer that was attached to the checkpoint, not any more-recently added sca's" do + create(:submitted_challenge_answer, + cohort: cohort, + user: student, + challenge: checkpoint_challenge_1, + status: SubmittedChallengeAnswer::STATUSES[:correct], + answer: "saddest little baby in the room") + + visit path + expect(page).to have_content "some answer" # factory generated + expect(page).to_not have_content "saddest little baby in the room" + end + + it "gives the instructor a score dial to choose and grade a score for the submission and only looks at the answer connected to the submission, not any more recently added scas" do + create( + :submitted_challenge_answer, + cohort: cohort, + user: student, + challenge: checkpoint_challenge_1, + status: SubmittedChallengeAnswer::STATUSES[:correct], + answer: "saddest little baby in the room" + ) + + visit path + + expect(page).to have_content "some answer" + expect(page).to_not have_content "saddest little baby in the room" + + expect(page).to have_css(".percentage-score", text: "2/3 pts") + expect(page).to have_css(".percentage-score", text: "66%") + + within(".challenge-grading-buttons", match: :first) do + expect(page).to have_content("1/1") + expect(page).to have_selector(".timestamp") + expect(page).to have_css(".svg.svg-24px.correct-check") + + find(".points-section").hover + expect(page).not_to have_selector(".timestamp") + + within(".points-section") do + find(".integer", match: :first).click + end + + within(".points-ratio") do + expect(page).to have_content("0/1") + end + + expect(page).to have_selector(".timestamp") + expect(page).to have_css(".svg.svg-24px.incorrect-check") + end + + expect(page).to have_css(".percentage-score", text: "1/3 pts") + expect(page).to have_css(".percentage-score", text: "33%") + end + + it "displays nothing when all graded but not marked done" do + within(".standards") do + expect(page).to have_css(".percentage-score", text: "2/3 pts") + expect(page).to have_css(".percentage-score", text: "66%") + expect(page).to_not have_css(".percentage-score", text: "unscored") + end + end + + it "displays grader's name after clicking mark done" do + within(".standardcard") do + find(".button-solid-primary").click + end + expect(page).to have_current_path(cohort_checkpoint_submission_path(cohort, checkpoint_submission)) + + within(".standards") do + expect(page).to have_css(".percentage-score", text: "2/3 pts") + expect(page).to have_css(".percentage-score", text: "66%") + expect(page).to_not have_css(".percentage-score", text: "unscored") + expect(page).to have_content(instructor.full_name) + expect(page).to_not have_content("No attempts remaining") + end + end + + context "max checkpoint submissions reached" do + let(:max_checkpoint_submissions) { 1 } + it "prompts the grader to unlock another attempt" do + within(".standardcard") do + find(".button-solid-primary").click + end + expect(page).to have_current_path(cohort_checkpoint_submission_path(cohort, checkpoint_submission)) + within(".standards") do + expect(page).to have_content("No attempts remaining") + end + end + end + end + end +end diff --git a/scripts/spec/features/cohorts/curriculum_feature_spec.rb b/scripts/spec/features/cohorts/curriculum_feature_spec.rb new file mode 100644 index 0000000..6300d0c --- /dev/null +++ b/scripts/spec/features/cohorts/curriculum_feature_spec.rb @@ -0,0 +1,360 @@ +require "features_helper" + +describe "Cohort curriculum page", js: true do + context "given there is a block with a release attached to the cohort" do + let!(:cohort) { create(:cohort) } + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:block) { create(:block) } + let!(:section) { create(:section, cohort: cohort) } + let!(:release) { create(:release, block: block) } + let!(:standard) { create(:standard, release: release) } + let!(:content_file) { create(:content_file, standard: standard, position: 1, uid: "uid") } + let!(:challenge) { create(:challenge, content_file: content_file) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release, section: section) } + + before do + sign_in(student) + end + + context "hidden things" do + it "hides standard information when all content files are hidden" do + visit cohort_path(cohort) + expect(page).to have_content(standard.title) + expect(page).to have_content(standard.description) + create(:content_visibility, cohort: cohort, content_type: "ContentFile", content_uid: content_file.uid) + visit cohort_path(cohort) + find(".slideshow") + end + + it "hides standard information when all standards are hidden" do + visit cohort_path(cohort) + expect(page).to have_content(standard.title) + expect(page).to have_content(standard.description) + create(:content_visibility, cohort: cohort, content_type: "Standard", content_uid: standard.uid) + visit cohort_path(cohort) + find(".slideshow") + end + end + + context "when cohort is in mastery mode" do + it "displays the cohort name and section title" do + visit(cohort_path(cohort)) + + expect(page).to have_content cohort.name + expect(page).to have_content section.title + end + + describe "curriculum progress" do + it "displays the mastery average" do + visit(cohort_path(cohort)) + + within(".masteryaveragevalue") do + expect(page).to have_content("N/A") + end + end + + it "displays the progress breakdown" do + visit(cohort_path(cohort)) + + within(".progresspercentages") do + within(".percentthrees") do + expect(page).to have_content("0%") + end + + within(".percenttwos") do + expect(page).to have_content("0%") + end + + within(".percentones") do + expect(page).to have_content("0%") + end + + within(".percentunscored") do + expect(page).to have_content("100%") + end + end + + expect(page).to_not have_selector(".donutsegment") + end + + context "when the cohort is enterprise" do + let!(:cohort) { create(:cohort, :enterprise) } + + it "does not show the mastery average" do + visit(cohort_path(cohort)) + + expect(page).to_not have_selector(".masteryaverage") + end + end + end + + describe "last viewed content file" do + context "when no content files have been visited in the cohort" do + it "does not show the last activity" do + visit(cohort_path(cohort)) + expect(page).to_not have_selector(".curriculum-last-viewed") + expect(page).to_not have_content("Last Activity") + expect(page).to_not have_content("Get Started") + + within("#standard-#{standard.id}") do + expect(page).to have_content("Start") + end + end + end + + context "when you have a last visited content file in a cohort" do + it "shows the last visited standards title and visted content files title" do + # perform_enqueued_jobs(only: ContentFileVisitJob) do + # visit(cohort_path(cohort)) + + # find("#standard-#{standard.id}").click + # expect(page).to have_content(standard.title) + + # visit(cohort_path(cohort)) + + # within(".curriculum-last-viewed") do + # expect(page).to have_content("Last Activity") + # expect(page).to have_content(standard.title) + # expect(page).to have_content(content_file.title) + # expect(page).to have_link(`#{standard.title} > #{content_file.title}`, href: content_file_path(cohort, release.block, content_file.path)) + # end + # end + end + end + end + + context "when there is a standard in the release" do + let!(:standard) { create(:standard, release: release) } + let!(:content_file) { create(:content_file, standard: standard) } + + before do + visit(cohort_path(cohort)) + wait_for_ajax + end + + it "indicates the mastery status of the standards in the release" do + expect(page).to have_selector(".standardbean.-unscored") + end + + it "displays the standard information" do + within("#standard-#{standard.id}") do + expect(page).to have_content(standard.title) + expect(page).to have_content(standard.description) + end + end + + context "hidden content files" do + it "hides standard information when all content files are hidden" do + expect(page).to have_content(standard.title) + expect(page).to have_content(standard.description) + create(:content_visibility, cohort: cohort, content_type: "ContentFile", content_uid: content_file.uid) + visit cohort_path(cohort) + expect(page).to_not have_content(standard.title) + expect(page).to_not have_content(standard.description) + end + end + + + context "when the user has a mastery score for a standard" do + let(:standard_2) { create(:standard, release: release, position: 1) } + let(:standard_3) { create(:standard, release: release, position: 2) } + let(:standard_4) { create(:standard, release: release, position: 3) } + let(:standard_5) { create(:standard, release: release, position: 4) } + + before do + create(:content_file, standard: standard_2) + create(:content_file, standard: standard_3) + create(:challenge, content_file: create(:content_file, standard: standard_4)) + create(:performance, cohort: cohort, standard: standard, user: student, score: 3) + create(:performance, cohort: cohort, standard: standard_2, user: student, score: 2) + create(:performance, cohort: cohort, standard: standard_3, user: student, score: 1) + create_checkpoint_submission(student, create(:challenge, content_file: create(:content_file, :checkpoint, standard: standard_5)), cohort_id: cohort.id, state: CheckpointSubmission::STATES[:needs_review]) + end + + it "displays the mastery score on the standard card" do + visit(cohort_path(cohort)) + + within("#standard-#{standard.id}.-score-3 .actioncircle") do + expect(page).to have_content("3") + end + + within("#standard-#{standard_2.id}.-score-2 .actioncircle") do + expect(page).to have_content("2") + end + + within("#standard-#{standard_3.id}.-score-1 .actioncircle") do + expect(page).to have_content("1") + end + + expect(page).to_not have_selector("#standard-#{standard_4.id} .actioncircle") + + expect(page).to have_selector("#standard-#{standard_5.id}.-completed-standard .actioncircle.-awaiting-grade") + end + end + + context "when a content file in the standard has been be viewed by the student" do + let!(:content_file_1) { create(:content_file, standard: standard, position: 0) } + + it "links to the content file and indicates it was most recently viewed" do + # page.find("#standard-#{standard.id}").click + # perform_enqueued_jobs(only: ContentFileVisitJob) do + # visit(cohort_path(cohort)) + # + # within("#standard-#{standard.id}") do + # expect(page).to have_content("In Progress") + # end + # + # expect(page).to have_link("", href: content_file_path(cohort.id, block.id, content_file_1.path)) + # end + end + end + end + + context "when the user is an instructor for the cohort" do + before do + instructor = create(:cohort_user, :instructor, cohort: cohort).user + sign_in(instructor) + end + it "does not display the donut or mastery details" do + visit(cohort_path(cohort)) + + expect(page).to_not have_selector(".progressdonut") + end + end + end + + context "when cohort is in percentage mode" do + before { cohort.update(mode: Cohort::MODES[:percentage]) } + + describe "curriculum progress" do + it "displays the progress breakdown" do + visit(cohort_path(cohort)) + within(".progressdonut") do + expect(page).to have_content("—") + end + + expect(page).to_not have_selector(".donutsegment") + end + + context "with work done" do + let(:standard_2) { create(:standard) } + let!(:cohort_release_2) { create(:cohort_release, cohort: cohort, release: standard_2.release, section: section) } + + let!(:content_file_2) { create(:content_file, :checkpoint, standard: standard_2, autoscore: true) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2) } + let!(:checkpoint_submission) { create_checkpoint_submission(student, challenge_2, cohort: cohort, state: CheckpointSubmission::STATES[:done], total_points: 2, correct_points: 2) } + let!(:performance) { create(:performance, cohort: cohort, standard: standard_2, user: student, checkpoint_submission_id: checkpoint_submission.id, score: 0) } + + it "shows completion progress" do + visit(cohort_path(cohort)) + + within(".progressdonut") do + expect(page).to have_content("50%") + end + end + end + end + + describe "last viewed content file" do + context "when no content files have been visited in the cohort" do + it "does not show the last activity" do + visit(cohort_path(cohort)) + expect(page).to_not have_selector(".curriculum-last-viewed") + expect(page).to_not have_content("Last Activity") + expect(page).to_not have_content("Get Started") + end + end + + context "when you have a last visited content file in a cohort" do + it "shows the last visited standards title and visted content files title" do + # perform_enqueued_jobs(only: ContentFileVisitJob) do + # visit(cohort_path(cohort)) + + # find("#standard-#{standard.id}").click + # expect(page).to have_content(standard.title) + + # visit(cohort_path(cohort)) + + # within(".curriculum-last-viewed") do + # expect(page).to have_content("Last Activity") + # expect(page).to have_content(standard.title) + # expect(page).to have_content(content_file.title) + # expect(page).to have_link(`#{standard.title} > #{content_file.title}`, href: content_file_path(cohort, release.block, content_file.path)) + # end + # end + end + end + end + + context "when there is a standard in the release" do + let!(:standard) { create(:standard, release: release) } + let!(:content_file) { create(:content_file, standard: standard) } + let!(:challenge) { create(:challenge, content_file: content_file) } + + before { visit(cohort_path(cohort)) } + + it "displays the standard information" do + within("#standard-#{standard.id}") do + expect(page).to have_content(standard.title) + expect(page).to have_content(standard.description) + end + end + + context "when the user has a mastery score for a standard" do + # standard = not started + # standard_2 = in progress + # standard_3 = pending + # standard_4 = completed + let!(:standard_2) { create(:standard, release: release, position: 3) } + let!(:content_file_2) { create(:content_file, standard: standard_2, autoscore: true) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2) } + let!(:student_sca_2) { create(:submitted_challenge_answer, user: student, challenge: challenge_2, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct]) } + let!(:content_file_3) { create(:content_file, standard: standard_2, autoscore: true) } + let!(:challenge_3) { create(:challenge, content_file: content_file_3) } + + it "displays the mastery score on the standard card" do + # TODO: figure why data is not coming through as it should, seems to overwrite to last standard when repeating the items above for a new standard + + # visit(cohort_path(cohort)) + # within("#standard-#{standard.id}") do + # expect(page).to have_content("Start") + # within(".progressbar") do + # progress_bar = first(".progress-percentage") + # expect(progress_bar[:style]).to match(/width: 0%;/) + # end + # end + # + # within("#standard-#{standard_2.id}") do + # within(".progressbar") do + # progress_bar = first(".progress-percentage") + # expect(progress_bar[:style]).to match(/width: 33.3333%;/) + # end + # end + # + # within("#standard-#{standard_3.id}") do + # within(".progressbar") do + # progress_bar = first(".progress-percentage") + # expect(progress_bar[:style]).to match(/width: 66.6667%;/) + # end + # end + + # expect(page).to have_selector("#standard-#{standard_4.id}.-completed-standard .percentage-badge.actioncircle") + # expect(page).to have_selector("#standard-#{standard_5.id}.-completed-standard .actioncircle.-awaiting-grade") + end + end + end + + context "when the user is an instructor for the cohort" do + before do + instructor = create(:cohort_user, :instructor, cohort: cohort).user + sign_in(instructor) + end + it "does not display the donut or mastery details" do + visit(cohort_path(cohort)) + + expect(page).to_not have_selector(".progressdonut") + end + end + end + end +end diff --git a/scripts/spec/features/cohorts/feed_feature_spec.rb b/scripts/spec/features/cohorts/feed_feature_spec.rb new file mode 100644 index 0000000..d722c67 --- /dev/null +++ b/scripts/spec/features/cohorts/feed_feature_spec.rb @@ -0,0 +1,25 @@ +require "features_helper" + +describe "Cohort feed", js: true do + let!(:cohort) { create(:cohort) } + + before { sign_in(create(:user, :admin)) } + + context "when there is an existing activity" do + let!(:activity) { create(:activity, :comment, cohort: cohort) } + + it "displays the activity" do + visit(feed_cohort_path(cohort)) + + within("#activity-#{activity.id}") do + expect(page).to have_selector(".activitylabel.-primary") + expect(page).to have_content("left a comment") + expect(page).to have_content("a few seconds ago") + + within(".user-avatar") do + expect(page).to have_content(activity.creator.initials) + end + end + end + end +end diff --git a/scripts/spec/features/cohorts/management_feature_spec.rb b/scripts/spec/features/cohorts/management_feature_spec.rb new file mode 100644 index 0000000..d5279ed --- /dev/null +++ b/scripts/spec/features/cohorts/management_feature_spec.rb @@ -0,0 +1,742 @@ +require "features_helper" + +describe "Cohorts management", js: true do + let!(:admin) { create(:user, :admin) } + let!(:dsi_cohort) { create(:cohort, name: "Boulder DSI", product_type: "DSI", campus_name: "boulder", starts_on: "2016-01-01") } + let!(:wdi_cohort) { create(:cohort, name: "Boulder WDI", product_type: "WDI", campus_name: "boulder", starts_on: "2017-01-02") } + let!(:workshop_cohort) { create(:cohort, name: "Phoenix Workshop", product_type: "Workshop Web Development", campus_name: "phoenix", starts_on: "2017-03-01") } + let!(:precourse_cohort) { create(:cohort, name: "WDI Pre-Course", product_type: "Pre-Course Web Development", campus_name: "denver gt", starts_on: "2018-01-01") } + + before do + sign_in(admin) + end + + describe "Cohorts list" do + before do + create(:cohort_user, :student, cohort: dsi_cohort) + create(:cohort_user, :student, cohort: dsi_cohort) + end + + it "allows all cohorts to be viewed and searched by name" do + visit(root_path) + + expect(page).to have_selector("h1", text: "Cohorts") + + within("#cohort-#{dsi_cohort.id}") do + expect(page).to have_content(dsi_cohort.name) + expect(page).to have_content(dsi_cohort.product_type) + expect(page).to have_content(dsi_cohort.campus_name) + expect(page).to have_content(dsi_cohort.starts_on.strftime("%-m/%-d")) + expect(page).to have_content(dsi_cohort.cohort_users.count) + end + + fill_in "search", with: "DSI" + expect(page).to_not have_content(wdi_cohort.name) + end + + it "allows cohort create button to be clicked for product_admins and disabled for non product_admins" do + visit(root_path) + + expect(page).to have_selector("div.new-cohort-btn") + expect(page).to have_selector(".lp-style-button.disabled") + + sign_in(create(:user, :product_admin)) + visit(root_path) + + expect(page).to have_selector("div.new-cohort-btn") + expect(page).to_not have_selector(".lp-style-button.disabled") + end + + it "allows the user to filter cohorts by product type" do + visit cohorts_path + within(".cohorts") do + expect(page).to have_content(dsi_cohort.name) + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(workshop_cohort.name) + expect(page).to have_content(precourse_cohort.name) + end + + select "WDI", from: "Types" + select "DSI", from: "Types" + within(".cohorts") do + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(dsi_cohort.name) + expect(page).to_not have_content(workshop_cohort.name) + expect(page).to_not have_content(precourse_cohort.name) + end + end + + it "allows the user to filter cohorts by campus" do + visit cohorts_path + within(".cohorts") do + expect(page).to have_content(dsi_cohort.name) + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(workshop_cohort.name) + expect(page).to have_content(precourse_cohort.name) + end + + select "boulder", from: "Campus" + within(".cohorts") do + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(dsi_cohort.name) + expect(page).to_not have_content(workshop_cohort.name) + expect(page).to_not have_content(precourse_cohort.name) + end + + select "phoenix", from: "Campus" + within(".cohorts") do + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(dsi_cohort.name) + expect(page).to have_content(workshop_cohort.name) + expect(page).to_not have_content(precourse_cohort.name) + end + end + + it "allows the user to filter cohorts by startsAfter date" do + visit cohorts_path + within(".cohorts") do + expect(page).to have_content(dsi_cohort.name) + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(workshop_cohort.name) + expect(page).to have_content(precourse_cohort.name) + end + + fill_in "startsAfter", with: "01/01/17" + within(".cohorts") do + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(workshop_cohort.name) + expect(page).to_not have_content(dsi_cohort.name) + expect(page).to have_content(precourse_cohort.name) + end + end + + it "allows the user to filter cohorts by startsBefore date" do + visit cohorts_path + within(".cohorts") do + expect(page).to have_content(dsi_cohort.name) + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(workshop_cohort.name) + expect(page).to have_content(precourse_cohort.name) + end + + fill_in "startsBefore", with: "01/01/18" + within(".cohorts") do + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(dsi_cohort.name) + expect(page).to have_content(workshop_cohort.name) + expect(page).to_not have_content(precourse_cohort.name) + end + end + + it "allows you to filter with multiple filters" do + visit cohorts_path + + within(".cohorts") do + expect(page).to have_content(dsi_cohort.name) + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(workshop_cohort.name) + expect(page).to have_content(precourse_cohort.name) + end + + select "boulder", from: "Campus" + within(".cohorts") do + expect(page).to have_content(wdi_cohort.name) + expect(page).to have_content(dsi_cohort.name) + expect(page).to_not have_content(workshop_cohort.name) + expect(page).to_not have_content(precourse_cohort.name) + end + + select "DSI", from: "Types" + within(".cohorts") do + expect(page).to_not have_content(wdi_cohort.name) + expect(page).to have_content(dsi_cohort.name) + expect(page).to_not have_content(workshop_cohort.name) + expect(page).to_not have_content(precourse_cohort.name) + end + end + + it "allows you to order cohorts by name, campus, type, starts-on, and users" do + visit cohorts_path + cohort_tables = all(".cohortstable") + within cohort_tables[1] do + expect(page).to have_content "Other Cohorts" + within("thead") do + find("th", text: "NAME").click + end + + within ("tbody") do + cohort_list = all("tr") + expect(cohort_list[0]).to have_content("Boulder DSI") + end + + within("thead") do + find("th", text: "NAME").click + end + + within ("tbody") do + cohort_list = all("tr") + expect(cohort_list[0]).to have_content("WDI Pre-Course") + end + end + end + + it "does not show sandbox cohorts" do + create(:cohort, name: "Sandbox Cohort", sandbox: true) + + visit cohorts_path + + expect(page).to_not have_content "Sandbox Cohort" + end + end + + describe "Cohort details" do + let!(:cohort) { create(:cohort, starts_on: Time.zone.today) } + let!(:student_cohort_user) { create(:cohort_user, :student, cohort: cohort) } + let!(:instructor_cohort_user) { create(:cohort_user, :instructor, cohort: cohort) } + + it "displays the details of the cohort" do + admin.roles << User::ROLES[:product_admin] + visit(setup_cohort_path(cohort)) + + within ".cohortinfo" do + expect(page).to have_content(cohort.name) + if cohort.campus_name + expect(page).to have_content(cohort.campus_name) + end + expect(page).to have_content(cohort.starts_on.strftime("%b %-d, %Y")) + # expect(page).to have_link("Edit", href: "#{Rails.application.secrets.auth_url}/admin/products/#{cohort.uid}/edit") + end + end + + context "when the cohort is not taught in Learn V1" do + context "sections" do + it "renders section separators for curriculum content" do + block = create(:block) + first_section = create(:section, title: "Futzy Butzy") + second_section = create(:section, title: "Zuul") + first_release = create(:release, block: block) + second_release = create(:release, block: create(:block)) + create(:cohort_release, release: first_release, cohort: cohort, position: 1, section: first_section) + create(:cohort_release, release: second_release, cohort: cohort, position: 2, section: second_section) + visit(setup_cohort_path(cohort)) + + separators = all(".sectiontitleseparator") + within separators[0] do + expect(page).to have_content first_section.title + end + within separators[1] do + expect(page).to have_content second_section.title + end + # content should be ordered + trs = all("tr") + within trs[1] do + expect(page).to have_content first_section.title + end + within trs[2] do + expect(page).to have_content block.repo_name + end + within trs[3] do + expect(page).to have_content second_section.title + end + within trs[4] do + expect(page).to have_content second_release.block.repo_name + end + end + end + + context "version management" do + it "allows the user to manage selected versions and auto updates of cohort releases" do + block = create(:block) + first_release = create(:release, block: block, notes: "initial release", github_sha: "abc123") + second_release = create(:release, block: block, notes: "secone time out", github_sha: "xyz456") + cohort_release = create(:cohort_release, release: first_release, cohort: cohort) + + visit(setup_cohort_path(cohort)) + + within ".settingsrow" do + expect(page).to have_content "Repos (1)" + expect(page).to have_content "Users (2)" + end + + within(".cohortsetuprow") do + expect(page).to have_content block.repo_name + expect(page).to have_selector("span.managereleases", text: "1 UPDATE") + expect(page).to have_selector("span.versionlabel", text: "v1") + expect(find("select").value).to eq("false") + select("Auto", from: "auto-update-cohort-release-#{cohort_release.id}") + end + + page.driver.browser.switch_to.alert.dismiss + + within(".cohortsetuprow") do + expect(find("select").value).to eq("false") + select("Auto", from: "auto-update-cohort-release-#{cohort_release.id}") + end + + page.driver.browser.switch_to.alert.accept + + within(".cohortsetuprow") do + wait_for_ajax + expect(find("select").value).to eq("true") + expect(page).to_not have_selector("span.managereleases") + expect(page).to have_selector("span.versionlabel", text: "v2") + + find(".action-kebab").click + find("a", text: "Switch version").click + end + + wait_for_ajax + expect(page).to have_selector(".pick-version-modal") + find(".modal-closebtn").click + expect(page).to_not have_selector(".pick-version-modal") + + find(".versionlabel").click + wait_for_ajax + expect(page).to have_selector(".pick-version-modal") + + within(".pick-version-table") do + [first_release, second_release].each do |release| + within("tr", id: "release-#{release.id}") do + expect(page).to have_content(release.label) + expect(page).to have_content(release.notes) + expect(page).to have_link(release.github_sha) + end + end + end + + within("tr", id: "release-#{second_release.id}") do + expect(page).to have_content "CURRENT" + expect(page).to have_link("1", href: "#") + + find(".cohorts-count").click + expect(page).to have_content(cohort.name) + end + within("tr", id: "release-#{first_release.id}") do + expect(page).to have_link("View diff on Github (#{first_release.label} vs current version)") + click_link("SELECT") + end + + expect(page).to_not have_selector(".pick-version-modal") + + within(".cohortsetuprow") do + expect(page).to have_content block.repo_name + expect(page).to have_selector("span.managereleases", text: "1 UPDATE") + expect(page).to have_selector("span.versionlabel", text: "v1") + expect(find("select").value).to eq("false") + end + end + end + + context "branch management" do + let!(:block) { create(:block) } + let!(:release) { create(:release, block: block, notes: "initial release", github_sha: "abc123") } + let!(:cohort_release) { create(:cohort_release, release: release, cohort: cohort) } + + it "yields errors when branches aren't found on github" do + visit(setup_cohort_path(cohort)) + + find(".action-kebab").click + within(".action-menu-list") do + click_link "Switch to feature branch" + end + + within(".branch-modal") do + expect(page).to have_content "Switch Branch" + expect(page).to have_content block.repo_name + fill_in "Branch name:", with: "test-branch" + find(".lp-style-button").click + end + wait_for_ajax + expect(page).to have_content "The provided branch 'test-branch' does not exist on repo: #{block.repo_url}" + end + + it "allows users to see any sync errors if they're present, otherwise display a successful branch release" do + expect(SwitchToBranchJob).to receive(:perform_later) + allow_any_instance_of(Mocktokit::Client).to receive(:branch).and_return(commit: { sha: "abc123babyyouandme" }) + + visit(setup_cohort_path(cohort)) + find(".action-kebab").click + within(".action-menu-list") do + click_link "Switch to feature branch" + end + wait_for_ajax + within(".branch-modal") do + expect(page).to have_content "Switch Branch" + expect(page).to have_content block.repo_name + fill_in "Branch name:", with: "test-branch" + find(".lp-style-button").click + end + wait_for_ajax + + expect(page).to_not have_content "The provided branch 'test-branch' does not exist on GitHub" + within "#cohort-release-#{cohort_release.id}" do + within(".versionlabel") do + expect(page).to have_content "v1" + end + within(".blockcolumn") do + expect(page).to have_selector ".processing-icon" + end + end + + Release.last.update(sync_errors: ["Validation run at bbbpppttt", "buuuuuuuuuuttterfreee", "I'm ok dad"], state: Release::STATES[:failed]) + visit(setup_cohort_path(cohort)) + + within "#cohort-release-#{cohort_release.id}" do + within ".error-sync" do + click_link "2 errors" + within ".errors-tooltip" do + expect(page).to have_content "Validation run at bbbpppttt" + expect(page).to have_content "buuuuuuuuuuttterfreee" + expect(page).to have_content "I'm ok dad" + end + end + end + + Release.last.update(sync_errors: [], state: Release::STATES[:success], branch_name: "not_master") + cohort_release.update(release_id: Release.last.id, pending_release_id: nil) + visit(setup_cohort_path(cohort)) + within "#cohort-release-#{cohort_release.id}" do + expect(page).to_not have_content "errors" + within(".updatecolumn") do + expect(find("svg use").native["xlink:href"]).to include("#ic_sync_24px") + end + end + end + + it "allows for two cohorts to sync the same feature branch and stay on the version they synced" do + expect(SwitchToBranchJob).to receive(:perform_later) + allow_any_instance_of(Mocktokit::Client).to receive(:branch).and_return(commit: { sha: "321cba" }) + + another_cohort = create(:cohort, starts_on: Time.zone.today) + another_cohort_release = create(:cohort_release, release: release, cohort: another_cohort) + + Release.last.update(sync_errors: [], state: Release::STATES[:success], branch_name: "not_master") + cohort_release.update(release_id: Release.last.id, pending_release_id: nil) + another_cohort_release.update(release_id: Release.last.id, pending_release_id: nil) + another_release = create(:release, block: block, notes: "branch rebuild", github_sha: "321cba", state: Release::STATES[:success], branch_name: "not_master") + cohort_release.update(release_id: another_release.id, pending_release_id: nil) + + visit(setup_cohort_path(cohort)) + within "#cohort-release-#{cohort_release.id}" do + within(".updatecolumn") do + expect(find("svg use").native["xlink:href"]).to include("#ic_sync_24px") + end + end + expect(page).to have_content("not_master") + + visit(setup_cohort_path(another_cohort)) + within "#cohort-release-#{another_cohort_release.id}" do + within(".updatecolumn") do + expect(find("svg use").native["xlink:href"]).to include("#ic_sync_24px") + end + within(".blockcolumn") do + expect(page).to have_content("UPDATES") + end + within(".updatecolumn") do + find("svg").click + end + another_cohort_release.update(release_id: another_release.id, pending_release_id: nil) + visit(setup_cohort_path(another_cohort)) + within(".blockcolumn") do + expect(page).to_not have_content("UPDATES") + end + expect(page).to have_content("not_master") + end + end + + it "enables switching a branch back to master, prompting auto updates only if switching to the latest version" do + allow_any_instance_of(Octokit::Client).to receive(:branch).and_return(double( + commit: { sha: "xyz" } + )) + create(:release, block: block, notes: "initial master release") + create(:release, block: block, notes: "latest master release") + release.update(branch_name: "test-branch", notes: "branch release", github_sha: "abc") + visit(setup_cohort_path(cohort)) + expect(page).to have_content("UPDATES") + + find(".action-kebab").click + within(".action-menu-list") do + click_link "Switch back to master" + wait_for_ajax + end + + within(".cohortsetuprow") do + expect(page).to have_content "Auto" + end + + # reset to branch release + CohortRelease.last.update(release: release) + visit(setup_cohort_path(cohort)) + + find(".action-kebab").click + within(".action-menu-list") do + click_link "Switch back to master" + end + + within(".cohortsetuprow") do + expect(page).to have_content "Auto" + end + end + + it "allows users to see updates to releases without reloading page" do + block = create(:block) + release = create(:release, block: block, notes: "initial release", github_sha: "abc123") + cohort_release = create(:cohort_release, release: release, cohort: cohort) + + expect(SwitchToBranchJob).to receive(:perform_later) + allow_any_instance_of(Mocktokit::Client).to receive(:branch).and_return(commit: { sha: "abc123babyyouandme" }) + + visit(setup_cohort_path(cohort)) + within("#cohort-release-#{cohort_release.id}") do + find(".action-kebab").click + click_link "Switch to feature branch" + end + wait_for_ajax + within(".branch-modal") do + expect(page).to have_content "Switch Branch" + expect(page).to have_content block.repo_name + fill_in "Branch name:", with: "test-branch" + find(".lp-style-button").click + end + wait_for_ajax + + within("#cohort-release-#{cohort_release.id}") do + expect(page).to have_selector ".processing-icon" + end + + cohort_release.update(release: Release.last, pending_release_id: nil) + Release.last.update(state: Release::STATES[:success]) + + wait_for_ajax + within("#cohort-release-#{cohort_release.id}") do + # something weird with data not updating between the this test and the controller data + # expect(page).to_not have_selector ".processing-icon" + end + end + end + + context "blocks management" do + before do + # Blocks must exist for ResyncCourseService to do its job + with_release = lambda {|block| create(:release, block: block, state: 'success')} + + with_release.call create(:block, repo_name: 'dsi-intro-to-ds-stats') + with_release.call create(:block, repo_name: 'ds-sql-block') + with_release.call create(:block, repo_name: 'ds-python-quizzes-block') + + visit(setup_cohort_path(cohort)) + end + + it "should have components displayed on the page" do + expect(page).to have_content("For help see https://github.com/gSchool/learn-course-files/README.md:") + expect(page).to have_content("RESYNC") + expect(page).to have_content("Never synced.") + end + + it "should have components displayed on the page" do + VCR.use_cassette("resync-course-job-success") do + within(".resync-ui") do + fill_in "resync-url", with: "https://github.com/gSchool/blocks-test/blob/master/course.yaml" + find(".lp-style-button").click + + expect(page).to have_content("Syncing...") + expect(page).to have_content("CANCEL") + + find(".lp-style-button").click + expect(page).to have_content("RESYNC") + end + end + # TODO: test that sync shows up on page + # TODO: test timeout after 1 minute + end + + it "should show error on the page" do + VCR.use_cassette("resync-course-job-success") do + within(".resync-ui") do + fill_in "resync-url", with: "https://github.com/gSchool/blocks-test/blob/master/course.yaml" + find(".lp-style-button").click + end + + expect(page).to have_content("Syncing...") + # force a bad resync + ResyncJobResult.create(type: "ResyncJobResult", status: "failure", edges: {"cohort_id"=>cohort.id}, data: {"error_type"=>"yaml_validation_error", "course_config_url"=>"https://github.com/gSchool/learn-course-files/blob/master/test/chads-course.yml", "error_data"=>{"url"=>"https://github.com/gSchool/learn-course-files/blob/master/test/chads-course.yml", "message"=>"(yaml_validation_error): did not find expected '-' indicator while parsing a block collection at line 3 column 5"}}) + expect(page).to have_content("(yaml_validation_error): did not find expected '-' indicator while parsing a block collection at line 3 column 5") + end + end + end + + context "visibility management" do + it "shows all header sections" do + block = create(:block) + first_section = create(:section, title: "Futzy Butzy") + second_section = create(:section, title: "Zuul") + first_release = create(:release, block: block) + second_release = create(:release, block: create(:block)) + create(:cohort_release, release: first_release, cohort: cohort, position: 1, section: first_section) + create(:cohort_release, release: second_release, cohort: cohort, position: 2, section: second_section) + first_standard = create(:standard, release: first_release) + second_standard = create(:standard, release: second_release) + create(:content_file, standard: first_standard, title: "butzy lesson") + create(:content_file, standard: second_standard, title: "zuul lesson") + + visit content_cohort_path(cohort) + + within(".selected") do + expect(page).to have_content("Content") + end + + within(".visibility") do + expect(page).to have_content("Futzy Butzy") + expect(page).to have_content("Zuul") + end + end + + it "toggling standards reveals the lessons" do + block = create(:block) + first_section = create(:section, title: "Futzy Butzy") + second_section = create(:section, title: "Zuul") + first_release = create(:release, block: block) + second_release = create(:release, block: create(:block)) + create(:cohort_release, release: first_release, cohort: cohort, position: 1, section: first_section) + create(:cohort_release, release: second_release, cohort: cohort, position: 2, section: second_section) + first_standard = create(:standard, release: first_release) + second_standard = create(:standard, release: second_release) + create(:content_file, standard: first_standard, title: "butzy lesson") + create(:content_file, standard: second_standard, title: "zuul lesson") + + visit content_cohort_path(cohort) + + within(".selected") do + expect(page).to have_content("Content") + end + + within(".visibility") do + expect(page).to_not have_content("butzy lesson") + expect(page).to_not have_content("zuul lesson") + within("#standard-#{first_standard.id}") do + find(".chevron").click + expect(page).to have_content("butzy lesson") + end + within("#standard-#{second_standard.id}") do + find(".chevron").click + expect(page).to have_content("zuul lesson") + end + end + end + end + end + + context "manage external users" do + + # Disabled since this feature currently does not exist + xit "allows users to view an auth Manage Users link if they have auth.admin or the auth.product_admin cohort_user role" do + visit(users_cohort_path(cohort)) + expect(page).to have_content(cohort.name) + expect(page).to_not have_link("Manage Users in Auth") + + admin.update(roles: [User::ROLES[:product_admin], User::ROLES[:admin]]) + visit(users_cohort_path(cohort)) + expect(page).to have_link("Manage Users in Auth") + + admin.update(roles: [User::ROLES[:admin]]) + create(:cohort_user, :instructor, user: admin, cohort: cohort, roles: [CohortUser::ROLES[:product_admin]]) + visit(users_cohort_path(cohort)) + expect(page).to have_link("Manage Users in Auth") + end + + it "allows users to view an auth Edit Cohort link if they have auth.admin or the auth.product_admin cohort_user role" do + visit(setup_cohort_path(cohort)) + expect(page).to have_content(cohort.name) + expect(page).to_not have_link("Manage Users in Auth") + + admin.update(roles: [User::ROLES[:product_admin], User::ROLES[:admin]]) + visit(setup_cohort_path(cohort)) + expect(page).to have_link("Edit") + + admin.update(roles: [User::ROLES[:admin]]) + create(:cohort_user, :instructor, user: admin, cohort: cohort, roles: [CohortUser::ROLES[:product_admin]]) + visit(setup_cohort_path(cohort)) + expect(page).to have_link("Edit") + end + + it "allows users to view an individual student's submissions from the cohort users tab" do + visit(users_cohort_path(cohort)) + all(".action-kebab").last.click + find("a", text: "View Submissions").click + assert_current_path(submissions_dashboard_cohort_user_path(cohort.id, cohort.students.last.id)) + end + + # Disabled since this feature currently does not exist + xit "allows users to view an individual in auth from the cohort users tab" do + visit(users_cohort_path(cohort)) + find(".action-kebab").click + expect(page).to have_link("View In Auth") + end + end + + context "partnerup" do + let!(:second_student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Savvy")) } + let!(:third_student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Sinon")) } + + it "allows users to utilize partnerup" do + # Visiting partnerup and creating new pair + visit(partnerup_cohort_path(cohort)) + expect(page).to have_content("View") + find(".action-item", text: "Create New").click + fill_in "Title", with: "Pair Force One" + find('select').find(:xpath, 'option[2]').select_option + expect(page).to have_content("Pair 1") + expect(page).to have_content("Pair 2") + expect(all(".student").length).to eq 3 + + # Save a pairing + click_button 'Save' + expect(page).to_not have_content("VIEW ALL PAST PAIRINGS") + expect(page).to have_content("Secret") + expect(page).to have_content("Savvy") + expect(page).to have_content("Sinon") + + find(".action-item", text: "Create New").click + fill_in "Title", with: "Pair Force One" + find('select').find(:xpath, 'option[2]').select_option + expect(page).to have_content("Pair 1") + expect(page).to have_content("Pair 2") + expect(all(".student").length).to eq 3 + click_button 'Save' + + expect(page).to have_content("VIEW ALL PAST PAIRINGS") + + # Editing a pair + click_button 'Edit' + fill_in "Title", with: "Hans Solo" + within all(".inner-student-list-wrapper").first do + expect(all('.student').length).to eq 2 + end + find('select').find(:xpath, 'option[3]').select_option + page.driver.browser.switch_to.alert.dismiss + within ".inner-student-list-wrapper" do + expect(all('.student').length).to eq 3 + end + click_button 'Save' + expect(page).to have_content("VIEW ALL PAST PAIRINGS") + expect(page).to have_content("Hans Solo") + within ".group" do + expect(all('.student').length).to eq 3 + end + + # Randomize and lock students + click_button 'Edit' + first_student = all('.student').first + name = first_student.text + within first_student do + find('.fa-unlock-alt').click + end + 5.times do + click_button "Random" + page.driver.browser.switch_to.alert.dismiss + expect(all('.student').first.text).to eq(name) + end + end + end + end +end diff --git a/scripts/spec/features/cohorts/sandboxes_spec.rb b/scripts/spec/features/cohorts/sandboxes_spec.rb new file mode 100644 index 0000000..62a4632 --- /dev/null +++ b/scripts/spec/features/cohorts/sandboxes_spec.rb @@ -0,0 +1,37 @@ +require "features_helper" + +describe "Sandbox Cohorts", js: true do + let!(:admin) { create(:user, :admin) } + let!(:sandbox) { create(:cohort, name: "Im a Sandbox", sandbox: true, starts_on: Time.now) } + + before do + sign_in(admin) + visit(cohort_path(sandbox)) + end + + it "does not show up in cohorts list page" do + visit cohorts_path + + expect(page).to_not have_content("Im a Sandbox") + end + + it "does not show you navigation that should not go with sandboxes" do + expect(page).to_not have_content("ACTIVITY") + expect(page).to_not have_content("UNIT PROGRES") + expect(page).to_not have_content("COURSE STATS") + expect(page).to_not have_content("SETUP") + expect(page).to_not have_content("CURRICULUM") + + visit(content_cohort_path(sandbox)) + + within(".cohortdetails") do + expect(page).to_not have_content("Edit in Auth") + end + + within(".settingsrow") do + expect(page).to_not have_content("Repos") + expect(page).to_not have_content("Users") + expect(page).to_not have_content("Partnerup") + end + end +end \ No newline at end of file diff --git a/scripts/spec/features/cohorts/student_progress_spec.rb b/scripts/spec/features/cohorts/student_progress_spec.rb new file mode 100644 index 0000000..cdbc12f --- /dev/null +++ b/scripts/spec/features/cohorts/student_progress_spec.rb @@ -0,0 +1,320 @@ +require "features_helper" + +feature "Instructor viewing student progress" do + let(:cohort) { create(:cohort) } + let(:release) { create(:release) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abel", last_name: "student_1")).user } + let(:student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Ben", last_name: "student_2")).user } + let(:student_3) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Cory", last_name: "student_3")).user } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + + let(:standard) { create(:standard, release: release) } + let(:standard_2) { create(:standard, release: release) } + let!(:performance_1) { create(:performance, user_id: student_1.id, cohort: cohort, standard: standard, standard_uid: standard.uid, score: 2) } + let!(:performance_2) { create(:performance, user_id: student_1.id, cohort: cohort, standard: standard_2, standard_uid: standard_2.uid, score: 3) } + let!(:performance_3) { create(:performance, user_id: student_2.id, cohort: cohort, standard: standard, standard_uid: standard.uid, score: 1) } + let!(:performance_4) { create(:performance, user_id: student_3.id, cohort: cohort, standard: standard, standard_uid: standard.uid, score: 2) } + + before do + sign_in(instructor) + visit course_stats_cohort_path(cohort) + end + + context "Instructor of a cohort", js: true do + context "over 100 students in the course" do + it "displays a message saying content is hidden" do + 100.times do + create(:cohort_user, :student, cohort: cohort, user: create(:user)) + end + visit course_stats_cohort_path(cohort) + expect(page).to have_content "Data not shown for cohorts with more than 100 students enrolled. Download CSV to see this data." + expect(page).to have_content "EXPORT CSV" + end + end + + context "when in mastery mode" do + it "displays students names with data" do + within(".progress-table") do + within(".progress-body") do + rows = all(".progress-row") + within(rows[0]) do + expect(page).to have_content(student_2.full_name) + expect(page).to have_content("0%\n0%\n100%") + within(".cell.mastery-progress") do + expect(page).to have_content("1 Standards Scored") + end + expect(find(".avg-box").text).to eq "1.00" + end + within(rows[1]) do + expect(page).to have_content(student_3.full_name) + expect(page).to have_content("0%\n100%\n0%") + within(".cell.mastery-progress") do + expect(page).to have_content("1 Standards Scored") + end + expect(find(".avg-box").text).to eq "2.00" + end + within(rows[2]) do + expect(page).to have_content(student_1.full_name) + expect(page).to have_content("50%\n50%\n0%") + within(".cell.mastery-progress") do + expect(page).to have_content("2 Standards Scored") + end + expect(find(".avg-box").text).to eq "2.50" + end + end + end + end + + it "can sort students by first name" do + within(".progress-table") do + within(".progress-body") do + rows = all(".progress-row") + within(rows[0]) do + expect(page).to have_content(student_2.full_name) + end + within(rows[1]) do + expect(page).to have_content(student_3.full_name) + end + within(rows[2]) do + expect(page).to have_content(student_1.full_name) + end + end + + find(".heading .student .label", text: "NAME").click + + within(".progress-body") do + rows = all(".progress-row") + within(rows[0]) do + expect(page).to have_content(student_3.full_name) + end + within(rows[1]) do + expect(page).to have_content(student_2.full_name) + end + within(rows[2]) do + expect(page).to have_content(student_1.full_name) + end + end + end + end + + it "can sort students by standard mastery" do + within(".progress-table") do + find(".heading .mastery-progress .label", text: "STANDARD MASTERY").click + + within(".progress-body") do + rows = all(".progress-row") + within(rows[0]) do + expect(page).to have_content(student_1.full_name) + end + within(rows[1]) do + expect(page).to have_content(student_3.full_name) + end + within(rows[2]) do + expect(page).to have_content(student_2.full_name) + end + end + + find(".heading .mastery-progress .label", text: "STANDARD MASTERY").click + + within(".progress-body") do + rows = all(".progress-row") + within(rows[0]) do + expect(page).to have_content(student_2.full_name) + end + within(rows[1]) do + expect(page).to have_content(student_3.full_name) + end + within(rows[2]) do + expect(page).to have_content(student_1.full_name) + end + end + end + end + + it "can sort students by average mastery" do + within(".progress-table") do + find(".heading .avg .label", text: "AVG").click + + within(".progress-body") do + rows = all(".progress-row") + within(rows[0]) do + expect(page).to have_content(student_1.full_name) + end + within(rows[1]) do + expect(page).to have_content(student_3.full_name) + end + within(rows[2]) do + expect(page).to have_content(student_2.full_name) + end + end + + find(".heading .avg .label", text: "AVG").click + + within(".progress-body") do + rows = all(".progress-row") + within(rows[0]) do + expect(page).to have_content(student_2.full_name) + end + within(rows[1]) do + expect(page).to have_content(student_3.full_name) + end + within(rows[2]) do + expect(page).to have_content(student_1.full_name) + end + end + end + end + end + + context "when in percentage mode" do + # skipping the student names as its the same as mastery + + let(:correct) { SubmittedChallengeAnswer::STATUSES[:correct] } + let(:ungraded) { SubmittedChallengeAnswer::STATUSES[:ungraded] } + let(:failed) { SubmittedChallengeAnswer::STATUSES[:failed] } + + let!(:content_file_0) { create(:content_file, standard: standard, title: "Fizz", position: 0) } + let!(:challenge_0_0) { create(:challenge, content_file: content_file_0, position: 0, title: "Fizz Tastic") } + let!(:challenge_0_1) { create(:challenge, content_file: content_file_0, position: 1, title: "Fizzlin") } + + let!(:content_file_1) { create(:content_file, standard: standard_2, title: "Buzz", position: 1) } + let!(:challenge_1_0) { create(:challenge, content_file: content_file_1, position: 0, title: "Buzz Nonedrin") } + let!(:challenge_1_1) { create(:challenge, content_file: content_file_1, position: 1, title: "Buzzelzebub") } + + let!(:student_challenge_0_0_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_0, status: correct) } + let!(:student_challenge_0_1_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_1, status: correct) } + + let!(:student_challenge_0_0_sca) { create(:submitted_challenge_answer, user: student_2, challenge: challenge_0_0, status: correct) } + let!(:student_challenge_0_1_sca) { create(:submitted_challenge_answer, user: student_2, challenge: challenge_0_1, status: correct) } + + let!(:student_challenge_1_0_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_1_0, status: correct) } + let!(:student_challenge_1_1_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_1_1, status: correct) } + + let!(:student_challenge_1_0_sca) { create(:submitted_challenge_answer, user: student_3, challenge: challenge_1_0, status: correct) } + let!(:student_challenge_1_1_sca) { create(:submitted_challenge_answer, user: student_3, challenge: challenge_1_1, status: correct) } + + let!(:content_file_checkpoint) { create(:content_file, :checkpoint, standard: standard, title: "Bazz", position: 2) } + let!(:challenge_in_checkpoint) { create(:challenge, content_file: content_file_checkpoint, position: 0, title: "Bazz Yetu") } + + let!(:student_challenge_2_0_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_in_checkpoint, status: ungraded) } + + let!(:checkpoint_submission) do + create(:checkpoint_submission, + content_file_uid: content_file_checkpoint.uid, + content_file_block_id: standard.release.block_id, + user_id: student_1.id, + state: CheckpointSubmission::STATES[:needs_review]) + end + + before(:each) do + cohort.update(mode: Cohort::MODES[:percentage]) + visit course_stats_cohort_path(cohort) + end + + # works locally but not on circle + + # it "can sort students by percentage" do + # within(".progress-table") do + # find(".heading .percentage .label", text: "COMPLETE").click + # + # within(".progress-body") do + # rows = all(".progress-row") + # within(rows[0]) do + # expect(page).to have_content(student_1.full_name) + # end + # within(rows[1]) do + # expect(page).to have_content(student_2.full_name) + # end + # within(rows[2]) do + # expect(page).to have_content(student_3.full_name) + # end + # end + # + # find(".heading .percentage .label", text: "COMPLETE").click + # + # within(".progress-body") do + # rows = all(".progress-row") + # within(rows[0]) do + # expect(page).to have_content(student_2.full_name) + # end + # within(rows[1]) do + # expect(page).to have_content(student_3.full_name) + # end + # within(rows[2]) do + # expect(page).to have_content(student_1.full_name) + # end + # end + # end + # end + # + # it "can sort students by pending" do + # within(".progress-table") do + # find(".heading .percentage .label", text: "PENDING").click + # + # within(".progress-body") do + # rows = all(".progress-row") + # within(rows[0]) do + # expect(page).to have_content(student_1.full_name) + # end + # within(rows[1]) do + # expect(page).to have_content(student_2.full_name) + # end + # within(rows[2]) do + # expect(page).to have_content(student_3.full_name) + # end + # end + # + # find(".heading .percentage .label", text: "PENDING").click + # + # within(".progress-body") do + # rows = all(".progress-row") + # within(rows[0]) do + # expect(page).to have_content(student_2.full_name) + # end + # within(rows[1]) do + # expect(page).to have_content(student_3.full_name) + # end + # within(rows[2]) do + # expect(page).to have_content(student_1.full_name) + # end + # end + # end + # end + # + # it "can sort students by Standard Completion" do + # within(".progress-table") do + # find(".heading .mastery-progress .label", text: "STANDARD COMPLETION").click + # + # within(".progress-body") do + # rows = all(".progress-row") + # within(rows[0]) do + # expect(page).to have_content(student_1.full_name) + # end + # within(rows[1]) do + # expect(page).to have_content(student_2.full_name) + # end + # within(rows[2]) do + # expect(page).to have_content(student_3.full_name) + # end + # end + # + # find(".heading .mastery-progress .label", text: "STANDARD COMPLETION").click + # + # within(".progress-body") do + # rows = all(".progress-row") + # within(rows[0]) do + # expect(page).to have_content(student_2.full_name) + # end + # within(rows[1]) do + # expect(page).to have_content(student_3.full_name) + # end + # within(rows[2]) do + # expect(page).to have_content(student_1.full_name) + # end + # end + # end + # end + end + end +end diff --git a/scripts/spec/features/cohorts/submissions_spec.rb b/scripts/spec/features/cohorts/submissions_spec.rb new file mode 100644 index 0000000..36b317f --- /dev/null +++ b/scripts/spec/features/cohorts/submissions_spec.rb @@ -0,0 +1,491 @@ +require "features_helper" + +describe "Submissions", js: true do + let(:cohort) { create(:cohort) } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, profile_image: "http://www.s3.com/test.com"), created_at: 2.days.ago).user } + let!(:student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Bob", last_name: "Cook", profile_image: "http://www.s3.com/test-2.com"), created_at: 1.days.ago).user } + let!(:block) { create(:block) } + let!(:release) { create(:release, block: block) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let!(:standard_1) { create(:standard, release: release, position: 1, uid: "abc") } + let!(:standard_2) { create(:standard, release: release, position: 2, title: "CSS", uid: "123") } + let!(:content_file_1) { create(:content_file, :lesson, standard: standard_1, position: 1, path: "title1.md") } + let!(:content_file_2) { create(:content_file, :lesson, standard: standard_2, position: 2, path: "title2.md") } + let!(:checkpoint_content_file_1) { create(:content_file, :checkpoint, standard: standard_1, position: 3, path: "check.md") } + let!(:checkpoint_content_file_2) { create(:content_file, :checkpoint, standard: standard_2, position: 3, path: "check.md") } + let!(:challenge_1) { create(:challenge, content_file: content_file_1) } + let!(:challenge_2) { create(:challenge, content_file: content_file_1) } + let!(:challenge_3) { create(:challenge, content_file: content_file_2) } + let!(:checkpoint_challenge_1) { create(:challenge, content_file: checkpoint_content_file_1) } + let!(:checkpoint_challenge_2) { create(:challenge, content_file: checkpoint_content_file_2) } + + let(:path) { unit_progress_cohort_path(cohort) } + + let!(:submitted_challenge_answer_correct) { create(:submitted_challenge_answer, cohort: cohort, user: student_1, challenge: challenge_1, status: SubmittedChallengeAnswer::STATUSES[:correct]) } + let!(:submitted_challenge_answer_ungraded) { create(:submitted_challenge_answer, cohort: cohort, user: student_1, challenge: challenge_2, status: SubmittedChallengeAnswer::STATUSES[:ungraded]) } + let!(:submitted_challenge_answer_incorrect) { create(:submitted_challenge_answer, cohort: cohort, user: student_2, challenge: challenge_1, status: SubmittedChallengeAnswer::STATUSES[:incorrect]) } + + before do + sign_in(instructor) + visit path + # then close open standards + all(".standardtitle.-open").each do |open_standard| + within open_standard do + find(".arrow").click + end + end + end + + describe "Large Cohorts" do + it "displays a message saying content is hidden" do + 500.times do + create(:cohort_user, :student, cohort: cohort, user: create(:user)) + end + visit unit_progress_cohort_path(cohort) + expect(page).to have_content "Data not shown for cohorts with more than 500 students enrolled. Visit the Cohort Users page to see individual student submission data." + end + end + + describe "Curriculum panel" do + it "displays standards" do + within(".curriculumpanel") do + standards = all(".standard") + expect(standards.length).to eq(2) + + within(standards[0]) do + within(".standardtitle") do + expect(page).to have_selector(".title", text: standard_1.title.upcase) + end + end + + within(standards[1]) do + within(".standardtitle") do + expect(page).to have_selector(".title", text: standard_2.title.upcase) + end + end + end + end + + it "displays content files" do + within(".curriculumpanel") do + standards = all(".standard") + + within(standards[0]) do + find(".arrow").click + wait_for_ajax + content_files = all(".standarditem") + expect(content_files.length).to eq(2) + end + + within(standards[1]) do + find(".arrow").click + wait_for_ajax + content_files = all(".standarditem") + expect(content_files.length).to eq(2) + end + end + end + + it "displays challenges for each content_file" do + visit path + within(".curriculumpanel") do + standards = all(".standard") + within(standards[0]) do + find(".arrow").click + wait_for_ajax + content_files = all(".standarditem") + + within(content_files[0]) do + find(".arrow").click + challenges = all(".itembody") + expect(challenges.length).to eq(2) + end + + within(content_files[1]) do + find(".arrow").click + challenges = all(".itembody") + expect(challenges.length).to eq(1) + end + end + end + end + end + + describe "Student avatars" do + it "diplays the student avatars" do + within(".studentspanel") do + student_columns = all(".studentcolumn .student .user-avatar") + expect(student_columns.length).to eq(2) + end + end + + it "links to the student's challenges page" do + within(".studentspanel") do + within(first(".studentcolumn")) do + expect(find(".student")[:href]).to include submissions_dashboard_cohort_user_path(cohort, student_2) + end + end + end + end + + describe "Student standard scores" do + let!(:older_checkpoint_submission) do + create(:checkpoint_submission, + cohort_id: cohort.id, + user_id: student_1.id, + content_file_uid: checkpoint_content_file_1.uid, + content_file_block_id: block.id, + state: CheckpointSubmission::STATES[:needs_review], + created_at: 1.day.ago) + end + let!(:current_checkpoint_submission) do + create(:checkpoint_submission, + cohort_id: cohort.id, + user_id: student_1.id, + content_file_uid: checkpoint_content_file_1.uid, + content_file_block_id: block.id, + state: CheckpointSubmission::STATES[:needs_review]) + end + let!(:old_challenge_submission) do + create(:submitted_challenge_answer, + user: student_1, + cohort: cohort, + challenge: checkpoint_challenge_1, + created_at: 1.day.ago, + checkpoint_submission_id: older_checkpoint_submission.id, + status: "incorrect") + end + let!(:current_challenge_submission) do + create(:submitted_challenge_answer, + user: student_1, + cohort: cohort, + checkpoint_submission_id: current_checkpoint_submission.id, + challenge: checkpoint_challenge_1, + status: "incorrect") + end + + context "in mastery mode" do + it "generates a link to the latest ungraded checkpoint submission" do + visit path + within all(".studentcolumn")[1] do + within all(".standard")[0] do + expect(page).to have_selector(:css, 'a[href="' + Rails.application.routes.url_helpers.cohort_checkpoint_submission_path(cohort, current_checkpoint_submission) + '"]') + end + end + end + + it "allows navigation to rejected checkpoint submissoins which have a grade" do + current_checkpoint_submission.update(state: CheckpointSubmission::STATES[:rejected], grader_id: create(:user).id) + create(:performance, + cohort: cohort, + user: student_1, + standard_id: standard_1.id, + standard_uid: standard_1.uid, + score: 3, + checkpoint_submission_id: current_checkpoint_submission.id + ) + visit path + within all(".studentcolumn")[1] do + within all(".standard")[0] do + find(:css, 'a[href="' + Rails.application.routes.url_helpers.cohort_checkpoint_submission_path(cohort, current_checkpoint_submission) + '"]') + end + end + end + + it "does not display 'started' state checkpoints" do + current_checkpoint_submission.update(state: CheckpointSubmission::STATES[:started], grader_id: create(:user).id) + SubmittedChallengeAnswer.destroy_all + visit path + within all(".studentcolumn")[1] do + within all(".standard")[0] do + expect(page).to_not have_css('a[href="' + Rails.application.routes.url_helpers.cohort_checkpoint_submission_path(cohort, current_checkpoint_submission) + '"]') + end + end + end + end + + context "in percentage mode" do + before do + cohort.update(mode: Cohort::MODES[:percentage]) + end + + context "checkpoint visibility changes" do + it "does not include checkpoint submission details when the checkpoint is hidden" do + current_checkpoint_submission.update(state: CheckpointSubmission::STATES[:done], total_points: 10, correct_points: 7) + create(:performance, score: 3, cohort: cohort, user: student_1, checkpoint_submission: current_checkpoint_submission, standard: standard_1, standard_uid: standard_1.uid) + visit path + within all(".studentcolumn")[1] do + within all(".standard")[0] do + expect(page).to have_content("70") + end + end + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: checkpoint_content_file_1.uid) + + visit path + within all(".studentcolumn")[1] do + within all(".standard")[0] do + expect(page).to_not have_content("70") + expect(all("#ic_check_24px").length).to eq 1 + end + end + end + end + + it "generates a link to the latest ungraded checkpoint submission" do + visit path + within all(".studentcolumn")[1] do + within all(".standard")[0] do + expect(page).to have_selector(:css, 'a[href="' + Rails.application.routes.url_helpers.cohort_checkpoint_submission_path(cohort, current_checkpoint_submission) + '"]') + end + end + end + + it "shows the percentage of a completed checkpoints" do + older_checkpoint_submission.update(state: CheckpointSubmission::STATES[:done]) + current_checkpoint_submission.update(state: CheckpointSubmission::STATES[:done]) + create(:performance, score: 3, cohort: cohort, user: student_1, checkpoint_submission: current_checkpoint_submission, standard: standard_1, standard_uid: standard_1.uid) + current_challenge_submission.update(status: SubmittedChallengeAnswer::STATUSES[:correct]) + visit path + + within all(".studentcolumn")[1] do + within all(".standard")[0] do + expect(page).to have_content("100%") + end + end + end + + it "shows the percentage of completed checkpoints in the checkpoint content file row" do + older_checkpoint_submission.update(state: CheckpointSubmission::STATES[:done]) + current_checkpoint_submission.update(state: CheckpointSubmission::STATES[:done]) + create(:performance, score: 3, cohort: cohort, user: student_1, checkpoint_submission: current_checkpoint_submission, standard: standard_1, standard_uid: standard_1.uid) + current_challenge_submission.update(status: SubmittedChallengeAnswer::STATUSES[:correct]) + visit path + + within all(".standardtitle")[0] do + find(".arrow").click + end + within all(".studentcolumn")[1] do + within ".checkpoint" do + expect(page).to have_content("100%") + end + end + end + + it "shows an emdash when there is an ungraded submission" do + older_checkpoint_submission.update(state: CheckpointSubmission::STATES[:needs_review]) + current_checkpoint_submission.update(state: CheckpointSubmission::STATES[:needs_review]) + visit path + + within all(".standardtitle")[0] do + find(".arrow").click + end + within all(".studentcolumn")[1] do + within ".checkpoint" do + find(".emdash") + end + end + end + + it "works when a student has zero percent" do + user = create(:user, first_name: 'Carly', last_name: 'Carlson', profile_image: "http://www.example.com") + create(:cohort_user, :student, cohort: cohort, user: user) + + visit path + links = all("a[href=\"/cohorts/#{cohort.id}/users/#{user.id}/submissions_dashboard\"]") + expect(links.count).to equal 1 + end + end + end + + describe "Student challenge statuses" do + it "displays challenge status for each student" do + visit path + within(".curriculumpanel") do + standards = all(".standard") + within(standards[0]) do + first(".arrow").click + wait_for_ajax + end + within(standards[1]) do + first(".arrow").click + wait_for_ajax + end + + within(standards[0]) do + content_files = all(".standarditem") + within(content_files[0]) do + if all(".itembody").length == 0 + first(".arrow").click + wait_for_ajax + end + challenges = all(".itembody") + expect(challenges.length).to eq(2) + end + + within(content_files[1]) do + if all(".itembody").length == 0 + first(".arrow").click + wait_for_ajax + end + challenges = all(".itembody") + expect(challenges.length).to eq(1) + end + end + end + within(".studentspanel") do + student_columns = all(".studentcolumn") + within(student_columns[0]) do + cells = all(".standard .standarditem") + within(cells[1]) do + # content file rollup + within(".itemheading") do + svg_circles = all("svg circle") + expect(svg_circles.count).to eq(3) + end + + # challenge status symbols + challenge_status_symbols = all(".itembody") + within(challenge_status_symbols[0]) do + expect(first("svg use").native["xlink:href"]).to eq("/assets/images/svg/svg-sprite-navigation-symbol.svg#ic_close_24px") + end + end + end + within(student_columns[1]) do + cells = all(".standard .standarditem") + within(cells[1]) do + # content file rollup + within(".itemheading") do + svg_circles = all("svg circle") + expect(svg_circles.count).to eq(4) + end + + # challenge status symbols + challenge_status_symbols = all(".itembody") + within(challenge_status_symbols[0]) do + expect(first("svg use").native["xlink:href"]).to eq("/assets/images/svg/svg-sprite-navigation-symbol.svg#ic_check_24px") + end + + expect(challenge_status_symbols[1].all("*").length).to eq(4) + end + end + end + end + end + + context "filtering in percentage mode" do + let!(:checkpoint_submission) { create_checkpoint_submission(student_1, checkpoint_challenge_1, cohort: cohort) } + let!(:submitted_challenge_answer_ungraded) { create(:submitted_challenge_answer, cohort: cohort, user: student_1, challenge: checkpoint_challenge_1, status: SubmittedChallengeAnswer::STATUSES[:ungraded], checkpoint_submission: checkpoint_submission) } + + before { cohort.update(mode: Cohort::MODES[:percentage]) } + + # Alpha and needs grading works the same way as mastery + it "can filter by standards progress" do + submitted_challenge_answer_ungraded.update(status: SubmittedChallengeAnswer::STATUSES[:correct]) + checkpoint_submission.update(state: CheckpointSubmission::STATES[:done]) + + create(:submitted_challenge_answer, cohort: cohort, user: student_1, challenge: challenge_1, status: SubmittedChallengeAnswer::STATUSES[:correct]) + create(:submitted_challenge_answer, cohort: cohort, user: student_1, challenge: challenge_2, status: SubmittedChallengeAnswer::STATUSES[:correct]) + + visit path + + find("#challengeIndexSortDropdown").find(:xpath, "option[2]").select_option + within(".studentspanel") do + within(first(".studentcolumn")) do + within(first(".student")) do + expect(page.find("div")["style"]).to eq("background-image: url(\"#{student_1.profile_image}\");") + end + end + end + + find("#challengeIndexSortDropdown").find(:xpath, "option[1]").select_option + + within(".studentspanel") do + within(first(".studentcolumn")) do + within(first(".student")) do + expect(page.find("div")["style"]).to eq("background-image: url(\"#{student_2.profile_image}\");") + end + end + end + end + + end + + context "filtering in mastery mode" do + let!(:checkpoint_submission) { create_checkpoint_submission(student_1, checkpoint_challenge_1, cohort: cohort) } + let!(:submitted_challenge_answer_ungraded) { create(:submitted_challenge_answer, cohort: cohort, user: student_1, challenge: checkpoint_challenge_1, status: SubmittedChallengeAnswer::STATUSES[:ungraded], checkpoint_submission: checkpoint_submission) } + + it "can filter students alphabetically" do + find("#challengeIndexSortDropdown").find(:xpath, "option[1]").select_option + + within(".studentspanel") do + within(first(".studentcolumn")) do + within(first(".student")) do + expect(page.find("div")["style"]).to eq("background-image: url(\"#{student_2.profile_image}\");") + end + end + end + end + + it "can filter students registration date asc" do + find("#challengeIndexSortDropdown").find(:xpath, "option[4]").select_option + + within(".studentspanel") do + within(first(".studentcolumn")) do + within(first(".student")) do + expect(page.find("div")["style"]).to eq("background-image: url(\"#{student_2.profile_image}\");") + end + end + end + end + + it "can filter by standards progress" do + submitted_challenge_answer_ungraded.update(status: SubmittedChallengeAnswer::STATUSES[:correct]) + checkpoint_submission.update(state: CheckpointSubmission::STATES[:done]) + create(:performance, score: 3, cohort: cohort, user: student_1, checkpoint_submission: checkpoint_submission, standard: standard_1, standard_uid: standard_1.uid) + + visit path + + find("#challengeIndexSortDropdown").find(:xpath, "option[2]").select_option + within(".studentspanel") do + within(first(".studentcolumn")) do + within(first(".student")) do + expect(page.find("div")["style"]).to eq("background-image: url(\"#{student_1.profile_image}\");") + end + end + end + + find("#challengeIndexSortDropdown").find(:xpath, "option[1]").select_option + + within(".studentspanel") do + within(first(".studentcolumn")) do + within(first(".student")) do + expect(page.find("div")["style"]).to eq("background-image: url(\"#{student_2.profile_image}\");") + end + end + end + end + + it "can filter by student awaiting grading" do + find("#challengeIndexSortDropdown").find(:xpath, "option[3]").select_option + + within(".studentspanel") do + within(first(".studentcolumn")) do + within(first(".student")) do + expect(page.find("div")["style"]).to eq("background-image: url(\"#{student_1.profile_image}\");") + end + end + end + + find("#challengeIndexSortDropdown").find(:xpath, "option[1]").select_option + + within(".studentspanel") do + within(first(".studentcolumn")) do + within(first(".student")) do + expect(page.find("div")["style"]).to eq("background-image: url(\"#{student_2.profile_image}\");") + end + end + end + end + end +end diff --git a/scripts/spec/features/cohorts/users/challenges_feature_spec.rb b/scripts/spec/features/cohorts/users/challenges_feature_spec.rb new file mode 100644 index 0000000..8db44db --- /dev/null +++ b/scripts/spec/features/cohorts/users/challenges_feature_spec.rb @@ -0,0 +1,361 @@ +require "features_helper" + +describe "Challenges" do + describe "#show, Single user, single challenge view", js: true do + let(:cohort) { create(:cohort) } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:block) { create(:block) } + let(:release) { create(:release, block: block) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let(:standard_1) { create(:standard, release: release, position: 1) } + let(:standard_2) { create(:standard, release: release, position: 2, title: "CSS") } + + let(:content_file_1) { create(:content_file, standard: standard_1) } + let(:content_file_2) { create(:content_file, standard: standard_2) } + + let(:challenge_1) { create(:challenge, content_file: content_file_1, position: 1, explanation: "foo", uid: "ooid") } + let(:challenge_2) { create(:challenge, content_file: content_file_1, position: 2) } + let!(:challenge_3) { create(:challenge, content_file: content_file_2, position: 2) } + + let!(:submitted_challenge_answer_1) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1, status: SubmittedChallengeAnswer::STATUSES[:correct], answer: "http://example.com") } + let!(:submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, status: SubmittedChallengeAnswer::STATUSES[:ungraded], answer: "http://example.com") } + + before do + sign_in(user) + visit path + end + + context "when the latest submitted challenge answer has already been graded" do + let(:path) { cohort_user_challenge_path(cohort, student, challenge_1) } + + context "as an instructor" do + let(:user) { instructor } + + it "shows the single student, single challenge view interactively" do + within(".student-name-bar") do + expect(page).to have_content(student.full_name) + + # It links back to the cohort challenges view + expect(page).to have_link("Back to Submissions", href: unit_progress_cohort_path(cohort)) + end + + find("#challenge_#{challenge_1.id}").click # ensure window focus + within("#challenge_#{challenge_1.id}") do + expect(find(".answer a")["href"]).to eq("http://example.com/") + expect(page).to have_selector(".challenge-grading-buttons") + expect(page).to have_content("HIDE EXPLANATION") + expect(page).to have_content(challenge_1.explanation) + + within(".challenge-grading-buttons") do + find(".correct-check").hover + find(".incorrect-status").click + find(".timestamp").hover # close scoring menu + within(".status-section") do + find(".incorrect-check") + end + expect(submitted_challenge_answer_1.reload.status).to eq("incorrect") + expect(find(".timestamp").text).to include("#{user.full_name} today at") + + find(".incorrect-check").hover + find(".correct-status").click + find(".timestamp").hover # close scoring menu + within(".status-section") do + find(".correct-check") + end + expect(submitted_challenge_answer_1.reload.status).to eq("correct") + expect(find(".timestamp").text).to include("#{user.full_name} today at") + end + + end + end + end + + context "as a student" do + let(:user) { student } + + it "shows the single student, single challenge view" do + within(".primary-header") do + expect(page).to have_content(challenge_1.title) + num_zero = content_file_1.position < 10 ? '0' : '' + expect(page).to have_content("LESSON #{num_zero}#{content_file_1.position}") + end + + within(".student-name-bar") do + # It links back to the single student challenges view + expect(page).to have_link("Back to Submissions", href: submissions_dashboard_cohort_user_path(cohort, student)) + + # It does not show the student avatar + expect(page).to_not have_selector(".user-avatar") + end + + within("#challenge_#{challenge_1.id}") do + expect(find(".answer a")["href"]).to eq("http://example.com/") + expect(page).to have_content("HIDE EXPLANATION") + expect(page).to have_content(challenge_1.explanation) + expect(page).to have_selector(".challenge-grading-buttons") + + within(".challenge-grading-buttons") do + expect(page).to_not have_css(".incorrect-status") + expect(find(".timestamp").text).to include("Autograded today at") + end + end + end + end + end + + context "when the latest submitted challenge answer has not been graded" do + let(:path) { cohort_user_challenge_path(cohort, student, challenge_2) } + + context "as an instructor" do + let(:user) { instructor } + + it "shows the single student, single challenge view interactively" do + within("#challenge_#{challenge_2.id}") do + expect(find(".answer a")["href"]).to eq("http://example.com/") + expect(page).to have_selector(".challenge-grading-buttons") + + within(".challenge-grading-buttons") do + expect(page).to_not have_selector(".timestamp", text: "Challenge not scored.") + end + end + end + + context "when the incorrect manual grade button is clicked" do + it "changes state and the incorrect button becomes active" do + within("#challenge_#{challenge_2.id}") do + within(".challenge-grading-buttons") do + find(".incorrect-status").click + within(".status-section") do + find(".incorrect-check") + end + expect(submitted_challenge_answer_2.reload.status).to eq("incorrect") + end + end + end + end + + context "when the correct manual grade button is clicked" do + it "changes state and the correct button becomes active" do + within("#challenge_#{challenge_2.id}") do + within(".challenge-grading-buttons") do + find(".correct-status").click + + within(".status-section") do + find(".correct-check") + end + expect(submitted_challenge_answer_2.reload.status).to eq("correct") + end + end + end + end + end + + context "as a student" do + let(:user) { student } + + it "shows the single student, single challenge view" do + within("#challenge_#{challenge_2.id}") do + expect(find(".answer a")["href"]).to eq("http://example.com/") + expect(page).to have_selector(".challenge-grading-buttons") + + within(".challenge-header") do + expect(page).to have_selector("#attempt_button_#{challenge_2.id}", text: "1 ATTEMPT") + end + + within(".challenge-grading-buttons") do + expect(page).to_not have_css(".correct-status") + expect(page).to_not have_css(".incorrect-status") + expect(find(".timestamp").text).to eq("Challenge not scored.") + end + end + + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, status: SubmittedChallengeAnswer::STATUSES[:incorrect]) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, status: SubmittedChallengeAnswer::STATUSES[:correct]) + visit path + + within(".challenge-header") do + expect(page).to have_selector("#attempt_button_#{challenge_2.id}", text: "3 ATTEMPTS") + end + end + end + end + + context "when the tests are still running" do + let!(:submitted_challenge_answer_1) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1, status: SubmittedChallengeAnswer::STATUSES[:processing]) } + let(:path) { cohort_user_challenge_path(cohort, student, challenge_1) } + let(:user) { student } + + it "should show the processing icon" do + within ".challenge-grading-buttons" do + expect(page).to_not have_css(".correct-status") + expect(page).to_not have_css(".incorrect-status") + expect(page).to have_css(".processing-icon") + expect(page).to have_content("Running Tests") + end + + submitted_challenge_answer_1.update!(status: SubmittedChallengeAnswer::STATUSES[:correct]) + + within ".challenge-grading-buttons" do + expect(page).to have_css(".correct-check") + expect(page).to_not have_css(".processing-icon") + expect(page).to_not have_content("Running Tests") + end + end + end + + describe "activity feed for comments on a submitted_challenge_answer for the challenge" do + let(:user) { student } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1) } + let(:path) { cohort_user_challenge_path(cohort, student, challenge_1) } + + before { visit path } + + it "displays the activity" do + within(".challenge-detail-comments-wrapper") do + expect(page).to have_selector(".null-comments-block", text: "There is no activity on this challenge yet.") + expect(page).to_not have_selector(".challenge-detail-comments") + end + + comment_two_days = create(:activity, subject: submitted_challenge_answer, content: "maaaaa, MEATLOAF!", creator: instructor, created_at: 2.days.ago, cohort: cohort, name: Activity::NAMES[:comment_created]) + comment_one_day = create(:activity, subject: submitted_challenge_answer, content: "Green Eggs and ham", creator: student, created_at: 1.day.ago, cohort: cohort, name: Activity::NAMES[:comment_created]) + comment_now = create(:activity, subject: submitted_challenge_answer, content: "Jeff is a bully.", creator: instructor, cohort: cohort, name: Activity::NAMES[:comment_created]) + visit path + + within(".challenge-detail-comments") do + within("#comment-#{comment_two_days.id}") do + expect(page).to have_content(comment_two_days.content) + expect(page).to have_content(instructor.full_name) + if instructor.profile_image + expect(page).to have_content(instructor.profile_image) + end + expect(page).to have_content("#{comment_two_days.created_at.localtime.strftime('%A')} at ") + end + + within("#comment-#{comment_one_day.id}") do + expect(page).to have_content(comment_one_day.content) + expect(page).to have_content(student.full_name) + if student.profile_image + expect(page).to have_content(student.profile_image) + end + expect(page).to have_content("Yesterday at ") + end + + within("#comment-#{comment_now.id}") do + expect(page).to have_content(comment_now.content) + expect(page).to have_content(instructor.full_name) + if instructor.profile_image + expect(page).to have_content(instructor.profile_image) + end + expect(page).to have_content("Today at ") + end + end + end + + it "allows a new comment to be made" do + within(".new-comment-form") do + fill_in("comment", with: "this is a ```test```") + click_on("Send") + end + wait_for_ajax + + within("#comment-#{Activity.last.id}") do + expect(page).to have_content("this is a ") + expect(page).to have_content(instructor.full_name) + if instructor.profile_image + expect(page).to have_content(instructor.profile_image) + end + expect(page).to have_content("Today at ") + + within("code") do + expect(page).to have_content("test") + end + end + end + end + + describe "timeline of submitted challenge answers" do + let(:path) { cohort_user_challenge_path(cohort, student, challenge_1) } + let!(:submitted_challenge_answer_3) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1, status: SubmittedChallengeAnswer::STATUSES[:ungraded]) } + + context "as a student" do + let(:user) { student } + + it "does not show drafts in submission history" do + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_1, status: SubmittedChallengeAnswer::UNGRADED_STATUSES[:draft]) + visit path + within(".challenge-header") do + expect(page).to have_content("2 ATTEMPTS") + end + end + + it "allows student to view submission history and traverse through their submissions" do + visit path + within(".challenge-header") do + expect(page).to have_content("2 ATTEMPTS") + find(".lp-style-button").click + expect(page).to have_content("EXIT HISTORY") + end + + expect(page).to have_css(".challenge-timeline") + + within(".challenge-timeline") do + expect(page).to have_css("li", count: 2) + #expect(page).to have_content(submitted_challenge_answer_3.created_at.localtime.strftime("%B #{submitted_challenge_answer_3.created_at.localtime.day.ordinalize} %Y at %l:%M%P").to_s) + expect(page).to have_content(submitted_challenge_answer_3.created_at.localtime.strftime("%B #{submitted_challenge_answer_3.created_at.localtime.day.ordinalize} %Y at %-l:%M%P").to_s) + + expect(page).to have_css("div.forward.disabled") + expect(find("div.forward svg use").native["xlink:href"]).to eq("/assets/images/svg/svg-sprite-av-symbol.svg#ic_fast_forward_24px") + expect(find("div.rewind svg use").native["xlink:href"]).to eq("/assets/images/svg/svg-sprite-av-symbol.svg#ic_fast_rewind_24px") + expect(page).to have_css("#submission-0.highlight") + end + + expect(page).to have_content("Challenge not scored.") + find("div.rewind").click + + within(".challenge-timeline") do + expect(page).to have_content(submitted_challenge_answer_1.created_at.localtime.strftime("%B #{submitted_challenge_answer_1.created_at.localtime.day.ordinalize} %Y at %-l:%M%P").to_s) + expect(page).to have_css("div.rewind.disabled") + expect(page).to have_css("#submission-1.highlight") + end + + expect(page).to have_content("Autograded") + + click_on("Exit History") + expect(page).to have_content("Challenge not scored.") + end + end + + context "as an instructor" do + let(:user) { instructor } + + it "allows instructor to grade each sumbission" do + visit path + within(".challenge-header") do + expect(page).to have_content("2 ATTEMPTS") + find(".lp-style-button").click + expect(page).to have_content("EXIT HISTORY") + end + + within(".challenge-timeline") do + expect(page).to have_css("li", count: 2) + expect(page).to have_content(submitted_challenge_answer_3.created_at.localtime.strftime("%B #{submitted_challenge_answer_3.created_at.localtime.day.ordinalize} %Y at %-l:%M%P").to_s) + + expect(page).to have_css("div.forward.disabled") + expect(find("div.forward svg use").native["xlink:href"]).to eq("/assets/images/svg/svg-sprite-av-symbol.svg#ic_fast_forward_24px") + expect(find("div.rewind svg use").native["xlink:href"]).to eq("/assets/images/svg/svg-sprite-av-symbol.svg#ic_fast_rewind_24px") + expect(page).to have_css("#submission-0.highlight") + end + + find("div.rewind").click + find(".correct-check").hover + find(".incorrect-status").click + + within(".status-section") do + find(".incorrect-check") + end + end + end + end + end +end diff --git a/scripts/spec/features/cohorts/users/submissions_dashboard_feature_spec.rb b/scripts/spec/features/cohorts/users/submissions_dashboard_feature_spec.rb new file mode 100644 index 0000000..7472fd5 --- /dev/null +++ b/scripts/spec/features/cohorts/users/submissions_dashboard_feature_spec.rb @@ -0,0 +1,52 @@ +require "features_helper" + +describe "Single-student view", js: true do + let(:cohort) { create(:cohort) } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, profile_image: "http://www.s3.com/test.com")).user } + let!(:student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Bob", last_name: "Cook", profile_image: "http://www.s3.com/test-2.com")).user } + let!(:block) { create(:block) } + let!(:release) { create(:release, block: block) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let!(:standard_1) { create(:standard, release: release, position: 1) } + let!(:standard_2) { create(:standard, release: release, position: 2, title: "CSS") } + let!(:content_file_1) { create(:content_file, :lesson, standard: standard_1, position: 1, path: "title1.md") } + let!(:content_file_2) { create(:content_file, :lesson, standard: standard_2, position: 2, path: "title2.md") } + let!(:checkpoint_content_file) { create(:content_file, :checkpoint, standard: standard_2, position: 3, path: "check.md") } + let!(:challenge_1) { create(:challenge, content_file: content_file_1, position: 1) } + let!(:challenge_2) { create(:challenge, content_file: content_file_1, position: 2) } + let!(:challenge_3) { create(:challenge, content_file: content_file_2, position: 2) } + let!(:checkpoint_challenge) { create(:challenge, content_file: checkpoint_content_file, position: 1) } + let(:path) { submissions_dashboard_cohort_user_path(cohort, student_2) } + let!(:submitted_challenge_answer_1) { create(:submitted_challenge_answer, cohort: cohort, user: student_1, challenge: challenge_1, status: SubmittedChallengeAnswer::STATUSES[:correct]) } + let!(:submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: student_1, challenge: challenge_2, status: SubmittedChallengeAnswer::STATUSES[:ungraded]) } + let!(:submitted_challenge_answer_3) { create(:submitted_challenge_answer, cohort: cohort, user: student_2, challenge: challenge_1, status: SubmittedChallengeAnswer::STATUSES[:incorrect]) } + + context "when signed in as a student" do + before do + sign_in(student_2) + visit path + end + + it "displays one student" do + within(".studentspanel") do + standards = all(".studentcolumn") + expect(standards.count).to eq(1) + end + end + end + + context "when signed in as an instructor" do + before do + sign_in(instructor) + visit path + end + + it "displays one student" do + within(".studentspanel") do + standards = all(".studentcolumn") + expect(standards.count).to eq(1) + end + end + end +end diff --git a/scripts/spec/features/content_files/checkpoint_assessments_spec.rb b/scripts/spec/features/content_files/checkpoint_assessments_spec.rb new file mode 100644 index 0000000..2796930 --- /dev/null +++ b/scripts/spec/features/content_files/checkpoint_assessments_spec.rb @@ -0,0 +1,632 @@ +require "features_helper" + +describe "Viewing checkpoints", js: true do + let(:cohort) { create(:cohort, mode: Cohort::MODES[:percentage]) } + let!(:release) { create(:cohort_release, cohort: cohort).release } + let!(:standard) { create(:standard, release: release) } + let!(:checkpoint) { create(:content_file, + :checkpoint, + html: '
    ', + standard: standard, + max_checkpoint_submissions: 5) } + + context "as a student" do + let(:student) { create(:cohort_user, cohort: cohort).user } + let!(:another_student) { create(:cohort_user, cohort: cohort).user } + + before do + sign_in(student) + end + + describe "student scores section" do + it "should not be visible for students" do + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to_not have_selector(".checkpoint-student-scores") + end + end + + describe "landing attributes" do + it "displays the checkpoint attributes" do + challenge_short_answer = create(:challenge, uid: "abc", content_file: checkpoint, challenge_type: Challenge::TYPES[:short_answer]) + visit content_file_path(cohort, release.block, checkpoint.path) + + within(".challenge-count") do + expect(page).to have_content("1") + expect(page).to have_content("Question") + end + + within(".checkpoint-attempts") do + expect(page).to have_content("5") + expect(page).to have_content("Attempts") + end + + create(:checkpoint_submission, user: student, content_file_uid: checkpoint.uid, content_file_block_id: release.block.id, cohort: cohort) + visit content_file_path(cohort, release.block, checkpoint.path) + + within(".checkpoint-attempts") do + expect(page).to have_content("4") + expect(page).to have_content("Attempts") + end + end + + it "lists challenge type 'buckets' (multiple choice, free-response, and coding challenges) in a paragraph with challenge count details" do + challenge_short_answer = create(:challenge, uid: "abc", content_file: checkpoint, challenge_type: Challenge::TYPES[:short_answer]) + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to_not have_content "Answer as many questions as possible and submit the entire assessment at once when you’re done." + expect(page).to have_content "This assessment contains free-response challenges." + + challenge_mutliple_choice = create(:challenge, + uid: "efg", + content_file: checkpoint, + challenge_type: Challenge::TYPES[:multiple_choice]) + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to have_content "Answer as many questions as possible and submit the entire assessment at once when you’re done." + expect(page).to have_content "This assessment contains multiple choice and free-response challenges." + + challenge_code_snippet = create(:challenge, uid: "hij", content_file: checkpoint, challenge_type: Challenge::TYPES[:code_snippet]) + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to have_content "Answer as many questions as possible and submit the entire assessment at once when you’re done." + expect(page).to have_content "This assessment contains multiple choice, free-response, and coding challenges." + end + end + + describe "taking a retry checkpoint" do + let!(:retry_checkpoint) { create(:checkpoint_submission, + cohort: cohort, + user: student, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block.id, + state: "retry", + correct_points: 5, + total_points: 10 + ) } + it "the landing displays previously scored checkpoints" do + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to have_content("5/10") + expect(page).to have_content "Retake Assessment" + end + end + + context "timed checkpoint" do + let!(:timed_checkpoint) { create(:content_file, + :checkpoint, + html: '
    ', + standard: standard, + max_checkpoint_submissions: 5, time_limit: 33) } + + describe "starting a time assessment" do + it "the landing page displays time remaining" do + visit content_file_path(cohort, release.block, timed_checkpoint.path) + + within(".checkpoint-time") do + expect(page).to have_content(33) + expect(page).to have_content("min") + end + + find(".checkpoint-landing-button").click + + within(".modal-main") do + expect(page).to have_content("33") + expect(page).to have_content("min") + expect(page).to have_content("This assessment is timed. Once you start the assessment, if you navigate away from the page or close your browser, the timer will continue to run.") + end + + within(".modal-actions") do + click_button("Start Assessment") + end + + within(".checkpoint-timer") do + expect(page).to have_content(33) + expect(page).to have_content("min") + end + end + end + + describe "started the checkpoint already" do + let!(:started_checkpoint) { create(:checkpoint_submission, + cohort: cohort, + user: student, + content_file_uid: timed_checkpoint.uid, + content_file_block_id: release.block.id, + state: "started", + correct_points: 5, + total_points: 10, + created_at: 10.minutes.ago + ) } + + it "the landing page displays time remaining" do + visit content_file_path(cohort, release.block, timed_checkpoint.path) + + within ".timed-checkpoint" do + expect(page).to have_content("23 minutes remaining!") + end + end + end + end + + describe "with pending and done submissions" do + let!(:done_checkpoint) { create(:checkpoint_submission, + cohort: cohort, + user: student, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block.id, + state: "done", + correct_points: 5, + total_points: 10, + created_at: 1.day.ago + ) } + let!(:pending_checkpoint) { create(:checkpoint_submission, + cohort: cohort, + user: student, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block.id, + state: "needs_review" + ) } + it "only displays 'pending' when the pending was created after the done" do + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to have_content "Pending" + expect(page).to have_content("5/10") + pending_checkpoint.update(created_at: 2.days.ago) + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to_not have_content "Pending" + expect(page).to have_content("5/10") + end + end + + describe "Save And Exit" do + before do + challenge_short_answer = create(:challenge, uid: "abc", content_file: checkpoint, challenge_type: Challenge::TYPES[:short_answer]) + visit content_file_path(cohort, release.block, checkpoint.path) + end + + context "when clicking Save And Exit" do + it "shows the proper modal" do + find(".checkpoint-landing-button").click + within(".checkpoint-navbar-details") do + click_button "Save And Exit" + end + expect(page).to have_content "Save progress and exit this page. Your answers will not be submitted for scoring." + within ".modal-actions" do + click_button("Back To Test") + end + within(".checkpoint-navbar-details") do + click_button "Save And Exit" + end + within ".modal-actions" do + click_button("Save And Exit") + end + expect(page).to have_content checkpoint.title + end + end + + context "when clicking on the galvanize logo" do + it "shows the proper modal" do + find(".checkpoint-landing-button").click + within(".checkpoint-navbar") do + find(".logo").click + end + expect(page).to have_content "Save progress and exit this page. Your answers will not be submitted for scoring." + within ".modal-actions" do + click_button("Back To Test") + end + within(".checkpoint-navbar") do + find(".logo").click + end + within ".modal-actions" do + click_button("Save And Exit") + end + expect(page).to have_content checkpoint.title + end + end + + context "when hitting the back button" do + xit "shows the proper modal" do # TODO Not sure how this will work + visit content_file_path(cohort, release.block, checkpoint.path) + find(".checkpoint-landing-button").click + page.evaluate_script('window.history.back()') + expect(page).to have_content "Save progress and exit this page. Your answers will not be submitted for scoring." + within ".modal-actions" do + click_button("Back To Test") + end + end + end + end + + describe "submit modal" do + context "checkpoint is manually scored" do + let!(:standard_one_attempt) { create(:standard, release: release) } + let!(:checkpoint_one_attempt) { create(:content_file, + :checkpoint, + html: '
    ', + standard: standard_one_attempt, + max_checkpoint_submissions: 1) } + let!(:challenge) { create(:challenge, uid: "abc", content_file: checkpoint_one_attempt) } + + it "displays number of challenges with the number of attempted and warns on last attempt, then prevents more attempts" do + visit content_file_path(cohort, release.block, checkpoint_one_attempt.path) + find(".checkpoint-landing-button").click + expect(page).to have_content challenge.title + within(".checkpoint-navbar-details") do + click_button("Submit") + end + expect(page).to have_content "You have attempted 0/1 questions. Once you submit, you will not be able to submit again." + within ".modal-actions" do + click_button("Back To Test") + end + within "#challenge-abc" do + find(:css, "input").set("answer") + end + within(".checkpoint-navbar-details") do + click_button("Submit") + end + expect(page).to have_content "You have attempted 1/1 questions. Once you submit, you will not be able to submit again." + within ".modal-actions" do + click_button("Submit All Answers") + end + click_button("Continue") + visit content_file_path(cohort, release.block, checkpoint_one_attempt.path) + expect(page).to have_content "Practice" + click_button("Practice") + within(".checkpoint-navbar-details") do + expect(page).to_not have_content("Submit") + end + end + end + + context "checkpoint is autoscored" do + let!(:standard_one_attempt) { create(:standard, release: release) } + let!(:checkpoint_one_attempt) { create(:content_file, + :checkpoint, + autoscore: true, + html: '
    ', + standard: standard_one_attempt, + max_checkpoint_submissions: 1) } + let!(:challenge) { create(:challenge, uid: "abc", content_file: checkpoint_one_attempt, challenge_type: "number", answer: "2", points: 2) } + + it "displays number of challenges with the number of attempted and warns on last attempt, then prevents more attempts" do + visit content_file_path(cohort, release.block, checkpoint_one_attempt.path) + click_button("Start Assessment") + expect(page).to have_content challenge.title + within(".checkpoint-navbar-details") do + click_button("Submit") + end + expect(page).to have_content "You have attempted 0/1 questions. Once you submit, you will not be able to submit again." + within ".modal-actions" do + click_button("Back To Test") + end + within "#challenge-abc" do + find(:css, "input").set("2") + end + within(".checkpoint-navbar-details") do + click_button("Submit") + end + expect(page).to have_content "You have attempted 1/1 questions. Once you submit, you will not be able to submit again." + within ".modal-actions" do + click_button("Submit All Answers") + end + expect(page).to have_content 'Submission Scored!' + expect(page).to have_content 'You received 2/2 pts (100%)' + click_button("Continue") + visit content_file_path(cohort, release.block, checkpoint_one_attempt.path) + expect(page).to have_content "Practice" + click_button("Practice") + within(".checkpoint-navbar-details") do + expect(page).to_not have_content("Submit") + end + expect(page).to_not have_content("Set Partner") + end + end + end + + describe "checkpoint state landing details" do + let(:challenge_short_answer) { create(:challenge, uid: "abc", content_file: checkpoint, challenge_type: Challenge::TYPES[:short_answer], answer: "answer") } + + it "prompts the student to 'Start Assessment'" do + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to have_content "Start Assessment" + end + + it "prompts the student to 'Continue Assessment'" do + create(:checkpoint_submission, cohort: cohort, user: student, content_file_uid: checkpoint.uid, content_file_block_id: release.block.id, state: "started") + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to have_content "Continue Assessment" + end + + it "prompts the student to 'Retake Assessment'" do + create(:checkpoint_submission, cohort: cohort, user: student, content_file_uid: checkpoint.uid, content_file_block_id: release.block.id, state: "done") + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to have_content "Retake Assessment" + end + + it "conveys to a student that the submissions is pending" do + create(:checkpoint_submission, cohort: cohort, user: student, content_file_uid: checkpoint.uid, content_file_block_id: release.block.id, state: "needs_review") + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to have_content "Pending" + end + + context "mastery mode" do + before do + cohort.update(mode: Cohort::MODES[:mastery]) + chckpt = create(:checkpoint_submission, cohort: cohort, user: student, content_file_uid: checkpoint.uid, content_file_block_id: release.block.id, state: "done") + create(:performance, cohort: cohort, standard: standard, user: student, score: 2, checkpoint_submission: chckpt) + end + + it "gives performance information" do + visit content_file_path(cohort, release.block, checkpoint.path) + within(".checkpoint-details") do + expect(page).to have_selector(".-score-2") + end + end + + context "when you have a pending and a graded submission" do + it "displays both the latest scored and pending submission details" do + create(:checkpoint_submission, cohort: cohort, user: student, content_file_uid: checkpoint.uid, content_file_block_id: release.block.id, state: "needs_review") + visit content_file_path(cohort, release.block, checkpoint.path) + within(".checkpoint-details") do + expect(page).to have_content "Pending" + expect(page).to have_selector(".-score-2") + end + end + end + end + + context "percentage mode" do + before do + cohort.update(mode: Cohort::MODES[:percentage]) + create(:checkpoint_submission, cohort: cohort, user: student, content_file_uid: checkpoint.uid, content_file_block_id: release.block.id, state: "done") + create(:submitted_challenge_answer, challenge: challenge_short_answer, user: student, cohort: cohort, answer: "answer") + end + + it "gives percentage information" do + visit content_file_path(cohort, release.block, checkpoint.path) + within(".checkpoint-points-earned") do + expect(page).to have_content("1/1") + expect(page).to have_content("100%") + end + end + + context "when you have a pending and a graded submission" do + it "displays both the latest scored and pending submission details" do + create(:checkpoint_submission, cohort: cohort, user: student, content_file_uid: checkpoint.uid, content_file_block_id: release.block.id, state: "needs_review") + visit content_file_path(cohort, release.block, checkpoint.path) + within(".checkpoint-details") do + expect(page).to have_content "Pending" + expect(page).to have_content("1/1") + expect(page).to have_content("100%") + end + end + end + end + end + + describe "challenges" do + context "that autosave [checkbox, radio, shortanswer, number, paragraph]" do + it "shows a timestamp of the draft that is saved" do + multi_choice_raw_json = '{"id": "123", "type": "multiple-choice", "hints": [], "setup": null, "title": "do some work", "answer": "Correct", "options": ["Correct", "B", "C", "D"], "question": {"content": [{"type": "markdown", "value": "\nSelect Correct!\n"}]}}' + create(:challenge, :multiple_choice, content_file: checkpoint, uid: "abc", raw_json: JSON.parse(multi_choice_raw_json), answer: "Correct", position: 0) + + visit content_file_path(cohort, release.block, checkpoint.path) + find(:css, ".checkpoint-landing-button").click + + wait_for_ajax + + within "#challenge-abc" do + options = all(".multi-choice-challenge-label") + options[0].click + within ".draft-timestamp" do + expect(page).to have_content "saved at" + end + end + end + end + end + + describe "paired submission" do + it "allows for submissions to be taken as pair on assessment start page" do + multi_choice_raw_json = '{"id": "123", "type": "multiple-choice", "hints": [], "setup": null, "title": "do some work", "answer": "Correct", "options": ["Correct", "B", "C", "D"], "question": {"content": [{"type": "markdown", "value": "\nSelect Correct!\n"}]}}' + create(:challenge, :multiple_choice, content_file: checkpoint, uid: "abc", raw_json: JSON.parse(multi_choice_raw_json), answer: "Correct", position: 0) + cohort.update(allow_paired_submissions: true) + visit content_file_path(cohort, release.block, checkpoint.path) + find(:css, ".checkpoint-landing-button").click + + wait_for_ajax + + within ".checkpoint-navbar-details" do + expect(page).to have_selector ".checkpoint-pair-menu" + find(".checkpoint-pair-menu").click + expect(page).to have_content "Work with partner" + find(".action-menu-list a").click + end + + within ".modal-main" do + expect(page).to have_content "Select Partner(s)" + expect(page).to have_content another_student.full_name + all("input[type='checkbox']")[0].click + click_button("Cancel") + end + + within ".checkpoint-navbar-details" do + find(".checkpoint-pair-menu").click + find(".action-menu-list a").click + end + + within ".modal-main" do + all("input[type='checkbox']")[0].click + click_button("Set Partner(s)") + end + + within ".checkpoint-navbar-details" do + avatars = all(".user-avatar") + expect(avatars.size).to eq 2 + find(".checkpoint-pair-menu").click + expect(page).to have_content "Clear partner" + find(".action-menu-list a").click + avatars = all(".user-avatar") + expect(avatars.size).to eq 0 + end + end + it "allows for pairs to be set and cleared on assessment landing page" do + multi_choice_raw_json = '{"id": "123", "type": "multiple-choice", "hints": [], "setup": null, "title": "do some work", "answer": "Correct", "options": ["Correct", "B", "C", "D"], "question": {"content": [{"type": "markdown", "value": "\nSelect Correct!\n"}]}}' + create(:challenge, :multiple_choice, content_file: checkpoint, uid: "abc", raw_json: JSON.parse(multi_choice_raw_json), answer: "Correct", position: 0) + cohort.update(allow_paired_submissions: true) + visit content_file_path(cohort, release.block, checkpoint.path) + click_button("Set Partner") + + wait_for_ajax + + within ".modal-main" do + all("input[type='checkbox']")[0].click + click_button("Set Partner(s)") + end + + within ".checkpoint-landing-button" do + avatars = all(".user-avatar") + expect(avatars.size).to eq 2 + end + + within ".checkpoint-landing" do + avatars = all(".user-avatar") + expect(page).to have_content ('Clear Partner') + click_button("Clear Partner") + expect(avatars.size).to eq 1 + end + end + + it "allows for pairs to be set on assessment landing page" do + multi_choice_raw_json = '{"id": "123", "type": "multiple-choice", "hints": [], "setup": null, "title": "do some work", "answer": "Correct", "options": ["Correct", "B", "C", "D"], "question": {"content": [{"type": "markdown", "value": "\nSelect Correct!\n"}]}}' + create(:challenge, :multiple_choice, content_file: checkpoint, uid: "abc", raw_json: JSON.parse(multi_choice_raw_json), answer: "Correct", position: 0) + cohort.update(allow_paired_submissions: true) + visit content_file_path(cohort, release.block, checkpoint.path) + click_button("Set Partner") + + wait_for_ajax + + within ".modal-main" do + all("input[type='checkbox']")[0].click + click_button("Set Partner(s)") + end + + within ".checkpoint-landing-button" do + avatars = all(".user-avatar") + expect(avatars.size).to eq 2 + end + + within ".checkpoint-landing" do + avatars = all(".user-avatar") + expect(page).to have_content ('Start Assessment') + click_button("Start Assessment") + expect(avatars.size).to eq 2 + end + end + end + end + + context "as an instructor" do + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:student_never_signed_in) { create(:cohort_user, cohort: cohort).user } + let(:student_needs_review) { create(:user, last_viewed_cohort_id: cohort.id) } + let!(:student_needs_review_checkpoint) { create(:checkpoint_submission, + cohort: cohort, + user: student_needs_review, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block.id, + state: "needs_review", + correct_points: 5, + total_points: 10 + ) } + let(:student_needs_review_and_done) { create(:user, last_viewed_cohort_id: cohort.id) } + let!(:student_needs_review_and_done_done_checkpoint) { create(:checkpoint_submission, + cohort: cohort, + user: student_needs_review_and_done, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block.id, + state: "done", + correct_points: 9, + total_points: 10 + ) } + let!(:student_needs_review_and_done_needs_review_checkpoint) { create(:checkpoint_submission, + cohort: cohort, + user: student_needs_review_and_done, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block.id, + state: "needs_review", + correct_points: 5, + total_points: 10 + ) } + + let(:student_done) { create(:user, last_viewed_cohort_id: cohort.id) } + let!(:student_done_checkpoint) { create(:checkpoint_submission, + cohort: cohort, + user: student_done, + content_file_uid: checkpoint.uid, + content_file_block_id: release.block.id, + state: "done", + correct_points: 5, + total_points: 10, + created_at: 1.day.ago + ) } + + before do + create(:cohort_user, cohort: cohort, user: student_needs_review) + create(:cohort_user, cohort: cohort, user: student_needs_review_and_done) + create(:cohort_user, cohort: cohort, user: student_done) + multi_choice_raw_json = '{"id": "123", "type": "multiple-choice", "hints": [], "setup": null, "title": "do some work", "answer": "Correct", "options": ["Correct", "B", "C", "D"], "question": {"content": [{"type": "markdown", "value": "\nSelect Correct!\n"}]}}' + challenge = create(:challenge, :multiple_choice, content_file: checkpoint, uid: "abc", raw_json: JSON.parse(multi_choice_raw_json), answer: "Correct", position: 0, points: 4) + create(:submitted_challenge_answer, challenge: challenge, user: student_needs_review, cohort: cohort, answer: "answer", points: 3) + create(:submitted_challenge_answer, challenge: challenge, user: student_needs_review_and_done, cohort: cohort, answer: "answer", points: 3) + create(:submitted_challenge_answer, challenge: challenge, user: student_done, cohort: cohort, answer: "answer", points: 3) + sign_in(instructor) + end + + describe "student scores section" do + it "should be visible for instructor" do + visit content_file_path(cohort, release.block, checkpoint.path) + expect(page).to have_selector(".checkpoint-student-scores") + within(".checkpoint-student-scores") do + expect(page).to have_content("Results") + + within(".checkpoints-scores") do + rows = all("tr") + expect(rows.size).to eq(5) + + expect(rows[0]).to have_content("Student") + expect(rows[0]).to have_content("Score") + expect(rows[0]).to have_content("Status") + + expect(rows[1]).to have_content(student_needs_review.full_name) + expect(rows[1]).to have_content("needs review") + + expect(rows[2]).to have_content(student_needs_review_and_done.full_name) + expect(rows[2]).to have_selector(".pending-submission") + expect(rows[2]).to have_content("scored") + + expect(rows[3]).to have_content(student_done.full_name) + expect(rows[3]).to have_content("scored") + + expect(rows[4]).to have_content(student_never_signed_in.full_name) + expect(rows[4]).to have_content("never signed in") + end + end + end + end + + describe "paired submission" do + before do + cohort.update!(allow_paired_submissions: true) + end + + it "hides submissions to be taken as pairs for instructors" do + multi_choice_raw_json = '{"id": "123", "type": "multiple-choice", "hints": [], "setup": null, "title": "do some work", "answer": "Correct", "options": ["Correct", "B", "C", "D"], "question": {"content": [{"type": "markdown", "value": "\nSelect Correct!\n"}]}}' + create(:challenge, :multiple_choice, content_file: checkpoint, uid: "abcd", raw_json: JSON.parse(multi_choice_raw_json), answer: "Correct", position: 0) + visit content_file_path(cohort, release.block, checkpoint.path) + find(:css, ".checkpoint-landing-button").click + + wait_for_ajax + + within ".checkpoint-navbar-details" do + expect(page).to_not have_selector ".checkpoint-pair-menu"#test + end + end + end + end +end diff --git a/scripts/spec/features/content_files/checkpoint_submissions_spec.rb b/scripts/spec/features/content_files/checkpoint_submissions_spec.rb new file mode 100644 index 0000000..9c695c7 --- /dev/null +++ b/scripts/spec/features/content_files/checkpoint_submissions_spec.rb @@ -0,0 +1,377 @@ +require "features_helper" + +describe "checkpoint submissions" do + let(:cohort) { create(:cohort) } + let(:release) { create(:release) } + let(:student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Ben", last_name: "Something")).user } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:standard) { create(:standard, release: release) } + let!(:content_file) { create(:content_file, :checkpoint, standard: standard) } + let!(:challenge) { create(:challenge, content_file: content_file) } + + before { create(:cohort_release, cohort: cohort, release: release) } + + + describe "viewing a checkpoint submission", js: true do + context "when signed in as an instructor" do + let(:checkpoint_submission) { create_checkpoint_submission(student, challenge_1, cohort: cohort, content_file_block_id: release.block_id) } + let(:challenge_1) { create(:challenge, content_file: content_file, topics: ["neat", "cool", "math"], release_id: release.id) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, challenge: challenge_1, user: student, checkpoint_submission: checkpoint_submission, points: 1) } + let!(:previous_submitted_challenge_answer) { create(:submitted_challenge_answer, + cohort: cohort, + challenge: challenge_1, + user: student, + checkpoint_submission: checkpoint_submission, + created_at: 1.day.ago, + answer: "human answer", + points: 1 + )} + + let!(:challenge_2) { create(:challenge, content_file: content_file, title: "A different challenge", points: 9, topics: ["neat", "cool", "math"]) } + let!(:another_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, challenge: challenge_2, user: student, checkpoint_submission: checkpoint_submission, status: "ungraded", points: 2) } + let!(:performance) { create(:performance, user_id: student.id, standard: content_file.standard, cohort: cohort) } + + before do + sign_in(instructor) + visit cohort_checkpoint_submission_path(cohort, checkpoint_submission) + end + + it "displays checkpoint info, student info, challenges with all submitted challenge answers, and associated standards with current scores" do + within ".primary-header" do + expect(page).to have_content(content_file.title) + expect(page).to have_link("CHECKPOINT") + end + + within ".student-name-bar-wrapper" do + expect(page).to have_content(student.full_name) + expect(page).to have_selector(".user-avatar") + end + + within ".challenges" do + within("#challenge_#{challenge_1.id}") do + expect(page).to have_content(challenge_1.title) + if challenge_1.answer + expect(page).to have_content(challenge_1.answer) + end + expect(page).to have_content("NEAT") + expect(page).to have_content("COOL") + expect(page).to have_content("MATH") + click_button "2 attempts" + within ".challenge-timeline" do + find(".forward").click + end + expect(page).to have_content submitted_challenge_answer.answer + click_button "Exit History" + expect(page).to have_content previous_submitted_challenge_answer.answer + end + + within("#challenge_#{challenge_2.id}") do + expect(page).to have_content(challenge_2.title) + end + end + + within ".standards-cards" do + within("#standard_#{standard.id}") do + expect(page).to have_content("CURRENT SCORE: #{performance.score}") + within(".standard-scoring-block") do + expect(page).to have_selector(".label", text: "STANDARD SCORE") + expect(page).to have_selector(".standard-scoring-button", count: 3) + end + + expect(page).to have_content(standard.description) + end + end + + click_link "Back to Submissions" + expect(page).to have_content release.block.title.upcase + visit cohort_checkpoint_submission_path(cohort, checkpoint_submission) + click_link "CHECKPOINT" + expect(page).to have_content content_file.title + end + + describe "standard score suggestion" do + before do + visit current_path + end + + it "suggests a standard score if one has never been entered" do + # challenges are not all scored, no suggested score + within(".standard-scoring-buttons") do + expect(page).to_not have_css(".-is-suggested") + end + + # challenges have all been scored and all are correct + within("#challenge_#{challenge_2.id}") do + find(".correct-check").click + end + + within(".standard-scoring-buttons") do + expect(page).to have_selector(".score-3.-is-suggested") + end + + # a challenge is marked incorrect + find("#challenge_#{challenge_2.id}").click + within("#challenge_#{challenge_2.id}") do + find(".correct-check").hover + find(".incorrect-status").click + end + within(".standard-scoring-buttons") do + expect(page).to have_selector(".score-2.-is-suggested") + end + + # a challenge marked incorrect and all are incorrect + within("#challenge_#{challenge_1.id}") do + find(".correct-check").hover + find(".incorrect-status").click + end + within(".standard-scoring-buttons") do + expect(page).to have_selector(".score-1.-is-suggested") + end + + # the standard is scored + within(".standard-scoring-buttons") do + find(".score-1.-is-suggested").click + expect(page).to_not have_selector(".-is-suggested") + expect(page).to have_selector(".-is-selected") + end + end + end + + describe "percentage mode" do + before do + cohort.update(mode: Cohort::MODES[:percentage]) + end + + it "displays the amount of points the user got correct over the available" do + visit current_path + + within("#standard_#{standard.id}") do + expect(page).to have_content("3/10 pts") + expect(page).to have_content("30%") + end + + within ".standard-topics-rollups" do + # displays topics and percentage rollups + expect(page).to have_content("NEAT 100%") + expect(page).to have_content("COOL 100%") + expect(page).to have_content("MATH 100%") + end + end + + it "allows for marking done when all the submissions are graded but there are new ones on top of the checkpoints submissions" do + # create a submission that is not attached to the checkpoint + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge_2, user: student, status: "ungraded", points: 2) + visit current_path + + within("#challenge_#{challenge_2.id}") do + find(".points-section").hover + find(".integer-container .integer:last-of-type").click + end + + within ".standardcard" do + expect(page).to_not have_selector(".input.lp-style-button.-disabled[value='Submit Score']") + end + end + end + + describe "disabled standard score suggestion" do + let(:content_file) { create(:content_file, :checkpoint, standard: standard, autoscore: true) } + + before do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge_2, user: student, checkpoint_submission: checkpoint_submission, status: "correct") + # create_challenge_standard(challenge: challenge_2, standard: challenge_standard_1.standard) + create(:performance, user: student, standard: standard, cohort: cohort, score: 3, checkpoint_submission_id: checkpoint_submission.id) + # challenge_standard_2.destroy + visit current_path + end + + it "does not suggest scores when the standard has already been scored" do + # challenges are all scored accurately, suggestion matches state so no suggestion + within(".standard-scoring-buttons") do + expect(page).to_not have_css(".-is-suggested") + expect(page).to have_css(".-is-selected") + end + + find("#challenge_#{challenge_2.id}").click + # re-score challenge such that the suggestion would change if there weren't already a performance + within("#challenge_#{challenge_2.id}") do + find(".correct-check").hover + find(".incorrect-status").click + end + within("#challenge_#{challenge_1.id}") do + find(".correct-check").hover + find(".incorrect-check").click + end + + # because there was already a performance for this standard, no suggestion is generated + within(".standard-scoring-buttons") do + expect(page).to_not have_css(".-is-suggested") + end + end + end + + it "allows an instructor to score standards and mark done" do + within("#standard_#{standard.id}") do + expect(page).to have_content("CURRENT SCORE: #{performance.score}") + + within(".standard-scoring-block") do + expect(page).to have_selector(".label", text: "STANDARD SCORE") + expect(page).to have_selector(".standard-scoring-button", count: 3) + expect(page).to_not have_selector(".-is-selected") + within(".standard-scoring-buttons") do + find(".score-3").click + expect(page).to have_selector(".score-3.-is-selected") + expect(Performance.count).to eq(2) + expect(Performance.last.score).to eq(3) + end + end + expect(page).to_not have_selector(".standard-current-score") + within(".standard-score-info p") do + expect(page).to have_content("#{instructor.full_name}, Today at ") + end + end + + within(".standardcard") do + click_button("Mark Done") + end + + within("#standard_#{standard.id}") do + within(".standard-scoring-buttons") do + expect(page).to have_selector(".score-3.-is-selected") + end + within(".standard-score-info p") do + expect(page).to have_content("#{instructor.full_name}, Today at ") + end + + new_performance = create(:performance, user_id: student.id, standard_id: standard.id, score: 2) + visit cohort_checkpoint_submission_path(cohort, checkpoint_submission) + + expect(page).to have_content("CURRENT SCORE: #{new_performance.score}") + within(".standard-scoring-buttons") do + expect(page).to have_selector(".score-3.-is-selected") + end + within(".standard-score-info p") do + expect(page).to have_content("#{instructor.full_name}, Today at ") + end + end + + within("#standard_#{standard.id}") do + within(".standard-scoring-block") do + within(".standard-scoring-buttons") do + find(".score-2").click + expect(page).to have_selector(".score-2.-is-selected") + expect(Performance.count).to eq(4) + expect(Performance.last.score).to eq(2) + end + end + end + end + + describe "submission state changes" do + context "when a checkpoint submission has been marked as done" do + it "does not allow an instructor to reject a checkpoint submission" do + checkpoint_submission.update(state: CheckpointSubmission::STATES[:done], grader_id: instructor.id) + visit cohort_checkpoint_submission_path(cohort, content_file, checkpoint_submission) + + expect(checkpoint_submission.reload.state).to eq(CheckpointSubmission::STATES[:done]) + + within(".student-name-bar") do + expect(page).to_not have_selector(".lp-style-button.-reject") + end + end + end + end + + describe "paired submission scoring" do + let(:paired_student) { create(:cohort_user, :student, cohort: cohort).user } + let(:paired_checkpoint_submission) { create_checkpoint_submission(paired_student, challenge_1, cohort: cohort, content_file_block_id: release.block_id) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, challenge: challenge_1, user: paired_student, checkpoint_submission: paired_checkpoint_submission, points: 1, status: "correct") } + let!(:another_pair_sca) { create(:submitted_challenge_answer, cohort: cohort, challenge: challenge_2, user: paired_student, checkpoint_submission: paired_checkpoint_submission, status: "ungraded", points: 2) } + + before do + cohort.update(mode: Cohort::MODES[:percentage]) + checkpoint_submission.update(pair_submission_ids: [paired_checkpoint_submission.id]) + visit cohort_checkpoint_submission_path(cohort, checkpoint_submission) + end + + it "allows for multiple submissions to be scored on a single users checkpoint" do + within ".student-name-bar" do + expect(page).to have_content(student.full_name) + expect(page).to have_content(paired_student.full_name) + end + + within ".challenges" do + within("#challenge_#{challenge_1.id}") do + status_bars = all('.challenge-grading-buttons') + expect(status_bars.length).to eq 2 + + within status_bars[0] do + find(".points-section").hover + find(".integer-container .integer:first-of-type").click + expect(page).to have_content "Graded by #{instructor.full_name}" + end + + within status_bars[1] do + find(".points-section").hover + find(".integer-container .integer:first-of-type").click + expect(page).to have_content "Graded by #{instructor.full_name}" + end + end + + within("#challenge_#{challenge_2.id}") do + status_bars = all('.challenge-grading-buttons') + expect(status_bars.length).to eq 2 + + within status_bars[0] do + expect(page).to have_content student.full_name + find(".points-section").hover + find(".integer-container .integer:last-of-type").click + expect(page).to have_content "Graded by #{instructor.full_name}" + end + + within status_bars[1] do + expect(page).to have_content paired_student.full_name + find(".points-section").hover + find(".integer-container .integer:last-of-type").click + expect(page).to have_content "Graded by #{instructor.full_name}" + end + end + end + + visit cohort_checkpoint_submission_path(cohort, checkpoint_submission) + + within ".challenges" do + within("#challenge_#{challenge_1.id}") do + status_bars = all('.challenge-grading-buttons') + + within status_bars[0] do + expect(page).to have_content "Graded by #{instructor.full_name}" + expect(page).to have_content "0/1" + end + + within status_bars[1] do + expect(page).to have_content "Graded by #{instructor.full_name}" + expect(page).to have_content "0/1" + end + end + + within("#challenge_#{challenge_2.id}") do + status_bars = all('.challenge-grading-buttons') + expect(status_bars.length).to eq 2 + + within status_bars[0] do + expect(page).to have_content "9/9" + expect(page).to have_content "Graded by #{instructor.full_name}" + end + + within status_bars[1] do + expect(page).to have_content "9/9" + expect(page).to have_content "Graded by #{instructor.full_name}" + end + end + end + end + end + end + end +end diff --git a/scripts/spec/features/content_files/navigation_feature_spec.rb b/scripts/spec/features/content_files/navigation_feature_spec.rb new file mode 100644 index 0000000..1534d95 --- /dev/null +++ b/scripts/spec/features/content_files/navigation_feature_spec.rb @@ -0,0 +1,127 @@ +require "features_helper" + +feature "Standard and content file navigation" do + let(:cohort) { create(:cohort) } + let(:student) { create(:user) } + let(:release) { create(:release) } + let(:standard) { create(:standard, release: release, title: "First standard", position: 0) } + let!(:content_file_1) { create(:content_file, standard: standard, position: 0) } + let!(:content_file_2) { create(:content_file, standard: standard, position: 1) } + let!(:content_file_3) { create(:content_file, :checkpoint, standard: standard, position: 2) } + + before do + create(:cohort_release, cohort: cohort, release: release) + create(:cohort_user, :student, cohort: cohort, user: student) + end + + describe "sidebar navigation" do + before do + sign_in(student) + visit content_file_path(cohort.id, release.block_id, content_file_2.path) + end + + it "allows students to navigate between content files", js: true do + within ".content-file-sidebar" do + expect(page).to have_content(standard.title) + + within ".bodyrow" do + content_files = all("a") + within content_files[0] do + expect(page).to_not have_selector(".-is-active") + within(".lessontitle") do + expect(page).to have_content(content_file_1.title) + end + end + + content_files = all("a") + within content_files[0] do + expect(page).to_not have_selector(".-is-active") + within(".lessontitle") do + expect(page).to have_content(content_file_1.title) + end + end + within content_files[1] do + within ".itemrow.-is-active" do + within(".lessontitle") do + expect(page).to have_content(content_file_2.title) + end + end + end + within content_files[2] do + expect(page).to_not have_selector(".-is-active") + within(".lessontitle") do + expect(page).to have_content(content_file_3.title) + end + end + + find("a[title='#{content_file_2.title}']").click + expect(current_path).to eq(content_file_path(cohort, release.block_id, content_file_2.path)) + within all("a")[1] do + expect(page).to have_selector(".-is-active") + end + + find("a[title='#{content_file_1.title}']").click + expect(current_path).to eq(content_file_path(cohort, release.block_id, content_file_1.path)) + within all("a")[0] do + expect(page).to have_selector(".-is-active") + end + + find("a[title='#{content_file_3.title}']").click + expect(current_path).to eq(content_file_path(cohort, release.block_id, content_file_3.path)) + within all("a")[2] do + expect(page).to have_selector(".-is-active") + end + end + end + end + end + + describe "footer navigation" do + context "when instructor or admin" do + before do + sign_in(create(:user, :admin)) + visit content_file_path(cohort.id, release.block_id, content_file_2.path) + end + + it "allows navigation between content files", js: true do + within(".content-arrow-container") do + expect(page).to have_selector(".-next") + expect(page).to have_selector(".-prev") + + find("a.-next").click + end + + expect(page).to have_content(content_file_3.title) + end + end + + context "when student" do + before do + sign_in(student) + end + + context "when the content file is a checkpoint" do + it "does not render the footer navigation" do + visit content_file_path(cohort.id, release.block_id, content_file_3.path) + + expect(page).to_not have_css(".content-arrow-container") + end + end + + context "when the content file is not a checkpoint" do + it "allows navigation between content files", js: true do + visit content_file_path(cohort.id, release.block_id, content_file_2.path) + + within(".content-arrow-container") do + expect(page).to have_selector(".-next") + expect(page).to have_selector(".-prev") + + find("a.-prev").click + end + + expect(page).to have_content(content_file_1.title) + end + end + end + end +end diff --git a/scripts/spec/features/content_files/permalinks_spec.rb b/scripts/spec/features/content_files/permalinks_spec.rb new file mode 100644 index 0000000..3a0f528 --- /dev/null +++ b/scripts/spec/features/content_files/permalinks_spec.rb @@ -0,0 +1,62 @@ +require "features_helper" + +describe "Clicking permalinks", js: true do + let(:cohort) { create(:cohort, name: "A Cohort") } + let(:user) { create(:cohort_user, cohort: cohort).user } + let!(:release) { create(:cohort_release, cohort: cohort).release } + let!(:standard) { create(:standard, release: release) } + let!(:content_file) { create(:content_file, standard: standard, path: "this_path", html: "

    HALLO

    ") } + + before do + sign_in(user) + end + + context "no referer present" do + context "single cohort contains content file" do + it "redirects to the desired content file" do + visit permalink_path(release.block.repo_name, content_file.path) + expect(page).to have_content "HALLO" + end + end + + context "multiple cohorts contains content file" do + let(:another_cohort) { create(:cohort, name: "zanzabar") } + let!(:another_release) { create(:release, block_id: release.block_id)} + let!(:another_standard) { create(:standard, release: another_release) } + let!(:another_content_file) { create(:content_file, standard: another_standard, path: "this_path") } + + before do + create(:cohort_release, cohort: another_cohort, release: another_release) + create(:cohort_user, cohort: another_cohort, user: user) + end + + it "renders a view declaring the clicked url and the cohort options" do + visit permalink_path(release.block.repo_name, content_file.path) + expect(page).to have_content "Choose a Cohort" + within ".mdown-container" do + within all("li")[0] do + expect(page).to have_link "A Cohort" + end + within all("li")[1] do + expect(page).to have_link "zanzabar" + end + end + end + + it "renders the cohort options view for admins when they aren't enrolled" do + user.update(roles: ["forge.admin"]) + CohortUser.destroy_all + visit permalink_path(release.block.repo_name, content_file.path) + expect(page).to have_content "Choose a Cohort" + within ".mdown-container" do + within all("li")[0] do + expect(page).to have_link "A Cohort" + end + within all("li")[1] do + expect(page).to have_link "zanzabar" + end + end + end + end + end +end diff --git a/scripts/spec/features/content_files/view_feature_spec.rb b/scripts/spec/features/content_files/view_feature_spec.rb new file mode 100644 index 0000000..b28178e --- /dev/null +++ b/scripts/spec/features/content_files/view_feature_spec.rb @@ -0,0 +1,1886 @@ +require "features_helper" + +describe "Viewing content files", js: true do + let(:cohort) { create(:cohort) } + let!(:release) { create(:cohort_release, cohort: cohort).release } + + context "as an instructor or admin" do + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:input_json) do + { + content: [{ type: "challenge", id: "1" }], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:number], + "title": "divide", + "placeholder": "enter answer", + "answer": "0.3", + "decimal": "1", + "question": { content: [{ "type": "markdown", "value": "1/3 = ?" }] } + } + } + } + end + + before do + sign_in(instructor) + end + + context "in a sandbox preview cohort" do + it "shows content file warnings at the top of the page" do + content_file = create(:content_file, html: "some content here!", warnings: ["This content file is too cold"], standard: create(:standard, release: release, position: 1)) + cohort.update(sandbox: true) + release.update(sync_warnings: ["This content file is too cold"]) + visit content_file_path(cohort, release.block, content_file.path) + expect(page).to have_content "1 error on this lesson" + find(".alert-danger").click + expect(page).to have_content("This content file is too cold") + end + end + + it "displays the processed html" do + cohort, block, content_file = create_content_file(html: "some content here!") + create(:cohort_user, cohort: cohort, user: instructor) + visit content_file_path(cohort, block, content_file.path) + + expect(page).to have_content("some content here!") + end + + it "displays the github_url and data export for instructors" do + content_file = create(:content_file, :checkpoint, html: "some content here!", standard: create(:standard, release: release, position: 1)) + visit content_file_path(cohort, release.block, content_file.path) + # Permalink + within(".instructor-actions") do + all(".icon")[0].click + expect(page).to have_link("Copy page permalink") + end + + # Github actions + within(".instructor-actions") do + all(".icon")[1].click + expect(page).to have_link("Instructions to edit this page") + expect(page).to have_link("View Page Source") + expect(page).to have_link("Copy SSH clone link") + expect(page).to have_link("Copy HTTPS clone link") + end + + # download actions + within(".instructor-actions") do + all(".icon")[2].click + expect(page).to have_link("Download student summary CSV") + expect(page).to have_link("Download challenge data CSV") + end + end + + context "viewing hidden content" do + describe "when standard and content_file are hidden" do + it "displays warning message" do + new_cohort, block, content_file = create_content_file(html: "some content here!") + create(:cohort_user, :instructor, cohort: new_cohort, user: instructor) + create(:content_visibility, cohort_id: new_cohort.id, content_uid: content_file.standard.uid) + create(:content_visibility, cohort_id: new_cohort.id, content_uid: content_file.uid) + + visit content_file_path(new_cohort, block, content_file.path) + expect(page).to have_content("Both the Unit and Lesson are currently hidden from students.") + end + end + describe "when standard is hidden" do + it "displays warning message" do + new_cohort, block, content_file = create_content_file(html: "some content here!") + create(:cohort_user, :instructor, cohort: new_cohort, user: instructor) + create(:content_visibility, cohort_id: new_cohort.id, content_uid: content_file.standard.uid) + + visit content_file_path(new_cohort, block, content_file.path) + expect(page).to have_content("This Unit is currently hidden from students.") + end + end + describe "when content_file is hidden"do + it "displays warning message" do + new_cohort, block, content_file = create_content_file(html: "some content here!") + create(:cohort_user, cohort: new_cohort, user: instructor) + create(:content_visibility, cohort_id: new_cohort.id, content_uid: content_file.uid) + + visit content_file_path(new_cohort, block, content_file.path) + expect(page).to have_content("This Lesson is currently hidden from students.") + end + end + end + + describe "hints" do + it "should allow an instructor to see all hints", js: true do + processed_text = BlockParser::BuildHtmlFromMdJson.process_json(json_hash: input_json, asset_uploader: nil, root_directory_path: "", current_content_file_path: "", content_file_paths: []) + + content_file = create(:content_file, html: processed_text[:html], standard: create(:standard, release: release, position: 1)) + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file, hints: ["hint 1", "hint 2"])) + + visit content_file_path(cohort, release.block, content_file.path) + + within ".challenge-block" do + expect(page).to have_css(".headingtitle", text: "SHOW HINTS") + + find(".headingtitle", text: "SHOW HINTS").click + expect(page).to have_css(".challenge-accordion.-is-expanded") + expect(page).to have_css(".headingtitle", text: "HIDE HINTS") + within(".challenge-accordion.-is-expanded") do + expect(page).to have_content("hint 1") + find(".get-hint-text", text: "Get Another Hint").click + expect(page).to have_content("hint 2") + end + + find(".headingtitle", text: "HIDE HINTS").click + expect(page).to_not have_css(".challenge-accordion.-is-expanded") + expect(page).to_not have_content("hint 1") + expect(page).to_not have_content("hint 2") + expect(page).to have_css(".headingtitle", text: "SHOW HINTS") + end + end + end + end + + context "as a student" do + let(:student) { create(:cohort_user, :student, cohort: cohort, user: create(:user)).user } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort, user: create(:user, first_name: "Ins")).user } + let(:html) { "some content here!" } + let(:standard) { create(:standard, release: release, position: 1) } + let(:content_file) { create(:content_file, html: html, standard: standard, position: 0) } + + before do + sign_in(student) + end + + it "displays the block & standard information", js: true do + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content(content_file.standard.title) + + expect(page).to_not have_selector(".instructor-actions") + end + + it "does not show the github_url for student" do + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to_not have_link("View on Github") + end + + context "when the student has a performance for the standard" do + before do + create(:performance, standard: content_file.standard, score: score, user: student, cohort: cohort) + visit content_file_path(cohort, release.block, content_file.path) + end + + context "when the standard score is 0" do + let(:score) { 0 } + + it "does not display the standard score" do + expect(page).to_not have_selector(".actioncircle.-score-1") + expect(page).to_not have_selector(".actioncircle.-score-2") + expect(page).to_not have_selector(".actioncircle.-score-3") + end + end + + context "when the score is nonzero" do + let(:score) { 3 } + + it "displays the current standard score", js: true do + within(".actioncircle.-score-3") do + expect(page).to have_content("3") + end + end + end + end + + context "sidebar" do + let!(:content_resource) { create(:content_file, content_file_type: ContentFile::TYPES[:resource], standard: standard, title: "Resource", html:"

    hi!

    ") } + let!(:challenge_resource_1) { create(:challenge, content_file: content_resource, title: "Buzz Nonedrin") } + let(:checkpoint_content_file) { create(:content_file, html: html, standard: standard, title: "Checkpt", position: 1, content_file_type: ContentFile::TYPES[:checkpoint]) } + let!(:instructor_content_file) { create(:content_file, html: html, standard: standard, title: "Instructor Only", position: 2, content_file_type: ContentFile::TYPES[:instructor]) } + let(:challenges_content_file) { create(:content_file, standard: standard, position: 0, html: '
    ') } + let(:raw_json_1) { '{"id": "abc", "type": "multiple-choice", "hints": [], "title": "do some work", "answer": "Correct", "options": ["Correct", "B", "C", "D"], "question": {"content": [{"type": "markdown", "value": "\nSelect Correct!\n"}]}}' } + let(:raw_json_2) { '{"id": "123", "type": "multiple-choice", "hints": [], "setup": null, "title": "do some work", "answer": "Incorrect", "options": ["Incorrect", "B", "C", "D"], "question": {"content": [{"type": "markdown", "value": "\nSelect Incorrect!\n"}]}}' } + let(:lesson_challenge_1) { create(:challenge, :multiple_choice, content_file: challenges_content_file, uid: "abc", raw_json: JSON.parse(raw_json_1), answer: "Correct", position: 0) } + let(:lesson_challenge_2) { create(:challenge, :multiple_choice, content_file: challenges_content_file, uid: "123", raw_json: JSON.parse(raw_json_2), answer: "Incorrect", position: 1) } + let(:checkpoint_challenge) { create(:challenge, :multiple_choice, content_file: checkpoint_content_file) } + + let!(:sca_1) { create(:submitted_challenge_answer, user: student, challenge: lesson_challenge_1, challenge_uid: lesson_challenge_1.uid, status: SubmittedChallengeAnswer::STATUSES[:correct], cohort: cohort) } + let!(:sca_2) { create(:submitted_challenge_answer, user: student, challenge: lesson_challenge_2, challenge_uid: lesson_challenge_2.uid, status: SubmittedChallengeAnswer::STATUSES[:incorrect], cohort: cohort) } + let(:checkpoint_submission) do + create(:checkpoint_submission, + content_file_uid: checkpoint_content_file.uid, + content_file_block_id: release.block_id, + user_id: student.id, + state: CheckpointSubmission::STATES[:needs_review]) + end + let!(:sca_checkpoint) { create(:submitted_challenge_answer, checkpoint_submission_id: checkpoint_submission.id, challenge: checkpoint_challenge, status: SubmittedChallengeAnswer::STATUSES[:ungraded], cohort: cohort) } + + it "does not display resource files" do + visit content_file_path(cohort, release.block, challenges_content_file.path) + expect(page).to_not have_content("Resource") + end + + it "displays content for resources files" do + visit content_file_path(cohort, release.block, content_resource.path) + expect(page).to have_content("hi!") + end + + it "does not show instructor files to students" do + visit content_file_path(cohort, release.block, challenges_content_file.path) + within(".content-file-sidebar") do + expect(page).to_not have_content("Instructor Only") + end + end + + it "displays updating progress donuts and checkpoint state", js: true do + visit content_file_path(cohort, release.block, challenges_content_file.path) + within(".content-file-sidebar") do + rows = all(".itemrow") + within(rows[0]) do + donuts = all(".donut-segment") + expect(donuts[0][:stroke]).to eq "#00A5B5" + expect(donuts[1][:stroke]).to eq "#E36875" + end + end + + # click to make the correct one wrong + within("#challenge-abc") do + options = all(".multi-choice-challenge-label") + options[1].click + click_button "CHECK ANSWER" + wait_for_ajax + assert_text("Correct") + end + within(".content-file-sidebar") do + changed_rows = all(".itemrow") + within(changed_rows[0]) do + changed_donuts = all(".donut-segment") + expect(changed_donuts.length).to eq 1 + expect(changed_donuts[0][:stroke]).to eq "#E36875" + end + end + + # make them both right + within("#challenge-abc") do + options = all(".multi-choice-challenge-label") + options[0].click + click_button "CHECK ANSWER" + wait_for_ajax + end + within("#challenge-123") do + options = all(".multi-choice-challenge-label") + options[0].click + click_button "CHECK ANSWER" + wait_for_ajax + end + within(".content-file-sidebar") do + changed_rows = all(".itemrow") + within(changed_rows[0]) do + expect(find("#Oval")[:stroke]).to eq "#51AFB9" + end + end + end + + context "percentage mode" do + it "displays the perctage correct for a checkpoint" do + cohort.update(mode: Cohort::MODES[:percentage]) + checkpoint_submission.update(state: CheckpointSubmission::STATES[:done], cohort: cohort) + visit content_file_path(cohort, release.block, checkpoint_content_file.path) + + within(".content-file-sidebar") do + expect(page).to have_content("100%") + end + end + end + + context "checkpoint is in started state" do + it "displays no checkpoint progress" do + checkpoint_submission.update(state: CheckpointSubmission::STATES[:started]) + visit content_file_path(cohort, release.block, checkpoint_content_file.path) + + within(".content-file-sidebar") do + expect(page).to_not have_content("100%") + end + end + end + + context "as an instructor or admin" do + before do + sign_in(instructor) + end + + it "displays the instructor content file for instructors" do + visit content_file_path(cohort, release.block, challenges_content_file.path) + within(".content-file-sidebar") do + expect(page).to have_content("Instructor Only") + end + end + end + end + + context "challenges" do + let(:processed_text) { BlockParser::BuildHtmlFromMdJson.process_json(json_hash: input_json, asset_uploader: nil, root_directory_path: "", current_content_file_path: "", content_file_paths: []) } + let(:html) { processed_text[:html] } + + context "code snippet challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:code_snippet], + "tests": "describe('repeats', function() {\n\n it(\"returns true when given an empty string (which seems strange, but go with it :)\", function() {\n expect(repeats(\"\"), \"Default value is incorrect\").to.deep.eq(true)\n })\n\n it(\"returns true when the second half of the string equals the first\", function() {\n expect(repeats(\"bahbah\")).to.deep.eq(true)\n expect(repeats(\"nananananananana\")).to.deep.eq(true)\n })\n\n it(\"returns false when the second half of the string does not equal the first\", function() {\n expect(repeats(\"bahba\")).to.deep.eq(false)\n expect(repeats(\"nananananann\")).to.deep.eq(false)\n })\n\n it(\"does not use .repeat\", function() {\n expect(repeats.toString()).to.not.match(/\\.repeat/)\n })\n\n})\n", + "title": "repeats", + "language": "javascript", + "question": { + "content": [ + { "type": "markdown", "value": "\nWrite a function named repeats that returns true if the first half of the string equals the last half, and false if not.\n" } + ] + }, + "explanation": { + "content": [ + { "type": "markdown", "value": "\nYou might have solved this with a while loop, but using recursion is another way to do this.\n" } + ] + }, + "placeholder": "function repeats(input) {\n// write your code here thatreturns true if the first half of the string equals the last half, and false if not\n}" + } + } + } + end + + describe "challenge points worth" do + context "when in mastery mode" do + it "does not show point value" do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file, points: 3)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to_not have_content("3 PT") + end + end + + context "when in percentage mode" do + # Disabled since gsap package removed + xit "does show point value" do + checkpoint_content_file = create(:content_file, html: html, standard: standard, position: 0, content_file_type: ContentFile::TYPES[:checkpoint]) + Challenge.create!(processed_text[:challenges][0].merge(content_file: checkpoint_content_file, points: 3)) + cohort.update(mode: "Percentage") + visit content_file_path(cohort, release.block, checkpoint_content_file.path) + + expect(page).to have_content("3 PT") + end + end + end + + it "displays a connection failure message when the submitted challenge answer fails", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content "repeats" + set_ace_editor_text "function repeats(myString) {" + click_button "RUN TESTS" + + expect(page).to have_content "CANCEL" + SubmittedChallengeAnswer.last.update(status: "failed", test_results: "Connection failed") + wait_for_ajax + + within(".feedback") do + expect(page).to have_content("Connection Failed") + end + end + + it "allows students to answer javascript code challenges", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content "repeats" + expect(page).to have_content "JAVASCRIPT" + expect(page).to have_content input_json[:challenges][:"1"][:placeholder] + + # Reset button should reset input to placeholder text + set_ace_editor_text "asdfjkl;" + expect(page).to have_content "asdfjkl;" + click_button "Reset Input" + expect(page).to_not have_content "asdfjkl;" + expect(page).to have_content input_json[:challenges][:"1"][:placeholder] + set_ace_editor_text "" + click_button "Reset Input" + expect(page).to have_content input_json[:challenges][:"1"][:placeholder] + + set_ace_editor_text %{function repeats(myString) { + if (myString.length % 2 != 0) { + return false; + } + var firstHalf = myString.substr(0, myString.length/2); + var secondHalf = myString.substr(myString.length/2); + return firstHalf == secondHalf; +}} + + click_button "RUN TESTS" + + expect(page).to have_content "CANCEL" + SubmittedChallengeAnswer.last.update(status: "correct", test_results: "1) You got the test right hooray") + + wait_for_ajax + expect(page).to have_content("Correct") + expect(page).to have_content("1) You got the test right hooray") + + visit current_path + + set_ace_editor_text %(wipple my stitches) + click_button "RUN TESTS" + + expect(page).to have_content "CANCEL" + SubmittedChallengeAnswer.last.update(status: "incorrect", test_results: "1) Y'all goofed the pooch") + + expect(page).to have_content("Err, try again…") + expect(page).to have_content("1) Y'all goofed the pooch") + end + + it "does not reset student work in one challenge if you submit another", js: true do + input_json[:content] << { type: "challenge", id: "2" } + input_json[:challenges][:"2"] = { + "id": "2", + "type": Challenge::TYPES[:code_snippet], + "tests": "describe('repeats', function() {\n\n it(\"returns true when given an empty string (which seems strange, but go with it :)\", function() {\n expect(repeats(\"\"), \"Default value is incorrect\").to.deep.eq(true)\n })\n\n it(\"returns true when the second half of the string equals the first\", function() {\n expect(repeats(\"bahbah\")).to.deep.eq(true)\n expect(repeats(\"nananananananana\")).to.deep.eq(true)\n })\n\n it(\"returns false when the second half of the string does not equal the first\", function() {\n expect(repeats(\"bahba\")).to.deep.eq(false)\n expect(repeats(\"nananananann\")).to.deep.eq(false)\n })\n\n it(\"does not use .repeat\", function() {\n expect(repeats.toString()).to.not.match(/\\.repeat/)\n })\n\n})\n", + "title": "repeats again (get it?)", + "language": "javascript", + "question": { + "content": [ + { "type": "markdown", "value": "\nWrite a function named repeats_again that returns true if the first half of the string equals the last half, and false if your name is Re-Pete.\n" } + ] + }, + "explanation": { + "content": [ + { "type": "markdown", "value": "\nYou might have solved this with a while loop, but using recursion is another way to do this.\n" } + ] + }, + "placeholder": "function repeats(input) {\n// write your code here thatreturns true if the first half of the string equals the last half, and false if not\n}" + } + + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + Challenge.create!(processed_text[:challenges][1].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content "repeats" + expect(page).to have_content "repeats_again" + + first_javascript_challenge_id = "#challenge_#{Challenge.find_by(title: 'repeats').id}" + second_javascript_challenge_id = "#challenge_#{Challenge.find_by(title: 'repeats again (get it?)').id}" + set_ace_editor_text("don't run me", first_javascript_challenge_id) + set_ace_editor_text("DO run me", second_javascript_challenge_id) + within second_javascript_challenge_id do + click_button "RUN TESTS" + end + expect(page).to have_content "CANCEL" + SubmittedChallengeAnswer.last.update(status: "correct", test_results: "1) You got the test right hooray") + wait_for_ajax + expect(page).to have_content("Correct") + + within first_javascript_challenge_id do + expect(page).to have_content "don't run me" + end + end + + describe "show_tests" do + it "should not show tests by default", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + within ".challenge-block" do + expect(page).not_to have_content "SHOW TESTS" + end + end + + it "should show tests when specified", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file, show_tests: true)) + + visit content_file_path(cohort, release.block, content_file.path) + + within ".challenge-block" do + expect(page).to have_content "SHOW TESTS" + end + end + end + + describe "hints" do + context "challenge has no hints" do + it "should not show hints", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + within ".challenge-block" do + expect(page).not_to have_content "Show Hints" + end + end + end + + context "challenge has hints" do + it "should allow a student to view a hint after 3 incorrect attempts", js: true do + challenge = Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file, hints: ["hint 1", "hint 2"])) + + visit content_file_path(cohort, release.block, content_file.path) + + within ".challenge-block" do + expect(page).not_to have_content "Show Hints" + end + + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, status: :incorrect, user_id: student.id) + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, status: :incorrect, user_id: student.id) + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, status: :incorrect, user_id: student.id) + + visit content_file_path(cohort, release.block, content_file.path) + + within ".challenge-block" do + expect(page).to have_css(".headingtitle", text: "SHOW HINTS") + + find(".headingtitle", text: "SHOW HINTS").click + expect(page).to have_css(".challenge-accordion.-is-expanded") + expect(page).to have_css(".headingtitle", text: "HIDE HINTS") + within(".challenge-accordion.-is-expanded") do + expect(page).to have_content("hint 1") + end + end + + visit content_file_path(cohort, release.block, content_file.path) + within ".challenge-block" do + expect(page).to have_css(".headingtitle", text: "SHOW HINTS") + expect(page).to_not have_css(".challenge-accordion.-is-expanded") + find(".headingtitle", text: "SHOW HINTS").click + within(".challenge-accordion.-is-expanded") do + expect(page).to have_content("hint 1") + end + find(".headingtitle", text: "HIDE HINTS").click + expect(page).to_not have_css(".challenge-accordion.-is-expanded") + expect(page).to_not have_content("hint 1") + expect(page).to have_css(".headingtitle", text: "SHOW HINTS") + end + + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, status: :correct, user_id: student.id) + visit content_file_path(cohort, release.block, content_file.path) + within ".challenge-block" do + expect(page).to have_css(".hints-wrapper") + end + end + end + + it "should allow a student to request more hints", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file, hints: ["hint 1", "hint 2", "hint 3"])) + + visit content_file_path(cohort, release.block, content_file.path) + + within ".challenge-block" do + find(".headingtitle", text: "SHOW HINTS").click + + within(".challenge-accordion.-is-expanded") do + expect(page).to have_content("hint 1") + expect(page).to_not have_content("hint 2") + expect(page).to_not have_content("hint 3") + + expect(page).to have_content("Get Another Hint") + + find("p", text: "Get Another Hint").click + expect(page).to have_content("hint 1") + expect(page).to have_content("hint 2") + expect(page).to_not have_content("hint 3") + expect(page).to have_content("Get Another Hint") + + find("p", text: "Get Another Hint").click + expect(page).to have_content("hint 1") + expect(page).to have_content("hint 2") + expect(page).to have_content("hint 3") + expect(page).to_not have_content("Get Another Hint") + end + + find(".headingtitle", text: "HIDE HINTS").click + expect(page).to_not have_css(".challenge-accordion.-is-expanded") + expect(page).to_not have_content("hint 1") + expect(page).to_not have_content("hint 2") + expect(page).to_not have_content("hint 3") + expect(page).to_not have_content("Get Another Hint") + + find(".headingtitle", text: "SHOW HINTS").click + expect(page).to have_css(".challenge-accordion.-is-expanded") + expect(page).to have_content("hint 1") + expect(page).to have_content("hint 2") + expect(page).to have_content("hint 3") + expect(page).to_not have_content("Get Another Hint") + end + end + end + end + + context "checkbox challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:checkbox], + "title": "People Skills", + "answer": ["James Bond", "Jaws"], + "options": [ + '`Peter Gr"unde`', + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nSelect the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause, duh.\n" + } + ] + } + } + } + } + end + + it "allows for any answer to be correct when the answer is just an asterisk" do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file, answer: ["*"])) + visit content_file_path(cohort, release.block, content_file.path) + find(:css, ".checkbox-challenge-input[name='`Peter Gr\"unde`']").set(true) + click_on "CHECK ANSWER" + expect(page).to have_content("Correct") + end + + + it "allows students to answer checkbox challenges", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content "Select the coolest?" + + find(:css, ".checkbox-challenge-input[name='Jaws']").set(true) + click_on "CHECK ANSWER" + + expect(page).to have_content("Err, try again…") + expect(page).to have_content("CHECK ANSWER") + + find(:css, ".checkbox-challenge-input[name='James Bond']").set(true) + click_on "CHECK ANSWER" + expect(page).to have_content("Correct") + find("div.headingtitle", text: "HIDE EXPLANATION") + expect(page).to have_content("Because, duh.") + expect(page).to_not have_content("Err, try again…") + + visit current_path + + expect(all("input:checked").length).to eq 2 + expect(page).to have_content("Correct") + find("div.headingtitle", text: "SHOW EXPLANATION").click + expect(page).to have_content("Because, duh.") + expect(page).to_not have_content("Err, try again…") + end + end + + context "multiple choice challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:multiple_choice], + "title": "People Skills", + "answer": '`Peter Gr"unde`', + "options": [ + '`Peter Gr"unde`', + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWho is the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause, duh.\n" + } + ] + } + } + } + } + end + + it "allows for any answer to be correct when the answer is just an asterisk" do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file, answer: "*")) + visit content_file_path(cohort, release.block, content_file.path) + choose('La Chaiffre') + click_on "CHECK ANSWER" + wait_for_ajax + expect(page).to have_content("Correct") + end + + it "allows students to navigate to challenge chat thread only if they have submitted an answer", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content "Who is the coolest?" + + expect(all(".chat-bubble").length).to eq(0) + choose('Peter Gr"unde') + click_on "CHECK ANSWER" + wait_for_ajax + expect(page).to have_content("Correct") + + expect(all(".vertical-dots").length).to eq(1) + + find(".vertical-dots").click + expect(page).to have_link("View Details") + end + + it "allows students to answer multiple choice challenges", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content "Who is the coolest?" + + choose("Jaws") + click_on "CHECK ANSWER" + + expect(page).to have_content("Err, try again…") + expect(page).to have_content("CHECK ANSWER") + + choose('Peter Gr"unde') + expect(page.html).to match('

    Peter Gr"unde

    ') + click_on "CHECK ANSWER" + expect(page).to have_content("Correct") + find("div.headingtitle", text: "HIDE EXPLANATION") + expect(page).to have_content("Because, duh.") + expect(page).to_not have_content("Err, try again…") + + visit current_path + + expect(find("input:checked").value).to eq('`Peter Gr"unde`') + expect(page).to have_content("Correct") + find("div.headingtitle", text: "SHOW EXPLANATION").click + expect(page).to have_content("Because, duh.") + expect(page).to_not have_content("Err, try again…") + end + + context "when the challenge contains mathjax" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:multiple_choice], + "title": "Mathjax nonsense", + "answer": "$$ \\sin \\theta = \\pm {\\sqrt {1-\\cos ^{2}\\theta }}$$ (it's this one)", + "options": [ + "$$ \\sin \\theta = \\pm {\\sqrt {1-\\cos ^{2}\\theta }}$$ (it's this one)", + "$$s = \\sqrt{\\frac{1}{N-1} \\sum_{i=1}^N (x_i - \\overline{x})^2}$$", + "$$x = {-b \\pm \\sqrt{b^2-4ac} \\over 2a}$$" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWhat is the right answer?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nEscaped mathjax stuff should work\n" + } + ] + } + } + } + } + end + + it "allows students to answer mathjax multiple choice challenges", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content "What is the right answer?" + radios = all("input[type='radio']") + radios[1].set(true) + click_on "CHECK ANSWER" + + expect(page).to have_content("Err, try again…") + expect(page).to have_content("CHECK ANSWER") + + radios[0].set(true) + click_on "CHECK ANSWER" + expect(page).to have_content("Correct") + find("div.headingtitle", text: "HIDE EXPLANATION") + expect(page).to have_content("Escaped mathjax stuff should work") + expect(page).to_not have_content("Err, try again…") + + visit current_path + + expect(find("input:checked").value).to eq "$$ \\sin \\theta = \\pm {\\sqrt {1-\\cos ^{2}\\theta }}$$ (it's this one)" + expect(page).to have_content("Correct") + expect(page).to_not have_content("Err, try again…") + end + end + + context "when there are multiple multiple choice challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + }, + { + type: "challenge", + id: "2" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:multiple_choice], + "title": "People Skills", + "answer": '`Peter Gr"unde`', + "options": [ + '`Peter Gr"unde`', + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWho is the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause, duh.\n" + } + ] + } + }, + "2": { + "id": "2", + "type": Challenge::TYPES[:multiple_choice], + "title": "Computer Skills", + "answer": "`Peter Gr'unde`", + "options": [ + "`Peter Gr'unde`", + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWho is the baddest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause, ruh roh.\n" + } + ] + } + } + } + } + end + + it "allows students to answer multiple multiple choice challenges", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + Challenge.create!(processed_text[:challenges][1].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + challenges = all(".challenge-block") + within challenges[0] do + expect(page).to have_content "Who is the coolest?" + choose('Peter Gr"unde') + expect(page.html).to match('

    Peter Gr"unde

    ') + click_on "CHECK ANSWER" + expect(page).to have_content("Correct") + find("div.headingtitle", text: "HIDE EXPLANATION") + expect(page).to have_content("Because, duh.") + expect(page).to have_content("CHECK ANSWER") + end + + within challenges[1] do + expect(page).to have_content "Who is the baddest?" + choose("Peter Gr'unde") + expect(page.html).to match("

    Peter Gr'unde

    ") + click_on "CHECK ANSWER" + expect(page).to have_content("Correct") + find("div.headingtitle", text: "HIDE EXPLANATION") + expect(page).to have_content("Because, ruh roh.") + choose("James Bond") + expect(page).to have_content("CHECK ANSWER") + end + + visit current_path + challenges = all(".challenge-block") + within challenges[0] do + expect(page).to have_content "Who is the coolest?" + expect(page.html).to match('

    Peter Gr"unde

    ') + find("div.headingtitle", text: "SHOW EXPLANATION").click + expect(page).to have_content("Correct") + expect(page).to have_content("Because, duh.") + end + + within challenges[1] do + expect(page).to have_content "Who is the baddest?" + expect(page.html).to match("

    Peter Gr'unde

    ") + expect(page).to have_content("Correct") + find("div.headingtitle", text: "SHOW EXPLANATION").click + expect(page).to have_content("Because, ruh roh.") + end + end + end + end + + context "project challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:project], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "## Markdown Parsing? $$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ [The famous Douglas Crockford gives a thorough introduction of Javascript](https://www.youtube.com/watch?v=t7_5-XYrkqg)" + } + ] + } + } + } + } + end + + it "allows the student to answer project challenges", js: true do + Timecop.freeze(DateTime.new(2016, 11, 1, 12)) do + expect(SubmittedChallengeAnswer.count).to eq(0) + + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content("Submit the link to your github repo") + fill_in "answer", with: "/user/test" + + click_button "SUBMIT" + find("div.headingtitle", text: "SHOW EXPLANATION").click + find("h2", text: "Markdown Parsing?") + # TODO: mathjax not running in feature spec anymore + # find("#MathJax-Span-3", text: "x") + # expect(page).to have_css(".mi", text: "x") + fill_in "answer", with: "" + find_field("answer").send_keys(:delete) + fill_in "answer", with: "/user/test" + find_field("answer").send_keys(:enter) + find("div.headingtitle", text: "HIDE EXPLANATION").click + find("div.headingtitle", text: "SHOW EXPLANATION").click + expect(page).to have_content("Markdown Parsing?") + find("h2", text: "Markdown Parsing?") + + expect(SubmittedChallengeAnswer.count).to eq(2) + + visit content_file_path(cohort, release.block, content_file.path) + find("div.headingtitle", text: "SHOW EXPLANATION").click + expect(page).to have_content("Markdown Parsing?") + expect(find_field("answer").value).to eq("/user/test") + end + end + end + + context "testable project challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:testable_project], + "title": "partial derivative", + "placeholder": "Enter the github URL to your work.", + "upstream_repo_path": "www.github.com/repo/with/test/suite", + "question": { + content: [ + { + "type": "markdown", + "value": "\nSubmit the link to your github repo. Make sure that the readme.md contains links to your Tracker backlog and hosted application.\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "## Your Tests results will appear here when done" + } + ] + } + } + } + } + end + + it "allows the student to answer testable project challenges", js: true do + Timecop.freeze(DateTime.new(2020, 11, 1, 12)) do + expect(SubmittedChallengeAnswer.count).to eq(0) + + allow_any_instance_of(BlockParser::ChallengeValidators::TestableProjectChallengeValidator).to receive(:valid_upstream_test_repo) + + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content("Submit the link to your github repo") + + fill_in "answer", with: "http://www.github.com/" + + # skip url validation and job creation + expect_any_instance_of(Cohorts::ContentFiles::SubmittedChallengeAnswersController).to receive(:enqueue_project_evaluation) + click_button "SUBMIT" + + wait_for_ajax + expect(page).to have_content("Running Tests (about a minute)…") + + expect(SubmittedChallengeAnswer.count).to eq(1) + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(submitted_challenge_answer.status).to eq("processing") + end + end + + it "allows the student to answer with enter key", js: true do + Timecop.freeze(DateTime.new(2020, 11, 1, 12)) do + allow_any_instance_of(BlockParser::ChallengeValidators::TestableProjectChallengeValidator).to receive(:valid_upstream_test_repo) + + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content("Submit the link to your github repo") + + fill_in "answer", with: "http://www.github.com/" + + # skip url validation and job creation + expect_any_instance_of(Cohorts::ContentFiles::SubmittedChallengeAnswersController).to receive(:enqueue_project_evaluation) + find_field("answer").send_keys(:enter) + + wait_for_ajax + expect(page).to have_content("Running Tests (about a minute)…") + + expect(SubmittedChallengeAnswer.count).to eq(1) + submitted_challenge_answer = SubmittedChallengeAnswer.last + expect(submitted_challenge_answer.status).to eq("processing") + end + end + end + + context "paragraph challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:paragraph], + "title": "explain partial derivative", + "placeholder": "Submit all the words", + "question": { + content: [ + { + "type": "markdown", + "value": "\nType all the words you know into this textbox\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "## Markdown Parsing? $$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ [The famous Douglas Crockford gives a thorough introduction of Javascript](https://www.youtube.com/watch?v=t7_5-XYrkqg)" + } + ] + } + } + } + } + end + + it "allows the student to answer paragraph challenges", js: true do + Timecop.freeze(DateTime.new(2020, 11, 1, 12)) do + expect(SubmittedChallengeAnswer.count).to eq(0) + + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + expect(page).to have_content("Type all the words you know into this textbox") + fill_in "answer", with: "Soooooo many words that it must be true." + + click_button "SUBMIT" + + find("div.headingtitle", text: "SHOW EXPLANATION").click + find("h2", text: "Markdown Parsing?") + # TODO: fix mathjax in tests + # find("#MathJax-Span-3", text: "x") + # expect(page).to have_css(".mi", text: "x") + + expect(SubmittedChallengeAnswer.count).to eq(1) + + visit content_file_path(cohort, release.block, content_file.path) + find("div.headingtitle", text: "SHOW EXPLANATION").click + expect(page).to have_content("Markdown Parsing?") + end + end + end + + context "number challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:number], + "title": "divide", + "placeholder": "enter answer", + "answer": "0.3", + "decimal": "1", + "question": { + content: [ + { + "type": "markdown", + "value": "1/3 = ?" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nYou got it right!\n" + } + ] + } + } + } + } + end + + it "allows students to answer number challenges", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + expect(page).to have_content("1/3 = ?") + fill_in "answer", with: ".333" + click_on "CHECK ANSWER" + + expect(page).to have_content("Correct") + find("div.headingtitle", text: "HIDE EXPLANATION") + expect(page).to have_content("You got it right!") + + visit current_path + expect(page).to have_content("1/3 = ?") + + find_field("answer").send_keys(:delete) + + fill_in "answer", with: ".5" + find_field("answer").send_keys(:enter) # test key submission + + expect(page).to have_content("Err, try again…") + expect(page).to_not have_content("You got it right!") + end + end + + context "short-answer challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:short_answer], + "title": "Titleicious", + "placeholder": "enter answer", + "answer": "Very cool", + "question": { + content: [ + { + "type": "markdown", + "value": "How cool is Logan?" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nYou got it right!\n" + } + ] + } + } + } + } + end + + # this is breaking on CI nondeterministically and passes local every time + xit "allows students to answer short-answer challenges", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + expect(page).to have_content "How cool is Logan?" + + fill_in "answer", with: "kinda cool" + click_on "CHECK ANSWER" + expect(page).to have_content("Err, try again…") + expect(page).to have_content("CHECK ANSWER") + + find_field("answer").send_keys(:delete) # test key submission + fill_in "answer", with: " Very cool " + find_field("answer").click + find_field("answer").send_keys(:enter) # test key submission + expect(page).to have_content("CHECK ANSWER") + expect(page).to have_content("CHECK ANSWER") + expect(page).to have_content("Correct") + find("div.headingtitle", text: "HIDE EXPLANATION") + expect(page).to have_content("You got it right!") + expect(page).to_not have_content("Err, try again…") + end + + xit "allows students to answer short-answer challenges by pressing enter", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + expect(page).to have_content "How cool is Logan?" + + fill_in "answer", with: "kinda cool" + find("#challenge-button-1").native.send_keys(:return) + expect(page).to have_content("Err, try again…") + expect(page).to have_content("CHECK ANSWER") + + find_field("answer").send_keys(:delete) # test key submission + fill_in "answer", with: " Very cool " + find_field("answer").click + find_field("answer").send_keys(:enter) # test key submission + find("div.headingtitle", text: "HIDE EXPLANATION") + expect(page).to have_content("You got it right!") + expect(page).to_not have_content("Err, try again…") + end + + it "allows students to answer short-answer challenges with regex", js: true do + input_json[:challenges][:"1"][:answer] = '/\{\{\s*article\.publishedOn\s+\|\s+date:\'MM-DD-YYYY\'\s*\}\}/' + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + expect(page).to have_content "How cool is Logan?" + + fill_in "answer", with: "{{article.Publishedon | date:'MM-DD-YYYY'}}" + click_on "CHECK ANSWER" + expect(page).to have_content("Err, try again…") + expect(page).to have_content("CHECK ANSWER") + + fill_in "answer", with: " {{article.publishedOn | date:'MM-DD-YYYY'}} " + click_on "CHECK ANSWER" + expect(page).to have_content("Correct") + find("div.headingtitle", text: "HIDE EXPLANATION") + expect(page).to have_content("You got it right!") + expect(page).to_not have_content("Err, try again…") + end + end + + context "advanced code snippet challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "123" + } + ], + challenges: { + "123": { + "id": "123", + "type": Challenge::TYPES[:code_snippet], + "title": "repeats", + "placeholder": "// Write your function in Javascript below", + "question": { + content: [ + { + "type": "markdown", + "value": "Write a function named repeats that returns true if the first half of the string equals the last half, and false if not." + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "You might have solved this with a while loop, but using recursion is another way to do this." + } + ] + }, + "language": "javascript", + "tests": %{describe('repeats', function() { + + it("returns true when given an empty string (which seems strange, but go with it :)", function() { + expect(repeats(""), "Default value is incorrect").to.deep.eq(true) + }) + + it("returns true when the second half of the string equals the first", function() { + expect(repeats("bahbah")).to.deep.eq(true) + expect(repeats("nananananananana")).to.deep.eq(true) + }) + + it("returns false when the second half of the string does not equal the first", function() { + expect(repeats("bahba")).to.deep.eq(false) + expect(repeats("nananananann")).to.deep.eq(false) + }) + + it("does not use .repeat", function() { + expect(repeats.toString()).to.not.match(/\.repeat/) + }) + +}) +} + } + } + } + end + + it "allows students to answer code snippet challenges", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + VCR.use_cassette("features/students/content_file/assessment_service") do + visit content_file_path(cohort, release.block, content_file.path) + + fill_in_editor_field "something that is wrong" + + click_on "RUN TESTS" + expect(page).to have_content("CANCEL") + end + + expect(page).to_not have_selector(".explanation") + + test_results = "2 passing 1 failing" + mock_assessment_callback(SubmittedChallengeAnswer.last, :incorrect, test_results) + expect(page).to have_content("something that is wrong") + + expect(page).to have_content("Err, try again…") + expect(page).to have_content(test_results) + find_ace_editor_field + fill_in_editor_field "something that will be canceled" + + click_on "RUN TESTS" + expect(page).to have_content("CANCEL") + + click_on "CANCEL" + mock_assessment_callback(SubmittedChallengeAnswer.last, "canceled", "Tests canceled by student.") + expect(page).to have_content("CANCELING...") + wait_for_ajax + expect(page).to have_content("RUN TESTS") + expect(page).to have_content("Tests canceled by student.") + expect(page).to_not have_content(test_results) + + find_ace_editor_field + fill_in_editor_field "something that is correct" + + find_ace_editor_field.first.send_keys(%i[command enter]) # testing key submits + + unless page.has_content?("Tests Running…") + # command + enter does not want to work on circle ci + click_on "RUN TESTS" + end + + within(".results") do + expect(page).to have_content("Tests Running…") + end + + correct_test_results = "3 passing" + mock_assessment_callback(SubmittedChallengeAnswer.last, :correct, correct_test_results) + find_ace_editor_field + expect(page).to have_content("something that is correct") + expect(page).to have_content(correct_test_results) + + expect(page).to have_content("Correct") + expect(page).to have_content("HIDE EXPLANATION") + + visit current_path + find_ace_editor_field + find("div.headingtitle", text: "SHOW EXPLANATION").click + + expect(page).to have_content("something that is correct") + expect(page).to_not have_content("Tests canceled by student.") + + expect(page).to have_content("Correct") + end + end + + context "local js challenges" do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:local_snippet], + "tests": "describe('repeats', function() {\n\n it(\"returns true when given an empty string (which seems strange, but go with it :)\", function() {\n expect(repeats(\"\"), \"Default value is incorrect\").to.deep.eq(true)\n })\n\n it(\"returns true when the second half of the string equals the first\", function() {\n expect(repeats(\"bahbah\")).to.deep.eq(true)\n expect(repeats(\"nananananananana\")).to.deep.eq(true)\n })\n\n it(\"returns false when the second half of the string does not equal the first\", function() {\n expect(repeats(\"bahba\")).to.deep.eq(false)\n expect(repeats(\"nananananann\")).to.deep.eq(false)\n })\n\n it(\"does not use .repeat\", function() {\n expect(repeats.toString()).to.not.match(/\\.repeat/)\n })\n\n})\n", + "title": "repeats", + "language": "javascript", + "question": { + "content": [ + { "type": "markdown", "value": "\nWrite a function named repeats that returns true if the first half of the string equals the last half, and false if not.\n" } + ] + }, + "explanation": { + "content": [ + { "type": "markdown", "value": "\nYou might have solved this with a while loop, but using recursion is another way to do this.\n" } + ] + }, + "placeholder": "function repeats(input) {\n// write your code here thatreturns true if the first half of the string equals the last half, and false if not\n}" + } + } + } + end + + it "allows input resets", js: true do + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content "repeats" + expect(page).to have_content "JAVASCRIPT" + expect(page).to have_content input_json[:challenges][:"1"][:placeholder] + + # Reset button should reset input to placeholder text + set_ace_editor_text "asdfjkl;" + expect(page).to have_content "asdfjkl;" + click_button "Reset Input" + expect(page).to_not have_content "asdfjkl;" + expect(page).to have_content input_json[:challenges][:"1"][:placeholder] + end + + it "does not reset student work in one challenge if you submit another", js: true do + input_json[:content] << { type: "challenge", id: "2" } + input_json[:challenges][:"2"] = { + "id": "2", + "type": Challenge::TYPES[:code_snippet], + "tests": "describe('repeats', function() {\n\n it(\"returns true when given an empty string (which seems strange, but go with it :)\", function() {\n expect(repeats(\"\"), \"Default value is incorrect\").to.deep.eq(true)\n })\n\n it(\"returns true when the second half of the string equals the first\", function() {\n expect(repeats(\"bahbah\")).to.deep.eq(true)\n expect(repeats(\"nananananananana\")).to.deep.eq(true)\n })\n\n it(\"returns false when the second half of the string does not equal the first\", function() {\n expect(repeats(\"bahba\")).to.deep.eq(false)\n expect(repeats(\"nananananann\")).to.deep.eq(false)\n })\n\n it(\"does not use .repeat\", function() {\n expect(repeats.toString()).to.not.match(/\\.repeat/)\n })\n\n})\n", + "title": "repeats again (get it?)", + "language": "javascript", + "question": { + "content": [ + { "type": "markdown", "value": "\nWrite a function named repeats_again that returns true if the first half of the string equals the last half, and false if your name is Re-Pete.\n" } + ] + }, + "explanation": { + "content": [ + { "type": "markdown", "value": "\nYou might have solved this with a while loop, but using recursion is another way to do this.\n" } + ] + }, + "placeholder": "function repeats(input) {\n// write your code here thatreturns true if the first half of the string equals the last half, and false if not\n}" + } + + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + Challenge.create!(processed_text[:challenges][1].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + expect(page).to have_content "repeats" + expect(page).to have_content "repeats_again" + + first_javascript_challenge_id = "#challenge_#{Challenge.find_by(title: 'repeats').id}" + second_javascript_challenge_id = "#challenge_#{Challenge.find_by(title: 'repeats again (get it?)').id}" + set_ace_editor_text("don't run me", first_javascript_challenge_id) + set_ace_editor_text("DO run me", second_javascript_challenge_id) + within second_javascript_challenge_id do + click_button "RUN TESTS" + end + expect(page).to have_content "CANCEL" + SubmittedChallengeAnswer.last.update(status: "correct", test_results: "1) You got the test right hooray") + wait_for_ajax + expect(page).to have_content("Correct") + + within first_javascript_challenge_id do + expect(page).to have_content "don't run me" + end + end + end + end + + context "taking a checkpoint", js: true do + let(:input_json) do + { + content: [ + { + type: "challenge", + id: "1" + }, + { + type: "challenge", + id: "2" + }, + { + type: "challenge", + id: "3" + } + ], + challenges: { + "1": { + "id": "1", + "type": Challenge::TYPES[:number], + "title": "divide", + "placeholder": "enter answer", + "answer": "0.3", + "decimal": "1", + "question": { + content: [ + { + "type": "markdown", + "value": "1/3 = ?" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nYou got it right!\n" + } + ] + } + }, + "2": { + "id": "2", + "type": Challenge::TYPES[:multiple_choice], + "title": "People Skills", + "answer": '`Peter Gr"unde`', + "options": [ + '`Peter Gr"unde`', + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nWho is the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause, duh.\n" + } + ] + } + }, + "3": { + "id": "3", + "type": Challenge::TYPES[:checkbox], + "title": "People Skills", + "answer": ["James Bond", "Jaws"], + "options": [ + "`Peter Grunde`", + "Dr. No", + "James Bond", + "La Chaiffre", + "Jaws" + ], + "question": { + content: [ + { + "type": "markdown", + "value": "\nSelect the coolest?\n" + } + ] + }, + "explanation": { + "content": [ + { + "type": "markdown", + "value": "\nBecause, duh.\n" + } + ] + } + } + } + } + end + let(:content_file) { create(:content_file, :checkpoint, html: processed_text[:html], standard: create(:standard, release: release, position: 1), max_checkpoint_submissions: 3) } + let(:processed_text) { BlockParser::BuildHtmlFromMdJson.process_json(json_hash: input_json, asset_uploader: nil, root_directory_path: "", current_content_file_path: "", content_file_paths: []) } + + it "toggles the disabled state of the checkpoint when questions are answered" do + Challenge.create!(processed_text[:challenges][2].merge(content_file: content_file)) + visit content_file_path(cohort, release.block, content_file.path) + + find(:css, ".checkpoint-landing-button").click + + within(".checkpoint-challenges-attempted") do + expect(page).to have_content("0/1") + end + + find(:css, ".checkbox-challenge-input[name='Jaws']").set(true) + + within(".checkpoint-challenges-attempted") do + expect(page).to have_content("1/1") + end + end + + it "shows a simple message for fresh, single-challenge checkpoints" do + create(:content_file, html: processed_text[:html], standard: create(:standard, release: release, position: 2)) # have a next content file + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + within(".challenge-count") do + expect(page).to have_content("1\nQuestion") + end + + within(".checkpoint-landing-summary") do + expect(page).to have_content("This assessment contains free-response challenges.") + end + end + + context "when in percentage mode" do + xit "shows checkpoints total points worth" do + cohort.update(mode: Cohort::MODES[:percentage]) + create(:content_file, html: processed_text[:html], standard: create(:standard, release: release, position: 2)) # have a next content file + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file, points: 32)) + Challenge.create!(processed_text[:challenges][1].merge(content_file: content_file, points: 18)) + + visit content_file_path(cohort, release.block, content_file.path) + + within(".checkpoint-info--header") do + expect(page).to have_content("50 points") + end + end + end + + it "shows a detailed message for multi-challenge checkpoints" do + create(:content_file, html: processed_text[:html], standard: create(:standard, release: release, position: 2)) # have a next content file + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + Challenge.create!(processed_text[:challenges][1].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + within(".checkpoint-landing-summary") do + expect(page).to have_content("This assessment contains multiple choice and free-response challenges. Answer as many questions as possible and submit the entire assessment at once when you’re done.") + end + end + + it "allows a student to save a checkpoint for non autoscored checkpoint" do + create(:content_file, html: processed_text[:html], standard: create(:standard, release: release, position: 2)) # have a next content file + Challenge.create!(processed_text[:challenges][0].merge(content_file: content_file)) + Challenge.create!(processed_text[:challenges][1].merge(content_file: content_file)) + + visit content_file_path(cohort, release.block, content_file.path) + + find(".checkpoint-landing-button").click + + # TODO: hover actions + # find(".actionrow").hover + # expect(page).to have_content("You must answer at least one challenge before submitting.") + + expect(page).to have_content "1/3 = ?" + fill_in "answer", with: "0.2" + + # expect(page).to_not have_selector(".-submitbttn.-disabled") + # find(".actionrow").hover + # expect(page).to_not have_content("You must answer at least one challenge before submitting.") + + expect(page).to have_content "Who is the coolest?" + choose("Jaws") + + within ".checkpoint-navbar-details" do + find("button.button-solid-primary").click + end + + within ".modal-contents" do + expect(page).to have_content("Submit your responses?") + end + + within ".modal-actions" do + find(".modal-action").click + end + + within ".modal-contents" do + expect(page).to have_content("Submitted!") + expect(page).to have_content("Your assessment was submitted at") + end + end + + it "allows a student to save a checkpoint for an autoscored checkpoint" do + cf = create(:content_file, :checkpoint, html: processed_text[:html], standard: create(:standard, release: release, position: 2), autoscore: true) + + Challenge.create!(processed_text[:challenges][0].merge(content_file: cf)) + Challenge.create!(processed_text[:challenges][1].merge(content_file: cf)) + + visit content_file_path(cohort, release.block, cf.path) + click_button("Start Assessment") + + expect(page).to have_content "1/3 = ?" + fill_in "answer", with: "0.2" + + within ".checkpoint-navbar-details" do + find("button.button-solid-primary").click + end + + within ".modal-contents" do + expect(page).to have_content("Submit your responses?") + end + + within ".modal-actions" do + find(".modal-action").click + end + + within ".modal-contents" do + expect(page).to have_content("Submission Scored!") + expect(page).to have_content("You received a mastery score of 1") + end + + within ".modal-actions" do + find(".modal-action").click + end + visit content_file_path(cohort, release.block, cf.path) + within ".checkpoint-details" do + expect(page).to have_content("Retake Assessment") + click_button("Retake Assessment") + end + + expect(page).to have_content "1/3 = ?" + find_field("answer").send_keys(:delete) + fill_in "answer", with: "0.33" + + expect(page).to have_content "Who is the coolest?" + choose('Peter Gr"unde') + + within ".checkpoint-navbar-details" do + find("button.button-solid-primary").click + end + + within ".modal-actions" do + find(".modal-action").click + end + + within ".modal-contents" do + expect(page).to have_content("Submission Scored!") + expect(page).to have_content("You received a mastery score of 3") + end + + within ".modal-actions" do + find(".modal-action").click + end + end + end + # rubocop:enable Metrics/LineLength + end +end diff --git a/scripts/spec/features/home/header_feature_spec.rb b/scripts/spec/features/home/header_feature_spec.rb new file mode 100644 index 0000000..4b327d0 --- /dev/null +++ b/scripts/spec/features/home/header_feature_spec.rb @@ -0,0 +1,181 @@ +require "features_helper" + +describe "Header", js: true do + let(:cohort) { create(:cohort) } + + before do + sign_in(user) + visit(setup_cohort_path(cohort)) + end + + context "when signed in as an blocks manager" do + let!(:user) { create(:user, :blocks_manager) } + + it "shows the blocks link in the menu" do + within(".primary-navigation") do + find(".label", text: "Admin").hover + expect(page).to have_link("Blocks") + end + end + end + + context "when user is an admin" do + let!(:user) { create(:user, :admin) } + + it "shows api token link" do + within(".primary-navigation") do + all(".navigation-dropdown").last.hover + expect(page).to have_link("API Token", href: "/api_token") + end + end + end + + context "when user is an instructor" do + let!(:user) { create(:cohort_user, :instructor, cohort: cohort).user } + + it "shows the cohorts menu item" do + within(".primary-navigation") do + find(".navigation-dropdown", text: cohort.name).hover + expect(page).to have_link(cohort.name) + end + + within(".secondary-navigation") do + # it links to the cohort curriculum page + expect(page).to have_link("Curriculum", href: cohort_path(cohort)) + + # it links to the cohort progress page + expect(page).to have_link("Course Stats", href: course_stats_cohort_path(cohort)) + + # it links to the cohort setup page + expect(page).to have_link("Setup") + end + end + + context "when you have already visited a setup url" do + it "has a setup url with correct href" do + within(".secondary-navigation") do + expect(page).to have_link("Setup", href: setup_cohort_path(cohort)) + visit(users_cohort_path(cohort)) + visit(cohort_path(cohort)) + expect(page).to have_link("Setup", href: users_cohort_path(cohort)) + end + end + end + + it "does not show the blocks link in the menu" do + within(".primary-navigation") do + expect(page).to_not have_link("Blocks") + end + end + + it "shows api token link" do + within(".primary-navigation") do + all(".navigation-dropdown").last.hover + expect(page).to have_link("API Token", href: "/api_token") + end + end + end + + context "when user is a student" do + let!(:user) { create(:cohort_user, :student, cohort: cohort).user } + + it "shows the cohorts menu item" do + within(".primary-navigation") do + find(".navigation-dropdown", text: cohort.name).hover + expect(page).to have_link(cohort.name) + end + # removed for students + expect(page).to_not have_link("Curriculum", href: cohort_path(cohort)) + expect(page).to_not have_link("Submission", href: submissions_dashboard_cohort_user_path(cohort, user)) + end + + it "does not show the blocks link in the menu" do + within(".primary-navigation") do + expect(page).to_not have_link("Blocks") + end + end + + it "does not show api token link" do + within(".primary-navigation") do + all(".navigation-dropdown").last.hover + expect(page).to_not have_link("API Token", href: "/api_token") + end + end + end + + context "when the user does not have a profile image" do + let!(:user) { create(:user, profile_image: "", first_name: "Foo", last_name: "Bar") } + + it "shows the user initials" do + within(".user-avatar") do + expect(page).to have_content("FB") + end + end + end + + context "when the user has a profile image" do + let!(:user) { create(:user, profile_image: "/favicon.ico") } + + it "shows the profile image" do + expect(page.find(".user-avatar")["style"]).to include(user.profile_image) + end + end + + context "when a user has a notification that their checkpoint has been scored" do + let!(:user) { create(:user) } + let!(:notification) { create(:notification, tagline: "Checkpoint scored by Jeff Dean", title: "Checkpoint foo", description: "You got a 1.", user: user) } + + it "links the user to the notifications list page" do + visit(cohort_path(cohort)) + + within(".notificationscount") do + expect(page).to have_content("1") + end + + page.find(".notificationsicon").click + + within("#notification-#{notification.id}.unread") do + within(".notificationtagline") do + expect(page).to have_content("Checkpoint scored by Jeff Dean") + end + + within(".notificationdescription") do + expect(page).to have_content("You got a 1.") + end + end + + page.find("#notification-#{notification.id}").click + + visit(cohort_path(cohort)) + + page.find(".notificationsicon").click + + expect(page).to have_selector("#notification-#{notification.id}.read") + end + + it "allows the user to mark all notifications as read" do + visit(cohort_path(cohort)) + + page.find(".notificationsicon").click + + expect(page).to have_selector("#notification-#{notification.id}.unread") + + find(".markread").click + + expect(page).to have_selector("#notification-#{notification.id}.read") + + expect(notification.reload.read_at).to_not eq(nil) + end + end + + describe "account management" do + let!(:user) { create(:user) } + + it "links out to auth account management" do + within(".primary-navigation") do + all(".navigation-dropdown").last.hover + # expect(page).to have_link("My Account", href: "#{Rails.application.secrets.auth_url}/account") + end + end + end +end diff --git a/scripts/spec/features_helper.rb b/scripts/spec/features_helper.rb new file mode 100644 index 0000000..90add4f --- /dev/null +++ b/scripts/spec/features_helper.rb @@ -0,0 +1,96 @@ +require "spec_helper" + +def register_chrome_driver(name, headless = false) + args = %w[disable-gpu disable-popup-blocking no-sandbox window-size=1920x1920] + caps = Selenium::WebDriver::Remote::Capabilities.chrome( + chromeOptions: { args: args + (headless ? ["headless"] : []) } + ) + Capybara.register_driver name do |app| + Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: caps) + end + name +end + +register_chrome_driver(:chrome, false) +register_chrome_driver(:headless_chrome, true) +Capybara.javascript_driver = :headless_chrome + +# configure capybara to run on a specific port +Capybara.server_host = "localhost" +Capybara.server_port = 31337 +Capybara.default_max_wait_time = 10 +Capybara.server = :puma + +RSpec.configure do |config| + config.use_transactional_fixtures = false + + config.before(:each) do + DatabaseCleaner.strategy = Capybara.current_driver == :rack_test ? :transaction : :truncation + DatabaseCleaner.start + + Capybara.page.driver.reset! + Capybara.reset_sessions! + end + + config.after(:each) do + Capybara.page.driver.reset! + Capybara.reset_sessions! + + DatabaseCleaner.clean + end + + config.before(:suite) do + begin + DatabaseCleaner.start + ensure + DatabaseCleaner.clean + end + end +end + +def fill_in_editor_field(text, n = 0) + find_ace_editor_field[n].set text +end + +# Ace uses textarea.ace_text-input as +# its input stream. +def find_ace_editor_field + input_field_locator = ".ace_text-input" + is_input_field_visible = false + all(input_field_locator, visible: is_input_field_visible) +end + +def mock_assessment_callback(submitted_challenge_answer, result, test_results) + submitted_challenge_answer.update(status: result, test_results: test_results) +end + +def set_ace_editor_text(text, wrapper_selector = nil) + if wrapper_selector + within wrapper_selector do + change_ace_editor(text) + end + else + change_ace_editor(text) + end +end + +def change_ace_editor(text) + within ".ace_editor" do + find("textarea", visible: false).set text + end +end + +# https://medium.com/doctolib/hunting-flaky-tests-2-waiting-for-ajax-bd76d79d9ee9 +def wait_for_ajax + begin + Timeout.timeout(Capybara.default_max_wait_time) do + loop until finished_all_ajax_requests? + end + rescue => e + end +end + +def finished_all_ajax_requests? + page.execute_script("return (window.pendingRequestCount === 0)") + # page.execute_script("window.pendingRequestCount").zero? +end diff --git a/scripts/spec/finders/block_finder_spec.rb b/scripts/spec/finders/block_finder_spec.rb new file mode 100644 index 0000000..ec0a235 --- /dev/null +++ b/scripts/spec/finders/block_finder_spec.rb @@ -0,0 +1,30 @@ +require "spec_helper" + +describe BlockFinder do + describe ".available_blocks_for" do + let(:cohort) { create(:cohort) } + + # block without release DOES NOT SHOW UP + let!(:no_release_block) { create(:block) } + + # block already attached to the cohort DOES NOT SHOW UP + let(:attached_block) { create(:block) } + let!(:attached_blocks_release) { create(:release, block: attached_block) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: attached_blocks_release) } + + # block with release unattached to cohort SHOWS UP + let(:available_block) { create(:block, title: "a") } + let!(:first_available_blocks_release) { create(:release, block: available_block) } + let!(:second_available_blocks_release) { create(:release, block: available_block) } + + # block attached to different cohort SHOWS UP + let!(:other_cohort) { create(:cohort) } + let(:attached_available_block) { create(:block, title: "z") } + let!(:attached_available_blocks_release) { create(:release, block: attached_available_block) } + let!(:other_cohort_release) { create(:cohort_release, cohort: other_cohort, release: attached_available_blocks_release) } + + it "yields the available block" do + expect(described_class.available_blocks_for(cohort.id).to_a).to eq([available_block, attached_available_block]) + end + end +end diff --git a/scripts/spec/finders/checkpoint_submission_finder_spec.rb b/scripts/spec/finders/checkpoint_submission_finder_spec.rb new file mode 100644 index 0000000..f2e71b8 --- /dev/null +++ b/scripts/spec/finders/checkpoint_submission_finder_spec.rb @@ -0,0 +1,183 @@ +require "spec_helper" + +describe CheckpointSubmissionFinder do + let!(:cohort) { create(:cohort) } + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:block) { create(:block) } + let!(:release) { create(:release, block: block) } + let!(:standard) { create(:standard, release: release, uid: "abc") } + let!(:content_file) { create(:content_file, uid: "foobaz", standard: standard) } + let!(:challenge) { create(:challenge, content_file: content_file) } + let!(:checkpoint_submission) { create_checkpoint_submission(student, challenge, cohort: cohort, created_at: 1.minute.ago, content_file_uid: content_file.uid, content_file_block_id: block.id, state: "needs_review") } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, checkpoint_submission: checkpoint_submission) } + + describe "#latest_two_by_state_by_standards" do + # student will have three checkpoint submissions in the needs_review state, we should receive the latest two + let!(:one_day_checkpoint_submission) { create_checkpoint_submission(student, challenge, cohort: cohort, created_at: 1.day.ago, content_file_uid: content_file.uid, content_file_block_id: block.id, state: "needs_review") } + let!(:one_day_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, checkpoint_submission: one_day_checkpoint_submission) } + let!(:oldest_checkpoint_submission) { create_checkpoint_submission(student, challenge, cohort: cohort, created_at: 2.days.ago, content_file_uid: content_file.uid, content_file_block_id: block.id, state: "needs_review" ) } + let!(:oldest_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, checkpoint_submission: oldest_checkpoint_submission) } + + # should still receive a single latest answer for other states if they exist + let!(:done_checkpoint_submission) { create_checkpoint_submission(student, challenge, cohort: cohort, content_file_uid: content_file.uid, content_file_block_id: block.id, state: "done") } + let!(:done_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, checkpoint_submission: done_checkpoint_submission) } + + # second content file will get created with a completely different standard and block - expect separate entries for separate standards + let(:block_2) { create(:block) } + let(:release_2) { create(:release, block: block_2) } + let(:standard_2) { create(:standard, release: release_2, uid: "123") } + let!(:content_file_2) { create(:content_file, standard: standard_2) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2) } + + let!(:checkpoint_submission_2) { create_checkpoint_submission(student, challenge_2, cohort: cohort, content_file_uid: content_file_2.uid, content_file_block_id: block_2.id, state: "needs_review") } + let!(:submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, checkpoint_submission: checkpoint_submission_2) } + let!(:one_day_checkpoint_submission_2) { create_checkpoint_submission(student, challenge_2, cohort: cohort, created_at: 1.day.ago, content_file_uid: content_file_2.uid, content_file_block_id: block_2.id, state: "needs_review") } + let!(:one_day_submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, checkpoint_submission: one_day_checkpoint_submission_2) } + let!(:oldest_checkpoint_submission_2) { create_checkpoint_submission(student, challenge_2, cohort: cohort, created_at: 2.days.ago, content_file_uid: content_file_2.uid, content_file_block_id: block_2.id, state: "needs_review") } + let!(:oldest_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, checkpoint_submission: oldest_checkpoint_submission_2) } + + it "yields the last two checkpoints per state per separate standard" do + all_results = described_class.latest_two_by_state_by_standards(cohort_id: cohort.id, student_ids: [student.id], standard_uids: [standard.uid, standard_2.uid], block_ids:[block.id, block_2.id]) + ids = all_results.map(&:id) + expected_set = [checkpoint_submission, one_day_checkpoint_submission, done_checkpoint_submission, checkpoint_submission_2, one_day_checkpoint_submission_2] + expect(ids.sort).to eq(expected_set.map(&:id).sort) + end + + end + + describe "#all_for_user_standards" do + let!(:checkpoint_submission_again) { create_checkpoint_submission(student, challenge, cohort: cohort) } + let!(:submitted_challenge_answer_again) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, checkpoint_submission: checkpoint_submission_again) } + + let(:content_file_2) { create(:content_file, uid: "bizzbuzz") } + let(:challenge_2) { create(:challenge, content_file: content_file_2) } + let!(:checkpoint_submission_2) { create_checkpoint_submission(student, challenge_2, cohort: cohort) } + let!(:submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge_2, checkpoint_submission: checkpoint_submission_2) } + + it "yields all checkpoint submissions for the given standard uids" do + all_results = described_class.all_for_user_standards(student.id, [content_file.standard.uid, content_file_2.standard.uid]) + expect(all_results).to eq([checkpoint_submission, checkpoint_submission_again, checkpoint_submission_2]) + + first_standard_results = described_class.all_for_user_standards(student.id, [content_file.standard.uid]) + expect(first_standard_results).to eq([checkpoint_submission, checkpoint_submission_again]) + + second_standard_results = described_class.all_for_user_standards(student.id, [content_file_2.standard.uid]) + expect(second_standard_results).to eq([checkpoint_submission_2]) + end + + it "addes the standard_uid attribute to the checkpoint submission" do + second_standard_results = described_class.all_for_user_standards(student.id, [content_file_2.standard.uid]) + expect(second_standard_results[0].standard_uid).to eq(content_file_2.standard.uid) + end + end + + describe ".latest_for_checkpoint_content_file_for_user_in_cohort" do + subject do + described_class.latest_for_checkpoint_content_file_for_user_in_cohort( + cohort_id: cohort.id, + content_file: content_file, + user_id: student.id + ) + end + + it "returns the latest checkpoint submission for the provided content file for user in cohort" do + different_user = create(:user) + different_student_checkpoint_submission = create_checkpoint_submission(different_user, challenge, cohort: cohort) + create(:submitted_challenge_answer, cohort: cohort, user: different_user, challenge: challenge, checkpoint_submission: different_student_checkpoint_submission) + + different_challenge = create(:challenge, content_file: create(:content_file, :checkpoint)) + different_content_file_checkpoint_submission = create_checkpoint_submission(student, different_challenge, cohort: cohort) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: different_challenge, checkpoint_submission: different_content_file_checkpoint_submission) + + different_cohort_checkpoint_submission = create_checkpoint_submission(student, challenge, cohort: create(:cohort)) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, checkpoint_submission: different_cohort_checkpoint_submission) + + more_recent_checkpoint_submission = create_checkpoint_submission(student, challenge, cohort: cohort) + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, checkpoint_submission: more_recent_checkpoint_submission) + + expect(subject).to eq(more_recent_checkpoint_submission) + end + + context "when a new release is created without changing the content file uid" do + let(:new_content_file) { create(:content_file, :checkpoint, uid: "foobaz", standard: create(:standard, release: create(:release, block: content_file.standard.release.block))) } + let(:new_challenge) { create(:challenge, content_file: new_content_file) } + let(:checkpoint_submission_2) { create_checkpoint_submission(student, new_challenge, cohort: cohort) } + + it "fetches the checkpoint submissions from the previous release" do + create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: new_challenge, checkpoint_submission: checkpoint_submission_2) + + expect(subject).to eq(checkpoint_submission_2) + end + end + end + + describe ".all_for_user_id_content_file_in_cohort" do + context "when there is a set of answers across multiple uid-matched content files" do + let(:block) { create(:block) } + let(:first_release) { create(:release, block: block) } + let(:first_standard) { create(:standard, release: first_release) } + let(:first_content_file) { create(:content_file, :checkpoint, standard: first_standard, uid: "match") } + + let(:second_release) { create(:release, block: block) } + let(:second_standard) { create(:standard, release: second_release) } + let(:second_content_file) { create(:content_file, :checkpoint, standard: second_standard, uid: "match") } + + let!(:first_challenge) { create(:challenge, content_file: first_content_file) } + let!(:second_challenge) { create(:challenge, content_file: second_content_file) } + + let(:first_checkpoint_submission) { create_checkpoint_submission(student, first_challenge, cohort: cohort) } + let!(:first_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort,user: student, challenge: first_challenge, checkpoint_submission: first_checkpoint_submission) } + + let(:unseen_block) { create(:block) } + let(:unseen_release) { create(:release, block: unseen_block) } + let(:unseen_standard) { create(:standard, release: unseen_release) } + let(:unseen_content_file) { create(:content_file, :checkpoint, standard: unseen_standard, uid: "match") } + let!(:unseen_challenge) { create(:challenge, content_file: unseen_content_file) } + let(:unseen_checkpoint_submission) { create_checkpoint_submission(student, unseen_challenge, cohort: cohort) } + let!(:unseen_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: unseen_challenge, checkpoint_submission: unseen_checkpoint_submission) } + + let(:unseen_student) { create(:cohort_user, :student, cohort: cohort).user } + let(:unseen_student_checkpoint_submission) { create_checkpoint_submission(unseen_student, first_challenge, cohort: cohort) } + let!(:unseen_student_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: unseen_student, challenge: first_challenge, checkpoint_submission: unseen_student_checkpoint_submission) } + + it "returns the full set of challenges with matching uids scoped by block" do + expect(described_class.all_for_user_id_content_file_in_cohort(student.id, second_content_file, cohort.id)).to eq([first_checkpoint_submission]) + end + + let(:diff_cohorts_first_checkpoint_submission) { create_checkpoint_submission(student, first_challenge) } + let!(:diff_cohorts_first_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort_id: first_checkpoint_submission.cohort_id, user: student, challenge: first_challenge, checkpoint_submission: first_checkpoint_submission) } + + it "scopes the collected checkpoint submissions to the cohort" do + expect(described_class.all_for_user_id_content_file_in_cohort(student.id, second_content_file, cohort.id)).to_not include diff_cohorts_first_checkpoint_submission + end + end + end + + describe ".lastest_by_state_for_students_in_cohort" do + let!(:started_checkpoint_submission) { create_checkpoint_submission(student, challenge, {cohort: cohort, state: "started"}) } + let!(:needs_review_checkpoint_submission) { create_checkpoint_submission(student, challenge, {cohort: cohort, state: "needs_review"}) } + let!(:first_done_checkpoint_submission) { create_checkpoint_submission(student, challenge, {cohort: cohort, state: "done", created_at: 1.day.ago}) } + let!(:second_done_checkpoint_submission) { create_checkpoint_submission(student, challenge, {cohort: cohort, state: "done"}) } + let!(:first_retry_checkpoint_submission) { create_checkpoint_submission(student, challenge, {cohort: cohort, state: "retry", created_at: 1.day.ago}) } + let!(:second_retry_checkpoint_submission) { create_checkpoint_submission(student, challenge, {cohort: cohort, state: "retry"}) } + + subject do + described_class.lastest_by_state_for_students_in_cohort( + cohort_id: cohort.id, + block_id: challenge.content_file.standard.release.block_id, + content_file_uid: content_file.uid, + student_ids: [student.id] + ) + end + + it "delivers the latest checkpoints by state" do + expect(subject.length).to eq(4) + expect(subject).to include(started_checkpoint_submission) + expect(subject).to include(needs_review_checkpoint_submission) + expect(subject).to include(second_done_checkpoint_submission) + expect(subject).to_not include(first_done_checkpoint_submission) + expect(subject).to include(second_retry_checkpoint_submission) + expect(subject).to_not include(first_retry_checkpoint_submission) + end + end +end diff --git a/scripts/spec/finders/cohort_user_finder_spec.rb b/scripts/spec/finders/cohort_user_finder_spec.rb new file mode 100644 index 0000000..c0f813c --- /dev/null +++ b/scripts/spec/finders/cohort_user_finder_spec.rb @@ -0,0 +1,33 @@ +require "spec_helper" + +describe CohortUserFinder do + describe ".instructors_for_cohorts_using_different_version_of_release_block" do + subject { described_class.instructors_for_cohorts_using_different_version_of_release_block(release) } + + context "when a cohort is using a different release for the same block as the given release" do + let!(:existing_release) { create(:release) } + let!(:cohort) { create(:cohort) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: existing_release) } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort) } + let!(:student) { create(:cohort_user, :student, cohort: cohort) } + let!(:release) { create(:release, block: existing_release.block) } + + it "returns all the instructors for that cohort" do + expect(subject).to match_array [instructor] + end + end + + context "when a cohort is using the given release" do + let!(:cohort) { create(:cohort) } + let!(:release) { create(:release) } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + + let!(:student) { create(:cohort_user, :student, cohort: cohort) } + + it "returns no instructors for that cohort" do + expect(subject).to match_array [] + end + end + end +end diff --git a/scripts/spec/finders/content_file_finder_spec.rb b/scripts/spec/finders/content_file_finder_spec.rb new file mode 100644 index 0000000..f3287ce --- /dev/null +++ b/scripts/spec/finders/content_file_finder_spec.rb @@ -0,0 +1,99 @@ +require "spec_helper" + +describe ContentFileFinder do + describe ".from_cohort_block_content_file_path" do + it "returns the content file for the given cohort, block and content file path" do + cohort, block, content_file = create_content_file + + expect( + ContentFileFinder.from_cohort_block_content_file_path( + cohort_id: cohort.id, + block_id: block.id, + content_file_path: content_file.path + ) + ).to eq content_file + end + + it "returns a resource content file givent cohort, block, and resource contentfile" do + cohort, block, resource_file = create_content_file({content_file_type: ContentFile::TYPES[:resource]}) + + expect( + ContentFileFinder.from_cohort_block_content_file_path( + cohort_id: cohort.id, + block_id: block.id, + content_file_path: resource_file.path + ) + ).to eq resource_file + end + + context "when passed a cohort with two blocks containing content files with the same path" do + it "returns the content file for the block specified" do + cohort = create(:cohort) + block_1 = create(:block) + block_2 = create(:block) + content_file_1 = create(:content_file, standard: create(:standard, release: create(:release, block: block_1)), path: "README.md") + create(:cohort_release, cohort: cohort, release: Release.where(block_id: block_1.id).last) + create(:content_file, standard: create(:standard, release: create(:release, block: block_2)), path: "README.md") + create(:cohort_release, cohort: cohort, release: Release.where(block_id: block_2.id).last) + + expect( + ContentFileFinder.from_cohort_block_content_file_path(cohort_id: cohort.id, block_id: block_1.id, content_file_path: "README.md") + ).to eq content_file_1 + end + end + + context "when passed a block and path that are used in a different cohort" do + it "returns the content file for the cohort specified" do + cohort_1 = create(:cohort) + cohort_2 = create(:cohort) + block = create(:block) + content_file = create(:content_file, standard: create(:standard, release: create(:release, block: block)), path: "README.md") + create(:cohort_release, cohort: cohort_1, release: Release.where(block_id: block.id).last) + + expect( + ContentFileFinder.from_cohort_block_content_file_path(cohort_id: cohort_2.id, block_id: block.id, content_file_path: "README.md") + ).to eq nil + + expect( + ContentFileFinder.from_cohort_block_content_file_path(cohort_id: cohort_1.id, block_id: block.id, content_file_path: "README.md") + ).to eq content_file + end + end + + context "when passed a cohort with two blocks containing content files with the same path" do + it "returns the content file for the block specified" do + cohort = create(:cohort) + block = create(:block) + release = create(:release, block: block) + create(:content_file, standard: create(:standard, release: release), path: "foo.md") + content_file_1 = create(:content_file, standard: create(:standard, release: release), path: "README.md") + create(:cohort_release, cohort: cohort, release: Release.where(block_id: block.id).last) + + expect( + ContentFileFinder.from_cohort_block_content_file_path(cohort_id: cohort.id, block_id: block.id, content_file_path: "README.md") + ).to eq content_file_1 + end + end + end + + describe ".first_for_standard" do + let(:standard) { create(:standard) } + let!(:content_file_1) { create(:content_file, standard: standard, position: 2) } + let!(:content_file_2) { create(:content_file, standard: standard, position: 0) } + let!(:content_file_3) { create(:content_file, standard: standard, position: 1) } + + it "returns the first content file for a standard" do + expect(described_class.first_for_standard(standard.id)).to eq(content_file_2) + end + end + + describe ".all_from_cohort_block" do + it "returns content files from a cohort and a block" do + cohort, block, content_file = create_content_file + another_cohort, another_block, another_content_file = create_content_file + + expect(described_class.all_from_cohort_block(cohort_id: cohort.id, block_id: block.id)).to include(content_file) + expect(described_class.all_from_cohort_block(cohort_id: cohort.id, block_id: block.id)).to_not include(another_content_file) + end + end +end diff --git a/scripts/spec/finders/performance_finder_spec.rb b/scripts/spec/finders/performance_finder_spec.rb new file mode 100644 index 0000000..b7b66a1 --- /dev/null +++ b/scripts/spec/finders/performance_finder_spec.rb @@ -0,0 +1,167 @@ +require "spec_helper" + +describe PerformanceFinder do + let!(:block) { create(:block) } + let!(:release) { create(:release, block: block) } + let!(:standard_1) { create(:standard, uid: "123", release: release) } + let!(:standard_2) { create(:standard, uid: "abc", release: release) } + let!(:standard_no_longer_in_block) { create(:standard, release: release) } + + let!(:cohort) { create(:cohort) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:excluded_student) { create(:cohort_user, :student).user } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + + let!(:performance) { create(:performance, user: student, cohort: cohort, standard: standard_1, block_id: block.id) } + let!(:performance_for_standard_no_longer_in_block) { create(:performance, user: student, cohort: cohort, standard: standard_no_longer_in_block) } + let!(:excluded_performance) { create(:performance, user: excluded_student, cohort: cohort, standard: standard_1) } + let!(:instructor_performance) { create(:performance, user: instructor, cohort: cohort, standard: standard_1) } + + let!(:new_release) { create(:release, block: block) } + let!(:new_standard_1) { create(:standard, uid: "123", release: new_release) } + let!(:new_standard_2) { create(:standard, uid: "abc", release: new_release) } + + let!(:old_performance) { create(:performance, user: student, cohort: cohort, standard: standard_2, created_at: 1.day.ago) } + let!(:new_performance) { create(:performance, user: student, cohort: cohort, standard: new_standard_2) } + + describe ".latest_for_standards_in_cohort" do + it "returns the latest performances for users for each standard uid" do + standard_ids = [standard_1.uid, standard_2.uid] + user_ids = [student.id, excluded_student.id] # excluded student is used here to test specific user ids + result = described_class.latest_for_standards_in_cohort(standard_uids: standard_ids, user_ids: user_ids, cohort_id: cohort.id) + expect(result).to eq([performance, new_performance, excluded_performance]) + end + + context "standard uids are identical across blocks" do + let!(:block_2) { create(:block) } + let!(:release_2) { create(:release, block: block_2) } + let!(:standard_repeated) { create(:standard, uid: "123", release: release_2) } + let!(:performance_repeated) { create(:performance, user: student, cohort: cohort, standard: standard_repeated, block_id: block_2.id) } + + it "returns performances for users" do + standard_uids = ['123'] + user_ids = [student.id] + result = described_class.latest_for_standards_in_cohort(standard_uids: standard_uids, user_ids: user_ids, cohort_id: cohort.id) + expect(result).to eq([performance, performance_repeated]) + end + end + + context "it does not share across cohorts" do + let!(:other_cohort) { create(:cohort) } + let!(:performance_not_in_cohort) { create(:performance, user: student, cohort: other_cohort, standard: new_standard_2) } + + it "does not pull other cohort performances for users" do + standard_uids = [new_standard_2.uid] + user_ids = [student.id] + result = described_class.latest_for_standards_in_cohort(standard_uids: standard_uids, user_ids: user_ids, cohort_id: cohort.id) + expect(result).to_not include performance_not_in_cohort + end + end + end + + describe ".latest_for_cohort_release" do + context "when a cohort switches to a new release for a block" do + before do + cohort_release.update(release: new_release) + end + + it "returns performances for all student in the cohort for all releases of the block" do + expect(described_class.latest_for_cohort_release(cohort_id: cohort.id, release: new_release)).to eq([performance, new_performance]) + end + end + end + + describe ".latest_for_cohort_student_release" do + context "when asking for a single students performances in a block with new releases" do + before do + cohort_release.update(release: new_release) + CohortUser.where(user: excluded_student).first.update(cohort: cohort) + end + + it "returns performances for all student in the cohort for all releases of the block" do + expect(described_class.latest_for_cohort_student_release(cohort_id: cohort.id, release: new_release, user_id: student.id)).to eq([performance, new_performance]) + end + end + end + + describe ".latest_for_cohort_user" do + it "returns all performances for the cohort" do + expect(described_class.latest_for_cohort_user(cohort_id: cohort.id, user_id: student.id)).to match_array([performance, new_performance, performance_for_standard_no_longer_in_block]) + end + end + + describe "latest data" do + let(:cohort) { create(:cohort) } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:student_1) { create(:cohort_user, :student, cohort: cohort).user } + let!(:student_2) { create(:cohort_user, :student, cohort: cohort).user } + let!(:student_no_perfs) { create(:cohort_user, :student, cohort: cohort).user } + let!(:block_1) { create(:block) } + let!(:block_2) { create(:block) } + let!(:release_1) { create(:release) } + let!(:release_2) { create(:release) } + let!(:release_3) { create(:release) } + let!(:standard_1) { create(:standard, uid: "yyy", release: release_1) } + let!(:standard_2) { create(:standard, uid: "zzz", release: release_2) } + let!(:standard_3) { create(:standard, uid: "no_chekpoints", release: release_3) } + let!(:standard_4) { create(:standard, uid: "no_chekpoints_with_ungraded") } + let!(:checkpoint_content_file_1) { create(:content_file, :checkpoint, uid: "abc", standard: standard_1) } + let!(:checkpoint_content_file_2) { create(:content_file, :checkpoint, uid: "123", standard: standard_2) } + let!(:checkpoint_content_file_3) { create(:content_file, :checkpoint, uid: "jkl", standard: standard_3) } + + let!(:checkpoint_submission_abc_1) { create(:checkpoint_submission, content_file_uid: "abc", content_file_block_id: block_1.id, user_id: student_1.id, cohort: cohort) } + let!(:checkpoint_submission_123_1) { create(:checkpoint_submission, content_file_uid: "123", content_file_block_id: block_2.id, user_id: student_1.id, cohort: cohort) } + + let!(:checkpoint_submission_abc_2) { create(:checkpoint_submission, content_file_uid: "abc", content_file_block_id: block_1.id, user_id: student_2.id, cohort: cohort) } + let!(:checkpoint_submission_123_2) { create(:checkpoint_submission, content_file_uid: "123", content_file_block_id: block_2.id, user_id: student_2.id, cohort: cohort) } + + let!(:old_performance_abc_1) { create(:performance, user: student_1, score: 1, checkpoint_submission: checkpoint_submission_abc_1, standard_uid: standard_1.uid, standard: standard_1, created_at: 1.day.ago, cohort: cohort) } + let!(:latest_performance_abc_1) { create(:performance, user: student_1, score: 3, checkpoint_submission: checkpoint_submission_abc_1, standard_uid: standard_1.uid, standard: standard_1, cohort: cohort) } + # student_1 has no performance for checkpoint submission 123 + let!(:latest_performance_abc_2) { create(:performance, user: student_2, score: 3, checkpoint_submission: checkpoint_submission_abc_2, standard_uid: standard_1.uid, standard: standard_1, cohort: cohort) } + let!(:old_latest_performance_123_2) { create(:performance, user: student_2, score: 1, checkpoint_submission: checkpoint_submission_123_2, standard_uid: standard_2.uid, standard: standard_2, created_at: 1.day.ago, cohort: cohort) } + let!(:latest_performance_123_2) { create(:performance, user: student_2, score: 2, checkpoint_submission: checkpoint_submission_123_2, standard_uid: standard_2.uid, standard: standard_2, created_at: 1.minute.ago, cohort: cohort) } + + let!(:non_checkpoint_performance_1) { create(:performance, user: student_1, score: 1, standard: standard_3, cohort: cohort) } + let!(:non_checkpoint_performance_2) { create(:performance, user: student_2, score: 1, standard: standard_3, cohort: cohort) } + + let!(:different_cohort_perf) { create(:performance, user: student_2, score: 2, checkpoint_submission: checkpoint_submission_123_2, standard_uid: standard_2.uid, standard: standard_2, created_at: 1.minute.ago) } + + describe ".latest_standard_scores_for_cohort" do + it "yields the checkpoint scores, checkpoint ids and checkpoint creation dates by standard for users" do + results = PerformanceFinder.latest_standard_scores_for_cohort(user_ids: [student_1.id, student_2.id, student_no_perfs.id], content_file_uids: nil, cohort_id: cohort.id) + expect(results[student_1.id][release_1.block_id][standard_1.uid][:score]).to eq 3 + expect(results[student_1.id][release_1.block_id][standard_1.uid][:checkpoint_submission_id]).to eq checkpoint_submission_abc_1.id + expect(results[student_1.id][release_1.block_id][standard_1.uid][:checkpoint_state]).to eq checkpoint_submission_abc_1.state + expect(results[student_1.id][release_1.block_id][standard_1.uid][:checkpoint_created_at].as_json.to_s[0..10]).to eq checkpoint_submission_abc_1.created_at.as_json.to_s[0..10] + + expect(results[student_2.id][release_1.block_id][standard_1.uid][:score]).to eq 3 + expect(results[student_2.id][release_1.block_id][standard_1.uid][:checkpoint_submission_id]).to eq checkpoint_submission_abc_2.id + expect(results[student_2.id][release_1.block_id][standard_1.uid][:checkpoint_state]).to eq checkpoint_submission_abc_2.state + expect(results[student_2.id][release_1.block_id][standard_1.uid][:checkpoint_created_at].as_json.to_s[0..10]).to eq checkpoint_submission_abc_2.created_at.as_json.to_s[0..10] + + expect(results[student_2.id][release_2.block_id][standard_2.uid][:score]).to eq 2 + expect(results[student_2.id][release_2.block_id][standard_2.uid][:checkpoint_submission_id]).to eq checkpoint_submission_123_2.id + expect(results[student_2.id][release_2.block_id][standard_2.uid][:checkpoint_state]).to eq checkpoint_submission_123_2.state + expect(results[student_2.id][release_2.block_id][standard_2.uid][:checkpoint_created_at].as_json.to_s[0..10]).to eq checkpoint_submission_123_2.created_at.as_json.to_s[0..10] + + # has non checkpoint performances included + expect(results[student_1.id][release_3.block_id][standard_3.uid][:score]).to eq 1 + expect(results[student_1.id][release_3.block_id][standard_3.uid][:checkpoint_submission_id]).to be nil + + expect(results[student_2.id][release_3.block_id][standard_3.uid][:score]).to eq 1 + expect(results[student_2.id][release_3.block_id][standard_3.uid][:checkpoint_submission_id]).to be nil + + expect(results[student_no_perfs.id]).to eq({}) + + # It surfaces the latest performance regardless of checkpoint or not + create(:performance, user: student_1, score: 1, standard: standard_1, cohort: cohort) + results = PerformanceFinder.latest_standard_scores_for_cohort(user_ids: [student_1.id], cohort_id: cohort.id) + expect(results[student_1.id][release_1.block_id][standard_1.uid][:score]).to eq 1 + expect(results[student_1.id][release_1.block_id][standard_1.uid][:checkpoint_submission_id]).to be nil + end + end + end +end diff --git a/scripts/spec/finders/release_finder_spec.rb b/scripts/spec/finders/release_finder_spec.rb new file mode 100644 index 0000000..7491ea6 --- /dev/null +++ b/scripts/spec/finders/release_finder_spec.rb @@ -0,0 +1,74 @@ +require "spec_helper" + +describe ReleaseFinder do + describe ".latest_for_all_blocks_not_attached_to_cohort" do + let(:cohort) { create(:cohort) } + + let(:block_1) { create(:block) } + let!(:old_release) { create(:release, block: block_1, created_at: 1.day.ago) } + let!(:release) { create(:release, block: block_1) } + + let(:block_2) { create(:block) } + let!(:release_attached_to_cohort) { create(:release, block: block_2) } + let!(:releas_not_attached_to_cohort) { create(:release, block: block_2) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release_attached_to_cohort) } + + it "returns the latest release for all blocks not attached to the cohort" do + expect(described_class.latest_for_all_blocks_not_attached_to_cohort(cohort.id)).to eq([release]) + end + end + + describe ".for_cohort" do + let!(:cohort) { create(:cohort) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, position: 1) } + let!(:other_cohort_release) { create(:cohort_release, cohort: cohort, position: 0) } + let!(:excluded_cohort_release) { create(:cohort_release) } + + it "returns releases for the cohort provided" do + expect(described_class.for_cohort(cohort.id)).to eq([other_cohort_release.release, cohort_release.release]) + end + end + + describe ".for_block" do + let!(:cohort_1) { create(:cohort) } + let!(:cohort_2) { create(:cohort) } + let(:block) { create(:block) } + let!(:release) { create(:release, block: block) } + let!(:cohort_1_release) { create(:cohort_release, cohort: cohort_1) } + let!(:cohort_2_release) { create(:cohort_release, cohort: cohort_2) } + + it "yields unique releases attached to a block" do + expect(described_class.for_block(block)).to eq([release]) + end + + context "when there are feature branch releases" do + let!(:branch_release) { create(:release, block: block, branch_name: "test-branch") } + + it "should exclude them" do + expect(described_class.for_block(block)).to_not include(branch_release) + end + end + end + + describe ".available_update_count_for" do + let(:block) { create(:block) } + let!(:old_release) { create(:release, block: block) } + let!(:current_release) { create(:release, block: block) } + let!(:other_block_release) { create(:release) } + + before { 3.times { create(:release, block: block) } } + + it "returns the number of releases newer than the provided release" do + expect(Release.count).to eq(6) + expect(described_class.available_update_count_for(current_release)).to eq(3) + end + + context "when there are feature branch releases" do + let!(:branch_release) { create(:release, block: block, branch_name: "test-branch") } + + it "should exclude them" do + expect(described_class.available_update_count_for(current_release)).to eq(3) + end + end + end +end diff --git a/scripts/spec/finders/standard_finder_spec.rb b/scripts/spec/finders/standard_finder_spec.rb new file mode 100644 index 0000000..102994f --- /dev/null +++ b/scripts/spec/finders/standard_finder_spec.rb @@ -0,0 +1,41 @@ +require "spec_helper" + +describe StandardFinder do + describe ".all_for_cohort" do + let(:cohort) { create(:cohort) } + let(:release_1) { create(:release) } + let(:release_2) { create(:release) } + + before do + create(:cohort_release, cohort: cohort, release: release_1) + create(:cohort_release, cohort: cohort, release: release_2) + end + + subject { described_class.all_for_cohort(cohort_id: cohort.id) } + + it "returns all the standards for the cohort" do + standard_1 = create(:standard, release: release_1) + standard_2 = create(:standard, release: release_1) + standard_3 = create(:standard, release: release_2) + create(:standard) + + expect(subject).to match_array([standard_1, standard_2, standard_3]) + end + end + + describe ".for_cohort_release" do + let(:cohort) { create(:cohort) } + let(:release) { create(:release) } + + before { create(:cohort_release, cohort: cohort, release: release) } + subject { described_class.for_cohort_release(cohort_id: cohort.id, release_id: release.id) } + + it "returns all standards for a given release attached to a cohort" do + standard_1 = create(:standard, release: release) + create(:standard) + standard_3 = create(:standard, release: release) + + expect(subject).to match_array([standard_1, standard_3]) + end + end +end diff --git a/scripts/spec/finders/submitted_challenge_answer_finder_spec.rb b/scripts/spec/finders/submitted_challenge_answer_finder_spec.rb new file mode 100644 index 0000000..88cf675 --- /dev/null +++ b/scripts/spec/finders/submitted_challenge_answer_finder_spec.rb @@ -0,0 +1,72 @@ +require "spec_helper" + +describe SubmittedChallengeAnswerFinder do + let!(:user) { create(:user) } + let!(:cohort) { create(:cohort) } + + describe ".latest_for_user_id_content_file_for_cohort" do + context "when there are answers for the current challenge ID" do + let(:challenge) { create(:challenge) } + let(:content_file) { challenge.content_file } + + it "returns latest submitted challenge answer for the given user and content file ids" do + create(:submitted_challenge_answer, user: user, challenge: challenge, created_at: 2.days.ago) + create(:submitted_challenge_answer, user: user, challenge: challenge, created_at: 1.day.ago) + create(:submitted_challenge_answer, user: user, challenge: challenge, created_at: 1.minute.ago) + submitted_challenge_answer = create(:submitted_challenge_answer, user: user, challenge: challenge, cohort: cohort) + + expect(described_class.latest_for_user_id_content_file_for_cohort(user.id, content_file, cohort.id)).to eq([submitted_challenge_answer]) + end + end + + context "when there are answers for a previous challenge with the same UID in the same block" do + let!(:old_challenge) { create(:challenge, uid: "foo") } + let!(:new_challenge) { create(:challenge, uid: "foo", content_file: create(:content_file, standard: create(:standard, release: create(:release, block_id: old_challenge.content_file.standard.release.block_id)))) } + let(:new_content_file) { new_challenge.content_file } + let!(:submitted_challenge_answer_1) { create(:submitted_challenge_answer, user: user, challenge: old_challenge, created_at: 1.day.ago, cohort: cohort) } + let!(:submitted_challenge_answer_2) { create(:submitted_challenge_answer, user: user, challenge: old_challenge, created_at: 1.minute.ago, cohort: cohort) } + + it "returns the latest submitted challenge answer for the challenge UID" do + expect(described_class.latest_for_user_id_content_file_for_cohort(user.id, new_content_file, cohort.id)).to eq([submitted_challenge_answer_2]) + end + end + + context "when there are answers for a previous challenge with the same UID in a different block" do + let!(:old_challenge) { create(:challenge, uid: "foo") } + let!(:new_challenge) { create(:challenge, uid: "foo") } + let(:new_content_file) { new_challenge.content_file } + let!(:submitted_challenge_answer_1) { create(:submitted_challenge_answer, user: user, challenge: old_challenge, created_at: 1.day.ago) } + + it "returns the latest submitted challenge answer for the challenge UID" do + expect(described_class.latest_for_user_id_content_file_for_cohort(user.id, new_content_file, cohort.id)).to eq([]) + end + end + end + + describe ".all_for_user_id_challenge_in_cohort" do + context "when there is a set of answers across multiple uid-matched challenges" do + let!(:old_challenge) { create(:challenge, uid: "foo") } + let!(:new_challenge) { create(:challenge, uid: "foo", content_file: create(:content_file, standard: create(:standard, release: create(:release, block_id: old_challenge.content_file.standard.release.block_id)))) } + let(:new_content_file) { new_challenge.content_file } + let!(:submitted_challenge_answer_1) { create(:submitted_challenge_answer, user: user, challenge: old_challenge, created_at: 1.day.ago, cohort: cohort) } + let!(:submitted_challenge_answer_2) { create(:submitted_challenge_answer, user: user, challenge: old_challenge, created_at: 1.minute.ago, cohort: cohort) } + let!(:submitted_challenge_answer_not_found) { create(:submitted_challenge_answer, user: user, challenge: create(:challenge), created_at: 1.minute.ago) } + it "returns the full set of challenges with matching uids scoped by block" do + expect(described_class.all_for_user_id_challenge_in_cohort(user.id, new_challenge, cohort.id)).to eq([submitted_challenge_answer_2, submitted_challenge_answer_1]) + end + it "does not include other cohorts work" do + expect(described_class.all_for_user_id_challenge_in_cohort(user.id, new_challenge, cohort.id)).to_not include submitted_challenge_answer_not_found + end + end + + context "when the content file is a resource" do + let!(:old_challenge) { create(:challenge, uid: "foo") } + let!(:resource_challenge) { create(:challenge, content_file: create(:content_file, content_file_type: ContentFile::TYPES[:resource], standard: create(:standard, release: create(:release, block_id: old_challenge.content_file.standard.release.block_id)), title: "Resource"), title: "Buzz Nonedrin") } + let!(:sca) { create(:submitted_challenge_answer, user: user, challenge: resource_challenge, created_at: 1.day.ago, cohort: cohort) } + let(:resource_content_file) { resource_challenge.content_file } + it "fetches the sca for a challenge on a resource file" do + expect(described_class.all_for_user_id_challenge_in_cohort(user.id, resource_challenge, cohort.id)).to eq([sca]) + end + end + end +end diff --git a/scripts/spec/finders/user_finder_spec.rb b/scripts/spec/finders/user_finder_spec.rb new file mode 100644 index 0000000..1ef9576 --- /dev/null +++ b/scripts/spec/finders/user_finder_spec.rb @@ -0,0 +1,15 @@ +require "spec_helper" + +describe UserFinder do + describe ".students_joined_cohort_yesterday" do + let!(:cohort) { create(:cohort) } + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:old_student) { create(:cohort_user, :student, cohort: cohort, created_at: Time.now - 2.days).user } + let!(:excluded_student) { create(:cohort_user, :student).user } + let!(:excluded_instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + + it "returns students for given cohort" do + expect(described_class.students_joined_cohort_yesterday(cohort.id)).to eq([student]) + end + end +end diff --git a/scripts/spec/fixtures/preview-content-file/preview.md b/scripts/spec/fixtures/preview-content-file/preview.md new file mode 100644 index 0000000..4c842ee --- /dev/null +++ b/scripts/spec/fixtures/preview-content-file/preview.md @@ -0,0 +1,105 @@ +Modify A Checkpoint +Peter piper packed a pack of swishers and a lighter + +# !challenge + +* type: paragraph +* id: 012101f3-53be-4a4d-8a71-3900ae9ad4cd +* title: Simple Checkpoint Paragraph +* points: 3 +* topics: ["Coding", "Conceptual", "Candy"] + +##### !question +Describe the Correct Checkpoint +##### !end-question + +##### !explanation +It is Correct +##### !end-explanation + +# !end-challenge + +# !challenge + +* type: multiple-choice +* id: 9d0716a9802e4f7daf7ace5fe9bc0e21 +* title: Simple Checkpoint Challenge +* points: 2 +* topics: ["Coding", "Conceptual", "Candy"] + +##### !question +Select the Right +##### !end-question + +##### !options +- Right +- Wrong +##### !end-options + +##### !answer +Right +##### !end-answer + +##### !explanation +It is Right +##### !end-explanation + +# !end-challenge + +OH BUT THAT'S NOT ALL + +# !challenge + +* type: code-snippet +* language: javascript +* id: smoketest-challenge-12 +* title: return true +* standard_uuids: W0018-V1 +* points: 4 + +##### !question + +Write a function that returns true. + +##### !end-question + +##### !placeholder + +```js +function returnTrue() { +// return true +} +``` + +##### !end-placeholder + +##### !setup +```js +var expect = require('chai').expect; +``` +##### !end-setup + +##### !tests + +```js +describe('returnTrue', function() { + + it("returns true", function() { + expect(returnTrue()).to.deep.eq(true) + }) + +}) +``` +##### !end-tests + +#### !explanation + +I had to have this talk with you +My happiness depends on you +And whatever you decide to do +Boolean + +#### !end-explanation + +# !end-challenge + diff --git a/scripts/spec/fixtures/test-block-repo/._docker_directories_test-docker-folder.zip b/scripts/spec/fixtures/test-block-repo/._docker_directories_test-docker-folder.zip new file mode 100644 index 0000000000000000000000000000000000000000..2ec6d50e3d0942dd3eadb7a1a3b9bfa87bf3c35f GIT binary patch literal 212 zcmWIWW@Zs#U|`^2aBr0g1Tq+yKm-tQ0kKPda&~G_T4qivGeZDQmFz&3C8@` +- `angular.module("app", [])` +- `.component('app', {` +- `vm.app = "app"` +- `

    {{$ctrl.app}}

    ` +##### !end-options + +##### !answer +`` +##### !end-answer + +##### !explanation +`.component('app', {` defines a component, and `` references that component. + +So if you change the component name to windsor, you need to change `` to `` +##### !end-explanation + +# !end-challenge + +# !challenge + +* type: number +* title: sample variance +* id: smoketest-challenge-7 +* decimal: 2 +* standard_uuids: W0018-V1 + +##### !question +What is + +$$ \frac{33}{100} $$ + +? + +Any answer rounded to 2 decimal places will work. +##### !end-question + +##### !placeholder +Write your answer to at least 2 decimal places. +##### !end-placeholder + +##### !answer +33/100 +##### !end-answer + +##### !explanation +Here is an equation-- + +$$ \begin{align} var(S) &= \frac{(1 - 0.6)^2 + (1 - 0.6)^2 + (0 - 0.6)^2 + (1 - 0.6)^2 + (0 - 0.6)^2}{5-1} \ &= \frac{0.16 + > 0.16 + 0.36 + 0.16 + 0.36}{4} \ &= \frac{1.2}{4} \ &= \frac{3}{10} \ &\approx 0.33 \end{align} $$ +##### !end-explanation + +# !end-challenge + +# !challenge + +* type: number +* title: sample variance +* id: smoketest-challenge-8 +* standard_uuids: W0018-V1 + +##### !question +What is + +$$ \frac{33}{100} $$ + +? + +Only the exact answer will work. +##### !end-question + +##### !placeholder +Enter the exact answer +##### !end-placeholder + +##### !answer +33/100 +##### !end-answer + +##### !explanation +Here is an equation-- + +$$ \begin{align} var(S) &= \frac{(1 - 0.6)^2 + (1 - 0.6)^2 + (0 - 0.6)^2 + (1 - 0.6)^2 + (0 - 0.6)^2}{5-1} \ &= \frac{0.16 + > 0.16 + 0.36 + 0.16 + 0.36}{4} \ &= \frac{1.2}{4} \ &= \frac{3}{10} \ &\approx 0.33 \end{align} $$ +##### !end-explanation + +# !end-challenge + +# !challenge + +* type: project +* id: smoketest-challenge-9 +* title: Submit your project +* standard_uuids: W0018-V1 + +##### !question + +### Question + +Submit a GitHub URL + +##### !end-question + +##### !placeholder + +Submit your github link + +##### !end-placeholder + +##### !explanation + +An instructor will review and grade this. Good Luck! + +##### !end-explanation + +# !end-challenge + +# !challenge + +* type: project +* id: smoketest-challenge-10 +* title: Submit your project +* standard_uuids: W0018-V1 + +##### !question + +### Question + +Submit another GitHub URL + +##### !end-question + +##### !placeholder + +Submit your other github link + +##### !end-placeholder + +# !end-challenge + +# !challenge + +* type: code-snippet +* language: javascript +* id: smoketest-challenge-11 +* title: repeats +* standard_uuids: W0018-V1 + +##### !question + +Write a function named repeats that returns true if the first half of the string equals the last half, and false if not. + +##### !end-question + +##### !placeholder + + +// updated 2/16 12:13 MST + +function repeats(input) { +// return true if the first half of the string equals the last half, false if not +} + +##### !end-placeholder + +##### !setup +```js +var expect = require('chai').expect; +``` +##### !end-setup + +##### !tests + +```js +describe('repeats', function() { + + it("returns true when given an empty string (which seems strange, but go with it :) )", function() { + expect(repeats(""), "Default value is incorrect").to.deep.eq(true) + }) + + it("returns true when the second half of the string equals the first", function() { + expect(repeats("bahbah")).to.deep.eq(true) + expect(repeats("nananananananana")).to.deep.eq(true) + }) + + it("returns false when the second half of the string does not equal the first", function() { + expect(repeats("bahba")).to.deep.eq(false) + expect(repeats("nananananann")).to.deep.eq(false) + }) + + it("does not use .repeat", function() { + expect(repeats.toString()).to.not.match(/\.repeat/) + }) + +}) +``` +##### !end-tests + +##### !explanation + +Be sure that you code checked for input of an string of odd-length. + +Here is one possible solution: + +```js +function repeats(myString) { + if (myString.length % 2 !== 0) { + return false; + } + var firstHalf = myString.substr(0, myString.length/2); + var secondHalf = myString.substr(myString.length/2); + return firstHalf == secondHalf; +} +``` + +##### !end-explanation + +# !end-challenge + +# !challenge + +* type: code-snippet +* language: javascript +* id: smoketest-challenge-12 +* title: return true +* standard_uuids: W0018-V1 + +##### !question + +Write a function that returns true. + +##### !end-question + +##### !placeholder + +```js +function returnTrue() { +// return true +} +``` + +##### !end-placeholder + +##### !setup +```js +var expect = require('chai').expect; +``` +##### !end-setup + +##### !tests + +```js +describe('returnTrue', function() { + + it("returns true", function() { + expect(returnTrue()).to.deep.eq(true) + }) + +}) +``` +##### !end-tests + +# !end-challenge + +# !challenge + +* type: code-snippet +* language: python2.7 +* id: ecf4d385-c118-41fe-996b-755c40905257 +* title: filter by class + +##### !question + + +Implement the function `filter_by_class`: It takes a feature matrix, `X`, an array of classes, `y`, and a class label, `label`. It should return all of the rows from X whose label is the given label. + +```python +>>> X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) +>>> y = np.array(["a", "c", "a", "b"]) +>>> filter_by_class(X, y, "a") +array([[1, 2, 3], + [7, 8, 9]]) +>>> filter_by_class(X, y, "b") +array([[10, 11, 12]]) +``` + +##### !end-question + +##### !placeholder + +```python +def filter_by_class(X, y, label): + ''' + INPUT: 2 dimensional numpy array, numpy array, object + OUTPUT: 2 dimensional numpy array + + Return the rows from X whose corresponding label from y is the given label. + ''' + return X[y == label] +``` +##### !end-placeholder + +##### !setup +```python +import unittest +import main as p +#import numpy as np +``` +##### !end-setup + +##### !tests +```python +class TestChallenge(unittest.TestCase): + def test_filter_by_class1(self): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "a") + answer = np.array([[1, 2, 3], [7, 8, 9]]) + assert np.array_equal(result, answer) + + def test_filter_by_class2(self): + X = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) + y = np.array(["a", "c", "a", "b"]) + result = p.filter_by_class(X, y, "b") + answer = np.array([[10, 11, 12]]) + assert np.array_equal(result, answer) +``` +##### !end-tests + +##### !explanation + +```python +def filter_by_class(X, y, label): + return X[y == label] +``` +##### !end-explanation + +# !end-challenge + +# !challenge + +* type: custom-snippet +* language: java +* id: 3f2b49c2-878a-4cbd-b95d-95298d9f73a7 +* title: spring-crud-check +* docker_directory_path: ./docker_directories/test-docker-folder + +##### !question + +Do some stuff here that makes the spring crud happen. + +##### !end-question + +##### !placeholder +```java +package com.galvanize; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} +``` +##### !end-placeholder + + +##### !explanation + +I don't know how to explain this stuff. + +##### !end-explanation + +# !end-challenge diff --git a/scripts/spec/fixtures/test-block-repo/config.yaml b/scripts/spec/fixtures/test-block-repo/config.yaml new file mode 100644 index 0000000..abccc27 --- /dev/null +++ b/scripts/spec/fixtures/test-block-repo/config.yaml @@ -0,0 +1,18 @@ +--- +Standards: + - + Title: Foo + Description: This is the description. It's a pretty short description and shouldn't wrap very much or at all even. + UID: abc + SuccessCriteria: + - Foo + ContentFiles: + - + Type: Lesson + UID: fuz + Path: /markdown-smoketesT.md + - + Type: Checkpoint + UID: baz + Path: /challenges-smoketest.md + DefaultVisibility: hidden diff --git a/scripts/spec/fixtures/test-block-repo/docker_directories/test-docker-folder/Dockerfile b/scripts/spec/fixtures/test-block-repo/docker_directories/test-docker-folder/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/scripts/spec/fixtures/test-block-repo/docker_directories/test-docker-folder/test.sh b/scripts/spec/fixtures/test-block-repo/docker_directories/test-docker-folder/test.sh new file mode 100644 index 0000000..e69de29 diff --git a/scripts/spec/fixtures/test-block-repo/folder/sibling.md b/scripts/spec/fixtures/test-block-repo/folder/sibling.md new file mode 100644 index 0000000..422dcbb --- /dev/null +++ b/scripts/spec/fixtures/test-block-repo/folder/sibling.md @@ -0,0 +1,3 @@ +I am the sibling + +* [parent link](../markdown-smoketesT.md) diff --git a/scripts/spec/fixtures/test-block-repo/folder/target.md b/scripts/spec/fixtures/test-block-repo/folder/target.md new file mode 100644 index 0000000..2901278 --- /dev/null +++ b/scripts/spec/fixtures/test-block-repo/folder/target.md @@ -0,0 +1 @@ +Top level target diff --git a/scripts/spec/fixtures/test-block-repo/images/galvanize-logo.png b/scripts/spec/fixtures/test-block-repo/images/galvanize-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..64319ca905483ea07335fb5f229576fec27d7036 GIT binary patch literal 4134 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 + +### Syntax Highlighting +Inline `code` has `back-ticks around` it. + +```javascript +var s = "JavaScript syntax highlighting"; +alert(s); +``` + +```python +s = "Python syntax highlighting" +print s +``` + +``` +For data point in training set: + calculate distance from data point to new_value +Order distances in increasing order and take the first k +Make the prediction + +No language indicated, so no syntax highlighting. +But let's throw in a tag. +``` + +### Maths +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ + +### Blockquotes + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +Quote break. + +> This is a very long line that will still be quoted properly when it wraps. + + + +### Tables + +| Tables | Are | Cool | +| ------------- |---------------| ------| +| enterprise | dsi | wdi | + +### Instructor tags (nothing should show up after this line) + +### !instructor + +# H1 + +Text + +![image](images/galvanize-logo.png) + +[relative link](target.md) + +Inline `code` has `back-ticks around` it. + +$$x = {-b \pm \sqrt{b^2-4ac} \over 2a}.$$ + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +### !end-instructor diff --git a/scripts/spec/fixtures/test-block-repo/target.md b/scripts/spec/fixtures/test-block-repo/target.md new file mode 100644 index 0000000..2901278 --- /dev/null +++ b/scripts/spec/fixtures/test-block-repo/target.md @@ -0,0 +1 @@ +Top level target diff --git a/scripts/spec/fixtures/test-block-repo/test.sql b/scripts/spec/fixtures/test-block-repo/test.sql new file mode 100644 index 0000000..e69de29 diff --git a/scripts/spec/fixtures/vcr_cassettes/github-branch-success.yml b/scripts/spec/fixtures/vcr_cassettes/github-branch-success.yml new file mode 100644 index 0000000..01f1020 --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/github-branch-success.yml @@ -0,0 +1,206 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/gSchool/blocks-test/git/refs/heads/test-branch + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 08 May 2018 16:21:51 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Server: + - GitHub.com + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4993' + X-Ratelimit-Reset: + - '1525797806' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding + Etag: + - W/"7796a92927963ae278bf676eae2fc093" + Last-Modified: + - Thu, 26 Apr 2018 22:22:23 GMT + X-Poll-Interval: + - '300' + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Runtime-Rack: + - '0.035759' + X-Github-Request-Id: + - C1DF:54C0:510C6C:67C0DA:5AF1CE9F + body: + encoding: ASCII-8BIT + string: '{"ref":"refs/heads/test-branch","url":"https://api.github.com/repos/gSchool/blocks-test/git/refs/heads/test-branch","object":{"sha":"38ace18a4d5584d1cc0ec17e5e904c888293596b","type":"commit","url":"https://api.github.com/repos/gSchool/blocks-test/git/commits/38ace18a4d5584d1cc0ec17e5e904c888293596b"}}' + http_version: + recorded_at: Tue, 08 May 2018 16:21:51 GMT +- request: + method: get + uri: https://api.github.com/repos/gSchool/blocks-test/zipball/38ace18a4d5584d1cc0ec17e5e904c888293596b + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 302 + message: Found + headers: + Date: + - Tue, 08 May 2018 16:21:57 GMT + Content-Type: + - text/html;charset=utf-8 + Content-Length: + - '0' + Server: + - GitHub.com + Status: + - 302 Found + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4993' + X-Ratelimit-Reset: + - '1525797806' + Cache-Control: + - public, must-revalidate, max-age=0 + Expires: + - Tue, 08 May 2018 16:21:57 GMT + Location: + - https://codeload.github.com/gSchool/blocks-test/legacy.zip/38ace18a4d5584d1cc0ec17e5e904c888293596b?token=AKh5Bwi_6wAsg2fTRdwkbcat8xDtkWMOks5a8c_RwA== + Access-Control-Expose-Headers: + - ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Runtime-Rack: + - '0.043351' + Vary: + - Accept-Encoding + X-Github-Request-Id: + - C1EC:54C3:5237DB:68A913:5AF1CEA5 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Tue, 08 May 2018 16:21:57 GMT +- request: + method: get + uri: https://codeload.github.com/gSchool/blocks-test/legacy.zip/38ace18a4d5584d1cc0ec17e5e904c888293596b?token=AKh5Bwi_6wAsg2fTRdwkbcat8xDtkWMOks5a8c_RwA== + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Transfer-Encoding: + - chunked + Access-Control-Allow-Origin: + - https://render.githubusercontent.com + Content-Security-Policy: + - default-src 'none'; style-src 'unsafe-inline'; sandbox + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization,Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-Xss-Protection: + - 1; mode=block + Etag: + - '"38ace18a4d5584d1cc0ec17e5e904c888293596b"' + Content-Type: + - application/zip + Content-Disposition: + - attachment; filename=gSchool-blocks-test-38ace18a4d5584d1cc0ec17e5e904c888293596b.zip + X-Geo-Block-List: + - '' + Date: + - Tue, 08 May 2018 16:21:57 GMT + X-Github-Request-Id: + - C1ED:7AF1:A9D4F:1D2FA4:5AF1CEA5 + body: + encoding: ASCII-8BIT + string: !binary |- + UEsDBAoAAAAAAMp6mkwAAAAAAAAAAAAAAAA9AAkAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL1VUBQABHVHiWlBLAwQKAAAACADKeppM3C5Vo7sMAADsLQAAVAAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9jaGFsbGVuZ2VzLXNtb2tldGVzdC5tZFVUBQABHVHiWu1a6XfbNhL/XP4VE6e7llKRuiz5SOy8NG1zvCbp1k6TPtu1QBK0EJMEQ5C29VT97zsDkLosy1LcbPthc9jSEBjM8ZsDAB8+fAgPvD4LQx6fc8t6BNkg4XuQcZUxN+R2kspP3MvwgfD3wOftDnNbDXu3223ZW52gY++w7rbttluB23F5wHxGPEQWIpNnh89fvYIXqfCRlicqSzmL9qCfZYnaq9fPRdbPXceTUf380OtLGdY/sUtmhyw+z9k5t70+9y4SKWJa/pKFwmcZPwtkeoECpjlHKkoZ+yz1z/Jc+GoPXj/77Vmj2diyf2ta1kP6Aw8+56iMkLEmwKsY5cg9IijLajrwE/ID5AJeKGMOx1lfKEh5IpXIZDo4rXyBvFWHOH9IRcbBkz6HTOLvKAk5ErI+EsdD9ch3scdhIHNgKQfFMqECwX24whWJnIKSYU4i18xMQ7wiS1gANvyax9Bz6kwprlTpsx5UPuUqg948WcbwQcS+vFJVCKWHzh9oA0TsAlfPUyMiQUBBgpO1iM9lFIlMj0tyVYiFyEHYKFLvjAhnQN7R4w9zl8YTp1DEFzRET6EB4PJQXjmW9VZmiJPfC8VZqCSglJfCL5Vn0DtH/Diq34NAhNqQ+C9FfTU3kopE+lmg63DqecoiB44k5IqDyMg6j0rr4DM/5Ffg5iL0e8RGfzKMCuNo/c55zFOEmnnS+8RSszgaYoadlgvQCj246nNUoIcDiUEPKaSQni/iJM/QjigZej9VZAFI0fgGdOjYKFY1RJxKUABxycOBU0KXx749A18iJiHzeF+GPk+tFxqT8P7Xn6FCBpO4lINArS4MsyckkI2mSWMU5uD2cJtafnq1Mf0aqTHTUk0NnSZbY+pUdlmca1RfppnNYnWFS5g8w9SOvx3s7qhtP4jVLn3BaB0nFpambADPf3qPJJ97ImLhHnQWpIMPjUZz5/Zc8J/xtw99loEvCcoI2ECGiE8Rn5vgRWz4ucefWlav1yObKS8VSWZdshSiwTMtzD4cb/wYCr5Rg43XLOYD+vCGZRn9/oWlFzylT0ci2jh9rGdiBPJrnNd+bBVMjjXplFaxliKgsJVVMp4ZXT5ciBfrxxgxaIBpBgIh11rqcXjQJ1Bo7ZUV5LHOnpiCBy/R1jytVGH/AIYI4SxPY9j0JUQyNZnvwSaMJgqZBQy3GdYxxjSnRFQjttDXfK2eQh9Q0Avv4jYOc3C0jsh/IsW8x0Me8ThDQ2unGj/1mRpbvlHDvAq90vrt0x4Se6VRezpQdbJGPUrt/1c+n42lG5F0M5YobFQkLzjl7clQuzUJm5Sf8+vbwm6N0HnOdPYFrrHUGw5Zih4KuZPkbihUn/vvYvgTqFrvbb55Y//wg/07/tkcjXomq/siCBB16BsWyTzGMiMDTKFYLVWC2HsKCxx/VyzUT4b4Vz0qZDmZFuZEfXfyJ/64IRGOPxmdjOoLA2jV/EWpHWtL0l/ihfbEC0VhnK4698lcL+UVFu54QN2ArzCHaddcxPLq6XLDzYS5aVUQ7BFW4XCFnDAfds9iBLfprDBqrgQySfmlwHKrKyqWXmIvlAMvpMR6nXsXD9YB/JebfuurmV4XDVa0Sx3kKJA7FnBCPiMBKdXAaywYh7pgPF2U0G66Z0XdozzMBHaUOFIKjy+xQGdiAXoyngnjmfczgTBVMxXn/ayoKndgTyZF//0Ivv0WTtAqcIIsMoa58SSJAAP5c5rBsGmfeFLBH8PWqHg+GuGECuX/GvZ3m0qDCjtaXtW8FDGgucOTIGXesDkavrWbI6Tl0dlQ7DdHf7yFyvWZwKb5RF7yFHtTPrweVf9oIWPNgvLz0Ha1HIaV+wfudZg3MjOgxWjotH5jdeZy0l+n28L8tCAMv+cew/6Olr6nKSxEBLFZ1RwUB7HMDAqcJUj/okBfHezdCdixFHt92iIkaEMsNg+ba0H9VaBTqdnjYFNPLbThVNlkSbJZg6HeRcw8wK7RVzLVD2tU01ACie5NaRfEMUnnuOPos0uzJdSsn64UKjbgZqlPPamN4CgY0Q4QEp4GmMnCAY7pPUHJDp7U6WePvlNzH7LUibCFDXllAx9gT3J8WtVPF6lE9MvIwa/oeT1ek54kB8Pht16WhvRoNHpSpxXuDoJZiVaD8WJT+zxAE+K2CcZPa2a7Nb0Elh3dW3i6l8fsNB6M+81DCWLaq0V7VwKEdkXkl8KJNT0y5uj8sbPmFiP3PymGI7H81Ls34OM8cnVfVmBZMTo9AGwiBYuXBsD29KaotRbii2xuEpdOGu32aNhsNEZAIf6Uyvyg3Dek2Ln5xjStckHQfYIy9f9KHwWs2oKYDmR6Y4KMUZyQMyxZ8ys4dzUnBbra7TqKvyLsXtIeXtC+HPjnXNNs2xjD5eciHrJQnMcjckLlsAr/3i+MVGliTDacLmZO+A7mvjVWejYadnRunjBtOM0ujjmA4kPDaZtf42+j4dbMjKbTmidp72nKCeI1ldc0sQ0naIhSGXTs34fVnb8Ynu/icKBjml8zr+xFvgSNZps8z+n/oPvngG72YHoRuHa/WuNfsGPwQmQvzfHbGnutaWHM4Zw+IV3xDGa6rzLtPNZ402EsPy35B+3a7nRds/HVfVfY7N4eNGxW8+OK9qHzJlvFIkk4qVoe0O7B1NnjEts1p098Es4ytZ69TCVmMD7oo7bIL3mZpsoc9Sl9DUMtVTY+dUM5AjrOIQoiixpWymuh2SOGrBhiOrcA6Xo+esNZwwFWvQ55Qsc5PrTqmJyarb1mG94cHlmT88lC4Io+gK/CkCYVR5T3EHtaZGt05yGJwgWTqbNDDBi6ZthHST7nIuWVTfSc2Kw65sFjfRA4xdPMLyn6TqY8ivQ5YcFFFoWm2CaXyldIXwvwj8gqGzPeuurzGPF6iT+p5kRJNig1rpj9iuI8UkSjhrcGbp7BuTQnd4j6vSpUN+ZWAv3HqFAp7b6xUa3Bxg88YLh5o/u7XBc6EXsyTXHgRtXJpONznjj8c4Vkq2pGo+oyybWDuCdj/w6fab+uJqjL+vhvo3qLQDcnxGz2761TF+liELSCMvo+gvbUWqsvUmpOML30CkrFt8+b1mgsIN22OYbRKsIh60OtYsUsQ9Ef0Wa9Uj8p2NQnq+H/uaAowmBRRft+fIPJMrPRoxscfbWFuYJO9M19HFoa0V8YGr9I37cpf2Z9zENlT0b3wYlUSrhYVssr2L0bFyGlBaOB0cqojSliTHEMa/gXbmQe7O9Do7RMkY+0fR8jZYT/KU1oP78kSOzDmInKXRS40qjBHN96q/q4mGjQdMvMxdNKGSZL7k+xeWyN1r2c+CpVbeYeY5zE71nZbtSydYrQDRQQmyNkUrlZbBYY8W+sFqWgKxWMOwJ6ovSSFHh7CN8XPMkg68u45WwX2OFesOW3dzq210QUbDUD3Afsdl17u9Pxthq7jU6rsz1BUiBC2ue5A/CwxK/ZJ1mvaJurrxh1ai6B0DNcz9zBmeba24NXOIRd6LOrAFMFJSjMd6m4rkHvY496oeJOGzORnsMVPhn0TJfEDA21dnmIdP275xBX1dfngAXS6OqmKCH6BYMglRF8xFIjFTezy7N6U/81ydEIMXa0Dg4OcMI+xImjBaocHzdr0KpB+7QGx1s16NSgSx+3a7BTg1362MSE1MRRzdbpaVVzGMxw2GB0C+rRD/3J3SiGzdmp8rEGAz2oat1YvIDdZOHlPNxpHjPy3Xm5Ph/jhWV8HtyymLZidU9LuLm5qX+/evvL+6M9OrYSCBCF/LGAx3mUDIyja7NfpKu3NTTz3fuj5VNNlP5a5JaFrtYNlkowg1N1M47XAwYL/T8teIGkj8cDKgN6wOl87C7NXIW5RJTIFBuDWGQU6uX3iAmEqYLkm29KUqEaNhLJKulssoQJiiOkPi8TRqVcz9Fkhk0L+YV8R9SzOQc2K4qHQeE6uCfwDY8VoG8GooOoLd6HxFkSB2ZscY51m3RTIVGMxwRCli1Gn+n+sWJWrBXcdBt3m2Fa/2jDuMsNs2Dx1Qyyepe5blpYJaruaqrWe1NzpxtgpWEt29/d3bK3Wqxpu7y7a/POTpv5za1ul/mTOvj6EN4yfXVt3mx5w1E5X6382qayYz3d1ua1IzO9jjN53ZfYeqe2zyO5/rnNbYJZU68XFmcv9MomZr44wxRDaW/8iiRIkydvkXL8MmJFn6bpbe4CXVWCm4IwZMuVVYmGTXWNErPoPb2pV/QWr7a0k7wvdtzujs+6rGF77raHPdSOa7vbO12b8a0db7cd7PodPvWWb44Ge6ZfMaVeaFXMMJxms/E0Y75PPAhKYYrsvzZk5uVZCyprYmFFJf46OMwtuBYM/gtQSwMECgAAAAgAynqaTO2UDqT3AAAARAIAAEgACQBnU2Nob29sLWJsb2Nrcy10ZXN0LTM4YWNlMThhNGQ1NTg0ZDFjYzBlYzE3ZTVlOTA0Yzg4ODI5MzU5NmIvY29uZmlnLnlhbWxVVAUAAR1R4lqVUcFqwzAMvecrdOvJ2T3XlEJhh0HbD9BsMXtx5GApCfn7eVnJRuhhBRtLT9LTk2yMqS6K7DA7aSoAUy7ANWikBk4pre6RxOYwaEjcwNUHgXLUE7jfQA1nPQggDJlUFxCfsv5NgNLlGx2j44PCnHGAifIC/Wg9pAyogDECTcT12vZ2PjaA73Z1LqO1JNLmoJQDNitY9G4i28RKrKcQSbbo/S0TLUMZ6LUwJN7AN1TfwEuPuXNpZiN96khJtO7dxv8/ijFqMDEwmR/T+hQsPc2zbs0gy0z56eJPnNDs5O8KW0+2G1Jg3RdbX5ZP/EHyaA33z3iUVH0BUEsDBAoAAAAAAMp6mkwAAAAAAAAAAAAAAABEAAkAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL2ZvbGRlci9VVAUAAR1R4lpQSwMECgAAAAAAynqaTAAAAAAAAAAAAAAAAFIACQBnU2Nob29sLWJsb2Nrcy10ZXN0LTM4YWNlMThhNGQ1NTg0ZDFjYzBlYzE3ZTVlOTA0Yzg4ODI5MzU5NmIvZm9sZGVyL2RlZXBseS1uZXN0ZWQvVVQFAAEdUeJaUEsDBAoAAAAIAMp6mkxyQaGKTAAAAGQAAABZAAkAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL2ZvbGRlci9kZWVwbHktbmVzdGVkL3Rlc3QubWRVVAUAAR1R4lrTUohOTCrOzyktSVXIyczLVijJVyjKzy+J1dDPTSzKTskvz9Mtzs3PTi1JLS7Ry03R5NLCoqO4NCktPycltQioDcLQL0ksSk+F6AAAUEsDBAoAAAAIAMp6mkxYuzEjYwAAAKkAAABOAAkAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL2ZvbGRlci9zaWJsaW5nLm1kVVQFAAEdUeJalcu7DYAwDEXRPlO4BCTCHMyAUhjFQJSPUWLE+gTo6Cjf0z0jYATZCIqbg0urUh1MO2ZKAnV702g9RMze8pn6EtmTUBEdbXuXOBcOh9DTgjC81DR/zMLBUh4E80p3VfX3atUFUEsDBAoAAAAIAMp6mkxWb3lEIwAAACcAAABNAAkAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL2ZvbGRlci90YXJnZXQubWRVVAUAAR1R4lrLSy0uSU1RKEksSk8t4eLSUoguzkzKycxLj9WAMvRyUzS5AFBLAwQKAAAAAADKeppMAAAAAAAAAAAAAAAAQQAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9mb28vVVQFAAEdUeJaUEsDBAoAAAAAAMp6mkxZ4sU5BgAAAAYAAABHAAkAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL2Zvby9iYXIubWRVVAUAAR1R4lpiYXIubWRQSwMECgAAAAAAynqaTLbKcfwGAAAABgAAAEcACQBnU2Nob29sLWJsb2Nrcy10ZXN0LTM4YWNlMThhNGQ1NTg0ZDFjYzBlYzE3ZTVlOTA0Yzg4ODI5MzU5NmIvZm9vL2Jhei5tZFVUBQABHVHiWmJhei5tZFBLAwQKAAAAAADKeppMF30VxQcAAAAHAAAASAAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9mb28vYnV6ei5tZFVUBQABHVHiWmJ1enoubWRQSwMECgAAAAAAynqaTAAAAAAAAAAAAAAAAEQACQBnU2Nob29sLWJsb2Nrcy10ZXN0LTM4YWNlMThhNGQ1NTg0ZDFjYzBlYzE3ZTVlOTA0Yzg4ODI5MzU5NmIvaW1hZ2VzL1VUBQABHVHiWlBLAwQKAAAACADKeppMr0XuPxcQAAAmEAAAVgAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9pbWFnZXMvZ2FsdmFuaXplLWxvZ28ucG5nVVQFAAEdUeJapVfnNxuO+0UJKvZKqFWzRsWqUVTUplZjU1GhRu1VFJVQahc1Ouy9R22qiT0bGrXHx6pVq4gR8ev5/gu/++K593lznzf3POfcd8aG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c3QubWRVVAUAAR1R4lpQSwECAAAKAAAACADKeppM7ZQOpPcAAABEAgAASAAJAAAAAAABAAAAAACaDQAAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL2NvbmZpZy55YW1sVVQFAAEdUeJaUEsBAgAACgAAAAAAynqaTAAAAAAAAAAAAAAAAEQACQAAAAAAAAAQAAAAAA8AAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9mb2xkZXIvVVQFAAEdUeJaUEsBAgAACgAAAAAAynqaTAAAAAAAAAAAAAAAAFIACQAAAAAAAAAQAAAAaw8AAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9mb2xkZXIvZGVlcGx5LW5lc3RlZC9VVAUAAR1R4lpQSwECAAAKAAAACADKeppMckGhikwAAABkAAAAWQAJAAAAAAABAAAAAADkDwAAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL2ZvbGRlci9kZWVwbHktbmVzdGVkL3Rlc3QubWRVVAUAAR1R4lpQSwECAAAKAAAACADKeppMWLsxI2MAAACpAAAATgAJAAAAAAABAAAAAACwEAAAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL2ZvbGRlci9zaWJsaW5nLm1kVVQFAAEdUeJaUEsBAgAACgAAAAgAynqaTFZveUQjAAAAJwAAAE0ACQAAAAAAAQAAAAAAiBEAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9mb2xkZXIvdGFyZ2V0Lm1kVVQFAAEdUeJaUEsBAgAACgAAAAAAynqaTAAAAAAAAAAAAAAAAEEACQAAAAAAAAAQAAAAHxIAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9mb28vVVQFAAEdUeJaUEsBAgAACgAAAAAAynqaTFnixTkGAAAABgAAAEcACQAAAAAAAQAAAAAAhxIAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9mb28vYmFyLm1kVVQFAAEdUeJaUEsBAgAACgAAAAAAynqaTLbKcfwGAAAABgAAAEcACQAAAAAAAQAAAAAA+xIAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9mb28vYmF6Lm1kVVQFAAEdUeJaUEsBAgAACgAAAAAAynqaTBd9FcUHAAAABwAAAEgACQAAAAAAAQAAAAAAbxMAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9mb28vYnV6ei5tZFVUBQABHVHiWlBLAQIAAAoAAAAAAMp6mkwAAAAAAAAAAAAAAABEAAkAAAAAAAAAEAAAAOUTAABnU2Nob29sLWJsb2Nrcy10ZXN0LTM4YWNlMThhNGQ1NTg0ZDFjYzBlYzE3ZTVlOTA0Yzg4ODI5MzU5NmIvaW1hZ2VzL1VUBQABHVHiWlBLAQIAAAoAAAAIAMp6mkyvRe4/FxAAACYQAABWAAkAAAAAAAAAAAAAAFAUAABnU2Nob29sLWJsb2Nrcy10ZXN0LTM4YWNlMThhNGQ1NTg0ZDFjYzBlYzE3ZTVlOTA0Yzg4ODI5MzU5NmIvaW1hZ2VzL2dhbHZhbml6ZS1sb2dvLnBuZ1VUBQABHVHiWlBLAQIAAAoAAAAIAMp6mkyCz7VajzMAAERRAABWAAkAAAAAAAAAAAAAAOQkAABnU2Nob29sLWJsb2Nrcy10ZXN0LTM4YWNlMThhNGQ1NTg0ZDFjYzBlYzE3ZTVlOTA0Yzg4ODI5MzU5NmIvaW1hZ2VzL3JlZ2lzdGVyX2tsYXNzLmdpZlVUBQABHVHiWlBLAQIAAAoAAAAIAMp6mkxOZbcBYgEAAGgCAABJAAkAAAAAAAEAAAAAAPBYAABnU2Nob29sLWJsb2Nrcy10ZXN0LTM4YWNlMThhNGQ1NTg0ZDFjYzBlYzE3ZTVlOTA0Yzg4ODI5MzU5NmIvamF2YS10ZXN0Lm1kVVQFAAEdUeJaUEsBAgAACgAAAAgAynqaTE8kcRUXBQAAyQoAAFIACQAAAAAAAQAAAAAAwloAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9tYXJrZG93bi1zbW9rZXRlc3QubWRVVAUAAR1R4lpQSwECAAAKAAAACADKeppML/HgrfMCAAAHBwAAVwAJAAAAAAABAAAAAABSYAAAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL211bHRpLWxpbmUtbXVsdGktY2hvaWNlLm1kVVQFAAEdUeJaUEsBAgAACgAAAAgAynqaTEzM9hIvAAAANQAAAEwACQAAAAAAAQAAAAAAw2MAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMzhhY2UxOGE0ZDU1ODRkMWNjMGVjMTdlNWU5MDRjODg4MjkzNTk2Yi9zaG9ydC1hbnN3ZXIubWRVVAUAAR1R4lpQSwECAAAKAAAACADKeppMxbUYFzEAAAA2AAAARgAJAAAAAAABAAAAAABlZAAAZ1NjaG9vbC1ibG9ja3MtdGVzdC0zOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZiL3RhcmdldC5tZFVUBQABHVHiWlBLBQYAAAAAFAAUADoKAAADZQAAKAAzOGFjZTE4YTRkNTU4NGQxY2MwZWMxN2U1ZTkwNGM4ODgyOTM1OTZi + http_version: + recorded_at: Tue, 08 May 2018 16:21:57 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/github-course-yaml-not-found.yml b/scripts/spec/fixtures/vcr_cassettes/github-course-yaml-not-found.yml new file mode 100644 index 0000000..fbc6723 --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/github-course-yaml-not-found.yml @@ -0,0 +1,70 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/gSchool/blocks-test/contents/kourse.yaml + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 404 + message: Not Found + headers: + Server: + - GitHub.com + Date: + - Thu, 08 Nov 2018 22:50:04 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Status: + - 404 Not Found + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4996' + X-Ratelimit-Reset: + - '1541720841' + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - '' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D712:4B74:133690A:29E31DB:5BE4BD9B + body: + encoding: ASCII-8BIT + string: '{"message":"Not Found","documentation_url":"https://developer.github.com/v3/repos/contents/#get-contents"}' + http_version: + recorded_at: Thu, 08 Nov 2018 22:50:04 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/github-course-yaml-success-2.yml b/scripts/spec/fixtures/vcr_cassettes/github-course-yaml-success-2.yml new file mode 100644 index 0000000..137249c --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/github-course-yaml-success-2.yml @@ -0,0 +1,285 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/gSchool/blocks-test/contents/test-inner-folder/duplicate-course.yaml + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Mon, 10 Dec 2018 21:27:33 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4969' + X-Ratelimit-Reset: + - '1544477734' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + Etag: + - W/"55a6658731508baad86af1ef7784f612239f3490" + Last-Modified: + - Mon, 10 Dec 2018 21:19:58 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - '' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - DC04:2075:B87798:1B2FEEA:5C0EDA45 + body: + encoding: ASCII-8BIT + string: '{"name":"duplicate-course.yaml","path":"test-inner-folder/duplicate-course.yaml","sha":"55a6658731508baad86af1ef7784f612239f3490","size":288,"url":"https://api.github.com/repos/gSchool/blocks-test/contents/test-inner-folder/duplicate-course.yaml?ref=master","html_url":"https://github.com/gSchool/blocks-test/blob/master/test-inner-folder/duplicate-course.yaml","git_url":"https://api.github.com/repos/gSchool/blocks-test/git/blobs/55a6658731508baad86af1ef7784f612239f3490","download_url":"https://raw.githubusercontent.com/gSchool/blocks-test/master/test-inner-folder/duplicate-course.yaml?token=Ab2h-LsuGvXx8meUaRPvgIojb0opNXY9ks5cDtqBwA%3D%3D","type":"file","content":"LS0tCkNvdXJzZToKICAtIFNlY3Rpb246IERTSSBBZG1pc3Npb25zIFByZXAK\nICAgIFJlcG9zOgogICAgICAtIFVSTDogaHR0cHM6Ly9naXRodWIuY29tL2dT\nY2hvb2wvZHNpLWludHJvLXRvLWRzLXN0YXRzCiAgICAgIC0gVVJMOiBodHRw\nczovL2dpdGh1Yi5jb20vZ1NjaG9vbC9kcy1zcWwtYmxvY2sKCiAgLSBTZWN0\naW9uOiBBZHZhbmNlZCBEU0kgQWRtaXNzaW9ucyBQcmVwCiAgICBSZXBvczoK\nICAgICAtIFVSTDogaHR0cHM6Ly9naXRodWIuY29tL2dTY2hvb2wvZHMtcHl0\naG9uLXF1aXp6ZXMtYmxvY2sK\n","encoding":"base64","_links":{"self":"https://api.github.com/repos/gSchool/blocks-test/contents/test-inner-folder/duplicate-course.yaml?ref=master","git":"https://api.github.com/repos/gSchool/blocks-test/git/blobs/55a6658731508baad86af1ef7784f612239f3490","html":"https://github.com/gSchool/blocks-test/blob/master/test-inner-folder/duplicate-course.yaml"}}' + http_version: + recorded_at: Mon, 10 Dec 2018 21:27:33 GMT +- request: + method: head + uri: https://api.github.com/ + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Mon, 10 Dec 2018 21:27:34 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '2039' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4968' + X-Ratelimit-Reset: + - '1544477734' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + Etag: + - '"5fec8610d639158b24e1de36cf28fe46"' + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - '' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - DC05:2076:148159C:2A5419D:5C0EDA45 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Mon, 10 Dec 2018 21:27:34 GMT +- request: + method: head + uri: https://api.github.com/ + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Mon, 10 Dec 2018 21:27:34 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '2039' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4967' + X-Ratelimit-Reset: + - '1544477734' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + Etag: + - '"5fec8610d639158b24e1de36cf28fe46"' + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - '' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - DC47:2074:5D865D:E53334:5C0EDA46 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Mon, 10 Dec 2018 21:27:34 GMT +- request: + method: head + uri: https://api.github.com/ + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Mon, 10 Dec 2018 21:27:34 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '2039' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4966' + X-Ratelimit-Reset: + - '1544477734' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + Etag: + - '"5fec8610d639158b24e1de36cf28fe46"' + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - '' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - DC4C:2076:14815D5:2A54211:5C0EDA46 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Mon, 10 Dec 2018 21:27:34 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/github-course-yaml-success.yml b/scripts/spec/fixtures/vcr_cassettes/github-course-yaml-success.yml new file mode 100644 index 0000000..389f3ec --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/github-course-yaml-success.yml @@ -0,0 +1,295 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/gSchool/learn-course-files/contents/test/integration.yaml + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Tue, 04 Aug 2020 22:52:49 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4994' + X-Ratelimit-Reset: + - '1596583625' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - W/"591662d53624e85d29ee35241ba396f8e63e1e4e" + Last-Modified: + - Tue, 04 Aug 2020 22:50:21 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - '' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - C7DA:25DD:318232B:556BD7D:5F29E6C1 + body: + encoding: ASCII-8BIT + string: '{"name":"integration.yaml","path":"test/integration.yaml","sha":"591662d53624e85d29ee35241ba396f8e63e1e4e","size":399,"url":"https://api.github.com/repos/gSchool/learn-course-files/contents/test/integration.yaml?ref=master","html_url":"https://github.com/gSchool/learn-course-files/blob/master/test/integration.yaml","git_url":"https://api.github.com/repos/gSchool/learn-course-files/git/blobs/591662d53624e85d29ee35241ba396f8e63e1e4e","download_url":"https://raw.githubusercontent.com/gSchool/learn-course-files/master/test/integration.yaml?token=AB2TTYKOECC643FWYOEXC7K7FHTP2","type":"file","content":"IyAgRE8gTk9UIERFTEVURSEgRE8gTk9UIE1PRElGWSEgVGhpcyBmaWxlIGlz\nIHVzdWFsbHkgc3R1YmJlZCwgYnV0IGNhbiBzb21ldGltZXMgYmUgdXRpbGl6\nZWQgYXMgcGFydCBvZiBzcGVjcy4KLS0tCkNvdXJzZToKICAtIFNlY3Rpb246\nIERTSSBBZG1pc3Npb25zIFByZXAKICAgIFJlcG9zOgogICAgICAtIFVSTDog\naHR0cHM6Ly9naXRodWIuY29tL2dTY2hvb2wvZHNpLWludHJvLXRvLWRzLXN0\nYXRzCiAgICAgIC0gVVJMOiBodHRwczovL2dpdGh1Yi5jb20vZ1NjaG9vbC9k\ncy1zcWwtYmxvY2sKICAtIFNlY3Rpb246IEFkdmFuY2VkIERTSSBBZG1pc3Np\nb25zIFByZXAKICAgIFJlcG9zOgogICAgICAtIFVSTDogaHR0cHM6Ly9naXRo\ndWIuY29tL2dTY2hvb2wvZHMtcHl0aG9uLXF1aXp6ZXMtYmxvY2sK\n","encoding":"base64","_links":{"self":"https://api.github.com/repos/gSchool/learn-course-files/contents/test/integration.yaml?ref=master","git":"https://api.github.com/repos/gSchool/learn-course-files/git/blobs/591662d53624e85d29ee35241ba396f8e63e1e4e","html":"https://github.com/gSchool/learn-course-files/blob/master/test/integration.yaml"}}' + http_version: + recorded_at: Tue, 04 Aug 2020 22:52:49 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/dsi-intro-to-ds-stats + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Tue, 04 Aug 2020 22:52:50 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6511' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4993' + X-Ratelimit-Reset: + - '1596583625' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"2b2cf77cb5f10a16550083ffd79753ea"' + Last-Modified: + - Mon, 10 Jun 2019 21:37:29 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - C7DB:16B2:19B1814:3D1AA30:5F29E6C1 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Tue, 04 Aug 2020 22:52:50 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/ds-sql-block + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Tue, 04 Aug 2020 22:52:50 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6022' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4992' + X-Ratelimit-Reset: + - '1596583625' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"6c49007f41bc900e030ccfa2aef98948"' + Last-Modified: + - Tue, 14 Aug 2018 02:48:21 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - C7DC:0DD0:B6F2D3:1DFE506:5F29E6C2 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Tue, 04 Aug 2020 22:52:51 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/ds-python-quizzes-block + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Tue, 04 Aug 2020 22:52:51 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6523' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4991' + X-Ratelimit-Reset: + - '1596583625' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"71e3813a731b9b7b9543f584ab9095dd"' + Last-Modified: + - Tue, 14 Aug 2018 03:34:36 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - C7DD:348C:348E6BF:54634AA:5F29E6C3 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Tue, 04 Aug 2020 22:52:51 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/github-not-found.yml b/scripts/spec/fixtures/vcr_cassettes/github-not-found.yml new file mode 100644 index 0000000..3dbbf25 --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/github-not-found.yml @@ -0,0 +1,69 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/gSchool/does-not-exist/git/refs/heads/master + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 404 + message: Not Found + headers: + Server: + - GitHub.com + Date: + - Tue, 06 Feb 2018 17:20:37 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Status: + - 404 Not Found + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4934' + X-Ratelimit-Reset: + - '1517937970' + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval + Access-Control-Allow-Origin: + - "*" + Content-Security-Policy: + - default-src 'none' + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-Xss-Protection: + - 1; mode=block + X-Runtime-Rack: + - '0.026103' + X-Github-Request-Id: + - EE07:20407:8C831:10109C:5A79E3E5 + body: + encoding: ASCII-8BIT + string: '{"message":"Not Found","documentation_url":"https://developer.github.com/v3"}' + http_version: + recorded_at: Tue, 06 Feb 2018 17:20:37 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/github-repo-success.yml b/scripts/spec/fixtures/vcr_cassettes/github-repo-success.yml new file mode 100644 index 0000000..2c71dd2 --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/github-repo-success.yml @@ -0,0 +1,199 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/gSchool/blocks-test/git/refs/heads/master + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 1234asdf + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Tue, 06 Feb 2018 17:20:37 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4933' + X-Ratelimit-Reset: + - '1517937970' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + Etag: + - W/"71084c3b43dda7dc8b69cc91b7db6c87" + Last-Modified: + - Tue, 10 Oct 2017 20:43:38 GMT + X-Poll-Interval: + - '300' + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - '' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval + Access-Control-Allow-Origin: + - "*" + Content-Security-Policy: + - default-src 'none' + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-Xss-Protection: + - 1; mode=block + X-Runtime-Rack: + - '0.043581' + X-Github-Request-Id: + - EE08:20406:5F42A:10BEB8:5A79E3E5 + body: + encoding: ASCII-8BIT + string: '{"ref":"refs/heads/master","url":"https://api.github.com/repos/gSchool/blocks-test/git/refs/heads/master","object":{"sha":"0711a9a1293ab2b9b77cf6280c1836c231c9c8d5","type":"commit","url":"https://api.github.com/repos/gSchool/blocks-test/git/commits/0711a9a1293ab2b9b77cf6280c1836c231c9c8d5"}}' + http_version: + recorded_at: Tue, 06 Feb 2018 17:20:37 GMT +- request: + method: get + uri: https://api.github.com/repos/gSchool/blocks-test/zipball/0711a9a1293ab2b9b77cf6280c1836c231c9c8d5 + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token df4a78780be0d20bd5ed37c6d25707c69b08c092 + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 302 + message: Found + headers: + Server: + - GitHub.com + Date: + - Tue, 06 Feb 2018 17:20:38 GMT + Content-Type: + - text/html;charset=utf-8 + Content-Length: + - '0' + Status: + - 302 Found + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4933' + X-Ratelimit-Reset: + - '1517937970' + Cache-Control: + - public, must-revalidate, max-age=0 + Expires: + - Tue, 06 Feb 2018 17:20:38 GMT + Location: + - https://codeload.github.com/gSchool/blocks-test/legacy.zip/0711a9a1293ab2b9b77cf6280c1836c231c9c8d5?token=AiHiknCqDgBLkHgWZ3gG2vlOWvAe5nfrks5aeeUSwA== + Access-Control-Expose-Headers: + - ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval + Access-Control-Allow-Origin: + - "*" + Content-Security-Policy: + - default-src 'none' + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-Xss-Protection: + - 1; mode=block + X-Runtime-Rack: + - '0.032232' + X-Github-Request-Id: + - EE09:20407:8C8AC:101168:5A79E3E6 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Tue, 06 Feb 2018 17:20:38 GMT +- request: + method: get + uri: https://codeload.github.com/gSchool/blocks-test/legacy.zip/0711a9a1293ab2b9b77cf6280c1836c231c9c8d5?token=AiHiknCqDgBLkHgWZ3gG2vlOWvAe5nfrks5aeeUSwA== + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token df4a78780be0d20bd5ed37c6d25707c69b08c092 + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Transfer-Encoding: + - chunked + Access-Control-Allow-Origin: + - https://render.githubusercontent.com + Content-Security-Policy: + - default-src 'none'; style-src 'unsafe-inline'; sandbox + Strict-Transport-Security: + - max-age=31536000 + Vary: + - Authorization,Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - deny + X-Xss-Protection: + - 1; mode=block + Etag: + - '"0711a9a1293ab2b9b77cf6280c1836c231c9c8d5"' + Content-Type: + - application/zip + Content-Disposition: + - attachment; filename=gSchool-blocks-test-0711a9a1293ab2b9b77cf6280c1836c231c9c8d5.zip + X-Geo-Block-List: + - '' + Date: + - Tue, 06 Feb 2018 17:20:38 GMT + X-Github-Request-Id: + - EE0A:0FDB:5EFF:7D1D:5A79E3E6 + body: + encoding: ASCII-8BIT + string: !binary |- + UEsDBAoAAAAAAJNyN0wAAAAAAAAAAAAAAAA9AAkAZ1NjaG9vbC1ibG9ja3MtdGVzdC0wNzExYTlhMTI5M2FiMmI5Yjc3Y2Y2MjgwYzE4MzZjMjMxYzljOGQ1L1VUBQABN7VnWlBLAwQKAAAACACTcjdM3A4LV9oKAAAAKQAAVAAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9jaGFsbGVuZ2VzLXNtb2tldGVzdC5tZFVUBQABN7VnWu1aeXfbNhL/u/oUE6W7klqRunzKR16adJvkNUm3djbZZzkWSEIWY5JgANK2nqrvvjMAqcuSLMXJtvve2rFEgpjBHL85AOYxPHL7LAh4dMkLhR8gGcS8DaovZGKxSN1wiYO+1wam9rzd3v6e2vV6kdqnm0gRgZ8ESMGkZAN49o93OORx1w9Z0IZtvFEJizwmvYs09T3Vhvf1emPP+lejUHhMP/Doc8pV4otID8A/x3fv+ywBT3AFSZ9DTwSBuPGjS3CFxyGWwktd/qRQ6Ha7n9g1U67046RwzSSEg6damCM4K/4c+LxYheIrFvEBXbxmSULfvzF5xSVdnfph8fxAU/qRx2+RrnVQyJic6aFzWmUsMI88a0ZoGsxsVcgZz8zOH44H44C5vC8Cj0Z/jhIuYSBSCWYi9LnkMwxm5pvhvh8lRntV6KWRS9JAIgcv0NZclitwdAxDkDxJZQQlT0AoJNfGe1SC0UQhs4DhNsM6EkiNyBhUiS30Nd8lNDRyi0JGzBjllDzmS5UAD3jIowRNq91oPNNnamzrehWUgG5u79Z5Fwe7uRm76HipKUnyXN//lpdndMrGpqJFj81EDwWKCsUVTxAek6lWcxIokl/y22WBtkGwPGMRYQa4Rk93OGQy8d2A23HqBL7qc+9tBH+AxxLeLr1+bT1/bv0bf0qjURdu/KQPnt/rIc7QNywUaZQoED246fsoesx0aK2D91pniL/qh2z1zvTyHfVj5w/8uCMDzu+MOqPawiC5a+d5KxvTxUyyS8ni/gq7tyZ2P0md0E9MmGH2+MTd5CHZ6YW4gZBFA7gR0lOYp7QzriJxc4/hZkL5vURzE7xDQJHXiPv5QHsaIZxVIlM3wTi58ZGJ5Nc+v8FU4gGaxyP2vrLhFyE8+DV1rx5tAvEvN/3WNzO9LgxMarvBNnL0kbt/zQnrjASk5AKvsCic6KKw1CFrahumQeLHAceZwnf5Cp23JzrTkzEljCkfprRvaqH0L/tJVivuQZuI6VaRMt9/Dx2Fhukgi4Rh/uvEIWDofpYJDBtWxxUKPg6bo+z5aIQEZcrxVfCTktIwAhHxiualiAHRDjs9ydxhYzR8YzVGOJaGF0P/qDH6+AbKtxc+WNAR11wGfsSHt6PKxyYy1iwoBw8tR8thWDkfm9YWc0eGApqMpk7rN1ZnLgt9Pd0WZqQFgfcTd1mqOC39QFMUEBHEZl1zEPIjkRgU2A8P5vXhvTOBNxZYtw/PRBij1bCEPG5sBO6XPZ0ukTXy9aBruzmnconFcakKwy4kYvYBdn+eElI/rFKlQgkEOlQCGRQTcRp42FpgIkBKw/rJWsFhwRvkQ72lhXDIGGFev4KYyx5mq2CAc7qHKNnxYY0+u3SPC6QBk3aIrWjAy0V8gJ3G2XlFP12kEo1fhzbeoq/1fD10GB8Ph9+7iQzo0Wh0WKMV7of9rETrAXexqT3eQxMqYDB+WtUVZGYJLC26Y3B1T475aDwZYXgiwJ/2ata05QCJWKj9kjmxqmdGHJ0/dtbcYuT+w2w6DuZX3QcDPkpDR3dbGZYVCylHY2vos2hlAOxOb26aGyE+y98mVek00WqNho16fQQU1E+olA/y/l9iP+YZ0zTzBUH3AsrUeAKnvXabYbqM6Q0GMkZxAs6wSM2vYN/XgGToarVqKP6asHuBuKHyhU0r/5zqMcsyxnD4pR8NWeBfRiNyQvmkAn8/yoxUbmBM1u0dzJXwI8zd1dd6Nhpu62w8YVq3Gzs45xiyi7rdMl/ju9Fwa4aiYTfnh7T39EgH8SrFLRG2oIOGyJVBx/55WN37yvB8GwUDHdP8lrl59/ElaDTb3XlO/wfdXwd0kx59Gbj2v1lzn7Fj8IufvEgdePf7rxvsp6aFucRNLjLA3uDqf2JPda/RG/VvbvXI9FMPtr1hs54H1rQPnf9YKvLjmJOqAXVf7BIfTJ3+rbBdY/oEJuYsUZvZy9RQBuOjNmpovJyXaYfMYRveyJRTM5SMT8FQjh4dr9AIIotaTcpIgdnPBSybYnquHo5revSGvYEDCrUapDEdtnjQrGFaaTTbjRa8PjktTE4IM4HLfhSnSQWGRJQdEj5A7GmRC6N7w03hgvHUWR4GDAIYe2KJ7H3JyyX0nF+q2ObBgT6Ym+Jp6PMRcrXKjwY9TlhwkEWmKTa4ufJl0rcA+OMn5eKMt276PEK8XuMnVYswTga5xmWz01Cch4rGqFWtgpMmcCnMSRqivl2BSnFuJdA/RoVybvdisVKF4nPeY7jtwsoTpLpE+ZErpMSJxYqdCNvjPLb55zLJVtGMRpVVkmsHcVdE3j0+035dT1CH9fFfsbJEoLsEEZv9XUq6SBeDoDWU0W8EaP+rtfoipeYE00uvoVS0nG5ao7GAKSpkG0brCIesT7SKZbMMRX9I2+xyrZOxqU1Ww7+5oMjCYFFF+wmtl+oDM5aYLRq9Q3H73L3CXEEn7DodkKUR/Zmh8UZ4nkX5M+ljHsq7KdzPQSyU8h1sRJUIUlqjfedVRG7BcGC0MmpjihiP2IY1/A23II+OjqCeWybLR9q+Bzgywj9KE9rPLwgSRzBmolIHBS7XqzDHt9asHGSEBk1LKBeT5TJMljyaYnNQGG36suCbVLWZ9wrjJP7Aynanlm1ShO6ggNicIpPy3WKzwIh/YrXIBV2rYNwT0BOlV6TA5SH8UPDEg6Qvoqa9m2GHu70tr7W3bbkNRMFWo4cd/P6OY+1ub7tb9f36dnN7d4Kknh/QDs0ZgIslfsM+qfCSNqj6lZ9OzTkQuobrhTO40Fy7bXiJU9iVPnXqYaqgBIX5Tvq3Veh+6FIvlL1VxkykabjCJ4Ou6ZKYGUOtHR7guP7u2sRV9fUJXoY0erGSlRApbrDSSBHCByw1QnFDnZ+rm/qvh2yNEGPHwvHxMRIcQRTbWqDy2VmjCs0qtM6rcLZVhe0q7NDlbhX2qrBPlw1MSA2c1Wien1c0h8EMhyKjt5Iufegrp5hNm7NT+UMVBnpSpXBn8Qx2k4VX83CmeczId+/r7fkYzyzj8d6SxbQVK20tYalU0t8v3/z27rRNB04+AkQhfyzgURrGA+Po6uyNcPS2hijfvjtdTWqi9Pcstyx0tW6wVIwZnKqbcbyeMFjo/2nBMyR9OBtQGdATzudjd2Xmyszlh7GQ2BhEfkKhnt+HzEeYKoi/+y4fylTDRiJeJ51NljBBcYqjz/KEUc7Xs/Uww6aF/EK+o9GLOQc2yooHvcx18EDgGx5rQN9MRAdRW3wEsb0iDszc7ARqmXRTIZHNxwRCls1mX+j+sWxWrGbcdBu3zDDNv7RhnNWGWbD4egZZv8vcNC2sE1X3NVWPF1dGkpJhf2rNnqrs7fSw0rCm5e3vb1lbTdawHL6zb/HtvRbzGls7O8yb1MFXJ/CG6RfL5n+avOaonEf1MI2xceQsbEM/SWLVrtXMUQe9W6ldnrh9IYLaJ2VFmtzS5rVCQ15DSl7zBLbe0vJ4KDY/t1kmWH4EY3KZPnvBLlxg5osSTDGU9vTpjH61JUyeXCIloDPEDVbBsn6Rrbe5C3RVMW4KgoCtVlbFGjaVDUrMgrUOcT8l6dTleNlqKzvJh2LH2dnz2A6rW66z62IPtedYzu7ejsX41p673+rte9t8gp2nKRrsKbUsinqhdTHDkMxiYzJjvk+818uFybL/xpCZl2cjqGyIhTWV+HpwmFtwIxj8B1BLAwQKAAAACACTcjdMJGmHbu0AAAAHAgAASAAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9jb25maWcueWFtbFVUBQABN7VnWpVQwWrDMAy95yt068nZ3deUQmGHQbsP0Gwxa3GsYCsp+ft5WclG6GEDG1tP0tN7MsY0F8XkMftiGwBTL8CVNZKFk8gaHqm4zKOyJAvXwAXq0UDgfxItnPVQAGHMpLpACZL1dwHUKV/oFH06KNwyjjBTXmCYXADJgAoYI9BMqV3Hvp6PFvDNrcFlco5K6TIrZUa7glXvJrKTpJT0xJHKlr2/1dEyVkPPlUHSBr6gBgtPA+beyy2ZMkhPSkXbwW/8f6OYorKJnMh8f10QdvRvng+c0ewU7Bq7QK4fhZPum12o+6P0TuWRk/s+HxU1n1BLAwQKAAAAAACTcjdMAAAAAAAAAAAAAAAARAAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb2xkZXIvVVQFAAE3tWdaUEsDBAoAAAAAAJNyN0wAAAAAAAAAAAAAAABSAAkAZ1NjaG9vbC1ibG9ja3MtdGVzdC0wNzExYTlhMTI5M2FiMmI5Yjc3Y2Y2MjgwYzE4MzZjMjMxYzljOGQ1L2ZvbGRlci9kZWVwbHktbmVzdGVkL1VUBQABN7VnWlBLAwQKAAAACACTcjdMckGhikwAAABkAAAAWQAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb2xkZXIvZGVlcGx5LW5lc3RlZC90ZXN0Lm1kVVQFAAE3tWda01KITkwqzs8pLUlVyMnMy1YoyVcoys8vidXQz00syk7JL8/TLc7Nz04tSS0u0ctN0eTSwqKjuDQpLT8nJbUIqA3C0C9JLEpPhegAAFBLAwQKAAAACACTcjdMWLsxI2MAAACpAAAATgAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb2xkZXIvc2libGluZy5tZFVUBQABN7VnWpXLuw2AMAxF0T5TuAQkwhzMgFIYxUCUj1FixPoE6Ogo39M9I2AE2QiKm4NLq1IdTDtmSgJ1e9NoPUTM3vKZ+hLZk1ARHW17lzgXDofQ04IwvNQ0f8zCwVIeBPNKd1X192rVBVBLAwQKAAAACACTcjdMVm95RCMAAAAnAAAATQAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb2xkZXIvdGFyZ2V0Lm1kVVQFAAE3tWday0stLklNUShJLEpPLeHi0lKILs5MysnMS4/VgDL0clM0uQBQSwMECgAAAAAAk3I3TAAAAAAAAAAAAAAAAEEACQBnU2Nob29sLWJsb2Nrcy10ZXN0LTA3MTFhOWExMjkzYWIyYjliNzdjZjYyODBjMTgzNmMyMzFjOWM4ZDUvZm9vL1VUBQABN7VnWlBLAwQKAAAAAACTcjdMWeLFOQYAAAAGAAAARwAJAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb28vYmFyLm1kVVQFAAE3tWdaYmFyLm1kUEsDBAoAAAAAAJNyN0y2ynH8BgAAAAYAAABHAAkAZ1NjaG9vbC1ibG9ja3MtdGVzdC0wNzExYTlhMTI5M2FiMmI5Yjc3Y2Y2MjgwYzE4MzZjMjMxYzljOGQ1L2Zvby9iYXoubWRVVAUAATe1Z1piYXoubWRQSwMECgAAAAAAk3I3TBd9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OTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb2xkZXIvZGVlcGx5LW5lc3RlZC9VVAUAATe1Z1pQSwECAAAKAAAACACTcjdMckGhikwAAABkAAAAWQAJAAAAAAABAAAAAAD5DQAAZ1NjaG9vbC1ibG9ja3MtdGVzdC0wNzExYTlhMTI5M2FiMmI5Yjc3Y2Y2MjgwYzE4MzZjMjMxYzljOGQ1L2ZvbGRlci9kZWVwbHktbmVzdGVkL3Rlc3QubWRVVAUAATe1Z1pQSwECAAAKAAAACACTcjdMWLsxI2MAAACpAAAATgAJAAAAAAABAAAAAADFDgAAZ1NjaG9vbC1ibG9ja3MtdGVzdC0wNzExYTlhMTI5M2FiMmI5Yjc3Y2Y2MjgwYzE4MzZjMjMxYzljOGQ1L2ZvbGRlci9zaWJsaW5nLm1kVVQFAAE3tWdaUEsBAgAACgAAAAgAk3I3TFZveUQjAAAAJwAAAE0ACQAAAAAAAQAAAAAAnQ8AAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb2xkZXIvdGFyZ2V0Lm1kVVQFAAE3tWdaUEsBAgAACgAAAAAAk3I3TAAAAAAAAAAAAAAAAEEACQAAAAAAAAAQAAAANBAAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb28vVVQFAAE3tWdaUEsBAgAACgAAAAAAk3I3TFnixTkGAAAABgAAAEcACQAAAAAAAQAAAAAAnBAAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb28vYmFyLm1kVVQFAAE3tWdaUEsBAgAACgAAAAAAk3I3TLbKcfwGAAAABgAAAEcACQAAAAAAAQAAAAAAEBEAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb28vYmF6Lm1kVVQFAAE3tWdaUEsBAgAACgAAAAAAk3I3TBd9FcUHAAAABwAAAEgACQAAAAAAAQAAAAAAhBEAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9mb28vYnV6ei5tZFVUBQABN7VnWlBLAQIAAAoAAAAAAJNyN0wAAAAAAAAAAAAAAABEAAkAAAAAAAAAEAAAAPoRAABnU2Nob29sLWJsb2Nrcy10ZXN0LTA3MTFhOWExMjkzYWIyYjliNzdjZjYyODBjMTgzNmMyMzFjOWM4ZDUvaW1hZ2VzL1VUBQABN7VnWlBLAQIAAAoAAAAIAJNyN0yvRe4/FxAAACYQAABWAAkAAAAAAAAAAAAAAGUSAABnU2Nob29sLWJsb2Nrcy10ZXN0LTA3MTFhOWExMjkzYWIyYjliNzdjZjYyODBjMTgzNmMyMzFjOWM4ZDUvaW1hZ2VzL2dhbHZhbml6ZS1sb2dvLnBuZ1VUBQABN7VnWlBLAQIAAAoAAAAIAJNyN0yCz7VajzMAAERRAABWAAkAAAAAAAAAAAAAAPkiAABnU2Nob29sLWJsb2Nrcy10ZXN0LTA3MTFhOWExMjkzYWIyYjliNzdjZjYyODBjMTgzNmMyMzFjOWM4ZDUvaW1hZ2VzL3JlZ2lzdGVyX2tsYXNzLmdpZlVUBQABN7VnWlBLAQIAAAoAAAAIAJNyN0xygUgJvAIAALsFAABJAAkAAAAAAAEAAAAAAAVXAABnU2Nob29sLWJsb2Nrcy10ZXN0LTA3MTFhOWExMjkzYWIyYjliNzdjZjYyODBjMTgzNmMyMzFjOWM4ZDUvamF2YS10ZXN0Lm1kVVQFAAE3tWdaUEsBAgAACgAAAAgAk3I3TE8kcRUXBQAAyQoAAFIACQAAAAAAAQAAAAAAMVoAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS9tYXJrZG93bi1zbW9rZXRlc3QubWRVVAUAATe1Z1pQSwECAAAKAAAACACTcjdML/HgrfMCAAAHBwAAVwAJAAAAAAABAAAAAADBXwAAZ1NjaG9vbC1ibG9ja3MtdGVzdC0wNzExYTlhMTI5M2FiMmI5Yjc3Y2Y2MjgwYzE4MzZjMjMxYzljOGQ1L211bHRpLWxpbmUtbXVsdGktY2hvaWNlLm1kVVQFAAE3tWdaUEsBAgAACgAAAAAAk3I3TBhVVdERAAAAEQAAAEYACQAAAAAAAQAAAAAAMmMAAGdTY2hvb2wtYmxvY2tzLXRlc3QtMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNS90YXJnZXQubWRVVAUAATe1Z1pQSwUGAAAAABMAEwC3CQAAsGMAACgAMDcxMWE5YTEyOTNhYjJiOWI3N2NmNjI4MGMxODM2YzIzMWM5YzhkNQ== + http_version: + recorded_at: Tue, 06 Feb 2018 17:20:39 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-branch-failure.yml b/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-branch-failure.yml new file mode 100644 index 0000000..bc1ecf6 --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-branch-failure.yml @@ -0,0 +1,118 @@ +--- +http_interactions: +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects?search=p1-test + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '3280' + Content-Type: + - application/json + Etag: + - W/"905a13fb3b949a706442a5f1b4b540d6" + Link: + - ; + rel="first", ; + rel="last" + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Next-Page: + - '' + X-Page: + - '1' + X-Per-Page: + - '20' + X-Prev-Page: + - '' + X-Request-Id: + - bjd2vFLwMO + X-Runtime: + - '0.063492' + X-Total: + - '1' + X-Total-Pages: + - '1' + Date: + - Tue, 06 Oct 2020 07:44:25 GMT + X-Envoy-Upstream-Service-Time: + - '66' + Server: + - istio-envoy + body: + encoding: UTF-8 + string: '[{"id":702,"description":"","name":"p1-test","name_with_namespace":"Charlie + Sakamaki / p1-test","path":"p1-test","path_with_namespace":"csakamaki/p1-test","created_at":"2020-09-23T19:53:49.310Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dsop.io:csakamaki/p1-test.git","http_url_to_repo":"https://code.il2.dsop.io/csakamaki/p1-test.git","web_url":"https://code.il2.dsop.io/csakamaki/p1-test","readme_url":null,"avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-24T00:07:57.336Z","namespace":{"id":288,"name":"Charlie + Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"_links":{"self":"https://code.il2.dsop.io/api/v4/projects/702","issues":"https://code.il2.dsop.io/api/v4/projects/702/issues","merge_requests":"https://code.il2.dsop.io/api/v4/projects/702/merge_requests","repo_branches":"https://code.il2.dsop.io/api/v4/projects/702/repository/branches","labels":"https://code.il2.dsop.io/api/v4/projects/702/labels","events":"https://code.il2.dsop.io/api/v4/projects/702/events","members":"https://code.il2.dsop.io/api/v4/projects/702/members"},"empty_repo":false,"archived":false,"visibility":"private","owner":{"id":238,"name":"Charlie + Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-07T20:50:13.933Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' + http_version: + recorded_at: Tue, 06 Oct 2020 07:44:25 GMT +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects/702/repository/files/course.yaml?ref=missingfile + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 404 + message: Not Found + headers: + Cache-Control: + - no-cache + Content-Length: + - '34' + Content-Type: + - application/json + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - lJJgxk2Wz06 + X-Runtime: + - '0.042335' + Date: + - Tue, 06 Oct 2020 07:44:25 GMT + X-Envoy-Upstream-Service-Time: + - '47' + Server: + - istio-envoy + body: + encoding: UTF-8 + string: '{"message":"404 Commit Not Found"}' + http_version: + recorded_at: Tue, 06 Oct 2020 07:44:25 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-success.yml b/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-success.yml new file mode 100644 index 0000000..549e280 --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-success.yml @@ -0,0 +1,281 @@ +--- +http_interactions: +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects?search=p1-test + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '3280' + Content-Type: + - application/json + Etag: + - W/"905a13fb3b949a706442a5f1b4b540d6" + Link: + - ; + rel="first", ; + rel="last" + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Next-Page: + - '' + X-Page: + - '1' + X-Per-Page: + - '20' + X-Prev-Page: + - '' + X-Request-Id: + - O1E5nWd8Oa5 + X-Runtime: + - '0.103785' + X-Total: + - '1' + X-Total-Pages: + - '1' + Date: + - Tue, 06 Oct 2020 07:37:19 GMT + X-Envoy-Upstream-Service-Time: + - '108' + Server: + - istio-envoy + body: + encoding: UTF-8 + string: '[{"id":702,"description":"","name":"p1-test","name_with_namespace":"Charlie + Sakamaki / p1-test","path":"p1-test","path_with_namespace":"csakamaki/p1-test","created_at":"2020-09-23T19:53:49.310Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dsop.io:csakamaki/p1-test.git","http_url_to_repo":"https://code.il2.dsop.io/csakamaki/p1-test.git","web_url":"https://code.il2.dsop.io/csakamaki/p1-test","readme_url":null,"avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-24T00:07:57.336Z","namespace":{"id":288,"name":"Charlie + Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"_links":{"self":"https://code.il2.dsop.io/api/v4/projects/702","issues":"https://code.il2.dsop.io/api/v4/projects/702/issues","merge_requests":"https://code.il2.dsop.io/api/v4/projects/702/merge_requests","repo_branches":"https://code.il2.dsop.io/api/v4/projects/702/repository/branches","labels":"https://code.il2.dsop.io/api/v4/projects/702/labels","events":"https://code.il2.dsop.io/api/v4/projects/702/events","members":"https://code.il2.dsop.io/api/v4/projects/702/members"},"empty_repo":false,"archived":false,"visibility":"private","owner":{"id":238,"name":"Charlie + Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-07T20:50:13.933Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' + http_version: + recorded_at: Tue, 06 Oct 2020 07:37:19 GMT +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects/702/repository/files/course.yaml?ref=master + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '648' + Content-Type: + - application/json + Etag: + - W/"58ce3aa47e9466c129ffaf1a1f8ab1fe" + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Gitlab-Blob-Id: + - 696c1f44c7233b2fd1c27c43c2b1106cc06f0eb3 + X-Gitlab-Commit-Id: + - c114c7afbf65344d93f697f34060ca901b3035df + X-Gitlab-Content-Sha256: + - d537f06b2b5bc5e1e1980872aac58766d2a57578d2f565e53b007debf05feb9f + X-Gitlab-Encoding: + - base64 + X-Gitlab-File-Name: + - course.yaml + X-Gitlab-File-Path: + - course.yaml + X-Gitlab-Last-Commit-Id: + - ac0da165cce031bf056866c3629dc7f221748c8f + X-Gitlab-Ref: + - master + X-Gitlab-Size: + - '211' + X-Request-Id: + - UlX7wrEymh2 + X-Runtime: + - '0.052112' + Date: + - Tue, 06 Oct 2020 07:37:19 GMT + X-Envoy-Upstream-Service-Time: + - '54' + Server: + - istio-envoy + body: + encoding: UTF-8 + string: '{"file_name":"course.yaml","file_path":"course.yaml","size":211,"encoding":"base64","content_sha256":"d537f06b2b5bc5e1e1980872aac58766d2a57578d2f565e53b007debf05feb9f","ref":"master","blob_id":"696c1f44c7233b2fd1c27c43c2b1106cc06f0eb3","commit_id":"c114c7afbf65344d93f697f34060ca901b3035df","last_commit_id":"ac0da165cce031bf056866c3629dc7f221748c8f","content":"LS0tCiAgQ291cnNlOgogICAgLSBTZWN0aW9uOiBEU09QIEdpdExhYgogICAgICBSZXBvczoKICAgICAgICAtIFVSTDogaHR0cHM6Ly9jb2RlLmlsMi5kc29wLmlvL2NzYWthbWFraS9wMS10ZXN0CiAgICAtIFNlY3Rpb246IGdTY2hvb2wgR2l0SHViCiAgICAgIFJlcG9zOgogICAgICAgIC0gVVJMOiBodHRwczovL2dpdGh1Yi5jb20vZ1NjaG9vbC9yZXZhY29tbS10ZXN0Cg=="}' + http_version: + recorded_at: Tue, 06 Oct 2020 07:37:19 GMT +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects?search=p1-test + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '3280' + Content-Type: + - application/json + Etag: + - W/"905a13fb3b949a706442a5f1b4b540d6" + Link: + - ; + rel="first", ; + rel="last" + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Next-Page: + - '' + X-Page: + - '1' + X-Per-Page: + - '20' + X-Prev-Page: + - '' + X-Request-Id: + - kFr194XN4I8 + X-Runtime: + - '0.065975' + X-Total: + - '1' + X-Total-Pages: + - '1' + Date: + - Tue, 06 Oct 2020 07:37:19 GMT + X-Envoy-Upstream-Service-Time: + - '68' + Server: + - istio-envoy + body: + encoding: UTF-8 + string: '[{"id":702,"description":"","name":"p1-test","name_with_namespace":"Charlie + Sakamaki / p1-test","path":"p1-test","path_with_namespace":"csakamaki/p1-test","created_at":"2020-09-23T19:53:49.310Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dsop.io:csakamaki/p1-test.git","http_url_to_repo":"https://code.il2.dsop.io/csakamaki/p1-test.git","web_url":"https://code.il2.dsop.io/csakamaki/p1-test","readme_url":null,"avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-24T00:07:57.336Z","namespace":{"id":288,"name":"Charlie + Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"_links":{"self":"https://code.il2.dsop.io/api/v4/projects/702","issues":"https://code.il2.dsop.io/api/v4/projects/702/issues","merge_requests":"https://code.il2.dsop.io/api/v4/projects/702/merge_requests","repo_branches":"https://code.il2.dsop.io/api/v4/projects/702/repository/branches","labels":"https://code.il2.dsop.io/api/v4/projects/702/labels","events":"https://code.il2.dsop.io/api/v4/projects/702/events","members":"https://code.il2.dsop.io/api/v4/projects/702/members"},"empty_repo":false,"archived":false,"visibility":"private","owner":{"id":238,"name":"Charlie + Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-07T20:50:13.933Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' + http_version: + recorded_at: Tue, 06 Oct 2020 07:37:20 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/revacomm-test + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 86f4c1b3eb7417dc81fe87466c5179106a6d213c + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Date: + - Tue, 06 Oct 2020 07:37:21 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6092' + Server: + - GitHub.com + Status: + - 200 OK + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"2575197bdf1788abeea33fa22b34b5e4e00fe10bb9a22582fbd0a22e3ae43d14"' + Last-Modified: + - Wed, 23 Sep 2020 20:21:08 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4999' + X-Ratelimit-Reset: + - '1601973441' + X-Ratelimit-Used: + - '1' + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Used, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, + X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D9CF:6749:3F7741:4BD406:5F7C1EB0 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Tue, 06 Oct 2020 07:37:21 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/gitlab-not-found.yml b/scripts/spec/fixtures/vcr_cassettes/gitlab-not-found.yml new file mode 100644 index 0000000..7d3fbbb --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/gitlab-not-found.yml @@ -0,0 +1,111 @@ +--- +http_interactions: +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects?membership=true&search=does-not-exist + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '2' + Content-Type: + - application/json + Etag: + - W/"4f53cda18c2baa0c0354bb5f9a3ecbe5" + Link: + - ; + rel="first", ; + rel="last" + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Next-Page: + - '' + X-Page: + - '1' + X-Per-Page: + - '20' + X-Prev-Page: + - '' + X-Request-Id: + - 5DtY450BKt2 + X-Runtime: + - '0.036173' + X-Total: + - '0' + X-Total-Pages: + - '1' + Date: + - Tue, 06 Oct 2020 07:49:47 GMT + X-Envoy-Upstream-Service-Time: + - '38' + Server: + - istio-envoy + body: + encoding: UTF-8 + string: "[]" + http_version: + recorded_at: Tue, 06 Oct 2020 07:49:47 GMT +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects//repository/branches/master + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 404 + message: Not Found + headers: + Cache-Control: + - no-cache + Content-Length: + - '25' + Content-Type: + - application/json + Vary: + - Origin + X-Request-Id: + - GZZKs8WY2z7 + X-Runtime: + - '0.007294' + Date: + - Tue, 06 Oct 2020 07:49:47 GMT + X-Envoy-Upstream-Service-Time: + - '9' + Server: + - istio-envoy + body: + encoding: UTF-8 + string: '{"error":"404 Not Found"}' + http_version: + recorded_at: Tue, 06 Oct 2020 07:49:47 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/gitlab-success.yml b/scripts/spec/fixtures/vcr_cassettes/gitlab-success.yml new file mode 100644 index 0000000..c701ecf --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/gitlab-success.yml @@ -0,0 +1,178 @@ +--- +http_interactions: +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects?membership=true&search=test-20200917 + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '3391' + Content-Type: + - application/json + Etag: + - W/"94af059ec19294995f506264a0cbc893" + Link: + - ; + rel="first", ; + rel="last" + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Next-Page: + - '' + X-Page: + - '1' + X-Per-Page: + - '20' + X-Prev-Page: + - '' + X-Request-Id: + - 6kMf53dilIa + X-Runtime: + - '0.069335' + X-Total: + - '1' + X-Total-Pages: + - '1' + Date: + - Tue, 06 Oct 2020 07:55:51 GMT + X-Envoy-Upstream-Service-Time: + - '71' + Server: + - istio-envoy + body: + encoding: UTF-8 + string: '[{"id":599,"description":"","name":"test-20200917","name_with_namespace":"Charlie + Sakamaki / test-20200917","path":"test-20200917","path_with_namespace":"csakamaki/test-20200917","created_at":"2020-09-18T20:08:10.651Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dsop.io:csakamaki/test-20200917.git","http_url_to_repo":"https://code.il2.dsop.io/csakamaki/test-20200917.git","web_url":"https://code.il2.dsop.io/csakamaki/test-20200917","readme_url":"https://code.il2.dsop.io/csakamaki/test-20200917/-/blob/master/README.md","avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-30T19:14:19.395Z","namespace":{"id":288,"name":"Charlie + Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"_links":{"self":"https://code.il2.dsop.io/api/v4/projects/599","issues":"https://code.il2.dsop.io/api/v4/projects/599/issues","merge_requests":"https://code.il2.dsop.io/api/v4/projects/599/merge_requests","repo_branches":"https://code.il2.dsop.io/api/v4/projects/599/repository/branches","labels":"https://code.il2.dsop.io/api/v4/projects/599/labels","events":"https://code.il2.dsop.io/api/v4/projects/599/events","members":"https://code.il2.dsop.io/api/v4/projects/599/members"},"empty_repo":false,"archived":false,"visibility":"public","owner":{"id":238,"name":"Charlie + Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-09T20:50:21.090Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' + http_version: + recorded_at: Tue, 06 Oct 2020 07:55:51 GMT +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects/599/repository/branches/master + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Length: + - '816' + Content-Type: + - application/json + Etag: + - W/"b75c5a8fd1ffffdfc9f75550b7c82847" + Vary: + - Origin + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - q2T3D9YGCS4 + X-Runtime: + - '0.053157' + Date: + - Tue, 06 Oct 2020 07:55:51 GMT + X-Envoy-Upstream-Service-Time: + - '57' + Server: + - istio-envoy + body: + encoding: UTF-8 + string: '{"name":"master","commit":{"id":"0d3ccb796757d84e99dcf43ef913821483a248a0","short_id":"0d3ccb79","created_at":"2020-09-30T09:12:44.000-10:00","parent_ids":["1ff934599cfdc0f21efe3d524609aec8c73b0632"],"title":"add + python-simple","message":"add python-simple\n","author_name":"Charlie Sakamaki","author_email":"csakamaki@revacomm.com","authored_date":"2020-09-30T09:12:44.000-10:00","committer_name":"Charlie + Sakamaki","committer_email":"csakamaki@revacomm.com","committed_date":"2020-09-30T09:12:44.000-10:00","web_url":"https://code.il2.dsop.io/csakamaki/test-20200917/-/commit/0d3ccb796757d84e99dcf43ef913821483a248a0"},"merged":false,"protected":true,"developers_can_push":false,"developers_can_merge":false,"can_push":true,"default":true,"web_url":"https://code.il2.dsop.io/csakamaki/test-20200917/-/tree/master"}' + http_version: + recorded_at: Tue, 06 Oct 2020 07:55:52 GMT +- request: + method: get + uri: https://code.il2.dsop.io/api/v4/projects/599/repository/archive.zip + body: + encoding: US-ASCII + string: '' + headers: + Private-Token: + - WmXSEG8RHyy2ud_esxfF + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - max-age=0, private, must-revalidate + Content-Disposition: + - attachment; filename="test-20200917-master-0d3ccb796757d84e99dcf43ef913821483a248a0.zip" + Content-Transfer-Encoding: + - binary + Content-Type: + - application/zip + Etag: + - W/"f6c043f69a6d473456415d31d209d042" + Vary: + - Origin + X-Accel-Buffering: + - 'no' + X-Content-Type-Options: + - nosniff + X-Frame-Options: + - SAMEORIGIN + X-Request-Id: + - fbo9AxdH8r1 + X-Runtime: + - '0.153419' + Date: + - Tue, 06 Oct 2020 07:55:52 GMT + X-Envoy-Upstream-Service-Time: + - '160' + Server: + - istio-envoy + Transfer-Encoding: + - chunked + body: + encoding: ASCII-8BIT + string: !binary |- + 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Y291cnNlZmlsZXMvVVQFAAGs2HRfUEsDBAoAAAAAAJaZPlEAAAAAAAAAAAAAAABRAAkAdGVzdC0yMDIwMDkxNy1tYXN0ZXItMGQzY2NiNzk2NzU3ZDg0ZTk5ZGNmNDNlZjkxMzgyMTQ4M2EyNDhhMC9jb3Vyc2VmaWxlcy9naXRodWIvVVQFAAGs2HRfUEsDBAoAAAAIAJaZPlHw+naYXQAAAHUAAABcAAkAdGVzdC0yMDIwMDkxNy1tYXN0ZXItMGQzY2NiNzk2NzU3ZDg0ZTk5ZGNmNDNlZjkxMzgyMTQ4M2EyNDhhMC9jb3Vyc2VmaWxlcy9naXRodWIvY291cnNlLnlhbWxVVAUAAazYdF81ijsOgCAQRHtOMRdYie22tlYYD6BmIyQiBFbP7ydazZs3Y4jIAF06ShW+CSAMsmhIO/+A9h0AJzlV/srzHF3P8Kq5srVrUH/MzZKidXJOd8bOT2ULYlWqUvmkuQBQSwMECgAAAAAAlpk+UQAAAAAAAAAAAAAAAFEACQB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvdXJzZWZpbGVzL2dpdGxhYi9VVAUAAazYdF9QSwMECgAAAAgAlpk+Uc9Kmm1jAAAAdQAAAFwACQB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvdXJzZWZpbGVzL2dpdGxhYi9jb3Vyc2UueWFtbFVUBQABrNh0XzWKOw4CMQwF+5ziXcBJNg3CLS3VIg4QZS0RLeAIe+/PR2w3o5lARAE46fYy4Q8BhIs07/rkHTD9AjDLUOO/fM/rfGbc3IdxSk0Xif1e4mI6YtfUrK71UdeeXMyp5JLzcTqEN1BLAwQKAAAACACWmT5RoKr04p4BAAAfAwAATgAJAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvcHl0aG9uLXNpbXBsZS5tZFVUBQABrNh0X2VTTXObMBC961dsh+kMzgCTxDZ2fE2bX5BLT0aGxWgqJKKPafn33Q3Y2KkuSG/fvt23QkmSwLe6k1qjOaMQDxDGAQ9Q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c3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvY2hlY2twb2ludC5tZFVUBQABrNh0X1BLAQIAAAoAAAAIAJaZPlE+YPWdeAEAANwCAABJAAkAAAAAAAEAAAAAAEgIAAB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvbmZpZy55YW1sVVQFAAGs2HRfUEsBAgAACgAAAAAAlpk+UQAAAAAAAAAAAAAAAEoACQAAAAAAAAAQAAAAMAoAAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvY291cnNlZmlsZXMvVVQFAAGs2HRfUEsBAgAACgAAAAAAlpk+UQAAAAAAAAAAAAAAAFEACQAAAAAAAAAQAAAAoQoAAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvY291cnNlZmlsZXMvZ2l0aHViL1VUBQABrNh0X1BLAQIAAAoAAAAIAJaZPlHw+naYXQAAAHUAAABcAAkAAAAAAAEAAAAAABkLAAB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvdXJzZWZpbGVzL2dpdGh1Yi9jb3Vyc2UueWFtbFVUBQABrNh0X1BLAQIAAAoAAAAAAJaZPlEAAAAAAAAAAAAAAABRAAkAAAAAAAAAEAAAAPkLAAB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvdXJzZWZpbGVzL2dpdGxhYi9VVAUAAazYdF9QSwECAAAKAAAACACWmT5Rz0qabWMAAAB1AAAAXAAJAAAAAAABAAAAAABxDAAAdGVzdC0yMDIwMDkxNy1tYXN0ZXItMGQzY2NiNzk2NzU3ZDg0ZTk5ZGNmNDNlZjkxMzgyMTQ4M2EyNDhhMC9jb3Vyc2VmaWxlcy9naXRsYWIvY291cnNlLnlhbWxVVAUAAazYdF9QSwECAAAKAAAACACWmT5RoKr04p4BAAAfAwAATgAJAAAAAAABAAAAAABXDQAAdGVzdC0yMDIwMDkxNy1tYXN0ZXItMGQzY2NiNzk2NzU3ZDg0ZTk5ZGNmNDNlZjkxMzgyMTQ4M2EyNDhhMC9weXRob24tc2ltcGxlLm1kVVQFAAGs2HRfUEsBAgAACgAAAAgAlpk+URN/NXSBAwAAIwgAAE4ACQAAAAAAAQAAAAAAag8AAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvcmV2YWNvbW0tcXVpei5tZFVUBQABrNh0X1BLBQYAAAAADAAMAEsGAABgEwAAKAAwZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEw + http_version: + recorded_at: Tue, 06 Oct 2020 07:55:52 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/resync-course-job-success.yml b/scripts/spec/fixtures/vcr_cassettes/resync-course-job-success.yml new file mode 100644 index 0000000..3b62c49 --- /dev/null +++ b/scripts/spec/fixtures/vcr_cassettes/resync-course-job-success.yml @@ -0,0 +1,587 @@ +--- +http_interactions: +- request: + method: get + uri: https://api.github.com/repos/gSchool/learn-course-files/contents/test/integration.yaml + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 34ed6e936ee7686b5a6adafb796fe4ad888c813a + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Wed, 05 Aug 2020 20:58:16 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4988' + X-Ratelimit-Reset: + - '1596664301' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - W/"591662d53624e85d29ee35241ba396f8e63e1e4e" + Last-Modified: + - Tue, 04 Aug 2020 22:50:21 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - '' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D6E5:7E3F:4F37EB:8604A2:5F2B1D68 + body: + encoding: ASCII-8BIT + string: '{"name":"integration.yaml","path":"test/integration.yaml","sha":"591662d53624e85d29ee35241ba396f8e63e1e4e","size":399,"url":"https://api.github.com/repos/gSchool/learn-course-files/contents/test/integration.yaml?ref=master","html_url":"https://github.com/gSchool/learn-course-files/blob/master/test/integration.yaml","git_url":"https://api.github.com/repos/gSchool/learn-course-files/git/blobs/591662d53624e85d29ee35241ba396f8e63e1e4e","download_url":"https://raw.githubusercontent.com/gSchool/learn-course-files/master/test/integration.yaml?token=AB2TTYLUKW4KG6SVURVFUH27FMO2I","type":"file","content":"IyAgRE8gTk9UIERFTEVURSEgRE8gTk9UIE1PRElGWSEgVGhpcyBmaWxlIGlz\nIHVzdWFsbHkgc3R1YmJlZCwgYnV0IGNhbiBzb21ldGltZXMgYmUgdXRpbGl6\nZWQgYXMgcGFydCBvZiBzcGVjcy4KLS0tCkNvdXJzZToKICAtIFNlY3Rpb246\nIERTSSBBZG1pc3Npb25zIFByZXAKICAgIFJlcG9zOgogICAgICAtIFVSTDog\naHR0cHM6Ly9naXRodWIuY29tL2dTY2hvb2wvZHNpLWludHJvLXRvLWRzLXN0\nYXRzCiAgICAgIC0gVVJMOiBodHRwczovL2dpdGh1Yi5jb20vZ1NjaG9vbC9k\ncy1zcWwtYmxvY2sKICAtIFNlY3Rpb246IEFkdmFuY2VkIERTSSBBZG1pc3Np\nb25zIFByZXAKICAgIFJlcG9zOgogICAgICAtIFVSTDogaHR0cHM6Ly9naXRo\ndWIuY29tL2dTY2hvb2wvZHMtcHl0aG9uLXF1aXp6ZXMtYmxvY2sK\n","encoding":"base64","_links":{"self":"https://api.github.com/repos/gSchool/learn-course-files/contents/test/integration.yaml?ref=master","git":"https://api.github.com/repos/gSchool/learn-course-files/git/blobs/591662d53624e85d29ee35241ba396f8e63e1e4e","html":"https://github.com/gSchool/learn-course-files/blob/master/test/integration.yaml"}}' + http_version: + recorded_at: Wed, 05 Aug 2020 20:58:16 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/dsi-intro-to-ds-stats + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 34ed6e936ee7686b5a6adafb796fe4ad888c813a + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Wed, 05 Aug 2020 20:58:17 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6511' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4987' + X-Ratelimit-Reset: + - '1596664302' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"9c59adb799a0df122387c6ac5ea06966"' + Last-Modified: + - Mon, 10 Jun 2019 21:37:29 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D6E6:24FF:2512A:6D4EA:5F2B1D69 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Wed, 05 Aug 2020 20:58:17 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/ds-sql-block + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 34ed6e936ee7686b5a6adafb796fe4ad888c813a + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Wed, 05 Aug 2020 20:58:17 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6022' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4986' + X-Ratelimit-Reset: + - '1596664302' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"576944f7ac108ac7ede058016fe2a9fb"' + Last-Modified: + - Tue, 14 Aug 2018 02:48:21 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D6E7:7284:2B9CAA:606E38:5F2B1D69 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Wed, 05 Aug 2020 20:58:17 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/ds-python-quizzes-block + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 34ed6e936ee7686b5a6adafb796fe4ad888c813a + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Wed, 05 Aug 2020 20:58:17 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6523' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4985' + X-Ratelimit-Reset: + - '1596664301' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"f7da89b2e1d6218b1c4e4292ef0cdf42"' + Last-Modified: + - Tue, 14 Aug 2018 03:34:36 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D6E8:6B08:4EA0A4:849122:5F2B1D69 + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Wed, 05 Aug 2020 20:58:17 GMT +- request: + method: get + uri: https://api.github.com/repos/gSchool/learn-course-files/contents/test/integration.yaml + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 34ed6e936ee7686b5a6adafb796fe4ad888c813a + User-Agent: + - Galvanize Forge + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Wed, 05 Aug 2020 20:58:18 GMT + Content-Type: + - application/json; charset=utf-8 + Transfer-Encoding: + - chunked + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4984' + X-Ratelimit-Reset: + - '1596664302' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - W/"591662d53624e85d29ee35241ba396f8e63e1e4e" + Last-Modified: + - Tue, 04 Aug 2020 22:50:21 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - '' + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D6E9:47EC:158B5C:34284F:5F2B1D6A + body: + encoding: ASCII-8BIT + string: '{"name":"integration.yaml","path":"test/integration.yaml","sha":"591662d53624e85d29ee35241ba396f8e63e1e4e","size":399,"url":"https://api.github.com/repos/gSchool/learn-course-files/contents/test/integration.yaml?ref=master","html_url":"https://github.com/gSchool/learn-course-files/blob/master/test/integration.yaml","git_url":"https://api.github.com/repos/gSchool/learn-course-files/git/blobs/591662d53624e85d29ee35241ba396f8e63e1e4e","download_url":"https://raw.githubusercontent.com/gSchool/learn-course-files/master/test/integration.yaml?token=AB2TTYLY3E3DTVYMKYEXIAK7FMO2M","type":"file","content":"IyAgRE8gTk9UIERFTEVURSEgRE8gTk9UIE1PRElGWSEgVGhpcyBmaWxlIGlz\nIHVzdWFsbHkgc3R1YmJlZCwgYnV0IGNhbiBzb21ldGltZXMgYmUgdXRpbGl6\nZWQgYXMgcGFydCBvZiBzcGVjcy4KLS0tCkNvdXJzZToKICAtIFNlY3Rpb246\nIERTSSBBZG1pc3Npb25zIFByZXAKICAgIFJlcG9zOgogICAgICAtIFVSTDog\naHR0cHM6Ly9naXRodWIuY29tL2dTY2hvb2wvZHNpLWludHJvLXRvLWRzLXN0\nYXRzCiAgICAgIC0gVVJMOiBodHRwczovL2dpdGh1Yi5jb20vZ1NjaG9vbC9k\ncy1zcWwtYmxvY2sKICAtIFNlY3Rpb246IEFkdmFuY2VkIERTSSBBZG1pc3Np\nb25zIFByZXAKICAgIFJlcG9zOgogICAgICAtIFVSTDogaHR0cHM6Ly9naXRo\ndWIuY29tL2dTY2hvb2wvZHMtcHl0aG9uLXF1aXp6ZXMtYmxvY2sK\n","encoding":"base64","_links":{"self":"https://api.github.com/repos/gSchool/learn-course-files/contents/test/integration.yaml?ref=master","git":"https://api.github.com/repos/gSchool/learn-course-files/git/blobs/591662d53624e85d29ee35241ba396f8e63e1e4e","html":"https://github.com/gSchool/learn-course-files/blob/master/test/integration.yaml"}}' + http_version: + recorded_at: Wed, 05 Aug 2020 20:58:18 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/dsi-intro-to-ds-stats + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 34ed6e936ee7686b5a6adafb796fe4ad888c813a + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Wed, 05 Aug 2020 20:58:18 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6511' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4983' + X-Ratelimit-Reset: + - '1596664301' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"fc2b97df6bbe4d357db4aac28250c78a"' + Last-Modified: + - Mon, 10 Jun 2019 21:37:29 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D6EA:72A9:2A928B:5C5AB3:5F2B1D6A + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Wed, 05 Aug 2020 20:58:19 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/ds-sql-block + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 34ed6e936ee7686b5a6adafb796fe4ad888c813a + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Wed, 05 Aug 2020 20:58:19 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6022' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4982' + X-Ratelimit-Reset: + - '1596664302' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"c6420c35b65442217c06e792eedcff00"' + Last-Modified: + - Tue, 14 Aug 2018 02:48:21 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D6EB:4316:292B74:5E0176:5F2B1D6B + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Wed, 05 Aug 2020 20:58:19 GMT +- request: + method: head + uri: https://api.github.com/repos/gSchool/ds-python-quizzes-block + body: + encoding: US-ASCII + string: '' + headers: + Authorization: + - token 34ed6e936ee7686b5a6adafb796fe4ad888c813a + User-Agent: + - Galvanize Forge + response: + status: + code: 200 + message: OK + headers: + Server: + - GitHub.com + Date: + - Wed, 05 Aug 2020 20:58:20 GMT + Content-Type: + - application/json; charset=utf-8 + Content-Length: + - '6523' + Status: + - 200 OK + X-Ratelimit-Limit: + - '5000' + X-Ratelimit-Remaining: + - '4981' + X-Ratelimit-Reset: + - '1596664302' + Cache-Control: + - private, max-age=60, s-maxage=60 + Vary: + - Accept, Authorization, Cookie, X-GitHub-OTP + - Accept-Encoding, Accept, X-Requested-With + Etag: + - '"b0b8f10f9c4438d5a8dd4ffd9cf7694b"' + Last-Modified: + - Tue, 14 Aug 2018 03:34:36 GMT + X-Oauth-Scopes: + - repo + X-Accepted-Oauth-Scopes: + - repo + X-Github-Media-Type: + - github.v3; format=json + Access-Control-Expose-Headers: + - ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, + X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, + X-GitHub-Media-Type, Deprecation, Sunset + Access-Control-Allow-Origin: + - "*" + Strict-Transport-Security: + - max-age=31536000; includeSubdomains; preload + X-Frame-Options: + - deny + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - 1; mode=block + Referrer-Policy: + - origin-when-cross-origin, strict-origin-when-cross-origin + Content-Security-Policy: + - default-src 'none' + X-Github-Request-Id: + - D6EC:360D:16646D:38A9DB:5F2B1D6B + body: + encoding: UTF-8 + string: '' + http_version: + recorded_at: Wed, 05 Aug 2020 20:58:20 GMT +recorded_with: VCR 4.0.0 diff --git a/scripts/spec/helpers/json_helper_spec.rb b/scripts/spec/helpers/json_helper_spec.rb new file mode 100644 index 0000000..c0f8a6e --- /dev/null +++ b/scripts/spec/helpers/json_helper_spec.rb @@ -0,0 +1,66 @@ +require "spec_helper" + +describe JsonHelper, type: :helper do + describe "#true?" do + context "string true" do + it "returns true" do + expect(helper.true?("true")).to be true + end + end + + context "bool true" do + it "returns true" do + expect(helper.true?(true)).to be true + end + end + + context "string false" do + it "returns false" do + expect(helper.true?("false")).to be false + end + end + + context "bool false" do + it "returns false" do + expect(helper.true?(false)).to be false + end + end + + context "string random" do + it "returns false" do + expect(helper.true?("random")).to be false + end + end + end + describe "#false?" do + context "string false" do + it "returns true" do + expect(helper.false?("false")).to be true + end + end + + context "bool false" do + it "returns true" do + expect(helper.false?(false)).to be true + end + end + + context "string true" do + it "returns false" do + expect(helper.false?("true")).to be false + end + end + + context "bool true" do + it "returns false" do + expect(helper.false?(true)).to be false + end + end + + context "string random" do + it "returns false" do + expect(helper.false?("random")).to be false + end + end + end +end diff --git a/scripts/spec/helpers/mastery_mode_percent_helper_spec.rb b/scripts/spec/helpers/mastery_mode_percent_helper_spec.rb new file mode 100644 index 0000000..7bb0015 --- /dev/null +++ b/scripts/spec/helpers/mastery_mode_percent_helper_spec.rb @@ -0,0 +1,46 @@ +require "spec_helper" + +describe MasteryModePercentService, type: :helper do + let!(:user) { create(:user) } + let!(:cohort) { create(:cohort) } + let!(:release) { create(:release) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let!(:standard_1) { create(:standard, release: release ) } + let!(:standard_2) { create(:standard, release: release ) } + let!(:standard_3) { create(:standard, release: release ) } + let!(:performance_1) { create(:performance, user: user, cohort: cohort, score: 2, standard_id: standard_1.id, standard_uid: standard_1.uid) } + let!(:performance_2) { create(:performance, user: user, cohort: cohort, score: 3, standard_id: standard_2.id, standard_uid: standard_2.uid) } + let!(:performance_3) { create(:performance, user: user, cohort: cohort, score: 3, standard_id: standard_3.id, standard_uid: standard_3.uid) } + + let!(:whack_release) { create(:release) } + let!(:whack_cohort_release) { create(:cohort_release, cohort: cohort, release: whack_release) } + let!(:whack_standard) { create(:standard) } + let!(:whack_performance) { create(:performance, user: user, cohort: cohort, score: 3, standard_id: whack_standard.id, standard_uid: whack_standard.uid) } + + it "yields the percent of 3s out of the total number of standards" do + expect(helper.mastery_percent(user, cohort)).to eq 67 + end + + it "yields 0 if there are no standards in the cohort" do + cohort_release.destroy + expect(helper.mastery_percent(user, cohort)).to eq 0 + end + + it "yields 0 if there are no standards in the cohort" do + cohort_release.destroy + expect(helper.mastery_percent(user, cohort)).to eq 0 + end + + it "yields the percent of threes of the latest performances" do + create(:performance, user: user, cohort: cohort, score: 2, standard_id: standard_2.id, standard_uid: standard_2.uid) + expect(helper.mastery_percent(user, cohort)).to eq 33 + end + + context "standards are hidden" do + it "calculates only using visible standards" do + create(:content_visibility, cohort_id: cohort.id, content_type: "Standard", content_uid: standard_2.uid) # performance was given a 3 + expect(helper.mastery_percent(user, cohort)).to eq 50 + end + end +end + diff --git a/scripts/spec/helpers/standard_navigation_helper_spec.rb b/scripts/spec/helpers/standard_navigation_helper_spec.rb new file mode 100644 index 0000000..316a11e --- /dev/null +++ b/scripts/spec/helpers/standard_navigation_helper_spec.rb @@ -0,0 +1,288 @@ +require "spec_helper" + +describe StandardNavigationHelper do + let(:cohort) { create(:cohort) } + let(:release) { create(:release, notes: 'Release 1') } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + + let(:standard_0) { create(:standard, release: release, position: 0) } + let!(:content_file_0_0) { create(:content_file, standard: standard_0, position: 0, title: "prev 1") } + let!(:content_file_0_1) { create(:content_file, standard: standard_0, position: 1, title: "prev 2") } + let(:standard_1) { create(:standard, release: release, position: 1) } + let!(:content_file_1_0) { create(:content_file, standard: standard_1, position: 0, title: "curr 1") } + let!(:content_file_1_1) { create(:content_file, standard: standard_1, position: 1, title: "curr 2") } + let(:standard_2) { create(:standard, release: release, position: 2) } + let!(:content_file_2_0) { create(:content_file, standard: standard_2, position: 0, title: "next 1") } + let!(:content_file_2_1) { create(:content_file, standard: standard_2, position: 1, title: "next 2") } + + + context "when navigating inbetween standards" do + describe "#get_previous_standard_link" do + subject { helper.get_previous_standard_link(current_standard: standard, cohort: cohort, content_file_id: content_file.id) } + + context "when there is a previous content file in the same standard" do + let(:standard) { standard_1 } + let(:content_file) { content_file_1_1 } + + it "should link to the previous content file in the same standard" do + expect(subject).to eq(title: content_file_1_0.title, + url: url_helpers.content_file_path( + cohort, + standard_1.release.block, + content_file_1_0.path + )) + end + end + + context "when the current content file is the first in a standard" do + context "and there is no previous standard" do + let(:standard) { standard_0 } + let(:content_file) { content_file_0_0 } + + it "returns nil" do + expect(subject).to eq(nil) + end + end + + context "and there is a previous standard" do + let(:standard) { standard_1 } + let(:content_file) { content_file_1_0 } + + it "should link to the last content file in the previous standard" do + expect(subject).to eq(title: content_file_0_1.title, + url: url_helpers.content_file_path( + cohort, + standard_0.release.block, + content_file_0_1.path + )) + end + end + end + end + + describe "#get_next_standard_link" do + subject { helper.get_next_standard_link(current_standard: standard, cohort: cohort, content_file_id: content_file.id) } + + context "when there is a next content file in the same standard" do + let(:standard) { standard_1 } + let(:content_file) { content_file_1_0 } + + it "should link to the next content file in the same standard" do + expect(subject).to eq(title: content_file_1_1.title, + url: url_helpers.content_file_path( + cohort, + standard_1.release.block, + content_file_1_1.path + )) + end + end + + context "when the current content file is the last in a standard" do + context "and there is no next standard" do + let(:standard) { standard_2 } + let(:content_file) { content_file_2_1 } + + it "returns nil" do + expect(subject).to eq(nil) + end + end + + context "and there is a next standard" do + let(:standard) { standard_1 } + let(:content_file) { content_file_1_1 } + + it "should link to the first content file in the next standard" do + expect(subject).to eq(title: content_file_2_0.title, + url: url_helpers.content_file_path( + cohort, + standard_2.release.block, + content_file_2_0.path + )) + end + end + + context "when the next content file is hidden, with no next standard" do + let(:standard) { standard_2 } + let(:content_file) { content_file_2_0 } + + it "returns nil" do + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_2_1.uid) + expect(subject).to eq(nil) + end + end + + context "and there is a next standard but it's hidden" do + let(:standard) { standard_1 } + let(:content_file) { content_file_1_1 } + + it "should link to the first content file in the next standard" do + create(:content_visibility, cohort_id: cohort.id, content_type: "Standard", content_uid: standard_2.uid) + expect(subject).to eq(nil) + end + end + end + end + end + + context "when navigating inbetween content files" do + describe "#get_previous_content_file_link" do + context "when current file is not first content file" do + subject { helper.get_previous_content_file_link(current_standard: standard_1, cohort: cohort, content_file_id: content_file_1_1.id) } + + it "returns the URL and Title of the previous content file" do + expect(subject).to eq(title: content_file_1_0.title, url: content_file_path(cohort, standard_1.release.block, content_file_1_0.path)) + end + end + + context "when current file is first content file of a standard" do + subject { helper.get_previous_content_file_link(current_standard: standard_1, cohort: cohort, content_file_id: content_file_1_0.id) } + + it "returns the url and title of the content file from previous standards last content file" do + expect(subject).to eq(title: content_file_0_1.title, url: content_file_path(cohort, standard_0.release.block, content_file_0_1.path)) + end + end + + context "when the current file is first content file in the first standard" do + let!(:diff_block_standard) { create(:standard, position: 1) } + let!(:diff_release) { create(:cohort_release, release: diff_block_standard.release, cohort: cohort, position: 2).release } + let!(:diff_block_content_file) { create(:content_file, standard: diff_block_standard, position: 2) } + + subject { helper.get_previous_content_file_link(current_standard: standard_0, cohort: cohort, content_file_id: content_file_0_0.id) } + + it "returns the url and title of the content file from last blocks standards last content file" do + expect(subject).to eq(nil) + end + end + + context "when the prevous standard has no visible content files" do + subject { helper.get_previous_content_file_link(current_standard: standard_1, cohort: cohort, content_file_id: content_file_1_0.id) } + it "should yield no link" do + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_0_1.uid) + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_0_0.uid) + expect(subject).to eq(nil) + end + end + end + + describe "#get_next_content_file_link" do + context "when current file is the first of multiple content files" do + subject { helper.get_next_content_file_link(current_standard: standard_1, cohort: cohort, content_file_id: content_file_1_0.id) } + + it "returns the url and title of the next content file" do + expect(subject).to eq(title: content_file_1_1.title, url: content_file_path(cohort, standard_1.release.block, content_file_1_1.path)) + end + end + + context "when current file is last content file" do + it "returns the url and title of the next visible content file from next standards first content file" do + # Expect content_file_2_0 to be linked to + expect( + helper.get_next_content_file_link( + current_standard: standard_1, + cohort: cohort, + content_file_id: content_file_1_1.id + ) + ).to eq( + title: content_file_2_0.title, + url: content_file_path(cohort, standard_2.release.block, content_file_2_0.path) + ) + + # Make content_file_2_0 invisible + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_2_0.uid) + + # Now expect the next content file after content_file_2_0 (content_file_2_1) to be linked to + expect( + helper.get_next_content_file_link( + current_standard: standard_1, + cohort: cohort, + content_file_id: content_file_1_1.id + ) + ).to eq( + title: content_file_2_1.title, + url: content_file_path(cohort, standard_2.release.block, content_file_2_1.path) + ) + end + end + + context "when the current file is last content file in the last standard" do + let!(:diff_block_standard) { create(:standard, position: 1) } + let!(:diff_release) { create(:cohort_release, release: diff_block_standard.release, cohort: cohort, position: 0).release } + let!(:diff_block_content_file) { create(:content_file, standard: diff_block_standard, position: 2) } + + subject { helper.get_next_content_file_link(current_standard: standard_2, cohort: cohort, content_file_id: content_file_2_1.id) } + + it "returns the url and title of the content file from next blocks standards first content file" do + expect(subject).to eq(nil) + end + end + end + end + + context "when navigating inbetween releases" do + let(:release_2) { create(:release, notes: 'Release 2') } + let(:standard_3) { create(:standard, release: release_2, position: 2) } + let!(:content_file_3_0) { create(:content_file, standard: standard_3, position: 0, title: "CF 3_0") } + let!(:content_file_3_1) { create(:content_file, standard: standard_3, position: 1, title: "CF 3_1") } + + let!(:cohort_release_2) { create(:cohort_release, cohort: cohort, release: release_2, position: cohort_release.position+1) } + + describe "#get_next_content_file_link" do + subject { helper.get_next_content_file_link(current_standard: target_standard, cohort: cohort, content_file_id: target_content_file.id) } + + let(:target_standard) { standard_2 } + let(:target_content_file) { content_file_2_1 } + + it "should link to the first content file in the next standard in the next release" do + expect(subject).to eq( + title: content_file_3_0.title, + url: content_file_path(cohort, standard_3.release.block, content_file_3_0.path) + ) + end + + context "when the next content file is hidden" do + before do + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_3_0.uid) + end + + it "should link to the first content file in the next standard in the next release" do + expect(subject).to eq( + title: content_file_3_1.title, + url: content_file_path(cohort, standard_3.release.block, content_file_3_1.path) + ) + end + end + + context "when the next standard is hidden" do + let(:standard_4) { create(:standard, release: release_2, position: 2) } + let!(:content_file_4_0) { create(:content_file, standard: standard_4, position: 0, title: "CF 4_0") } + let!(:content_file_4_1) { create(:content_file, standard: standard_4, position: 1, title: "CF 4_1") } + + let(:target_standard) { standard_1 } + let(:target_content_file) { content_file_1_1 } + + before do + create(:content_visibility, cohort_id: cohort.id, content_type: "Standard", content_uid: standard_2.uid) + create(:content_visibility, cohort_id: cohort.id, content_type: "Standard", content_uid: standard_3.uid) + end + + it "should link to the first content file in the next standard in the next release" do + expect(subject).to eq( + title: content_file_4_0.title, + url: content_file_path(cohort, standard_4.release.block, content_file_4_0.path) + ) + end + end + end + + describe "#get_previous_content_file_link" do + subject { helper.get_previous_content_file_link(current_standard: standard_3, cohort: cohort, content_file_id: content_file_3_0.id) } + + it "should link to the first content file in the next standard in the next release" do + expect(subject).to eq( + title: content_file_2_1.title, + url: content_file_path(cohort, standard_2.release.block, content_file_2_1.path) + ) + end + end + end +end diff --git a/scripts/spec/integration_helper.rb b/scripts/spec/integration_helper.rb new file mode 100644 index 0000000..f910f5f --- /dev/null +++ b/scripts/spec/integration_helper.rb @@ -0,0 +1,16 @@ +require "spec_helper" +require "rspec_api_documentation/dsl" + +module RspecApiDocumentation::DSL + module Endpoint + def response_json + @response_json ||= JSON.parse(response_body) + end + end +end + +RspecApiDocumentation.configure do |config| + config.format = :json + config.keep_source_order = true + config.post_body_formatter = ->(params) { params.blank? ? nil : params.to_json } +end diff --git a/scripts/spec/jobs/branch_release_notifier_job_spec.rb b/scripts/spec/jobs/branch_release_notifier_job_spec.rb new file mode 100644 index 0000000..9da0110 --- /dev/null +++ b/scripts/spec/jobs/branch_release_notifier_job_spec.rb @@ -0,0 +1,33 @@ +require "spec_helper" + +describe BranchReleaseNotifierJob do + describe ".perform" do + context "when there are cohorts using an older release of the block" do + let(:cohort_1) { create(:cohort) } + let!(:cohort_release_1) { create(:cohort_release, cohort: cohort_1, release: existing_release) } + let(:cohort_2) { create(:cohort) } + let!(:cohort_release_2) { create(:cohort_release, cohort: cohort_2, release: existing_release) } + let(:instructor) { create(:user) } + let(:block) { create(:block) } + let!(:existing_release) { create(:release, block: block, branch_name: "custom") } + + before do + create(:cohort_user, :instructor, user: instructor, cohort: cohort_1) + create(:cohort_user, :instructor, user: instructor, cohort: cohort_2) + end + + subject { described_class.perform_now(create(:release, block: block, notes: "Foo", branch_name: "custom").id) } + + it "sends notifications do the instructor" do + expect { subject }.to change { Notification.count }.from(0).to(2) + + expect(Notification.all.map(&:tagline).uniq).to match_array(["Release Branch Created"]) + expect(Notification.all.map(&:title).uniq).to match_array(["New release created for #{block.title} on branch custom"]) + expect(Notification.all.map(&:description)).to match_array(["You are currently using an older version of #{block.title} on branch custom in #{cohort_1.name}.", "You are currently using an older version of #{block.title} on branch custom in #{cohort_2.name}."]) + + expect(Notification.first.url).to eq(Rails.application.routes.url_helpers.setup_cohort_path(cohort_1.id)) + expect(Notification.last.url).to eq(Rails.application.routes.url_helpers.setup_cohort_path(cohort_2.id)) + end + end + end +end diff --git a/scripts/spec/jobs/checkpoint_paired_submission_job_spec.rb b/scripts/spec/jobs/checkpoint_paired_submission_job_spec.rb new file mode 100644 index 0000000..b1b5787 --- /dev/null +++ b/scripts/spec/jobs/checkpoint_paired_submission_job_spec.rb @@ -0,0 +1,53 @@ +require "spec_helper" +require 'sidekiq/testing' + +describe CheckpointPairedSubmissionJob do + + let(:cohort) { create(:cohort) } + let(:student) { create(:cohort_user, cohort: cohort).user } + let(:pair_partner) { create(:cohort_user, cohort: cohort).user } + let(:content_file) { create(:content_file, :checkpoint) } + let!(:student_checkpoint) do + create(:checkpoint_submission, + user_id: student.id, + content_file_uid: content_file.uid, + content_file_block_id: content_file.standard.release.block_id, + cohort_id: cohort.id, + state: "needs_review") + end + let!(:pair_partner_checkpoint) do + create(:checkpoint_submission, + user_id: pair_partner.id, + content_file_uid: content_file.uid, + content_file_block_id: content_file.standard.release.block_id, + cohort_id: cohort.id, + state: "needs_review") + end + let!(:challenge_with_submission) { create(:challenge, uid: "xyz", content_file: content_file) } + let!(:sca_with_submission) { create(:submitted_challenge_answer, checkpoint_submission_id: student_checkpoint.id, user_id: student.id, cohort_id: cohort.id, challenge: challenge_with_submission, challenge_uid: challenge_with_submission.uid, block_id: content_file.standard.release.block_id, answer: "", status: "graded", points: 1) } + + subject { described_class.perform_now(student_checkpoint.id, content_file.autoscore) } + + describe ".perform" do + before { student_checkpoint.update(pair_submission_ids: [pair_partner_checkpoint.id]) } + + context "checkpoint has all submitted challenge answers scored" do + it "does generate submitted challenge answers for pair check" do + expect { subject }.to change { SubmittedChallengeAnswer.count }.by 1 + expect(SubmittedChallengeAnswer.last.user_id).to eq(pair_partner.id) + expect(SubmittedChallengeAnswer.last.checkpoint_submission_id).to eq(pair_partner_checkpoint.id) + end + end + + context "when not all answers are done processing" do + let!(:challenge_processing) { create(:challenge, uid: "123", content_file: content_file, challenge_type: "code-snippet") } + let!(:sca_processing) { create(:submitted_challenge_answer, checkpoint_submission_id: student_checkpoint.id, user_id: student.id, cohort_id: cohort.id, challenge: challenge_processing, challenge_uid: challenge_processing.uid, block_id: content_file.standard.release.block_id, answer: "", status: "processing") } + + it "retries to create submitted challenge answers if one is processing" do + expect { + subject + }.to change(GradeTimedCheckpointJob.queue_adapter.enqueued_jobs, :size).by(1) + end + end + end +end diff --git a/scripts/spec/jobs/content_file_default_visibility_job_spec.rb b/scripts/spec/jobs/content_file_default_visibility_job_spec.rb new file mode 100644 index 0000000..99a1a9f --- /dev/null +++ b/scripts/spec/jobs/content_file_default_visibility_job_spec.rb @@ -0,0 +1,41 @@ +require "spec_helper" + +describe ContentFileDefaultVisibilityJob do + let(:cohort) { create(:cohort) } + let(:release) { create(:release) } + let!(:cohort_release) { create(:cohort_release, release: release, cohort: cohort) } + let(:standard) { create(:standard, uid: "123", release: release) } + let!(:content_file) { create(:content_file, uid: "abc", standard: standard) } + let!(:hidden_content_file) { create(:content_file, uid: "abc", standard: standard, default_visibility: "hidden") } + + context "when using release ids" do + it "creates content visibility objects" do + described_class.perform_now(cohort_id: cohort.id, release_ids: [release.id], cohort_release_ids: nil) + + expect(ContentVisibility.count).to eq(1) + expect(ContentVisibility.last.cohort).to eq(cohort) + expect(ContentVisibility.last.content_uid).to eq(hidden_content_file.uid) + expect(ContentVisibility.last.content_type).to eq("ContentFile") + end + end + + context "when using cohort release ids" do + it "creates content visibility objects" do + described_class.perform_now(cohort_id: cohort.id, release_ids: nil, cohort_release_ids: [cohort_release.id]) + + expect(ContentVisibility.count).to eq(1) + expect(ContentVisibility.last.cohort).to eq(cohort) + expect(ContentVisibility.last.content_uid).to eq(hidden_content_file.uid) + expect(ContentVisibility.last.content_type).to eq("ContentFile") + end + end + + context "when a content_visibility already exists" do + it "creates content visibility objects" do + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: hidden_content_file.uid) + expect do + described_class.perform_now(cohort_id: cohort.id, release_ids: nil, cohort_release_ids: [cohort_release.id]) + end.to_not(change { ContentVisibility.count }) + end + end +end diff --git a/scripts/spec/jobs/content_file_visit_job_spec.rb b/scripts/spec/jobs/content_file_visit_job_spec.rb new file mode 100644 index 0000000..b870aa7 --- /dev/null +++ b/scripts/spec/jobs/content_file_visit_job_spec.rb @@ -0,0 +1,66 @@ +require "spec_helper" + +describe ContentFileVisitJob do + let(:user) { create(:user) } + let(:cohort) { create(:cohort) } + let(:release) { create(:release) } + let(:standard) { create(:standard, uid: "123") } + let(:content_file) { create(:content_file, uid: "abc", standard: standard) } + let!(:challenge_1) { create(:challenge, content_file: content_file) } + let!(:challenge_2) { create(:challenge, content_file: content_file) } + let(:user) { create(:user) } + + it "creates a UserLastViewedStandardPath, Activity, and LessonVisit" do + described_class.perform_now(user.id, release.block_id, cohort.id, content_file.id, standard.uid) + + expect(Activity.count).to eq(1) + expect(Activity.last.creator).to eq(user) + expect(Activity.last.cohort).to eq(cohort) + expect(Activity.last.subject).to eq(content_file) + expect(Activity.last.name).to eq(Activity::NAMES[:content_file_viewed]) + + expect(LessonVisit.count).to eq(1) + lesson = LessonVisit.last + expect(lesson.user_id).to eq(user.id) + expect(lesson.block_id).to eq(release.block_id) + expect(lesson.cohort_id).to eq(cohort.id) + expect(lesson.content_file_uid).to eq(content_file.uid) + expect(lesson.standard_uid).to eq(standard.uid) + expect(lesson.visit_count).to eq(1) + expect(lesson.challenge_count).to eq(2) + end + + context "lesson visists and standard path's exist" do + it "updates an existing UserLastViewedStandardPath and LessonVisit" do + last_viewed_standard_path = create( + :user_last_viewed_standard_path, + user: user, + block_id: release.block_id, + standard_uid: standard.uid, + content_file_path: content_file.path, + cohort_id: cohort.id + ) + + create( + :lesson_visit, + user: user, + block_id: release.block_id, + cohort_id: cohort.id, + standard_uid: standard.uid, + content_file_uid: content_file.uid, + content_file_path: "changeme", + visit_count: 1 + ) + + described_class.perform_now(user.id, release.block_id, cohort.id, content_file.id, standard.uid) + + expect(LessonVisit.count).to eq(1) + lesson = LessonVisit.last + expect(lesson.content_file_path).to eq(content_file.path) + expect(lesson.visit_count).to eq(2) + + updated_last_viewed_standard = UserLastViewedStandardPath.where(user_id: user.id).last + expect(updated_last_viewed_standard.id).to eq last_viewed_standard_path.id + end + end +end diff --git a/scripts/spec/jobs/create_api_interaction_job_spec.rb b/scripts/spec/jobs/create_api_interaction_job_spec.rb new file mode 100644 index 0000000..e9ba4dc --- /dev/null +++ b/scripts/spec/jobs/create_api_interaction_job_spec.rb @@ -0,0 +1,39 @@ +require "spec_helper" + +describe CreateApiInteractionJob do + describe ".perform" do + context "when there are cohorts using an older release of the block" do + let!(:user) { create(:user) } + + subject { + described_class.perform_now( + user_id: user.id, + ip: '127.0.0.1', + path: '/api/v1/pineapple', + method: 'GET', + action_name: 'pineapple', + duration: 517, + controller_name: 'Api::V1::Pineapple', + response_code: 200, + metadata: { neat: 'things' }, + ) + } + + it "sends creates an ApiInteraction" do + expect { subject }.to change { ApiInteraction.count }.from(0).to(1) + + interaction = ApiInteraction.last + + expect(interaction.user_id).to eq(user.id) + expect(interaction.ip).to eq('127.0.0.1') + expect(interaction.path).to eq('/api/v1/pineapple') + expect(interaction.method).to eq('GET') + expect(interaction.action_name).to eq('pineapple') + expect(interaction.duration).to eq(517) + expect(interaction.controller_name).to eq('Api::V1::Pineapple') + expect(interaction.response_code).to eq(200) + expect(interaction.metadata).to eq({ "neat" => "things" }) + end + end + end +end diff --git a/scripts/spec/jobs/create_release_job_spec.rb b/scripts/spec/jobs/create_release_job_spec.rb new file mode 100644 index 0000000..78e0b35 --- /dev/null +++ b/scripts/spec/jobs/create_release_job_spec.rb @@ -0,0 +1,231 @@ +require "spec_helper" + +describe CreateReleaseJob do + subject { described_class.perform_now(args) } + + let(:block) { create(:block) } + let(:pending_release) { create(:release, block: block, github_sha: "pending", state: Release::STATES[:pending], notes: "Hi!") } + let(:args) { { pending_release_id: pending_release.id } } + + before do + allow_any_instance_of(described_class).to receive(:github_token) { "foobar" } + allow_any_instance_of(S3AssetUploaderService).to receive(:find_or_create_content) { "foobar" } + end + + context "when parsing the test-block-repo fixture" do + before do + allow_any_instance_of(DownloadGithubRepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: Rails.root.join("spec", "fixtures", "test-block-repo").to_s, sha: "12340987") + ) + end + + it "creates a release, standard, and content files" do + allow_any_instance_of(described_class).to receive(:zipped_docker_path_md5).and_return("/path/to/zip/") + allow_any_instance_of(S3AssetUploaderService).to receive(:find_or_create_content).with(full_path: "/path/to/zip/", directory: "docker_zips").and_return("docker_dir.zip") + + expect { subject }.to change { Release.count }.from(0).to(1) + last_release = Release.last + expect(last_release.notes).to eq("Hi!") + expect(last_release.readme_text).to eq("This is the first line\nthis is the second line...\nLook, here is a third line") + expect(last_release.github_sha).to eq("12340987") + expect(last_release.standards.count).to eq(1) + expect(last_release.standards.first.content_files.count).to eq(2) + expect(last_release.standards.first.content_files.first.path).to eq("/markdown-smoketesT.md") + expect(last_release.standards.first.content_files.first.default_visibility).to eq("visible") + expect(last_release.standards.first.content_files.first.content_file_type).to eq(ContentFile::TYPES[:lesson]) + expect(last_release.standards.first.content_files.last.path).to eq("/challenges-smoketest.md") + expect(last_release.standards.first.content_files.last.default_visibility).to eq("hidden") + expect(last_release.standards.first.content_files.last.content_file_type).to eq(ContentFile::TYPES[:checkpoint]) + end + + it "passes the new release to AutoAssignReleaseService" do + expect(AutoAssignReleaseService).to receive(:execute) do |release| + expect(release).to eq(Release.where(block_id: block.id).last) + end + allow_any_instance_of(described_class).to receive(:zipped_docker_path_md5).and_return("/path/to/zip/") + allow_any_instance_of(S3AssetUploaderService).to receive(:find_or_create_content).with(full_path: "/path/to/zip/", directory: "docker_zips").and_return("docker_dir.zip") + + subject + end + + it "passes the new release to ReleaseNotifierJob" do + allow_any_instance_of(described_class).to receive(:zipped_docker_path_md5).and_return("/path/to/zip/") + allow_any_instance_of(S3AssetUploaderService).to receive(:find_or_create_content).with(full_path: "/path/to/zip/", directory: "docker_zips").and_return("docker_dir.zip") + expect(ReleaseNotifierJob).to receive(:perform_later) do |release_id| + expect(release_id).to eq(Release.where(block_id: block.id).last.id) + end + + subject + end + + context "when there is a sql challenge" do + before do + allow_any_instance_of(BlockParser::ParseDirectory).to( + receive(:execute).and_return( + errors: [], + warnings: [], + readme_text: "Some Readme text\nParsed in blockparser\nOnly 3 lines of it though", + block_hash: [{ standard: { title: "Foo", uid: "1234", description: "This is the description", success_criteria: ["Foo"] }, + content_file_attribute_hashes: [{ + html: "

    hello from the parser!

    ", + path: "/target.md", + title: "foo", + content_file_type: ContentFile::TYPES[:checkpoint], + uid: "1234", + challenges: [attributes_for(:challenge, :sql)], + autoscore: true, + warnings: [] + }] }] + ) + ) + end + + it "creates a challenge with data_path and data_path_checksum populated" do + allow(SqlChallengeDBService).to( receive_message_chain(:new, :execute).and_return([]) ) + expect { subject }.to change { Challenge.count }.from(0).to(1) + challenge = Challenge.last + expect(challenge.data_path).to eq("/test.sql") + expect(challenge.data_path_checksum.blank?).to be_falsey + end + + describe "failed db stuff" do + it "does not create challenge and therefore release" do + allow(SqlChallengeDBService).to( receive_message_chain(:new, :execute).and_return(["YOU FAILED"]) ) + subject + expect(Challenge.count).to eq(0) + expect(Release.count).to eq(0) + end + end + end + end + + context "when a release already exists for the block/sha" do + before do + create(:release, block: block, github_sha: "12340987") + + allow_any_instance_of(DownloadGithubRepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: "/foo", sha: "12340987") + ) + end + + it "saves the errors to the block" do + subject + + expect(block.reload.sync_errors).to include("A release has already been created for SHA 12340987.") + end + end + + context "when a release already exists for the block/sha but not on the same branch as the previous sha" do + before do + create(:release, block: block, github_sha: "12340987", branch_name: "test") + + allow_any_instance_of(DownloadGithubRepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: "/foo", sha: "12340987") + ) + end + + it "saves no errors to the block" do + subject + + expect(block.reload.sync_errors).to_not include("A release has already been created for SHA 12340987.") + end + end + + context "given valid parameters" do + before do + allow_any_instance_of(DownloadGithubRepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: "/foo", sha: "12340987") + ) + + allow_any_instance_of(BlockParser::ParseDirectory).to( + receive(:execute).and_return( + errors: [], + warnings: [], + readme_text: "Some Readme text\nParsed in blockparser\nOnly 3 lines of it though", + block_hash: [{ standard: { title: "Foo", uid: "1234", description: "This is the description", success_criteria: ["Foo"] }, + content_file_attribute_hashes: [{ + html: "

    hello from the parser!

    ", + path: "/target.md", + title: "foo", + content_file_type: ContentFile::TYPES[:checkpoint], + uid: "1234", + challenges: [attributes_for(:challenge)], + autoscore: true, + max_checkpoint_submissions: 3, + warnings: [] + }] }] + ) + ) + end + + it "creates a release" do + expect { subject }.to change { Release.count }.from(0).to(1) + expect(Release.last.notes).to eq("Hi!") + expect(Release.last.readme_text).to eq("Some Readme text\nParsed in blockparser\nOnly 3 lines of it though") + end + + it "creates a standard" do + expect { subject }.to change { Standard.count }.from(0).to(1) + standard = Standard.last + expect(standard.uid).to eq("1234") + expect(standard.title).to eq("Foo") + expect(standard.description).to eq("This is the description") + expect(standard.success_criteria).to eq(["Foo"]) + end + + it "creates content files" do + expect { subject }.to change { ContentFile.count }.from(0).to(1) + content_file = ContentFile.last + expect(content_file.standard).to eq Standard.last + expect(content_file.position).to eq 1 + expect(content_file.html).to eq("

    hello from the parser!

    ") + expect(content_file.title).to eq("foo") + expect(content_file.content_file_type).to eq ContentFile::TYPES[:checkpoint] + expect(content_file.uid).to eq "1234" + expect(content_file.autoscore).to eq(true) + expect(content_file.max_checkpoint_submissions).to eq(3) + end + + context "when the block has existing sync_errors" do + let(:block) { create(:block, sync_errors: ["foo"]) } + + it "resets the sync_errors" do + expect { subject }.to change { block.reload.sync_errors }.from(["foo"]).to([]) + end + end + end + + context "given an invalid github url" do + before do + allow_any_instance_of(DownloadGithubRepositoryService).to receive(:execute).and_return(errors: ["Could not download repository."]) + end + + it "does not create a release" do + expect { subject }.to_not(change { Release.count }) + end + + it "saves the errors to the block" do + subject + + expect(block.reload.sync_errors).to include("Could not download repository.") + end + end + + context "given an invalid block directory" do + before do + allow_any_instance_of(DownloadGithubRepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: Rails.root.join("tmp"), sha: "12340987") + ) + end + + it "does not create a release" do + expect { subject }.to_not(change { Release.count }) + end + + it "saves the errors to the block" do + subject + + expect(block.reload.sync_errors).to include("Root directory does not contain a config.yaml or config.yml file") + end + end +end diff --git a/scripts/spec/jobs/evaluate_code_snippet_job_spec.rb b/scripts/spec/jobs/evaluate_code_snippet_job_spec.rb new file mode 100644 index 0000000..8718e41 --- /dev/null +++ b/scripts/spec/jobs/evaluate_code_snippet_job_spec.rb @@ -0,0 +1,19 @@ +require "spec_helper" + +describe EvaluateCodeSnippetJob do + let(:challenge) { create(:challenge) } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, challenge: challenge) } + let(:cohort) { create(:cohort) } + + it "should kick off the assessment service code snippet evaluator" do + callback_url = Rails.application.routes.url_helpers.webhooks_assessments_service_submitted_challenge_answer_url( + host: "#{Rails.application.secrets.actionmailer_host}:#{Rails.application.secrets.actionmailer_port}", + id: submitted_challenge_answer.id, + token: Rails.application.secrets.assessments_callback_token, + cohort_id: cohort.id + ) + expect(AssessmentService).to receive(:evaluate_code_snippet).with(challenge, submitted_challenge_answer, callback_url) + + described_class.perform_now(challenge.id, submitted_challenge_answer.id, cohort.id) + end +end diff --git a/scripts/spec/jobs/evaluate_custom_snippet_job_spec.rb b/scripts/spec/jobs/evaluate_custom_snippet_job_spec.rb new file mode 100644 index 0000000..75e6e65 --- /dev/null +++ b/scripts/spec/jobs/evaluate_custom_snippet_job_spec.rb @@ -0,0 +1,19 @@ +require "spec_helper" + +describe EvaluateCustomSnippetJob do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:custom_snippet]) } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, challenge: challenge) } + let(:cohort) { create(:cohort) } + + it "should kick off the assessment service custom snippet evaluator" do + callback_url = Rails.application.routes.url_helpers.webhooks_assessments_service_submitted_challenge_answer_url( + host: "#{Rails.application.secrets.actionmailer_host}:#{Rails.application.secrets.actionmailer_port}", + id: submitted_challenge_answer.id, + token: Rails.application.secrets.assessments_callback_token, + cohort_id: cohort.id + ) + expect(AssessmentService).to receive(:evaluate_custom_snippet).with(challenge, submitted_challenge_answer, callback_url) + + described_class.perform_now(challenge.id, submitted_challenge_answer.id, cohort.id) + end +end diff --git a/scripts/spec/jobs/evaluate_project_job_spec.rb b/scripts/spec/jobs/evaluate_project_job_spec.rb new file mode 100644 index 0000000..b0bf33b --- /dev/null +++ b/scripts/spec/jobs/evaluate_project_job_spec.rb @@ -0,0 +1,20 @@ +require "spec_helper" + +describe EvaluateProjectJob do + let(:challenge) { create(:challenge) } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, challenge: challenge) } + let(:cohort) { create(:cohort) } + + it "should kick off the assessment service project evaluator" do + callback_url = Rails.application.routes.url_helpers.webhooks_assessments_service_submitted_challenge_answer_url( + protocol: Rails.application.secrets.protocol, + host: "#{Rails.application.secrets.actionmailer_host}:#{Rails.application.secrets.actionmailer_port}", + id: submitted_challenge_answer.id, + token: Rails.application.secrets.assessments_callback_token, + cohort_id: cohort.id + ) + expect(AssessmentService).to receive(:evaluate_project).with(submitted_challenge_answer, callback_url) + + described_class.perform_now(submitted_challenge_answer.id, cohort.id) + end +end diff --git a/scripts/spec/jobs/grade_timed_checkpoint_job_spec.rb b/scripts/spec/jobs/grade_timed_checkpoint_job_spec.rb new file mode 100644 index 0000000..d81b1fa --- /dev/null +++ b/scripts/spec/jobs/grade_timed_checkpoint_job_spec.rb @@ -0,0 +1,99 @@ +require "spec_helper" +require 'sidekiq/testing' + +describe GradeTimedCheckpointJob do + describe ".perform" do + let!(:cohort) { create(:cohort) } + let!(:user) { create(:cohort_user, cohort: cohort).user } + let!(:content_file) { create(:content_file, :checkpoint, time_limit: 1) } + let!(:block_id) { content_file.standard.release.block_id } + let!(:challenge_draft) { create(:challenge, uid: "abc", content_file: content_file, challenge_type: "number", answer: "1") } + let!(:challenge_processing) { create(:challenge, uid: "123", content_file: content_file, challenge_type: "code-snippet") } + let!(:challenge_with_submission) { create(:challenge, uid: "xyz", content_file: content_file) } + let!(:challenge_no_submission) { create(:challenge, uid: "456", content_file: content_file, raw_json: { "placeholder": "whup" }) } + + # draft has two scas, one old and wrong, should not see it + let!(:old_draft_sca) { create(:submitted_challenge_answer, user_id: user.id, cohort_id: cohort.id, challenge: challenge_draft, challenge_uid: challenge_draft.uid, block_id: block_id, answer: "0", status: "draft", created_at: 1.day.ago) } + let!(:new_draft_sca) { create(:submitted_challenge_answer, user_id: user.id, cohort_id: cohort.id, challenge: challenge_draft, challenge_uid: challenge_draft.uid, block_id: block_id, answer: "1", status: "draft") } + let!(:sca_processing) { create(:submitted_challenge_answer, user_id: user.id, cohort_id: cohort.id, challenge: challenge_processing, challenge_uid: challenge_processing.uid, block_id: block_id, answer: "", status: "processing") } + let!(:sca_with_submission) { create(:submitted_challenge_answer, user_id: user.id, cohort_id: cohort.id, challenge: challenge_with_submission, challenge_uid: challenge_with_submission.uid, block_id: block_id, answer: "", status: "graded", points: 1) } + # there is no sca for challenge_no_submission + + let!(:checkpoint) do + create(:checkpoint_submission, + user_id: user.id, + content_file_uid: content_file.uid, + content_file_block_id: content_file.standard.release.block_id, + cohort_id: cohort.id, + state: "started") + end + + context "with various statuses on the latest submissions for a checkpoint" do + subject { described_class.perform_now(content_file.id, checkpoint.id) } + + it "grades drafts, attaches processing, dupes others, and creates incorrects for missing" do + expect { subject }.to change { checkpoint.submitted_challenge_answers.count }.from(0).to(4) + checkpoint.reload + expect(checkpoint.state).to eq "needs_review" + + graded_draft = checkpoint.submitted_challenge_answers.find {|s| s.challenge_id == challenge_draft.id } + expect(graded_draft.status).to eq "correct" + expect(graded_draft.id).to_not eq new_draft_sca.id + + processing = checkpoint.submitted_challenge_answers.find {|s| s.challenge_id == challenge_processing.id } + expect(processing.status).to eq "processing" + expect(processing.id).to eq sca_processing.id + + with_submission = checkpoint.submitted_challenge_answers.find {|s| s.challenge_id == challenge_with_submission.id } + expect(with_submission.status).to eq "graded" + expect(with_submission.points).to eq 1 + expect(with_submission.id).to_not eq sca_with_submission.id + + no_submission = checkpoint.submitted_challenge_answers.find {|s| s.challenge_id == challenge_no_submission.id } + expect(no_submission.status).to eq "incorrect" + expect(no_submission.points).to eq 0 + expect(no_submission.answer).to eq "" + end + end + + context "checkpoint is not in 'started' state" do + it "does not generate submitted challenge answers" do + checkpoint.update(state: "needs_review") + expect { subject }.to_not change { checkpoint.submitted_challenge_answers.count } + end + end + + describe "new releases attached to the current cohort mid started" do + subject { described_class.perform_now(content_file.id, checkpoint.id) } + + before do + Sidekiq::Worker.clear_all + end + + context "when more time is added" do + it "sets up a new job for grading" do + create(:cohort_release, cohort: cohort, release: content_file.standard.release) + new_release = create(:release, block: content_file.standard.release.block) + new_standard = create(:standard, release: new_release) + new_checkpoint = create(:content_file, :checkpoint, standard: new_standard, time_limit: 99999, path: content_file.path, uid: content_file.uid) + + CohortRelease.last.update(release: new_release) + + expect { + subject + }.to change(GradeTimedCheckpointJob.queue_adapter.enqueued_jobs, :size).by(1) + end + end + + context "when time is decreased" do + it "does nothing in regards to setting up a new job" do + create(:cohort_release, cohort: cohort, release: content_file.standard.release) + + expect { + subject + }.to change(GradeTimedCheckpointJob.queue_adapter.enqueued_jobs, :size).by(0) + end + end + end + end +end diff --git a/scripts/spec/jobs/import_student_work_job_spec.rb b/scripts/spec/jobs/import_student_work_job_spec.rb new file mode 100644 index 0000000..ced0ae0 --- /dev/null +++ b/scripts/spec/jobs/import_student_work_job_spec.rb @@ -0,0 +1,78 @@ +require "spec_helper" + +describe ImportStudentWorkJob do + let!(:cohort_a) { create(:cohort) } + let!(:cohort_b) { create(:cohort) } + let!(:cohort_user_a) { create(:cohort_user, cohort: cohort_a) } + let!(:cohort_user_b) { create(:cohort_user, user: cohort_user_a.user, cohort: cohort_b) } + let!(:standard) { create(:standard) } + let!(:content_file) { create(:content_file, standard: standard) } + let!(:challenge) { create(:challenge, content_file: content_file) } + + let!(:checkpoint) { create(:content_file, :checkpoint, standard: standard) } + let!(:checkpoint_challenge) { create(:challenge, content_file: checkpoint) } + + let!(:dud1) { create(:submitted_challenge_answer, challenge: challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "doh") } + let!(:dud2) { create(:submitted_challenge_answer, challenge: challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "doh") } + let!(:dud3) { create(:submitted_challenge_answer, challenge: challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "doh") } + let!(:dud4) { create(:submitted_challenge_answer, challenge: challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "doh") } + let!(:dud5) { create(:submitted_challenge_answer, challenge: challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "doh") } + let!(:submitted_challenge_answer_first_try) { create(:submitted_challenge_answer, challenge: challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "doh") } + let!(:submitted_challenge_answer_second_try) { create(:submitted_challenge_answer, challenge: challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "Ah!") } + + let!(:checkpoint_submission) { create(:checkpoint_submission, cohort: cohort_a, user: cohort_user_a.user, content_file_block_id: standard.release.block_id, content_file_uid: checkpoint.uid) } + let!(:performance) { create(:performance, checkpoint_submission: checkpoint_submission, user: cohort_user_a.user) } + let!(:checkpoint_challenge_submitted_challenge_answer_first_try) { create(:submitted_challenge_answer, challenge: checkpoint_challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "doh", checkpoint_submission: checkpoint_submission) } + let!(:checkpoint_challenge_submitted_challenge_answer_second_try) { create(:submitted_challenge_answer, challenge: checkpoint_challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "Ah!", checkpoint_submission: checkpoint_submission) } + + let!(:cohort_release_a) { create(:cohort_release, release: standard.release, cohort: cohort_a) } + let!(:cohort_release_b) { create(:cohort_release, release: standard.release, cohort: cohort_b) } + + subject { described_class.perform_now(cohort_b.id, "email") } + + it "should copy students work from all other cohorts that have the same block" do + expect{ + subject + }.to change { SubmittedChallengeAnswer.count }.by(3) + .and change { CheckpointSubmission.count }.by(1) + .and change { Performance.count }.by(1) + + expect(SubmittedChallengeAnswer.where(cohort_id: cohort_b.id, checkpoint_submission_id: nil).count).to eq 1 + expect(SubmittedChallengeAnswer.where(cohort_id: cohort_b.id, checkpoint_submission_id: nil).map(&:answer).uniq).to eq ["Ah!"] + expect(SubmittedChallengeAnswer.last(3).map(&:cohort_id).uniq).to eq [cohort_b.id] + expect(CheckpointSubmission.last.cohort_id).to eq cohort_b.id + expect(Performance.last.cohort_id).to eq cohort_b.id + end + + it "does not copy checkpoint submissions beyond the first two of each state" do + older = create(:checkpoint_submission, cohort: cohort_a, user: cohort_user_a.user, content_file_block_id: standard.release.block_id, content_file_uid: checkpoint.uid, created_at: 1.day.ago) + create(:submitted_challenge_answer, challenge: checkpoint_challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "old", checkpoint_submission: older) + + # oldest and its answer should not be duplicated + oldest = create(:checkpoint_submission, cohort: cohort_a, user: cohort_user_a.user, content_file_block_id: standard.release.block_id, content_file_uid: checkpoint.uid, created_at: 2.days.ago) + create(:submitted_challenge_answer, challenge: checkpoint_challenge, user: cohort_user_a.user, cohort: cohort_a, answer: "oldest", checkpoint_submission: oldest) + + expect{ + subject + }.to change { SubmittedChallengeAnswer.count }.by(4) # copy of submitted_challenge_answer_first_try already created, so first run changes only by 3 + .and change { CheckpointSubmission.count }.by(2) + .and change { Performance.count }.by(1) + expect(SubmittedChallengeAnswer.find_by(cohort_id: cohort_b.id, answer: "oldest")).to eq nil + end + + it "should not copy students work from other cohorts if they exist at the same timestamp (already copied)" do + sca = create(:submitted_challenge_answer, challenge: challenge, user: cohort_user_a.user, cohort_id: cohort_b.id, answer: "doh", created_at: submitted_challenge_answer_first_try.created_at) + expect{ + subject + }.to change { SubmittedChallengeAnswer.count }.by(3) # copy of submitted_challenge_answer_first_try already created, so first run changes only by 3 + .and change { CheckpointSubmission.count }.by(1) + .and change { Performance.count }.by(1) + + # subsequent runs will not duplicate + expect{ + described_class.perform_now(cohort_b.id, "email") + }.to change { SubmittedChallengeAnswer.count }.by(0) + .and change { CheckpointSubmission.count }.by(0) + .and change { Performance.count }.by(0) + end +end diff --git a/scripts/spec/jobs/release_notifier_job_spec.rb b/scripts/spec/jobs/release_notifier_job_spec.rb new file mode 100644 index 0000000..393d70a --- /dev/null +++ b/scripts/spec/jobs/release_notifier_job_spec.rb @@ -0,0 +1,33 @@ +require "spec_helper" + +describe ReleaseNotifierJob do + describe ".perform" do + context "when there are cohorts using an older release of the block" do + let(:cohort_1) { create(:cohort) } + let!(:cohort_release_1) { create(:cohort_release, cohort: cohort_1, release: existing_release) } + let(:cohort_2) { create(:cohort) } + let!(:cohort_release_2) { create(:cohort_release, cohort: cohort_2, release: existing_release) } + let(:instructor) { create(:user) } + let(:block) { create(:block) } + let!(:existing_release) { create(:release, block: block) } + + before do + create(:cohort_user, :instructor, user: instructor, cohort: cohort_1) + create(:cohort_user, :instructor, user: instructor, cohort: cohort_2) + end + + subject { described_class.perform_now(create(:release, block: block, notes: "Foo").id) } + + it "sends notifications do the instructor" do + expect { subject }.to change { Notification.count }.from(0).to(2) + + expect(Notification.all.map(&:tagline).uniq).to match_array(["Block Released"]) + expect(Notification.all.map(&:title).uniq).to match_array(["New release created for #{block.title}"]) + expect(Notification.all.map(&:description)).to match_array(["You are currently using an older version of this block in #{cohort_1.name}.", "You are currently using an older version of this block in #{cohort_2.name}."]) + + expect(Notification.first.url).to eq(Rails.application.routes.url_helpers.setup_cohort_path(cohort_1.id)) + expect(Notification.last.url).to eq(Rails.application.routes.url_helpers.setup_cohort_path(cohort_2.id)) + end + end + end +end diff --git a/scripts/spec/jobs/resync_course_job_spec.rb b/scripts/spec/jobs/resync_course_job_spec.rb new file mode 100644 index 0000000..cf2571b --- /dev/null +++ b/scripts/spec/jobs/resync_course_job_spec.rb @@ -0,0 +1,95 @@ +require "spec_helper" + +describe ResyncCourseJob do + let(:url) { "https://github.com/gSchool/learn-course-files/blob/master/test/integration.yaml" } + let(:cohort_1) { create(:cohort) } + + before do + # Blocks must exist for ResyncCourseService to do its job + with_release = lambda {|block| create(:release, block: block, state: 'success')} + + with_release.call create(:block, repo_name: 'dsi-intro-to-ds-stats', origin: "github.com", org: "gSchool") + with_release.call create(:block, repo_name: 'ds-sql-block', origin: "github.com", org: "gSchool") + with_release.call create(:block, repo_name: 'ds-python-quizzes-block', origin: "github.com", org: "gSchool") + end + + after do + DatabaseCleaner.clean_with :deletion + end + + it "gets the job done", use_transactional_fixtures: false do + VCR.use_cassette("resync-course-job-success") do + expect(JobResult.count).to eq(0) + + ResyncCourseJob.perform_now(cohort_id: cohort_1.id, course_url: url) + + expect(JobResult.count).to eq(1) + result = JobResult.last + expect(result.status).to eq('success') + expect(result.data).to eq({ "course_config_url" => url }) + end + end + + it "gets the job done twice", use_transactional_fixtures: false do + VCR.use_cassette("resync-course-job-success", allow_playback_repeats: true) do + expect(JobResult.count).to eq(0) + + ResyncCourseJob.perform_now(cohort_id: cohort_1.id, course_url: url) + + expect(JobResult.count).to eq(1) + result = JobResult.last + expect(result.status).to eq('success') + expect(result.data).to eq({ "course_config_url" => url }) + + ResyncCourseJob.perform_now(cohort_id: cohort_1.id, course_url: url) + + expect(JobResult.count).to eq(2) + result = JobResult.last + expect(result.status).to eq('success') + expect(result.data).to eq({ "course_config_url" => url }) + end + end + + it "reports errors", use_transactional_fixtures: false do + expect_any_instance_of(CourseValidator).to receive(:run).and_return( + SolidUseCase::Either::failure(:mock_error, x: 10, y: 20) + ) + ResyncCourseJob.perform_now(cohort_id: cohort_1.id, course_url: url) + + result = JobResult.last + expect(result.status).to eq('failure') + expect(result.data).to eq({ + "course_config_url" => url, + "error_type" => "mock_error", + "error_data" => { "x" => 10, "y" => 20 } + }) + end + + # skipping because it fails on CI frequently, may need to come back to fix + xit "protects against race conditions", use_transactional_fixtures: false do + # https://blog.arkency.com/2015/09/testing-race-conditions/ + VCR.use_cassette("resync-course-job-success", allow_playback_repeats: true) do + begin + expect(ActiveRecord::Base.connection.pool.size).to eq(5) + concurrency_level = 3 + + fail_occurred = false + wait_for_it = true + + threads = concurrency_level.times.map do |i| + Thread.new do + true while wait_for_it + ResyncCourseJob.perform_now(cohort_id: cohort_1.id, course_url: url) + end + end + wait_for_it = false + threads.each(&:join) + + expect(JobResult.count).to eq(1) + ensure + ActiveRecord::Base.connection_pool.disconnect! + end + end + + end +end diff --git a/scripts/spec/jobs/set_block_caches_job_spec.rb b/scripts/spec/jobs/set_block_caches_job_spec.rb new file mode 100644 index 0000000..89e496a --- /dev/null +++ b/scripts/spec/jobs/set_block_caches_job_spec.rb @@ -0,0 +1,29 @@ +require "spec_helper" +require 'sidekiq/testing' + +describe SetBlockCachesJob do + let!(:block) { create(:block) } + let!(:cohort_1) { create(:cohort) } + let!(:cohort_2) { create(:cohort) } + let!(:cohort_user) { create(:cohort_user, cohort: cohort_1) } + let!(:release) { create(:release, block: block) } + let!(:cohort_release_1) { create(:cohort_release, cohort: cohort_1, release: release) } + let!(:cohort_release_2) { create(:cohort_release, cohort: cohort_2, release: release) } + + let!(:standard) { create(:standard, release: release) } + let!(:content_file) { create(:content_file, standard: standard) } + let!(:challenge) { create(:challenge, content_file: content_file) } + let!(:sca_1) { create(:submitted_challenge_answer, challenge: challenge, block_id: block.id) } + let!(:sca_2) { create(:submitted_challenge_answer, challenge: challenge, block_id: block.id) } + let!(:sca_draft) { create(:submitted_challenge_answer, challenge: challenge, block_id: block.id, status: "draft") } + + describe ".perform" do + it "sets aggregate cache values" do + described_class.perform_now(block_id: block.id) + block.reload + expect(block.cohort_count_cache).to eq 2 + expect(block.user_count_cache).to eq 1 + expect(block.submission_count_cache).to eq 2 + end + end +end diff --git a/scripts/spec/jobs/slack_ds_prep_job_spec.rb b/scripts/spec/jobs/slack_ds_prep_job_spec.rb new file mode 100644 index 0000000..c1c6478 --- /dev/null +++ b/scripts/spec/jobs/slack_ds_prep_job_spec.rb @@ -0,0 +1,19 @@ +require "spec_helper" + +describe SlackDSPrepJob do + describe ".perform" do + let(:message) { "Hi there" } + + it "sends the correct payload to Slack" do + allow(HTTParty).to receive(:post) do |url, args| + expect(url).to include("https://hooks.slack.com/services") + + parsed_body = JSON.parse(args[:body]) + expect(parsed_body["text"]).to eq(message) + expect(url).to eq("https://hooks.slack.com/services/") + end + + SlackDSPrepJob.perform_now(message) + end + end +end diff --git a/scripts/spec/jobs/slack_se_prep_job_spec.rb b/scripts/spec/jobs/slack_se_prep_job_spec.rb new file mode 100644 index 0000000..0fab759 --- /dev/null +++ b/scripts/spec/jobs/slack_se_prep_job_spec.rb @@ -0,0 +1,19 @@ +require "spec_helper" + +describe SlackSEPrepJob do + describe ".perform" do + let(:message) { "Hi there" } + + it "sends the correct payload to Slack" do + allow(HTTParty).to receive(:post) do |url, args| + expect(url).to include("https://hooks.slack.com/services") + + parsed_body = JSON.parse(args[:body]) + expect(parsed_body["text"]).to eq(message) + expect(url).to eq("https://hooks.slack.com/services/") + end + + SlackSEPrepJob.perform_now(message) + end + end +end diff --git a/scripts/spec/jobs/slack_student_job_spec.rb b/scripts/spec/jobs/slack_student_job_spec.rb new file mode 100644 index 0000000..ed0e55e --- /dev/null +++ b/scripts/spec/jobs/slack_student_job_spec.rb @@ -0,0 +1,21 @@ +require "spec_helper" + +describe SlackStudentJob do + describe ".perform" do + let(:message) { "Hi there" } + let(:username) { "@joel.hawksley" } + + it "sends the correct payload to Slack" do + allow(HTTParty).to receive(:post) do |url, args| + expect(url).to include("https://hooks.slack.com/services") + + parsed_body = JSON.parse(args[:body]) + expect(parsed_body["channel"]).to eq(username) + expect(parsed_body["text"]).to eq(message) + expect(url).to eq("https://hooks.slack.com/services/") + end + + SlackStudentJob.perform_now(message, username) + end + end +end diff --git a/scripts/spec/jobs/switch_to_branch_job_spec.rb b/scripts/spec/jobs/switch_to_branch_job_spec.rb new file mode 100644 index 0000000..e0ab2d5 --- /dev/null +++ b/scripts/spec/jobs/switch_to_branch_job_spec.rb @@ -0,0 +1,185 @@ +require "spec_helper" + +describe SwitchToBranchJob do + subject { described_class.perform_now(args) } + before do + allow(Rails.application.secrets).to receive(:github_token) { "foobar" } + allow_any_instance_of(S3AssetUploaderService).to receive(:find_or_create_content) { "foobar" } + end + + context "when parsing the test-block-repo fixture" do + let!(:pending_release) { create(:release, :pending) } + let!(:cohort_release) { create(:cohort_release, pending_release_id: pending_release.id) } + + let(:args) { { pending_release_id: pending_release.id, cohort_release_id: cohort_release.id } } + + before do + allow_any_instance_of(DownloadGithubRepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: Rails.root.join("spec", "fixtures", "test-block-repo").to_s, sha: "12340987") + ) + end + + it "creates a release, standard, and content files" do + allow_any_instance_of(described_class).to receive(:zipped_docker_path_md5).and_return("/path/to/zip/") + allow_any_instance_of(S3AssetUploaderService).to receive(:find_or_create_content).with(full_path: "/path/to/zip/", directory: "docker_zips").and_return("docker_dir.zip") + + expect { subject }.to change { Release.count }.by(0) + expect(cohort_release.reload.pending_release_id).to eq nil + expect(cohort_release.release_id).to eq pending_release.id + expect(pending_release.reload.standards.count).to eq(1) + expect(pending_release.standards.first.content_files.count).to eq(2) + expect(pending_release.standards.first.content_files.first.path).to eq("/markdown-smoketesT.md") + expect(pending_release.standards.first.content_files.first.content_file_type).to eq(ContentFile::TYPES[:lesson]) + expect(pending_release.standards.first.content_files.last.path).to eq("/challenges-smoketest.md") + expect(pending_release.standards.first.content_files.last.content_file_type).to eq(ContentFile::TYPES[:checkpoint]) + end + end + + context "given valid parameters" do + let!(:pending_release) { create(:release, :pending) } + let!(:cohort_release) { create(:cohort_release, pending_release_id: pending_release.id) } + + let(:args) { { pending_release_id: pending_release.id, cohort_release_id: cohort_release.id } } + + before do + allow_any_instance_of(DownloadGithubRepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: "/foo", sha: "12340987") + ) + + allow_any_instance_of(BlockParser::ParseDirectory).to( + receive(:execute).and_return( + errors: [], + warnings: [], + block_hash: [{ standard: { title: "Foo", uid: "1234", description: "This is the description", success_criteria: ["Foo"] }, + content_file_attribute_hashes: [{ + html: "

    hello from the parser!

    ", + path: "/target.md", + title: "foo", + content_file_type: ContentFile::TYPES[:checkpoint], + uid: "1234", + challenges: [attributes_for(:challenge)], + autoscore: true, + warnings: [] + }] }] + ) + ) + end + + it "marks a pending release a success" do + expect { subject }.to_not ( change { Release.count }) + expect(pending_release.reload.state).to eq(Release::STATES[:success]) + expect(cohort_release.reload.pending_release_id).to eq nil + expect(cohort_release.release_id).to eq pending_release.id + end + + it "creates a standard" do + expect { subject }.to change { Standard.count }.from(0).to(1) + standard = Standard.last + expect(standard.uid).to eq("1234") + expect(standard.title).to eq("Foo") + expect(standard.description).to eq("This is the description") + expect(standard.success_criteria).to eq(["Foo"]) + end + + it "creates content files" do + expect { subject }.to change { ContentFile.count }.from(0).to(1) + content_file = ContentFile.last + expect(content_file.standard).to eq Standard.last + expect(content_file.position).to eq 1 + expect(content_file.html).to eq("

    hello from the parser!

    ") + expect(content_file.title).to eq("foo") + expect(content_file.content_file_type).to eq ContentFile::TYPES[:checkpoint] + expect(content_file.uid).to eq "1234" + expect(content_file.autoscore).to eq(true) + end + + context "when the block has existing sync_errors" do + it "resets the sync_errors" do + pending_release.update(sync_errors: ["foo"]) + expect { subject }.to change { pending_release.reload.sync_errors }.from(["foo"]).to([]) + end + end + end + + context "given an invalid github url" do + let!(:pending_release) { create(:release, :pending) } + let!(:cohort_release) { create(:cohort_release, pending_release_id: pending_release.id) } + + let(:args) { { pending_release_id: pending_release.id, cohort_release_id: cohort_release.id } } + + before do + allow_any_instance_of(DownloadGithubRepositoryService).to receive(:execute).and_return(errors: ["Could not download repository."]) + end + + it "sets sync errors on the pending release" do + subject + expect(pending_release.reload.sync_errors).to include ("Could not download repository.") + end + end + + context "given an invalid block directory" do + let!(:pending_release) { create(:release, :pending) } + let!(:cohort_release) { create(:cohort_release, pending_release_id: pending_release.id) } + + let(:args) { { pending_release_id: pending_release.id, cohort_release_id: cohort_release.id } } + + before do + allow_any_instance_of(DownloadGithubRepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: Rails.root.join("tmp"), sha: "12340987") + ) + end + + it "sets sync errors on the pending release" do + subject + expect(pending_release.reload.sync_errors).to include ("Root directory does not contain a config.yaml or config.yml file") + end + end + + context "when there is a sql challenge" do + let!(:pending_release) { create(:release, :pending) } + let!(:cohort_release) { create(:cohort_release, pending_release_id: pending_release.id) } + let(:args) { { pending_release_id: pending_release.id, cohort_release_id: cohort_release.id } } + + before do + allow_any_instance_of(DownloadGithubRepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: Rails.root.join("spec", "fixtures", "test-block-repo").to_s, sha: "12340987") + ) + allow_any_instance_of(BlockParser::ParseDirectory).to( + receive(:execute).and_return( + errors: [], + warnings: [], + readme_text: "Some Readme text\nParsed in blockparser\nOnly 3 lines of it though", + block_hash: [{ standard: { title: "Foo", uid: "1234", description: "This is the description", success_criteria: ["Foo"] }, + content_file_attribute_hashes: [{ + html: "

    hello from the parser!

    ", + path: "/target.md", + title: "foo", + content_file_type: ContentFile::TYPES[:checkpoint], + uid: "1234", + challenges: [attributes_for(:challenge, :sql)], + autoscore: true, + warnings: [] + }] }] + ) + ) + end + + it "creates a challenge with data_path and data_path_checksum populated" do + allow(SqlChallengeDBService).to( receive_message_chain(:new, :execute).and_return([]) ) + expect { subject }.to change { Challenge.count }.from(0).to(1) + challenge = Challenge.last + expect(challenge.data_path).to eq("/test.sql") + expect(challenge.data_path_checksum.blank?).to be_falsey + end + + describe "failed db stuff" do + it "does not create challenge and therefore release" do + expect(Release.count).to eq(2) + allow(SqlChallengeDBService).to( receive_message_chain(:new, :execute).and_return(["YOU FAILED"]) ) + subject + expect(Challenge.count).to eq(0) + expect(Release.count).to eq(2) + end + end + end +end diff --git a/scripts/spec/models/activity_spec.rb b/scripts/spec/models/activity_spec.rb new file mode 100644 index 0000000..2399ebc --- /dev/null +++ b/scripts/spec/models/activity_spec.rb @@ -0,0 +1,11 @@ +require "spec_helper" + +describe Activity do + subject { create(:activity, :comment) } + + it { is_expected.to validate_presence_of(:cohort) } + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_presence_of(:subject) } + it { is_expected.to validate_inclusion_of(:name).in_array(described_class::NAMES.values) } + +end diff --git a/scripts/spec/models/api_interaction_spec.rb b/scripts/spec/models/api_interaction_spec.rb new file mode 100644 index 0000000..d528096 --- /dev/null +++ b/scripts/spec/models/api_interaction_spec.rb @@ -0,0 +1,16 @@ +require "spec_helper" + +describe ApiInteraction do + it 'validates the presence of all columns' do + expect(ApiInteraction.create.errors.full_messages).to eq([ + "User must exist", + "Ip can't be blank", + "Path can't be blank", + "Method can't be blank", + "Action name can't be blank", + "Duration can't be blank", + "Controller name can't be blank", + "Response code can't be blank" + ]) + end +end diff --git a/scripts/spec/models/block_spec.rb b/scripts/spec/models/block_spec.rb new file mode 100644 index 0000000..8c0ddf2 --- /dev/null +++ b/scripts/spec/models/block_spec.rb @@ -0,0 +1,80 @@ +require "spec_helper" + +describe Block do + subject { create(:block) } + + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_uniqueness_of(:title) } + it { is_expected.to validate_presence_of(:repo_name) } + + describe "repo_name case uniqueness" do + it "should fail validation with case insensitive match" do + block = Block.new(title: "a", repo_name: subject.repo_name.upcase) + expect(block.valid?).to eq(true) + end + end + + describe "#cohorts" do + let!(:cohort_1) { create(:cohort) } + let!(:cohort_2) { create(:cohort) } + let!(:release) { create(:release, block: subject) } + let!(:cohort_release_1) { create(:cohort_release, cohort: cohort_1, release: release) } + let!(:cohort_release_2) { create(:cohort_release, cohort: cohort_2, release: release) } + it "should include the repo name into the url" do + expect(subject.cohorts.map(&:id).sort).to eq([cohort_1.id, cohort_2.id].sort) + end + end + + describe "#git_url" do + it "should include the repo name into the url" do + expect(subject.git_url).to eq( + "https://#{subject.origin}/#{subject.org}/#{subject.repo_name}" + ) + end + end + + + describe "scopes" do + context "without_preview" do + let!(:block) { create(:block) } + let!(:preview) { create(:block, title: "preview", repo_name: "preview") } + + it "does not include the preview block" do + expect(Block.all.without_preview).to_not include(preview) + end + end + + context "active" do + let!(:block) { create(:block) } + let!(:archived) { create(:block, archived_at: 1.day.ago) } + + it "does not include the preview block" do + expect(Block.all.active).to_not include(archived) + end + end + end + + describe "#validate_github_org_and_branch" do + context "when repo name is not valid" do + context "when given anything other than the repo name" do + subject { build(:block, repo_name: "/foo") } + # The check has been removed to allow for gitlab subprojects + xit "fails validation" do + subject.save + expect(subject.valid?).to eq false + expect(subject.errors[:repo]).to include("URL cannot include a branch") + end + end + end + + context "when repo name is valid" do + context "when given just a title repo name" do + subject { build(:block, repo_name: "how-to_train-your_dragon-333") } + it "passes validation" do + subject.save + expect(subject.save).to eq true + end + end + end + end +end diff --git a/scripts/spec/models/challenge_spec.rb b/scripts/spec/models/challenge_spec.rb new file mode 100644 index 0000000..3be9d49 --- /dev/null +++ b/scripts/spec/models/challenge_spec.rb @@ -0,0 +1,184 @@ +require "spec_helper" + +describe Challenge do + describe "immutability" do + subject { create(:challenge) } + + it "cannot be updated" do + expect { subject.update(title: "foo") }.to raise_error(ActiveRecord::ReadOnlyRecord) + end + + it "cannot be destroyed" do + expect { subject.destroy }.to raise_error(ActiveRecord::ReadOnlyRecord) + end + end + + describe "validations" do + describe "factory" do + subject { create(:challenge) } + + it "passes validation" do + expect(subject).to be_valid + end + end + + describe "uid" do + context "uid nil" do + subject { build(:challenge, uid: nil, challenge_type: Challenge::TYPES[:code_snippet]) } + + it "fails validation" do + subject.save + expect(subject).to_not be_valid + expect(subject.errors[:uid]).to include("can't be blank") + end + end + end + + describe "sql checksum" do + context "checksum nil, language sql" do + subject { build(:challenge, data_path_checksum: nil, language: 'sql', challenge_type: Challenge::TYPES[:code_snippet]) } + + it "fails validation" do + subject.save + expect(subject).to_not be_valid + expect(subject.errors[:data_path_checksum]).to include("can't be nil") + end + end + + context "checksum not nil, language sql" do + subject { build(:challenge, data_path_checksum: 'abc123', language: 'sql', challenge_type: Challenge::TYPES[:code_snippet]) } + + it "passes validation" do + subject.save + expect(subject).to be_valid + end + end + end + + describe "hints" do + context "when hints is set to nil" do + subject { build(:challenge, hints: nil) } + + it "fails validation" do + subject.save + expect(subject).to_not be_valid + expect(subject.errors[:hints]).to include("can't be nil") + end + end + + context "when hints is set to an empty array" do + subject { build(:challenge, hints: []) } + + it "passes validation" do + subject.save + expect(subject).to be_valid + end + end + end + + describe "challenge_type" do + context "when the challenge type is included in the list of valid options" do + subject { build(:challenge, challenge_type: Challenge::TYPES[:number]) } + + it "passes validation" do + subject.save + expect(subject).to be_valid + end + end + + context "when the challenge type is not included in the list of valid options" do + subject { build(:challenge, challenge_type: "foo") } + + it "passes validation" do + subject.save + expect(subject.errors[:challenge_type]).to include("foo is not a valid challenge type") + end + end + end + + describe "#validates_uniqness_of_uid_within_release" do + subject { create(:challenge, uid: "some-unique-uid") } + + context "when there are two challenges with the same uid within a release" do + it "fails validation" do + dupe_challenge = subject.dup + other_content_file = create(:content_file, standard: subject.content_file.standard) + dupe_challenge.content_file = other_content_file + paths = [subject.content_file.path, dupe_challenge.content_file.path].join(", ") + + expect(dupe_challenge.valid?).to eq false + expect(dupe_challenge.errors.full_messages).to include( + "Uid Error: Challenge with uid #{dupe_challenge.uid} must be unique within this release. Found in [#{paths}]" + ) + end + end + + context "when there are two challenges with the same uid in different releases" do + it "passes validation" do + dupe_challenge = subject.dup + dupe_challenge.content_file = create(:content_file) + expect(dupe_challenge.valid?).to eq true + end + end + end + end + + describe ".exists_for_content_file_id?" do + let!(:content_file) { create(:content_file) } + + context "when at least one challenge exists for a given content file" do + before { create(:challenge, content_file: content_file) } + + it "returns true" do + expect(described_class.exists_for_content_file_id?(content_file.id)).to eq(true) + end + end + + context "when no challenges exist for a given content file" do + it "returns false" do + expect(described_class.exists_for_content_file_id?(content_file.id)).to eq(false) + end + end + end + + describe "#human_title" do + it "removes dashes, file extensions, and titleizes the title attribute" do + challenge = create(:challenge, title: "number-challenge") + expect(challenge.human_title).to eq("Number Challenge") + end + end + + describe "#gradeable?" do + context "mulitple-choice, short-answer, code-snippet, number, testable-project, custom-snippet" do + it "returns true" do + expect(create(:challenge, challenge_type: Challenge::TYPES[:multiple_choice]).gradeable?).to be true + + expect(create(:challenge, challenge_type: Challenge::TYPES[:short_answer]).gradeable?).to be true + + expect(create(:challenge, challenge_type: Challenge::TYPES[:code_snippet]).gradeable?).to be true + + expect(create(:challenge, challenge_type: Challenge::TYPES[:number]).gradeable?).to be true + + expect(create(:challenge, challenge_type: Challenge::TYPES[:testable_project], upstream_repo_path: "http://github.com/Org/Repo/Branch/").gradeable?).to be true + + expect(create(:challenge, challenge_type: Challenge::TYPES[:custom_snippet], docker_directory_path: "/path/to/docker").gradeable?).to be true + end + end + end + + describe "#testable?" do + it "returns true for challenges whose submissions requiring running a test suite" do + expect(create(:challenge, challenge_type: Challenge::TYPES[:multiple_choice]).testable?).to be false + + expect(create(:challenge, challenge_type: Challenge::TYPES[:short_answer]).testable?).to be false + + expect(create(:challenge, challenge_type: Challenge::TYPES[:code_snippet]).testable?).to be true + + expect(create(:challenge, challenge_type: Challenge::TYPES[:number]).testable?).to be false + + expect(create(:challenge, challenge_type: Challenge::TYPES[:testable_project], upstream_repo_path: "http://github.com/Org/Repo/Branch/").testable?).to be true + + expect(create(:challenge, challenge_type: Challenge::TYPES[:custom_snippet], docker_directory_path: "/path/to/docker").testable?).to be true + end + end +end diff --git a/scripts/spec/models/checkpoint_submission_spec.rb b/scripts/spec/models/checkpoint_submission_spec.rb new file mode 100644 index 0000000..e2c76f9 --- /dev/null +++ b/scripts/spec/models/checkpoint_submission_spec.rb @@ -0,0 +1,276 @@ +require "spec_helper" + +describe CheckpointSubmission do + describe ".correct_answers" do + let(:user) { create(:user) } + let(:content_file) { create(:content_file) } + let(:challenge) { create(:challenge, content_file: content_file) } + let!(:checkpoint_submission) { + create(:checkpoint_submission, content_file_block_id: content_file.standard.release.block_id, content_file_uid: content_file.uid, user: user) + } + let!(:submitted_challenge_answer_1) { create(:submitted_challenge_answer, challenge: challenge, user: user, checkpoint_submission: checkpoint_submission, status: SubmittedChallengeAnswer::STATUSES[:incorrect]) } + let!(:submitted_challenge_answer_2) { create(:submitted_challenge_answer, challenge: challenge, user: user, checkpoint_submission: checkpoint_submission) } + + it "yeilds only the correct answers on the checkpoint submission" do + expect(checkpoint_submission.correct_answers).to eq [submitted_challenge_answer_2] + end + end + + describe ".standard" do + let(:user) { create(:user) } + let(:release) { create(:release) } + let(:standard) { create(:standard, release: release) } + let(:content_file) { create(:content_file, :checkpoint, standard: standard) } + let(:challenge) { create(:challenge, content_file: content_file) } + let(:checkpoint_submission) { + create(:checkpoint_submission, content_file_block_id: release.block_id, content_file_uid: content_file.uid, user: user) + } + let!(:sca) { + create(:submitted_challenge_answer, challenge: challenge, checkpoint_submission: checkpoint_submission, status: "correct", user: user) + } + it "yields the standard of the checkpoint" do + expect(checkpoint_submission.standard).to eq(standard) + end + + it "yields nil when the checkpoint submission has no submitted challenge answers" do + sca.destroy! + expect(checkpoint_submission.standard).to eq(nil) + end + end + + describe ".other_correct_checkpoints?" do + let(:user) { create(:user) } + let(:release) { create(:release) } + let(:standard) { create(:standard, release: release) } + let(:content_file) { create(:content_file, :checkpoint, standard: standard) } + let(:challenge) { create(:challenge, content_file: content_file) } + let(:checkpoint_submission) { + create(:checkpoint_submission, content_file_block_id: release.block_id, content_file_uid: content_file.uid, user: user) + } + let!(:sca) { + create(:submitted_challenge_answer, challenge: challenge, checkpoint_submission: checkpoint_submission, status: "correct", user: user) + } + it "yields true when the student has a checkpoint submission for the standard whose submissions are all correct" do + second = create(:checkpoint_submission, content_file_block_id: release.block_id, content_file_uid: content_file.uid, user: user) + create(:submitted_challenge_answer, challenge: challenge, checkpoint_submission: second, status: "correct", user: user) + expect(second.other_correct_checkpoints?).to eq true + end + + it "yields false when the student has no other all-correct checkpoint submission for the standard" do + expect(checkpoint_submission.other_correct_checkpoints?).to eq false + end + end + + describe ".all_correct?" do + let(:user) { create(:user) } + let(:release) { create(:release) } + let(:standard) { create(:standard, release: release) } + let(:content_file) { create(:content_file, :checkpoint, standard: standard) } + let(:challenge_1) { create(:challenge, content_file: content_file) } + let(:challenge_2) { create(:challenge, content_file: content_file) } + let(:checkpoint_submission) { + create(:checkpoint_submission, content_file_block_id: release.block_id, content_file_uid: content_file.uid, user: user) + } + let!(:sca_1) { + create(:submitted_challenge_answer, challenge: challenge_1, checkpoint_submission: checkpoint_submission, status: "correct", user: user) + } + let!(:sca_2) { + create(:submitted_challenge_answer, challenge: challenge_1, checkpoint_submission: checkpoint_submission, status: "correct", user: user) + } + + it "returns true when all submitted challenge answers are correct" do + expect(checkpoint_submission.all_correct?).to eq true + end + + it "returns false if any submitted challenge answers aren't correct" do + sca_2.update(status: "incorrect") + expect(checkpoint_submission.all_correct?).to eq false + end + end + + describe ".latest_ungraded_checkpoints" do + let(:cohort) { create(:cohort) } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:student_1) { create(:cohort_user, :student, cohort: cohort).user } + let!(:student_2) { create(:cohort_user, :student, cohort: cohort).user } + let!(:student_unseen) { create(:cohort_user, :student, cohort: cohort).user } + let!(:block_1) { create(:block) } + let!(:block_2) { create(:block) } + let!(:release_1) { create(:release) } + let!(:release_2) { create(:release) } + let!(:standard_1) { create(:standard, uid: "yyy", release: release_1) } + let!(:standard_2) { create(:standard, uid: "zzz", release: release_2) } + let!(:checkpoint_content_file_1) { create(:content_file, :checkpoint, uid: "abc", standard: standard_1) } + let!(:checkpoint_content_file_2) { create(:content_file, :checkpoint, uid: "123", standard: standard_2) } + let!(:challenge_1) { create(:challenge, content_file: checkpoint_content_file_1) } + let!(:challenge_2) { create(:challenge, content_file: checkpoint_content_file_2) } + + let!(:checkpoint_submission_abc_1) { create(:checkpoint_submission, content_file_uid: "abc", content_file_block_id: block_1.id, user_id: student_1.id, state: "needs_review") } + let!(:sca_abc_1) { create(:submitted_challenge_answer, challenge: challenge_1, checkpoint_submission: checkpoint_submission_abc_1, user: student_1) } + let!(:checkpoint_submission_123_1) { create(:checkpoint_submission, content_file_uid: "123", content_file_block_id: block_2.id, user_id: student_1.id, state: "done") } + let!(:sca_123_1) { create(:submitted_challenge_answer, challenge: challenge_2, checkpoint_submission: checkpoint_submission_123_1, user: student_1) } + + let!(:checkpoint_submission_abc_2) { create(:checkpoint_submission, content_file_uid: "abc", content_file_block_id: block_1.id, user_id: student_2.id, state: "needs_review") } + let!(:sca_abc_2) { create(:submitted_challenge_answer, challenge: challenge_1, checkpoint_submission: checkpoint_submission_abc_2, user: student_2) } + let!(:checkpoint_submission_123_2_old) { create(:checkpoint_submission, content_file_uid: "123", content_file_block_id: block_2.id, user_id: student_2.id, created_at: 1.day.ago, state: "needs_review") } + let!(:sca_123_2_old) { create(:submitted_challenge_answer, challenge: challenge_2, checkpoint_submission: checkpoint_submission_123_2_old, user: student_2) } + let!(:checkpoint_submission_123_2) { create(:checkpoint_submission, content_file_uid: "123", content_file_block_id: block_2.id, user_id: student_2.id, state: "needs_review") } + let!(:sca_123_2) { create(:submitted_challenge_answer, challenge: challenge_2, checkpoint_submission: checkpoint_submission_123_2, user: student_2) } + + let!(:checkpoint_submission_unseen) { create(:checkpoint_submission, content_file_uid: "abc", content_file_block_id: block_1.id, user_id: student_unseen.id) } + let!(:sca_unseen) { create(:submitted_challenge_answer, challenge: challenge_1, checkpoint_submission: checkpoint_submission_unseen, user: student_unseen) } + + it "yields the latest ungraded checkpoints" do + results = CheckpointSubmission.latest_ungraded_checkpoints(student_1.id, student_2.id) + ids = results.map(&:id) + expect(ids).to include(checkpoint_submission_abc_1.id) + expect(ids).to include(checkpoint_submission_abc_2.id) + expect(ids).to include(checkpoint_submission_123_2.id) + expect(ids).to_not include(checkpoint_submission_123_1.id) # marked done + expect(ids).to_not include(checkpoint_submission_123_2_old.id) # older than other + expect(ids).to_not include(checkpoint_submission_unseen.id) # not in user id set + end + end + + describe "#update_points" do + let!(:checkpoint_submission) { create_checkpoint_submission(create(:user), challenge_1, {state: 'done', total_points: 0, correct_points:0}) } + let!(:challenge_1) { create(:challenge, points: 3) } + let!(:challenge_2) { create(:challenge, content_file: challenge_1.content_file, points: 2) } + let!(:challenge_3) { create(:challenge, content_file: challenge_1.content_file, points: 4) } + let!(:sca_1) { create(:submitted_challenge_answer, challenge: challenge_1, checkpoint_submission_id: checkpoint_submission.id, status: 'correct', points: 3) } + let!(:sca_2) { create(:submitted_challenge_answer, challenge: challenge_2, checkpoint_submission_id: checkpoint_submission.id, status: 'incorrect', points: 0) } + let!(:sca_3) { create(:submitted_challenge_answer, challenge: challenge_3, checkpoint_submission_id: checkpoint_submission.id, status: 'correct', points: 3) } + it "sets the total_points and correct_points based on submitted challenge answer states" do + checkpoint_submission.update_points + expect(checkpoint_submission.reload.total_points).to eq 9 + expect(checkpoint_submission.correct_points).to eq 6 + end + + it "changes nothing if the checkpoint is not done" do + checkpoint_submission.update(state: 'needs_review') + checkpoint_submission.update_points + expect(checkpoint_submission.reload.total_points).to eq 0 + expect(checkpoint_submission.correct_points).to eq 0 + end + end + + describe "#topic_averages?" do + let!(:block) { create(:block) } + let!(:release_2) { create(:release, block: block) } + let!(:cohort_2) { create(:cohort, mode: 'Percentage') } + let!(:cohort_2_release) { create(:cohort_release, release: release_2, cohort: cohort_2) } + let!(:standard_2) { create(:standard, release: release_2) } + let!(:content_file_2) { create(:content_file, standard: standard_2, autoscore: true, content_file_type: ContentFile::TYPES[:checkpoint]) } + let!(:student_2) { create(:cohort_user, :student).user } + let!(:challenge_2) { create(:challenge, content_file: content_file_2, topics: ['very neat', 'super cool'], release_id: standard_2.release.id, points: 5) } + let!(:checkpoint_submission_2) { create(:checkpoint_submission, content_file_uid: content_file_2.uid, content_file_block_id: block.id , cohort: cohort_2, user: student_2, state: CheckpointSubmission::STATES[:done], correct_points: 3, total_points: 5) } + let!(:student_sca) { create(:submitted_challenge_answer, checkpoint_submission: checkpoint_submission_2, user: student_2, cohort: cohort_2, challenge: challenge_2, created_at: 1.day.ago, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct], points: 3) } + + it "returns an object with topic averages information" do + expect(checkpoint_submission_2.topic_averages).to eq({ + "very neat": { earned: 3, possible: 5 }, + "super cool": { earned: 3, possible: 5 } + }) + end + end + + describe "state scopes" do + describe "::needs_review" do + subject { create_checkpoint_submission(create(:user), create(:challenge, content_file: create(:content_file, :checkpoint)), state: described_class::STATES[:needs_review]) } + let(:instructor) { create(:user) } + before { create_checkpoint_submission(create(:user), create(:challenge, content_file: create(:content_file, :checkpoint)), state: described_class::STATES[:done], grader_id: instructor.id) } + + it "only returns checkpoint submissions that need reviewed" do + expect(described_class.needs_review).to eq([subject]) + end + end + end + + describe "state predicate methods" do + describe "::needs_review?" do + subject { create_checkpoint_submission(create(:user), create(:challenge, content_file: create(:content_file, :checkpoint)), state: described_class::STATES[:needs_review]) } + + it "returns true if the subject needs review" do + expect(subject.needs_review?).to eq(true) + end + end + + describe "::retry?" do + subject { create_checkpoint_submission(create(:user), create(:challenge, content_file: create(:content_file, :checkpoint)), state: described_class::STATES[:retry]) } + + it "returns true if the subject needs review" do + expect(subject.retry?).to eq(true) + end + end + end + + describe "validations" do + describe "checkpoint_id" do + describe "presence" do + context "when no cohort_id is set" do + it "raises an error" do + expect(described_class.create.errors[:cohort]).to eq(["must exist"]) + end + end + end + end + + describe "state" do + describe "presence" do + context "when no state is set" do + it "raises an error" do + expect(described_class.create.errors[:state]).to include("can't be blank") + end + end + end + + describe "inclusion" do + context "when the value is not in the list of valid states" do + it "raises an error" do + expect(described_class.create(state: "foo").errors[:state]).to eq(["is not included in the list"]) + end + end + end + end + + describe "grader_id" do + context "when grader id is set and state is needs_review" do + subject { create_checkpoint_submission(create(:user), create(:challenge, content_file: create(:content_file, :checkpoint)), state: described_class::STATES[:needs_review]) } + before { subject.update(grader_id: 0) } + + it "raises an error" do + expect(subject.errors[:grader_id]).to include("cannot be set when state is needs_review") + end + end + + context "when state is changed to rejected without grader id" do + it "raises an error" do + checkpoint_submission = create_checkpoint_submission(create(:user), create(:challenge, content_file: create(:content_file, :checkpoint))) + checkpoint_submission.update(state: described_class::STATES[:rejected]) + expect(checkpoint_submission.errors[:grader_id]).to eq(["grader id is required to reject"]) + end + end + end + + describe "can_be_rejected" do + let(:admin) { create(:user, :admin) } + + context "when previous state is 'done'" do + it "raises an error" do + checkpoint_submission = create_checkpoint_submission(create(:user), create(:challenge, content_file: create(:content_file, :checkpoint)), state: described_class::STATES[:done], grader: admin) + checkpoint_submission.update(state: described_class::STATES[:rejected], grader: admin) + expect(checkpoint_submission.errors.messages).to eq(state: ["only checkpoint submissions that need review can be rejected."]) + end + end + + context "when standards for this checkpoint submission have already been scored" do + it "raises an error" do + checkpoint_submission = create_checkpoint_submission(create(:user), create(:challenge, content_file: create(:content_file, :checkpoint))) + create(:performance, checkpoint_submission_id: checkpoint_submission.id) + checkpoint_submission.update(state: described_class::STATES[:rejected], grader: admin) + expect(checkpoint_submission.errors.messages).to eq(performances: ["only checkpoint submissions that have no scored standards can be rejected."]) + end + end + end + end +end diff --git a/scripts/spec/models/cohort_release_spec.rb b/scripts/spec/models/cohort_release_spec.rb new file mode 100644 index 0000000..84dac13 --- /dev/null +++ b/scripts/spec/models/cohort_release_spec.rb @@ -0,0 +1,40 @@ +require "spec_helper" + +describe CohortRelease do + subject { create(:cohort_release) } + + it "validates the uniqueness of cohort id within the scope of the release" do + cohort_release = CohortRelease.create(cohort: subject.cohort, release: subject.release) + + expect(cohort_release.errors[:cohort_id]).to include("has already been taken") + end + + describe ".use_latest_release" do + let!(:not_using_latest) { create(:cohort_release) } + let!(:using_latest) { create(:cohort_release, use_latest_release: true) } + + it "returns cohort releases set to use latest release" do + expect(described_class.use_latest_release).to include(using_latest) + expect(described_class.use_latest_release).to_not include(not_using_latest) + end + end + + describe ".attached_to_cohort" do + let(:cohort_1) { create(:cohort) } + + before do + create(:cohort_release, cohort: cohort_1) + create(:cohort_release, cohort: cohort_1) + create(:cohort_release) + create(:cohort_release) + create(:cohort_release) + end + + subject { described_class.attached_to_cohort(cohort_1.id) } + + it "returns cohort releases attached to the given cohort" do + expect(subject.length).to eq(2) + expect(subject.map(&:cohort_id).uniq).to eq([cohort_1.id]) + end + end +end diff --git a/scripts/spec/models/cohort_spec.rb b/scripts/spec/models/cohort_spec.rb new file mode 100644 index 0000000..8e8e424 --- /dev/null +++ b/scripts/spec/models/cohort_spec.rb @@ -0,0 +1,278 @@ +require "spec_helper" + +describe Cohort do + subject { create(:cohort) } + + it { is_expected.to validate_presence_of(:uid) } + it { is_expected.to validate_uniqueness_of(:uid) } + it { is_expected.to validate_uniqueness_of(:user_id) } + it { is_expected.to validate_presence_of(:product_type) } + + describe "default_scope" do + it "excludes cohorts that have deleted_at timestamp" do + create(:cohort, :deleted) + expect(Cohort.all).to eq([subject]) + end + end + + describe "without_sandboxes scope" do + it "excludes cohorts that have deleted_at timestamp" do + create(:cohort, sandbox: true) + expect(Cohort.all.without_sandboxes).to eq([subject]) + end + end + + describe ".recently_active_students" do + let!(:active_user_1) { create(:user) } + let!(:cohort_user_1) { create(:cohort_user, user: active_user_1, cohort: subject) } + let!(:activity_1) { create(:activity, :comment, creator: active_user_1, cohort: subject) } + + let!(:active_user_2) { create(:user) } + let!(:cohort_user_2) { create(:cohort_user, user: active_user_2, cohort: subject) } + let!(:activity_2) { create(:activity, :comment, creator: active_user_2, cohort: subject) } + + let!(:inactive_user) { create(:user) } + let!(:cohort_user_3) { create(:cohort_user, user: inactive_user, cohort: subject) } + let!(:activity_3) { create(:activity, :comment, created_at: 15.days.ago, creator: inactive_user, cohort: subject) } + + it "returns users which have activity for a cohort within 14 days" do + results = subject.recently_active_students + expect(results.length).to eq 2 + expect(results.map(&:id).sort).to eq([active_user_1.id, active_user_2.id].sort) + end + end + + describe "mode validation" do + it "has mastery as default mode" do + expect(subject.mode).to eq(Cohort::MODES[:mastery]) + end + + it "can update to percentage mode" do + subject.update(mode: Cohort::MODES[:percentage]) + expect(subject.reload.mode).to eq(Cohort::MODES[:percentage]) + end + + it "wont allow you to update to modes outside enum" do + subject.update(mode: "Bob") + expect(subject.reload.errors).to_not be(nil) + expect(subject.reload.errors.first).to eq([:mode, "is not included in the list"]) + end + end + + describe "show_mastery_average?" do + it { expect(create(:cohort).show_mastery_average?).to eq(true) } + it { expect(create(:cohort, :enterprise).show_mastery_average?).to eq(false) } + end + + describe "#student_ids" do + context "when students exist in cohort" do + it "returns user ids" do + cohort_user = create(:cohort_user, :student, cohort: subject) + expect(subject.student_ids).to eq([cohort_user.user_id]) + end + end + + context "when no students exist in cohort" do + it "returns an empty array" do + expect(subject.student_ids).to eq([]) + end + end + end + + describe "#standard_uids" do + let!(:release_1) { create(:release) } + let!(:release_2) { create(:release) } + let!(:cohort_release_1) { create(:cohort_release, release: release_1, cohort: subject) } + let!(:cohort_release_2) { create(:cohort_release, release: release_2, cohort: subject) } + let!(:standard_1) { create(:standard, release: release_1, uid: "abc") } + let!(:standard_2) { create(:standard, release: release_1, uid: "123") } + let!(:standard_3) { create(:standard, release: release_2, uid: "456") } + let!(:standard_4) { create(:standard, release: release_2, uid: "xyz") } + it "returns the uids of all standards currently attached to the cohort via releases" do + result = subject.standard_uids + expect(result.length).to eq(4) + expect(result).to include("abc") + expect(result).to include("123") + expect(result).to include("456") + expect(result).to include("xyz") + end + end + + describe "#visible_standard_uids" do + let!(:release_1) { create(:release) } + let!(:release_2) { create(:release) } + let!(:cohort_release_1) { create(:cohort_release, release: release_1, cohort: subject) } + let!(:cohort_release_2) { create(:cohort_release, release: release_2, cohort: subject) } + let!(:standard_1) { create(:standard, release: release_1, uid: "abc") } + let!(:standard_2) { create(:standard, release: release_1, uid: "123") } + let!(:standard_3) { create(:standard, release: release_2, uid: "456") } + let!(:standard_hidden) { create(:standard, release: release_2, uid: "xyz") } + let!(:content_visibility) { create(:content_visibility, cohort: subject, content_type: "Standard", content_uid: standard_hidden.uid) } + it "returns the uids of all standards currently attached to the cohort via releases" do + result = subject.visible_standard_uids + expect(result.length).to eq(3) + expect(result).to include("abc") + expect(result).to include("123") + expect(result).to include("456") + expect(result).to_not include("xyz") + end + end + + describe "#visible_standards" do + let!(:release_1) { create(:release) } + let!(:release_2) { create(:release) } + let!(:cohort_release_1) { create(:cohort_release, release: release_1, cohort: subject) } + let!(:cohort_release_2) { create(:cohort_release, release: release_2, cohort: subject) } + let!(:standard_1) { create(:standard, release: release_1, uid: "abc") } + let!(:standard_2) { create(:standard, release: release_1, uid: "123") } + let!(:standard_3) { create(:standard, release: release_2, uid: "456") } + let!(:standard_hidden) { create(:standard, release: release_2, uid: "xyz") } + let!(:content_visibility) { create(:content_visibility, cohort: subject, content_type: "Standard", content_uid: standard_hidden.uid) } + it "returns the uids of all standards currently attached to the cohort via releases" do + result = subject.visible_standards + expect(result.length).to eq(3) + expect(result).to include(standard_1) + expect(result).to include(standard_2) + expect(result).to include(standard_3) + expect(result).to_not include(standard_hidden) + end + end + + describe "#block_ids" do + let!(:release_1) { create(:release) } + let!(:release_2) { create(:release) } + let!(:release_3) { create(:release) } + let!(:cohort_release_1) { create(:cohort_release, release: release_1, cohort: subject) } + let!(:cohort_release_2) { create(:cohort_release, release: release_2, cohort: subject) } + let!(:cohort_release_3) { create(:cohort_release, release: release_3, cohort: subject) } + it "returns the block ids of all blocks currently attached to the cohort via releases" do + result = subject.block_ids + expect(result.length).to eq(3) + expect(result).to include(release_1.block_id) + expect(result).to include(release_2.block_id) + expect(result).to include(release_3.block_id) + end + end + + describe "grade_autoscore_checkpoint_submissions" do + # build four releases with two blocks, two 'previously attached' to cohort, two currently attached + # operation spans across blocks, check that works with at least two + let(:block_1) { create(:block) } + let(:block_2) { create(:block) } + + # students don't actually have to be attached to cohort for this to work + let(:student_1) { create(:user) } + let(:student_2) { create(:user) } + + # ungraded releases have checkpoint submissions that weren't scored, need challenge set for SCAs to calculate score + let!(:ungraded_release_1) { create(:release, block: block_1) } + let!(:ungraded_standard_1) { create(:standard, release: ungraded_release_1) } + let!(:ungraded_checkpoint_file_1) { create(:content_file, standard: ungraded_standard_1, content_file_type: 'checkpoint', uid: "abc", autoscore: false) } + let!(:ungraded_challenge_1) { create(:challenge, content_file: ungraded_checkpoint_file_1, challenge_type: 'number') } + let!(:ungraded_challenge_2) { create(:challenge, content_file: ungraded_checkpoint_file_1, challenge_type: 'number') } + + let!(:ungraded_release_2) { create(:release, block: block_2) } + let!(:ungraded_standard_2) { create(:standard, release: ungraded_release_2) } + let!(:ungraded_checkpoint_file_2) { create(:content_file, standard: ungraded_standard_2, content_file_type: 'checkpoint', uid: "123", autoscore: false) } + let!(:ungraded_challenge_3) { create(:challenge, content_file: ungraded_checkpoint_file_2, challenge_type: 'number') } + let!(:ungraded_challenge_4) { create(:challenge, content_file: ungraded_checkpoint_file_2, challenge_type: 'number') } + + # student work was done on previously 'attached' curriculum + let!(:submission_all_correct) { # block 1, student_1 gets them all right + create(:checkpoint_submission, cohort: subject, user: student_1, content_file_uid: "abc", content_file_block_id: block_1.id) + } + let!(:sca_correct_1) { create(:submitted_challenge_answer, + user: student_1, + challenge: ungraded_challenge_1, + checkpoint_submission_id: submission_all_correct.id, + status: 'correct') } + let!(:sca_correct_2) { create(:submitted_challenge_answer, + user: student_1, + challenge: ungraded_challenge_2, + checkpoint_submission_id: submission_all_correct.id, + status: 'correct') } + + let!(:submission_all_incorrect) { # block 1, student_2 gets them all wrong + create(:checkpoint_submission, cohort: subject, user: student_2, content_file_uid: "abc", content_file_block_id: block_1.id) + } + let!(:sca_incorrect_1) { create(:submitted_challenge_answer, + user: student_2, + challenge: ungraded_challenge_1, + checkpoint_submission_id: submission_all_incorrect.id, + status: 'incorrect') } + let!(:sca_incorrect_2) { create(:submitted_challenge_answer, + user: student_2, + challenge: ungraded_challenge_2, + checkpoint_submission_id: submission_all_incorrect.id, + status: 'incorrect') } + + let!(:submission_half_correct) { # block 2, student_1 gets them half right + create(:checkpoint_submission, cohort: subject, user: student_1, content_file_uid: "123", content_file_block_id: block_2.id) + } + let!(:sca_half_1) { create(:submitted_challenge_answer, + user: student_1, + challenge: ungraded_challenge_3, + checkpoint_submission_id: submission_half_correct.id, + status: 'correct') } + let!(:sca_half_2) { create(:submitted_challenge_answer, + user: student_1, + challenge: ungraded_challenge_4, + checkpoint_submission_id: submission_half_correct.id, + status: 'incorrect') } + + let!(:submission_incomplete) { # block 2, student_2 has one ungraded, therefore incomplete + create(:checkpoint_submission, cohort: subject, user: student_2, content_file_uid: "123", content_file_block_id: block_2.id) + } + let!(:sca_incomplete_1) { create(:submitted_challenge_answer, + user: student_2, + challenge: ungraded_challenge_3, + checkpoint_submission_id: submission_incomplete.id, + status: 'ungraded') } + let!(:sca_incomplete_2) { create(:submitted_challenge_answer, + user: student_2, + challenge: ungraded_challenge_4, + checkpoint_submission_id: submission_incomplete.id, + status: 'correct') } + + # to be graded releases have had checkpoint content files marked autoscore and are attached to cohort + let!(:to_be_graded_release_1) { create(:release, block: block_1) } + let!(:to_be_graded_standard_1) { create(:standard, release: to_be_graded_release_1) } + let!(:to_be_graded_checkpoint_file_1) { create(:content_file, standard: to_be_graded_standard_1, content_file_type: 'checkpoint', uid: "abc", autoscore: true) } + + let!(:to_be_graded_release_2) { create(:release, block: block_2) } + let!(:to_be_graded_standard_2) { create(:standard, release: to_be_graded_release_2) } + let!(:to_be_graded_checkpoint_file_2) { create(:content_file, standard: to_be_graded_standard_2, content_file_type: 'checkpoint', uid: "123", autoscore: true) } + + # to be graded are currently attached + let!(:cohort_release_1) { create(:cohort_release, release: to_be_graded_release_1, cohort: subject) } + let!(:cohort_release_2) { create(:cohort_release, release: to_be_graded_release_2, cohort: subject) } + + it "properly grades completed and autoscore" do + subject.grade_autoscore_checkpoint_submissions + + submission_all_correct.reload + submission_all_incorrect.reload + submission_half_correct.reload + submission_incomplete.reload + + expect(submission_all_correct.state).to eq 'done' + expect(submission_all_incorrect.state).to eq 'done' + expect(submission_half_correct.state).to eq 'done' + expect(submission_incomplete.state).to eq 'needs_review' + + expect(submission_all_correct.performances.last.score).to eq 3 + expect(submission_all_incorrect.performances.last.score).to eq 1 + expect(submission_half_correct.performances.last.score).to eq 2 + expect(submission_incomplete.performances.length).to eq 0 + end + end + + describe "finding" do + let!(:cohort) { create(:cohort, uid: "uidx") } + + it "allows finding by the id or uid" do + expect(described_class.find("uidx")).to eq cohort + expect(described_class.find(cohort.id)).to eq cohort + end + end +end diff --git a/scripts/spec/models/cohort_user_spec.rb b/scripts/spec/models/cohort_user_spec.rb new file mode 100644 index 0000000..bfe0af6 --- /dev/null +++ b/scripts/spec/models/cohort_user_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe CohortUser do + subject { create(:cohort_user, :student) } + + it { is_expected.to validate_presence_of(:cohort) } + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_uniqueness_of(:cohort_id).scoped_to(:user_id) } + + describe "role scopes" do + let!(:instructor) { create(:cohort_user, :instructor) } + + describe ".instructor" do + it "only returns instructor cohort_users" do + expect(described_class.instructor).to eq([instructor]) + end + end + + describe ".student" do + it "only returns student cohort_users" do + other_student = create(:cohort_user, :student) + expect(described_class.student).to match_array([subject, other_student]) + end + end + + describe ".product_admin" do + it "determines if cohort_user can perform onboarding tasks" do + product_admin = create(:cohort_user, roles: [CohortUser::ROLES[:product_admin]]) + instructor_1 = create(:cohort_user, :instructor, roles: [CohortUser::ROLES[:product_admin]]) + create(:cohort_user, :instructor) + expect(described_class.product_admin).to match_array([product_admin, instructor_1]) + end + end + end +end diff --git a/scripts/spec/models/content_file_spec.rb b/scripts/spec/models/content_file_spec.rb new file mode 100644 index 0000000..6d368ac --- /dev/null +++ b/scripts/spec/models/content_file_spec.rb @@ -0,0 +1,90 @@ +require "spec_helper" + +describe ContentFile do + subject { create(:content_file) } + + it { is_expected.to validate_presence_of(:standard) } + it { is_expected.to validate_presence_of(:position) } + it { is_expected.to validate_presence_of(:html) } + it { is_expected.to validate_presence_of(:path) } + it { is_expected.to validate_presence_of(:title) } + + describe "validations" do + describe "uid" do + it "must be set" do + subject.update(uid: nil) + expect(subject.errors[:uid]).to include("must be set for all lessons and checkpoints; fix #{subject.path} in the config") + end + end + end + + describe "immutability" do + it "cannot be updated" do + expect { subject.update(html: "foo") }.to raise_error(ActiveRecord::ReadOnlyRecord) + end + + it "cannot be destroyed" do + expect { subject.destroy }.to raise_error(ActiveRecord::ReadOnlyRecord) + end + end + + describe "content_file_type" do + context "when the content file type is not included in the list of valid options" do + it "fails validation" do + expected_types = ContentFile::TYPES.values.join(", ") + subject.update(content_file_type: "foo") + expect(subject.errors[:content_file_type]).to include("foo is not a valid type. Valid types: [#{expected_types}]") + end + end + + context "default scoping scopes out resources" do + it "does not return resources" do + resource = create(:content_file, content_file_type: "resource") + expect(ContentFile.all).to_not include(resource) + end + end + end + + describe "visible_for" do + let!(:cohort) { create(:cohort) } + let!(:content_file_1) { create(:content_file, uid: "abc") } + let!(:content_file_2) { create(:content_file, uid: "123") } + let!(:content_file_hidden) { create(:content_file, uid: "xyz") } + let!(:content_visibility) { create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: "xyz") } + it "scopes content files for a given cohort base on content visibilities" do + expect(ContentFile.visible_for(cohort.id).order(id: :asc)).to eq([content_file_1, content_file_2]) + end + end + + describe "hidden_for" do + let!(:cohort) { create(:cohort) } + let!(:content_file) { create(:content_file, uid: "abc") } + + it "determines whether a single content file is hidden for a cohort" do + expect(content_file.hidden_for(nil)).to be_falsey + expect(content_file.hidden_for(cohort.id)).to be_falsey + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: "abc") + expect(content_file.hidden_for(cohort.id)).to be_truthy + end + end + + describe "predicate methods" do + describe "#checkpoint?" do + subject { create(:content_file, :checkpoint) } + + it "returns true if the content file is a checkpoint" do + expect(subject.checkpoint?).to be_truthy + expect(subject.lesson?).to be_falsey + end + end + + describe "#lesson?" do + subject { create(:content_file, :lesson) } + + it "returns true if the content file is a lesson" do + expect(subject.lesson?).to be_truthy + expect(subject.checkpoint?).to be_falsey + end + end + end +end diff --git a/scripts/spec/models/content_visibility_spec.rb b/scripts/spec/models/content_visibility_spec.rb new file mode 100644 index 0000000..907c41a --- /dev/null +++ b/scripts/spec/models/content_visibility_spec.rb @@ -0,0 +1,55 @@ +require "spec_helper" + +describe ContentVisibility do + let(:cohort) { create(:cohort) } + subject { create(:content_visibility, cohort: cohort) } + + it { is_expected.to validate_presence_of(:visibility_type) } + + describe "validations" do + describe "visibility_type" do + it "must be set" do + subject.update(visibility_type: nil) + expect(subject.errors[:visibility_type]).to include("can't be blank") + end + end + end + + describe "scopes" do + let(:hidden) { create(:content_visibility, cohort: cohort, visibility_type: ContentVisibility::VISIBILITY_TYPES[:hidden]) } + let(:optional) { create(:content_visibility, cohort: cohort, visibility_type: ContentVisibility::VISIBILITY_TYPES[:optional]) } + + context "hidden_content" do + it "does not include optional" do + expect( ContentVisibility.hidden_content ).to_not include(optional) + end + end + + context "optional_content" do + it "does not include hidden" do + + expect( ContentVisibility.optional_content ).to_not include(hidden) + end + end + end + + describe "predicate methods" do + describe "#hidden?" do + subject { create(:content_visibility, cohort: cohort, visibility_type: ContentVisibility::VISIBILITY_TYPES[:hidden]) } + + it "returns true if the visibility_type is hidden" do + expect(subject.hidden?).to be_truthy + expect(subject.optional?).to be_falsey + end + end + + describe "#optional?" do + subject { create(:content_visibility, cohort: cohort, visibility_type: ContentVisibility::VISIBILITY_TYPES[:optional]) } + + it "returns true if the visibility_type is optional" do + expect(subject.optional?).to be_truthy + expect(subject.hidden?).to be_falsey + end + end + end +end diff --git a/scripts/spec/models/lesson_visit_spec.rb b/scripts/spec/models/lesson_visit_spec.rb new file mode 100644 index 0000000..16f92f3 --- /dev/null +++ b/scripts/spec/models/lesson_visit_spec.rb @@ -0,0 +1,89 @@ +require "spec_helper" + +describe LessonVisit do + let(:cohort) { create(:cohort) } + subject { create(:lesson_visit, cohort: cohort) } + + it { is_expected.to validate_presence_of(:standard_uid) } + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:block) } + + describe "#standard_in_cohort" do + let(:student) { create(:cohort_user, cohort: cohort).user } + let(:release) { create(:release) } + let!(:cohort_release) { create(:cohort_release, release: release, cohort: cohort) } + let!(:standard) { create(:standard, uid: "abc", release: release) } + let(:content_file) { create(:content_file, standard: standard) } + let!(:lesson_visit) { + create(:lesson_visit, + user: student, + cohort: cohort, + standard_uid: standard.uid, + block: release.block, + content_file_uid: content_file.uid, + content_file_path: content_file.path + ) + } + # create a second standard with the same uid but not attached to the cohort + let!(:unrelated_standard) { create(:standard, uid: "abc", release: create(:release)) } + + it "yields the standard with the uid of the lesson visit that is attached to the cohort" do + expect(lesson_visit.standard_in_cohort).to eq standard + end + + it "yields nil if there is no longer a standard associated to the cohort" do + cohort_release.destroy! + expect(lesson_visit.standard_in_cohort).to eq nil + end + end + + describe ".visible_for" do + let(:student) { create(:cohort_user, cohort: cohort).user } + let(:release) { create(:release) } + let!(:cohort_release) { create(:cohort_release, release: release, cohort: cohort) } + let!(:standard_hidden) { create(:standard, release: release, uid: "abc") } + let!(:standard_shown) { create(:standard, release: release) } + let(:content_file_standard_hidden) { create(:content_file, standard: standard_hidden, uid: "mystandardishidden") } + let(:content_file_shown) { create(:content_file, standard: standard_shown, uid: "efg") } + let(:content_file_hidden) { create(:content_file, standard: standard_shown, uid: "hij") } + + let!(:content_visibility_0) { create(:content_visibility, cohort_id: cohort.id, content_type: "Standard", content_uid: "abc") } + let!(:content_visibility_1) { create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: "hij") } + + let!(:lesson_visit_standard_hidden) { + create(:lesson_visit, + user: student, + cohort: cohort, + standard_uid: standard_hidden.uid, + block: release.block, + content_file_uid: content_file_standard_hidden.uid, + content_file_path: content_file_standard_hidden.path + ) + } + let!(:lesson_visit_content_file_hidden) { + create(:lesson_visit, + user: student, + cohort: cohort, + standard_uid: standard_shown.uid, + block: release.block, + content_file_uid: content_file_hidden.uid, + content_file_path: content_file_hidden.path + ) + } + let!(:lesson_visit_content_file_shown) { + create(:lesson_visit, + user: student, + cohort: cohort, + standard_uid: standard_shown.uid, + block: release.block, + content_file_uid: content_file_shown.uid, + content_file_path: content_file_shown.path + ) + } + + it "yields only the lesson visits which are not related to hidden content" do + expect(LessonVisit.visible_for(cohort.id)).to eq([lesson_visit_content_file_shown]) + end + + end +end diff --git a/scripts/spec/models/notification_spec.rb b/scripts/spec/models/notification_spec.rb new file mode 100644 index 0000000..d168ebb --- /dev/null +++ b/scripts/spec/models/notification_spec.rb @@ -0,0 +1,21 @@ +require "spec_helper" + +describe Notification do + subject { create(:notification) } + + it { is_expected.to validate_presence_of(:tagline) } + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_presence_of(:url) } + + describe ".unread" do + context "when there are both read and unread notifications" do + let!(:unread_notification) { create(:notification) } + let!(:read_notification) { create(:notification, :read) } + + it "only returns unread notifications" do + expect(described_class.unread).to include(unread_notification) + expect(described_class.unread).to_not include(read_notification) + end + end + end +end diff --git a/scripts/spec/models/pairing_spec.rb b/scripts/spec/models/pairing_spec.rb new file mode 100644 index 0000000..6f9157f --- /dev/null +++ b/scripts/spec/models/pairing_spec.rb @@ -0,0 +1,10 @@ +require "spec_helper" + +describe Pairing do + let(:cohort) { create(:cohort) } + subject { create(:pairing, cohort: cohort) } + + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_presence_of(:size) } + it { is_expected.to validate_presence_of(:groups) } +end \ No newline at end of file diff --git a/scripts/spec/models/performance_spec.rb b/scripts/spec/models/performance_spec.rb new file mode 100644 index 0000000..ba91d0b --- /dev/null +++ b/scripts/spec/models/performance_spec.rb @@ -0,0 +1,56 @@ +require "spec_helper" + +describe Performance do + describe "immutability" do + it "cannot be updated" do + expect { create(:performance).update(score: 2) }.to raise_error(ActiveRecord::ReadOnlyRecord) + end + + it "cannot be destroyed" do + expect { create(:performance).destroy }.to raise_error(ActiveRecord::ReadOnlyRecord) + end + end + + describe "denormalization" do + let(:standard) { create(:standard) } + subject { described_class.create!(cohort: create(:cohort), user: create(:user), standard: standard, score: 1) } + + it "sets block and standard_uid" do + expect(subject.block_id).to eq(standard.release.block_id) + expect(subject.standard_uid).to eq(standard.uid) + end + end + + describe ".scored" do + context "when there are performances with scores of 0 and not 0" do + let!(:included_performance) { create(:performance, score: 1) } + let!(:excluded_performance) { create(:performance, score: 0) } + + it "only returns performances with a score > 0" do + expect(described_class.scored).to eq([included_performance]) + end + end + end + + describe "validations" do + describe "score" do + it "must be 1, 2, or 3" do + nil_score = build(:performance, score: nil) + nil_score.save + expect(nil_score.errors[:score]).to include("is not included in the list") + + four_score = build(:performance, score: 4) + four_score.save + expect(four_score.errors[:score]).to include("is not included in the list") + + user = build(:user) + cohort = build(:cohort, user_id: user.id) + standard = build(:standard) + block = build(:block) + + valid_score = build(:performance, cohort: cohort, user: user, standard: standard, block: block, score: 2) + expect(valid_score).to be_valid + end + end + end +end diff --git a/scripts/spec/models/release_spec.rb b/scripts/spec/models/release_spec.rb new file mode 100644 index 0000000..c1a78c6 --- /dev/null +++ b/scripts/spec/models/release_spec.rb @@ -0,0 +1,149 @@ +require "spec_helper" + +describe Release do + subject { create(:release) } + + describe "validations" do + it { is_expected.to validate_presence_of(:block) } + it { is_expected.to validate_presence_of(:branch_name) } + it { is_expected.to validate_presence_of(:github_sha) } + + context "when the branch is default master" do + it { should_not validate_uniqueness_of(:branch_name) } + end + + context "when the branch is something other than master" do + let(:release) { create(:release, branch_name: "test-branch") } + + context "and there is another release with the same branch name attached to the block" do + it "should validate the uniqueness of the branch name in concert with the github sha" do + expect(release.valid?).to be true + create(:release, block: release.block, branch_name: "new-branch", github_sha: "somethingelse") + expect(release.valid?).to be true + rel = build(:release, block: release.block, branch_name: "new-branch", github_sha: "somethingelse") + expect(rel.valid?).to be false + end + end + + context "and there is another release with the same branch name attached another block" do + it "should not validate the uniqueness of the branch name" do + expect(release.valid?).to be true + create(:release, branch_name: "test-branch") + expect(release.valid?).to be true + end + end + end + end + + describe "#label" do + context "when on master branch" do + context "when there are releases for multiple blocks" do + let!(:block) { create(:block) } + + before do + create(:release) + create(:release) + create(:release, block: block) + end + + it "returns the release number relative to the associated block" do + expect(Release.where(block_id: block.id).last.label).to eq("v1") + end + + it "only itterates over master branch releases" do + create(:release, block: block, branch_name: "false") + create(:release, block: block) + expect(Release.where(block_id: block.id).count).to eq(3) + expect(Release.where(block_id: block.id).last.label).to eq("v2") + end + end + end + + context "when on a feature branch" do + let(:release) { create(:release, branch_name: "test-branch") } + + it "returns the branch name instead of version" do + expect(release.label).to eq(release.branch_name) + end + end + end + + describe "#previous_github_sha" do + let!(:block) { create(:block) } + + context "when there is a previous release" do + let!(:release_1) { create(:release, block: block, github_sha: "123") } + let!(:release_2) { create(:release, block: block) } + + it "returns the SHA of the previous release" do + expect(release_2.previous_github_sha).to eq("123") + end + end + + context "when there is not a previous release" do + let!(:release_1) { create(:release, block: block, github_sha: "123") } + + it "returns the SHA of the previous release" do + expect(release_1.previous_github_sha).to eq(nil) + end + end + end + + describe "#readme_text_html and #readme_link" do + context "when there is readme text" do + let(:release) { create(:release, readme_text: "test\ntest\ntest") } + + it "returns html friendly notes based on new lines" do + expect(release.readme_text_html).to eq("test
    test
    test
    ") + end + + it "returns a link to the readme on the branch the release is on" do + expect(release.readme_link).to eq("
    View Readme") + end + end + end + + describe "#successfully_synced?" do + let!(:release) { create(:release, state: Release::STATES[:failed]) } + + it "reporst unsuccessful syncing if the state is not success and it has no content" do + expect(release.successfully_synced?).to eq false + end + + it "reports unsuccessful syncing if the state is success but it has no content" do + release.update(state: Release::STATES[:success]) + expect(release.successfully_synced?).to eq false + end + + it "reports successful syncing if the state is success and it has content (at least one standard)" do + release.update(state: Release::STATES[:success]) + create(:standard, release: release) + expect(release.successfully_synced?).to eq true + end + end + + describe "#update_count" do + context "on master branch" do + let!(:release) { create(:release, branch_name: "master", github_sha: "abc") } + + it "calls out to ReleaseFinder.available_update_count_for" do + expect(ReleaseFinder).to receive(:available_update_count_for) do |args| + expect(args).to eq(release) + end.and_return(12) + expect(release.update_count).to eq(12) + end + end + + context "on feature branch" do + let!(:release) { create(:release, branch: "featurey", github_sha: "abc") } + end + end + + describe "#preveiw?" do + let!(:release) { create(:release, branch_name: "preview", github_sha: "preview-release21341234123412") } + + it "detects if the release is a preview release" do + expect(release.preview?).to be_truthy + end + end +end diff --git a/scripts/spec/models/resync_job_result_spec.rb b/scripts/spec/models/resync_job_result_spec.rb new file mode 100644 index 0000000..f4c8bf4 --- /dev/null +++ b/scripts/spec/models/resync_job_result_spec.rb @@ -0,0 +1,60 @@ +require "spec_helper" + +describe ResyncJobResult do + let(:cohort) { create(:cohort) } + + describe "#synced_before?" do + before do + create(:resync_job_result, created_at: 1.day.ago, status: 'success', edges: { cohort_id: cohort.id }, data: {course_config_url: "https://success.com"}) + end + it "returns true if course yaml has been synced before" do + expect(ResyncJobResult.synced_before?(cohort.id, "https://success.com")).to be_truthy + end + + it "returns false if course yaml has NOT been synced before" do + expect(ResyncJobResult.synced_before?(cohort.id, "https://false.com")).to be_falsey + end + end + + describe "#archive_latest_failure!" do + it "clears" do + r1 = create(:resync_job_result, created_at: 1.day.ago, status: 'failure', edges: { cohort_id: cohort.id }) + expect(ResyncJobResult.count).to eq(1) + expect(ResyncJobResult.find_latest(cohort.id)).to eq(r1) + + ResyncJobResult.archive_latest_failure!(cohort.id) + expect(ResyncJobResult.count).to eq(1) + + latest = ResyncJobResult.find_latest(cohort.id) + expect(latest.id).to eq(r1.id) + expect(latest.archived?).to eq(true) + end + + it "ignores successes" do + create(:resync_job_result, created_at: 1.day.ago, status: 'success', edges: { cohort_id: cohort.id }) + + original = ResyncJobResult.find_latest(cohort.id) + expect(original).to_not eq(nil) + + ResyncJobResult.archive_latest_failure!(cohort.id) + expect(ResyncJobResult.count).to eq(1) + expect(ResyncJobResult.find_latest(cohort.id)).to eq(original) + end + + it "is idempotent" do + r1 = create(:resync_job_result, created_at: 6.day.ago, status: 'failure', edges: { cohort_id: cohort.id }) + r2 = create(:resync_job_result, created_at: 3.day.ago, status: 'failure', edges: { cohort_id: cohort.id }) + + ResyncJobResult.archive_latest_failure!(cohort.id) + ResyncJobResult.archive_latest_failure!(cohort.id) + expect(ResyncJobResult.count).to eq(2) + + latest = ResyncJobResult.find_latest(cohort.id) + expect(latest.id).to eq(r2.id) + expect(latest.archived?).to eq(true) + + r1.reload + expect(r1.archived?).to eq(false) + end + end +end diff --git a/scripts/spec/models/section_spec.rb b/scripts/spec/models/section_spec.rb new file mode 100644 index 0000000..935a7f4 --- /dev/null +++ b/scripts/spec/models/section_spec.rb @@ -0,0 +1,7 @@ +require "spec_helper" + +describe Section do + subject { create(:section) } + + it { is_expected.to validate_presence_of(:title) } +end diff --git a/scripts/spec/models/standard_spec.rb b/scripts/spec/models/standard_spec.rb new file mode 100644 index 0000000..48fd5b3 --- /dev/null +++ b/scripts/spec/models/standard_spec.rb @@ -0,0 +1,52 @@ +require "spec_helper" + +describe Standard do + subject { create(:standard) } + + it { is_expected.to validate_presence_of(:release) } + it { is_expected.to validate_presence_of(:uid) } + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_presence_of(:description) } + it { is_expected.to validate_presence_of(:success_criteria) } + it { is_expected.to validate_presence_of(:position) } + + describe "immutability" do + it "cannot be updated" do + expect { subject.update(title: "foo") }.to raise_error(ActiveRecord::ReadOnlyRecord) + end + + it "cannot be destroyed" do + expect { subject.destroy }.to raise_error(ActiveRecord::ReadOnlyRecord) + end + end + + describe "#position_label" do + subject { create(:standard, position: 2) } + + it "returns the position + 1" do + expect(subject.position_label).to eq("Standard 3") + end + end + + describe "#checkpoint_content_file" do + subject { create(:standard, position: 2) } + + it "returns the checkpoint content file for standard" do + checkpoint = create(:content_file, :checkpoint, standard: subject) + create(:content_file, standard: subject) + + expect(subject.checkpoint_content_file).to eq(checkpoint) + end + end + + describe "visible_for" do + let!(:cohort) { create(:cohort) } + let!(:standard_1) { create(:standard, uid: "abc") } + let!(:standard_2) { create(:standard, uid: "123") } + let!(:standard_hidden) { create(:standard, uid: "xyz") } + let!(:content_visibility) { create(:content_visibility, cohort_id: cohort.id, content_type: "Standard", content_uid: "xyz") } + it "scopes content files for a given cohort base on content visibilities" do + expect(Standard.visible_for(cohort.id).order(id: :asc)).to eq([standard_1, standard_2]) + end + end +end diff --git a/scripts/spec/models/submitted_challenge_answer_spec.rb b/scripts/spec/models/submitted_challenge_answer_spec.rb new file mode 100644 index 0000000..2e0b7c2 --- /dev/null +++ b/scripts/spec/models/submitted_challenge_answer_spec.rb @@ -0,0 +1,189 @@ +require "spec_helper" + +describe SubmittedChallengeAnswer do + let!(:user) { create(:user) } + let!(:challenge) { create(:challenge, points: 2) } + let!(:content_file) { challenge.content_file } + + describe "validations" do + describe "checkpoint_submission_id constraint" do + it "does not allow two different different users to create a SubmittedChallengeAnswer for the same checkpoint_submission_id" do + sca = create(:submitted_challenge_answer, user: user) + expect { create(:submitted_challenge_answer, checkpoint_submission_id: sca.id) }.to raise_error( + ActiveRecord::StatementInvalid + ) + end + end + + describe "points_in_range" do + it "prevents negative points" do + sca = build(:submitted_challenge_answer, user: user, challenge: challenge, points: -1) + expect(sca.valid?).to eq(false) + expect(sca.errors.full_messages).to include("Points must be non-negative") + end + + it "prevents points from exceeding challenge points" do + sca = build(:submitted_challenge_answer, user: user, challenge: challenge, points: 3) + expect(sca.valid?).to eq(false) + expect(sca.errors.full_messages).to include("Points must not exceed challenge points") + end + end + end + + describe "denormalization" do + subject { create(:submitted_challenge_answer, user: user, challenge: challenge) } + + it "sets block and challenge_uid" do + expect(subject.block_id).to eq(content_file.standard.release.block_id) + expect(subject.challenge_uid).to eq(challenge.uid) + end + end + + describe "#set_graded_at" do + subject { create(:submitted_challenge_answer, status: :ungraded) } + + context "when status has been changed to a graded status" do + before { subject.update(status: :correct) } + + it "sets graded_at" do + expect(subject.reload.status.to_sym).to eq(:correct) + expect(subject.reload.graded_at).to_not eq(nil) + end + end + + context "when status has been changed to an ungraded status" do + before { subject.update(status: :failed) } + + it "does not set graded_at" do + expect(subject.reload.status.to_sym).to eq(:failed) + expect(subject.reload.graded_at).to eq(nil) + end + end + end + + describe "answered?" do + context "checkbox challenge" do + let!(:challenge) { create(:challenge, challenge_type: "checkbox") } + it "is not answered if the answer is '[]'" do + sca = create(:submitted_challenge_answer, user: user, challenge: challenge, answer: "[]") + expect(sca.answered?).to eq false + end + + it "is not answered if the answer is '[]'" do + sca = create(:submitted_challenge_answer, user: user, challenge: challenge, answer: "['anything']") + expect(sca.answered?).to eq true + end + + end + context "challenge has placeholder and is testable" do + let!(:challenge) { create(:challenge, challenge_type: "code-snippet", raw_json: { placeholder: "here" } ) } + it "is not answered if the answer equals the placeholder" do + sca = create(:submitted_challenge_answer, user: user, challenge: challenge, answer: "here") + expect(sca.answered?).to eq false + end + it "is answered if the answer does not equal the placeholder" do + sca = create(:submitted_challenge_answer, user: user, challenge: challenge, answer: "there") + expect(sca.answered?).to eq true + end + + end + context "no placeholder and is testable" do + let!(:challenge) { create(:challenge, challenge_type: "code-snippet") } + it "is not answered if the answer is blank" do + sca = create(:submitted_challenge_answer, user: user, challenge: challenge, answer: "") + expect(sca.answered?).to eq false + end + it "is answered if the answer is not blank" do + sca = create(:submitted_challenge_answer, user: user, challenge: challenge, answer: "there") + expect(sca.answered?).to eq true + end + end + + context "has placeholder for non-testable" do + let!(:challenge) { create(:challenge, challenge_type: "short-answer", raw_json: { placeholder: "here" }) } + it "is not answered if the answer is blank" do + sca = create(:submitted_challenge_answer, user: user, challenge: challenge, answer: "") + expect(sca.answered?).to eq false + end + it "is answered if the answer matches the placeholder" do + sca = create(:submitted_challenge_answer, user: user, challenge: challenge, answer: "here") + expect(sca.answered?).to eq true + end + end + end + + describe ".latest_for_user_id_challenge_id" do + it "returns latest submitted challenge answer for the given user and challenge ids" do + create(:submitted_challenge_answer, user: user, challenge: challenge, created_at: 1.day.ago) + submitted_challenge_answer = create(:submitted_challenge_answer, user: user, challenge: challenge) + create(:submitted_challenge_answer, user: user) + create(:submitted_challenge_answer, challenge: challenge) + + expect(described_class.latest_for_user_id_challenge_id(user.id, challenge.id)).to eq(submitted_challenge_answer) + end + end + + describe "scope#latest_for_user_id_challenge_uid" do + it "returns latest submitted challenge answer for the given user and challenge ids" do + create(:submitted_challenge_answer, user: user, challenge: challenge, created_at: 1.day.ago) + create(:submitted_challenge_answer, user: user, challenge: challenge) + + challenge_with_same_uid = create(:challenge, uid: challenge.uid) + latest_submitted_challenge_answer_by_uid = create(:submitted_challenge_answer, user: user, challenge: challenge_with_same_uid) + + challenge_answer_with_diff_challenge_uid = create(:submitted_challenge_answer, user: user) + create(:submitted_challenge_answer, challenge: challenge) + + expect(described_class.where(user_id: user.id).latest_for_user_id_challenge_uid.to_a).to match_array([latest_submitted_challenge_answer_by_uid, challenge_answer_with_diff_challenge_uid]) + end + end + + describe ".all_for_user_id_challenge_id" do + it "returns all submitted challenge answers for the given user and content file id" do + old_submitted_challenge_answer = create(:submitted_challenge_answer, user: user, challenge: challenge, created_at: 2.days.ago) + new_submitted_challenge_answer = create(:submitted_challenge_answer, user: user, challenge: challenge) + create(:submitted_challenge_answer, user: user) + create(:submitted_challenge_answer, challenge: challenge) + + expect(described_class.all_for_user_id_challenge_id(user.id, challenge.id)).to eq([old_submitted_challenge_answer, new_submitted_challenge_answer]) + end + end + + describe ".count_for_content_file_id" do + before do + @cohort = create(:cohort) + @content_file = create(:content_file) + @challenge = create(:challenge, content_file: @content_file, uid: "foo") + 5.times { |_i| create(:cohort_user, :student, cohort: @cohort) } + end + + context "when students have submitted challenge answers" do + before do + create(:submitted_challenge_answer, user_id: @cohort.student_ids.first, challenge: @challenge) + create(:submitted_challenge_answer, user_id: @cohort.student_ids.last, challenge: @challenge) + end + + it "returns the count of submitted challenge answers for the given content file from the students" do + expect(described_class.count_for_content_file_id(@content_file.id, @cohort.student_ids)).to eq(2) + end + + it "returns the count of submitted challenge answers for the given content file from the student when a new release is created" do + new_content_file_with_same_challenge_uid = create(:challenge, uid: "foo").content_file_id + expect(described_class.count_for_content_file_id(new_content_file_with_same_challenge_uid, @cohort.student_ids)).to eq(2) + end + end + + context "when students havent submitted challenge answers" do + it "returns zero" do + expect(described_class.count_for_content_file_id(@content_file_id, @cohort.student_ids)).to eq(0) + end + end + end + + describe "#formatted_test_results" do + it "replaces newlines with
    tags and renders safe html" do + sca = create(:submitted_challenge_answer, test_results: "\\n \\n doSomethingFunction \n 3 passing \n 1 failing \\n \\n") + expect(sca.formatted_test_results).to eq("

    doSomethingFunction \n 3 passing \n 1 failing

    ") + end + end +end diff --git a/scripts/spec/models/user_last_viewed_standard_path_spec.rb b/scripts/spec/models/user_last_viewed_standard_path_spec.rb new file mode 100644 index 0000000..81f3daf --- /dev/null +++ b/scripts/spec/models/user_last_viewed_standard_path_spec.rb @@ -0,0 +1,21 @@ +require "spec_helper" + +describe UserLastViewedStandardPath do + subject { create(:user_last_viewed_standard_path) } + + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:block) } + it { is_expected.to validate_presence_of(:standard_uid) } + it { is_expected.to validate_presence_of(:content_file_path) } + + it "validates the uniqueness of standard_uid and block_id within the scope of the user" do + duplicate = described_class.create( + user: subject.user, + block_id: subject.block_id, + standard_uid: subject.standard_uid, + content_file_path: "foo" + ) + + expect(duplicate.errors[:standard_uid]).to include("has already been taken") + end +end diff --git a/scripts/spec/models/user_spec.rb b/scripts/spec/models/user_spec.rb new file mode 100644 index 0000000..191e9e6 --- /dev/null +++ b/scripts/spec/models/user_spec.rb @@ -0,0 +1,336 @@ +require "spec_helper" + +describe User do + subject { create(:user) } + + it { is_expected.to validate_presence_of(:email) } + it { is_expected.to validate_uniqueness_of(:uid) } + + describe "#sandbox" do + it "returns the users sandbox cohort" do + admin = create(:user, :admin) + sb = create(:cohort, user_id: admin.id, sandbox: true) + expect(admin.sandbox.uid).to eq(sb.uid) + end + + it "creates a new sandbox is user doesn't have one." do + admin = create(:user, :admin) + sb = admin.sandbox + expect(sb).to_not eq(nil) + expect(Cohort.last.id).to eq(sb.id) + expect(sb.uid).to eq(Cohort.last.uid) + expect(sb.name).to eq("Preview") + expect(sb.sandbox).to eq(true) + expect(sb.user_id).to eq(admin.id) + expect(CohortUser.last.user_id).to eq(sb.user_id) + expect(CohortUser.last.cohort_id).to eq(sb.id) + expect(CohortUser.last.roles).to eq(["learn.instructor", "forge.instructor"]) + end + end + + describe "#admin?" do + it "returns if a user is a forge admin" do + expect(create(:user, :admin).admin?).to eq true + end + end + + describe "#onboarder?" do + it "returns if a user is an onboarder" do + expect(create(:user, :product_admin).onboarder?).to eq true + end + end + + describe "#blocks_manager?" do + it "returns if a user is a forge blocks manager" do + expect(create(:user, :blocks_manager).blocks_manager?).to eq true + end + end + + describe "#full_name" do + it "returns a full name" do + expect(create(:user, first_name: "Bob", last_name: "Wills").full_name).to eq("Bob Wills") + end + end + + describe "#initials" do + it "returns upcased initials" do + user = described_class.new(first_name: "Bob", last_name: "Wills") + expect(user.initials).to eq("BW") + end + end + + describe "#instructor_or_admin?" do + let(:cohort_1) { create(:cohort) } + let(:cohort_2) { create(:cohort) } + + it "returns true if a instructor belongs to the given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.instructor_or_admin?(cohort_1.id)).to be true + end + + it "returns true if a admin" do + expect(create(:user, :admin).instructor_or_admin?(cohort_1.id)).to be true + end + + it "returns false if a student belongs to the given cohort" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.instructor_or_admin?(cohort_1.id)).to be false + end + + it "returns false if a instructor does not belong to the given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.instructor_or_admin?(cohort_2.id)).to be false + end + + it "returns false if a the cohort is nil" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.instructor_or_admin?(nil)).to be false + end + + it "returns false if a the user is nil" do + expect(described_class.new.instructor_or_admin?(cohort_1)).to be false + end + end + + describe "#onboarder_or_admin?" do + let(:cohort_1) { create(:cohort) } + let(:cohort_2) { create(:cohort) } + + it "returns true if a product admin belongs to the given cohort" do + expect(create(:user, :product_admin).onboarder_or_admin?(cohort_1.id)).to be true + end + + it "returns true if a admin" do + expect(create(:user, :admin).onboarder_or_admin?(cohort_1.id)).to be true + end + + it "returns true if an onboarder" do + expect(create(:user, :product_admin).onboarder_or_admin?(cohort_1.id)).to be true + end + + it "returns false if cohort is nil" do + expect(create(:user, :blocks_manager).onboarder_or_admin?(nil)).to be false + end + + it "returns false if a the user is nil" do + expect(described_class.new.onboarder_or_admin?(cohort_1)).to be false + end + end + + describe "#instructor_of?" do + let(:cohort_1) { create(:cohort) } + let(:cohort_2) { create(:cohort) } + + it "returns true if a instructor belongs to the given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.instructor_of?(cohort_1.id)).to be true + end + + it "returns false if a student belongs to the given cohort" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.instructor_of?(cohort_1.id)).to be false + end + + it "returns false if a instructor does not belong to the given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.instructor_of?(cohort_2.id)).to be false + end + + it "returns false if a the cohort is nil" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.instructor_of?(nil)).to be false + end + + it "returns false if a the instructor is nil" do + expect(described_class.new.student_of?(cohort_1.id)).to be false + end + end + + describe "#student_of?" do + let(:cohort_1) { create(:cohort) } + let(:cohort_2) { create(:cohort) } + + it "returns true if a student belongs to the given cohort" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.student_of?(cohort_1.id)).to be true + end + + it "returns false if a instructor belongs to the given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.student_of?(cohort_1.id)).to be false + end + + it "returns false if a student does not belong to the given cohort" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.student_of?(cohort_2.id)).to be false + end + + it "returns false if a the cohort is nil" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.student_of?(nil)).to be false + end + + it "returns false if a the student is nil" do + expect(described_class.new.student_of?(cohort_1)).to be false + end + end + + describe "#signed_in?" do + context "never before" do + it "returns false" do + expect(subject.signed_in?).to be false + end + end + context "has viewed cohort" do + before { subject.update(last_viewed_cohort_id: 2)} + + it "returns true" do + expect(subject.signed_in?).to be true + end + end + end + + describe "#student_or_instructor_of?" do + let(:cohort_1) { create(:cohort) } + let(:cohort_2) { create(:cohort) } + + it "returns true if a student belongs to the given cohort" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.student_or_instructor_of?(cohort_1.id)).to be true + end + + it "returns true if a instructor belongs to the given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.student_or_instructor_of?(cohort_1.id)).to be true + end + + it "returns false if a student does not belong to the given cohort" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.student_or_instructor_of?(cohort_2.id)).to be false + end + + it "returns false if a instructor does not belong to the given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.student_or_instructor_of?(cohort_2.id)).to be false + end + + it "returns false if a the cohort is nil" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.student_or_instructor_of?(nil)).to be false + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.student_or_instructor_of?(nil)).to be false + end + + it "returns false if a the student is nil" do + expect(described_class.new.student_or_instructor_of?(cohort_1.id)).to be false + end + end + + describe "#student_instructor_or_admin_of?" do + let(:cohort_1) { create(:cohort) } + let(:cohort_2) { create(:cohort) } + + it "returns true if a student belongs to the given cohort" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.student_instructor_or_admin_of?(cohort_1.id)).to be true + end + + it "returns true if a instructor belongs to the given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.student_instructor_or_admin_of?(cohort_1.id)).to be true + end + + it "returns false if a student does not belong to the given cohort" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.student_instructor_or_admin_of?(cohort_2.id)).to be false + end + + it "returns false if a instructor does not belong to the given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.student_instructor_or_admin_of?(cohort_2.id)).to be false + end + + it "returns false if a the cohort is nil" do + expect(create(:cohort_user, :student, cohort: cohort_1).user.student_instructor_or_admin_of?(nil)).to be false + expect(create(:cohort_user, :instructor, cohort: cohort_1).user.student_instructor_or_admin_of?(nil)).to be false + end + + it "returns true if the user is an admine" do + expect(create(:user, :admin).student_instructor_or_admin_of?(cohort_1.id)).to be true + end + end + + describe "#product_admin_of?" do + let(:cohort_1) { create(:cohort) } + let(:cohort_2) { create(:cohort) } + + it "returns true if the user has the auth.product_admin role for a given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1, roles: [CohortUser::ROLES[:product_admin]]).user.product_admin_of?(cohort_1.id)).to be true + end + + it "returns false if the user does not have the auth.product_admin role for a given cohort" do + expect(create(:cohort_user, :instructor, cohort: cohort_1, roles: [CohortUser::ROLES[:instructor]]).user.product_admin_of?(cohort_1.id)).to be false + end + end + + describe "#role?" do + before do + subject.update(roles: [User::ROLES[:product_admin], "futzy.butzy"]) + end + + it "returns true if the user has at least one of the roles given" do + expect(subject.role?(User::ROLES[:product_admin], "donot.have")).to eq true + end + + it "returns false if the user has none of the roles given" do + expect(subject.role?("donot.have")).to eq false + end + end + + describe "#notify" do + it "creates a notification" do + subject.notify(tagline: "Tag", title: "Title", url: "http://www.google.com", description: "Description") + + new_notification = subject.notifications.last + expect(new_notification.tagline).to eq("Tag") + expect(new_notification.title).to eq("Title") + expect(new_notification.url).to eq("http://www.google.com") + expect(new_notification.description).to eq("Description") + end + end + + describe "#checkpoint_submissions_count" do + let(:block) { create(:block) } + let(:cohort) { create(:cohort) } + let!(:checkpoint_submission_rejected) { + checkpoint = create( + :checkpoint_submission, + state: 'needs_review', + user_id: subject.id, + content_file_block_id: block.id, + cohort_id: cohort.id, + content_file_uid: 'abc123' + ) + checkpoint.update(state: 'rejected', grader_id: create(:user).id) + checkpoint + } + let!(:checkpoint_submission_needs_review) { + create( + :checkpoint_submission, + state: 'needs_review', + user_id: subject.id, + content_file_block_id: block.id, + cohort_id: cohort.id, + content_file_uid: 'abc123' + ) + } + let!(:checkpoint_submission_done) { + create( + :checkpoint_submission, + state: 'done', + user_id: subject.id, + content_file_block_id: block.id, + cohort_id: cohort.id, + content_file_uid: 'abc123' + ) + } + let!(:checkpoint_submission_done_bad_block) { + create( + :checkpoint_submission, + state: 'done', + user_id: subject.id, + content_file_block_id: 999999999, + cohort_id: cohort.id, + content_file_uid: 'abc123' + ) + } + it "returns the number of times user has attempted a checkpoint" do + expect(subject.checkpoint_submissions_count(cohort.id, block.id, 'abc123')).to eq(2) + end + end + + describe "#auth_edit_url" do + # This path no longer exists + xit "returns the correct url" do + expect(subject.auth_edit_url).to eq("#{Rails.application.secrets.auth_url}/admin/users/#{subject.uid}/edit") + end + end +end diff --git a/scripts/spec/policies/cohort_policy_spec.rb b/scripts/spec/policies/cohort_policy_spec.rb new file mode 100644 index 0000000..39c9811 --- /dev/null +++ b/scripts/spec/policies/cohort_policy_spec.rb @@ -0,0 +1,41 @@ +require "spec_helper" + +describe CohortPolicy::Scope do + describe "#resolve" do + let(:cohort) { create(:cohort) } + + subject { CohortPolicy::Scope.new(user, Cohort).resolve } + + context "when the user is an admin" do + let(:user) { create(:user, :admin) } + + it "returns all cohorts" do + expect(subject).to include(cohort) + end + end + + context "when the user is an instructor of the cohort" do + let(:user) { create(:cohort_user, :instructor, cohort: cohort).user } + + it "returns the cohort" do + expect(subject).to include(cohort) + end + end + + context "when the user is a student in the cohort" do + let(:user) { create(:cohort_user, :student, cohort: cohort).user } + + it "returns the cohort" do + expect(subject).to include(cohort) + end + end + + context "when the user is a student in the cohort" do + let(:user) { create(:user) } + + it "returns the cohort" do + expect(subject).to be_empty + end + end + end +end diff --git a/scripts/spec/policies/content_file_policy_spec.rb b/scripts/spec/policies/content_file_policy_spec.rb new file mode 100644 index 0000000..4db3141 --- /dev/null +++ b/scripts/spec/policies/content_file_policy_spec.rb @@ -0,0 +1,65 @@ +require "spec_helper" + +describe ContentFilePolicy::Scope do + let(:cohort_block_content_file) { create_content_file } + let!(:cohort) { cohort_block_content_file[0] } + let!(:block) { cohort_block_content_file[1] } + let!(:content_file) { cohort_block_content_file[2] } + + + describe "#resolve" do + subject { ContentFilePolicy.new(user, ContentFile).show? } + context "given an admin" do + let(:user) { create(:user, :admin) } + it "returns all content files" do + expect(subject).to eq(true) + end + end + + context "non admin" do + let(:user) { create(:cohort_user, :student, cohort: cohort).user } + let(:seperate_cohort_block_content_file) { create_content_file } + it "returns all content files" do + expect(subject).to eq(false) + end + end + end + + context "when using visible_for_cohort" do + subject { ContentFilePolicy.new(user, content_file).show?(visible_for_cohort: cohort) } + + context "when a resource content file" do + describe "default scoping is not used" do + + let(:content_file) { create(:content_file, :resource, standard: cohort.visible_standards.last) } + let(:user) { create(:cohort_user, :student, cohort: cohort).user } + + it "properly authorizes the content file of type resource" do + expect(subject).to eq(true) + end + end + end + + context "when an instructor content file" do + describe "default scoping is not used" do + + let(:content_file) { create(:content_file, content_file_type: ContentFile::TYPES[:instructor], standard: cohort.visible_standards.last) } + context "when student" do + let(:user) { create(:cohort_user, :student, cohort: cohort).user } + + it "properly does not authorizes the content file of type instructor" do + expect(subject).to eq(false) + end + end + + context "when instructor" do + let(:user) { create(:cohort_user, :instructor, cohort: cohort).user } + + it "properly does not authorizes the content file of type instructor" do + expect(subject).to eq(true) + end + end + end + end + end +end diff --git a/scripts/spec/policies/user_policy_spec.rb b/scripts/spec/policies/user_policy_spec.rb new file mode 100644 index 0000000..5b1a02b --- /dev/null +++ b/scripts/spec/policies/user_policy_spec.rb @@ -0,0 +1,42 @@ +require "spec_helper" + +describe UserPolicy::Scope do + describe "#resolve" do + let(:cohort) { create(:cohort) } + + subject { UserPolicy::Scope.new(user, User).resolve } + + context "given an admin" do + let(:user) { create(:user, :admin) } + let!(:return_user) { create(:user) } + it "returns all users" do + expect(subject).to include(user) + expect(subject).to include(return_user) + end + end + + context "given an instructor" do + let!(:cohort) { create(:cohort) } + let!(:user) { create(:cohort_user, :instructor, cohort: cohort).user } + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:excluded_student) { create(:cohort_user, :student).user } + it "returns the instructor and users they teach" do + expect(subject).to include(student) + expect(subject).to include(user) + expect(subject).to_not include(excluded_student) + end + end + + context "given a student" do + let!(:cohort) { create(:cohort) } + let!(:user) { create(:cohort_user, :student, cohort: cohort).user } + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:excluded_student) { create(:cohort_user, :student).user } + it "returns on themselves" do + expect(subject).to include(user) + expect(subject).to_not include(student) + expect(subject).to_not include(excluded_student) + end + end + end +end diff --git a/scripts/spec/presenters/block_presenter/for_cohort_releases_new_spec.rb b/scripts/spec/presenters/block_presenter/for_cohort_releases_new_spec.rb new file mode 100644 index 0000000..aa85c0a --- /dev/null +++ b/scripts/spec/presenters/block_presenter/for_cohort_releases_new_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +describe BlockPresenter::ForCohortReleasesNew do + let(:release) { create(:release, created_at: DateTime.yesterday) } + let(:other_cohort) { create(:cohort) } + let(:cohort_1) { create(:cohort) } + let(:cohort_2) { create(:cohort) } + let(:cohort_3) { create(:cohort) } + + before do + create(:cohort_release, release: release, cohort: cohort_1) + create(:cohort_release, release: release, cohort: cohort_2) + create(:cohort_release, release: release, cohort: cohort_3) + end + + subject { described_class.new(block_release: release) } + + it "assigns the necessary attributes" do + expect(subject.cohort_release_cohorts.length).to eq(3) + expect(subject.cohort_release_cohorts).to match_array([cohort_1, cohort_2, cohort_3]) + expect(subject.last_update).to eq(release.created_at.strftime("%-m/%-d/%y")) + expect(subject.name).to eq(release.block.title) + expect(subject.release_id).to eq(release.id) + expect(subject.repo_name).to eq(release.block.repo_name) + expect(subject.repo_url).to eq(release.block.git_url) + end +end diff --git a/scripts/spec/presenters/block_presenter/for_cohort_releases_spec.rb b/scripts/spec/presenters/block_presenter/for_cohort_releases_spec.rb new file mode 100644 index 0000000..b995771 --- /dev/null +++ b/scripts/spec/presenters/block_presenter/for_cohort_releases_spec.rb @@ -0,0 +1,77 @@ +require "spec_helper" + +describe BlockPresenter::ForCohortReleases do + let(:release_1) { create(:release, created_at: DateTime.yesterday) } + let(:release_2) { create(:release, created_at: DateTime.yesterday, github_sha: "pre-sha-implementation", block: release_1.block) } + let(:release_3) { create(:release, created_at: DateTime.yesterday, github_sha: "pre-sha-implementation", block: release_1.block) } + + before do + create(:cohort_release, release: release_2) + create(:cohort_release, release: release_2) + create(:cohort_release, release: release_2) + end + + subject { described_class.new(release: release_2, current_release: release_2) } + + it "assigns the necessary attributes" do + expect(subject.cohorts_using_release.length).to eq(3) + expect(subject.last_update).to eq(release_2.created_at) + expect(subject.name).to eq(release_2.block.title) + expect(subject.release_id).to eq(release_2.id) + expect(subject.repo_name).to eq(release_2.block.repo_name) + expect(subject.repo_url).to eq(release_2.block.git_url) + expect(subject.commit_url).to eq("#{release_2.block.git_url}/tree/#{release_2.github_sha}") + expect(subject.github_sha).to eq(release_2.github_sha) + expect(subject.diff_url).to eq(nil) + end + + context ".releases_for_cohort" do + let(:another_release) { create(:release, block: release_2.block, github_sha: "abc123") } + let(:cohort_release) { create(:cohort_release, release: release_2) } + + subject { described_class.releases_for_cohort(releases: [release_2, another_release], cohort: cohort_release.cohort, block_id: release_2.block_id) } + + it "finds the current release for cohort, also initializes presenters" do + expect(subject.first.current).to eq(true) + expect(subject.last.current).to eq(false) + end + end + + context "#diff_url" do + it "returns nil when the current sha is the comparing sha" do + expect(described_class.new(release: release_2, current_release: release_2).diff_url).to eq(nil) + end + + it "returns nil when the current sha is pre-sha-implementation" do + expect(described_class.new(release: release_2, current_release: create(:release, github_sha: "pre-sha-implementation", block: release_2.block)).diff_url).to eq(nil) + end + + it "returns nil when the comparing sha is pre-sha-implementation" do + pre_sha_release = create(:release, github_sha: "pre-sha-implementation", block: release_2.block) + expect(described_class.new(release: pre_sha_release, current_release: release_2).diff_url).to eq(nil) + end + + it "builds the diff url when the current sha is newer than the comparing sha" do + comparing_release = create(:release, created_at: 2.days.ago, github_sha: "4f48644e307f4664f0de878c93d4a0b02163ea2c", block: release_1.block) + expectation = "#{comparing_release.block.git_url}/compare/#{comparing_release.github_sha}...#{release_1.github_sha}" + expect(described_class.new(release: comparing_release, current_release: release_1).diff_url).to eq(expectation) + end + + it "builds the diff url when the current sha is older than the comparing sha" do + comparing_release = create(:release, created_at: Time.zone.today, github_sha: "4f48644e307f4664f0de878c93d4a0b02163ea2c", block: release_1.block) + expectation = "#{comparing_release.block.git_url}/compare/#{release_1.github_sha}...#{comparing_release.github_sha}" + expect(described_class.new(release: comparing_release, current_release: release_1).diff_url).to eq(expectation) + end + end + + context "when many releases exist with the same 'pre-sha' sha" do + it "does not double assign the current variable to true" do + first = described_class.new(release: release_2, current_release: release_2) + second = described_class.new(release: release_3, current_release: release_3) + expect(first.current).to eq(true) + expect(second.current).to eq(true) + third = described_class.new(release: release_1, current_release: release_2) + expect(third.current).to eq(false) + end + end +end diff --git a/scripts/spec/presenters/block_presenter/for_releases_spec.rb b/scripts/spec/presenters/block_presenter/for_releases_spec.rb new file mode 100644 index 0000000..cb0b063 --- /dev/null +++ b/scripts/spec/presenters/block_presenter/for_releases_spec.rb @@ -0,0 +1,18 @@ +require "spec_helper" + +describe BlockPresenter::ForReleases do + describe "#cohorts_using_release" do + let(:cohort_1) { create(:cohort) } + let(:cohort_2) { create(:cohort) } + + let(:available_block) { create(:block, title: "a") } + let!(:block_release) { create(:release, block: available_block) } + let!(:first_cohort_release) { create(:cohort_release, cohort: cohort_1, release: block_release) } + + let!(:second_cohort_release) { create(:cohort_release, cohort: cohort_2, release: block_release) } + + it "should yield the count of cohorts using any release of the block" do + expect(described_class.new(release: block_release).cohorts_using_release.count).to eq 2 + end + end +end diff --git a/scripts/spec/presenters/challenge_with_submitted_challenge_answers_presenter_spec.rb b/scripts/spec/presenters/challenge_with_submitted_challenge_answers_presenter_spec.rb new file mode 100644 index 0000000..b5fd825 --- /dev/null +++ b/scripts/spec/presenters/challenge_with_submitted_challenge_answers_presenter_spec.rb @@ -0,0 +1,169 @@ +require "spec_helper" + +describe ChallengeWithSubmittedChallengeAnswersPresenter do + let(:user) { create(:user) } + let(:cohort) { create(:cohort) } + let(:challenge) { create(:challenge, { hints: ["hint 1", "hint 2"] }.merge(challenge_attributes)) } + let(:challenge_attributes) { {} } + + context "challenge yields an option" do + let(:presenter) { described_class.new(cohort, challenge, [], false, user_context: user) } + let(:challenge_attributes) { { challenge_type: Challenge::TYPES[:multiple_choice], raw_json: { "options": ["\r", "wrong", "right", "wrong again", "```wrong again again```", ""] } } } + + it "yields github markdown parsed options for options in raw_json while excluding blanks and carriage returns" do + expect(presenter.options).to eq [ + { rendered_option: "

    wrong

    \n", option: "wrong" }, + { rendered_option: "

    right

    \n", option: "right" }, + { rendered_option: "

    wrong again

    \n", option: "wrong again" }, + { rendered_option: "

    wrong again again

    \n", option: "```wrong again again```" } + ] + end + end + + context "can_view_status" do + let(:presenter) { described_class.new(cohort, challenge, [], false, is_checkpoint: false, can_view_status: false, user_context: user) } + + it "passes the value through" do + expect(presenter.can_view_status).to eq(false) + end + end + + context "can_view_explanation" do + let(:presenter) { described_class.new(cohort, challenge, [], false, can_view_explanation: false, user_context: user) } + + it "passes the value through" do + expect(presenter.can_view_explanation).to eq(false) + end + end + + context "local snippets" do + let(:presenter) { described_class.new(cohort, challenge, [], false, can_view_explanation: false, user_context: user) } + let(:challenge_attributes) { { challenge_type: Challenge::TYPES[:local_snippet], raw_json: { "tests" => "some code" } } } + + it "passes the value through" do + expect(presenter.tests).to eq("some code") + end + end + + context "submission exists" do + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, status: status, user: user) } + let(:presenter) { described_class.new(cohort, challenge, [submitted_challenge_answer], false, user_context: user) } + + context "status is incorrect" do + let(:status) { "incorrect" } + + it "yields challenge details with submitted_challenge_answer with no answer" do + expect(presenter.id).to eq challenge.id + expect(presenter.submitted_challenge_answer_presenters.to_json).to eq [SubmittedChallengeAnswerPresenter.new(submitted_challenge_answer, false)].to_json + expect(presenter.challenge_type).to eq challenge.challenge_type + expect(presenter.title).to eq challenge.title + expect(presenter.decimal).to eq challenge.decimal + expect(presenter.points).to eq challenge.points + expect(presenter.question).to eq challenge.question + expect(presenter.language).to eq challenge.language + expect(presenter.placeholder).to eq challenge.raw_json["placeholder"] + expect(presenter.options).to eq nil + expect(presenter.number_of_hints).to eq 2 + expect(presenter.can_check_answer).to eq true + expect(presenter.can_view_status).to eq true + expect(presenter.can_view_explanation).to eq true + end + end + + context "status is correct" do + let(:status) { "correct" } + + it "yields challenge details with submitted_challenge_answer with an answer" do + expect(presenter.id).to eq challenge.id + expect(presenter.submitted_challenge_answer_presenters.to_json).to eq [SubmittedChallengeAnswerPresenter.new(submitted_challenge_answer, false)].to_json + expect(presenter.challenge_type).to eq challenge.challenge_type + expect(presenter.title).to eq challenge.title + expect(presenter.decimal).to eq challenge.decimal + expect(presenter.question).to eq challenge.question + expect(presenter.language).to eq challenge.language + expect(presenter.placeholder).to eq challenge.raw_json["placeholder"] + expect(presenter.options).to eq nil + expect(presenter.number_of_hints).to eq 2 + end + end + end + + context "submitted_challenge_answer does not exist" do + let(:presenter) { described_class.new(cohort, challenge, [], false, user_context: user) } + it "yields challenge details with no submitted_challenge_answer info" do + expect(presenter.id).to eq challenge.id + expect(presenter.challenge_type).to eq challenge.challenge_type + expect(presenter.title).to eq challenge.title + expect(presenter.explanation).to eq nil + expect(presenter.decimal).to eq challenge.decimal + expect(presenter.question).to eq challenge.question + expect(presenter.language).to eq challenge.language + expect(presenter.placeholder).to eq challenge.raw_json["placeholder"] + expect(presenter.options).to eq nil + expect(presenter.number_of_hints).to eq 2 + end + end + + context "code type challenge" do + let(:presenter) { described_class.new(cohort, challenge, [], false, user_context: user) } + let(:challenge_attributes) { { challenge_type: Challenge::TYPES[:code_snippet], language: "Javascript" } } + it "yields code type" do + expect(presenter.language).to eq "Javascript" + end + end + + context "challenge is in a checkpoint" do + let(:presenter) { described_class.new(cohort, challenge, [], false, is_checkpoint: true, user_context: user) } + + it "does not set student challenge link paths" do + expect(presenter.next_student).to eq "" + expect(presenter.previous_student).to eq "" + end + + context "and is a code challenge" do + let(:challenge_attributes) { { challenge_type: Challenge::TYPES[:code_snippet], language: "Javascript" } } + + it "sets can check answer to true" do + expect(challenge.code_snippet?).to eq true + expect(presenter.language).to eq "Javascript" + expect(presenter.can_check_answer).to eq true + end + end + + context "and is a custom snippet challenge" do + let(:challenge_attributes) { { challenge_type: Challenge::TYPES[:custom_snippet], language: "Javascript" } } + + it "sets can check answer to true" do + expect(challenge.custom_snippet?).to eq true + expect(presenter.language).to eq "Javascript" + expect(presenter.can_check_answer).to eq true + end + end + + context "and is a testable project challenge" do + let(:challenge_attributes) { { challenge_type: Challenge::TYPES[:testable_project] } } + + it "sets can check answer to true" do + expect(challenge.testable_project?).to eq true + expect(presenter.can_check_answer).to eq true + end + end + + context "and is not a code or project challenge" do + let(:challenge_attributes) { { challenge_type: Challenge::TYPES[:multiple_choice] } } + + it "sets can_check_answer to false" do + expect(challenge.multiple_choice?).to eq true + expect(presenter.can_check_answer).to eq true + end + end + end + + context "user is an instructor or admin" do + let(:presenter) { described_class.new(cohort, challenge, [], true, user_context: user) } + + it "provides all hints in the presenter" do + expect(presenter.hints).to eq(challenge.hints) + end + end +end diff --git a/scripts/spec/presenters/checkpoint_submission_presenter/for_index_spec.rb b/scripts/spec/presenters/checkpoint_submission_presenter/for_index_spec.rb new file mode 100644 index 0000000..54ef959 --- /dev/null +++ b/scripts/spec/presenters/checkpoint_submission_presenter/for_index_spec.rb @@ -0,0 +1,161 @@ +require "spec_helper" + +describe CheckpointSubmissionPresenter::ForIndex do + let(:cohort) { create(:cohort) } + let(:release) { create(:release) } + let(:content_file) { create(:content_file, :checkpoint, standard: create(:standard, release: release)) } + let(:challenge) { create(:challenge, content_file: content_file) } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Arthur", last_name: "Aardvark")).user } + + subject { described_class.new(content_file: content_file, cohort: cohort) } + before { create(:cohort_release, cohort: cohort, release: release) } + + context "when there are students in multiple states for a checkpoint" do + let(:checkpoint_submission) { create_checkpoint_submission(student, challenge, cohort: cohort) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: student, challenge: challenge, checkpoint_submission: checkpoint_submission) } + + let!(:no_submission_student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Amy", last_name: "Derpington")).user } + + let(:rejected_student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Peter", last_name: "Piper")).user } + let(:rejected_checkpoint_submission) { create_checkpoint_submission(rejected_student, challenge, cohort: cohort) } + let!(:rejected_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: rejected_student, challenge: challenge, checkpoint_submission: rejected_checkpoint_submission) } + + let(:done_student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Joe", last_name: "K")).user } + let(:done_checkpoint_submission) { create_checkpoint_submission(done_student, challenge, cohort: cohort) } + let!(:done_submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, user: done_student, challenge: challenge, checkpoint_submission: done_checkpoint_submission) } + + before do + rejected_checkpoint_submission.update(state: CheckpointSubmission::STATES[:rejected], grader: instructor) + done_checkpoint_submission.update(state: CheckpointSubmission::STATES[:done], grader: instructor) + end + + it "returns the checkpoint submissions for the current checkpoint" do + expect(subject.no_or_rejected.length).to eq(2) + expect(subject.no_or_rejected.first).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.no_or_rejected.first.submission[:state]).to eq(CheckpointSubmission::STATES[:rejected]) + expect(subject.no_or_rejected.last).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.no_or_rejected.last.submission).to be_nil + + expect(subject.needs_review.length).to eq(1) + expect(subject.needs_review.first).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.needs_review.first.submission[:state]).to eq(CheckpointSubmission::STATES[:needs_review]) + + expect(subject.done.length).to eq(1) + expect(subject.done.first).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.done.first.submission[:state]).to eq(CheckpointSubmission::STATES[:done]) + expect(subject.done.first.submission[:grader_name]).to eq("#{instructor.first_name} #{instructor.last_name}") + end + + context "when there is a resubmission" do + let(:resubmitted_checkpoint_submission) { create_checkpoint_submission(done_student, challenge, cohort: cohort) } + before { create(:submitted_challenge_answer, cohort: cohort, user: done_student, challenge: challenge, checkpoint_submission: resubmitted_checkpoint_submission) } + + it "should set is_resubmitted to true" do + expect(subject.needs_review.length).to eq(2) + + expect(subject.needs_review.first).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.needs_review.first.is_resubmitted).to be false + expect(subject.needs_review.first.submission[:state]).to eq(CheckpointSubmission::STATES[:needs_review]) + + expect(subject.needs_review.last).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.needs_review.last.is_resubmitted).to be true + expect(subject.needs_review.last.submission[:state]).to eq(CheckpointSubmission::STATES[:needs_review]) + end + end + + context "when there are submissions across releases" do + let(:release_2) { create(:release) } + let(:content_file_2) { create(:content_file, :checkpoint, uid: content_file.uid, standard: create(:standard, release: release_2)) } + let(:challenge_2) { create(:challenge, uid: challenge.uid, content_file: content_file_2) } + + subject { described_class.new(content_file: content_file_2, cohort: cohort) } + + it "returns the checkpoint submissions that were submitted on a previous release" do + expect(subject.no_or_rejected.length).to eq(2) + expect(subject.no_or_rejected.first).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.no_or_rejected.first.submission[:state]).to eq(CheckpointSubmission::STATES[:rejected]) + expect(subject.no_or_rejected.last).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.no_or_rejected.last.submission).to be_nil + + expect(subject.needs_review.length).to eq(1) + expect(subject.needs_review.first).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.needs_review.first.submission[:state]).to eq(CheckpointSubmission::STATES[:needs_review]) + + expect(subject.done.length).to eq(1) + expect(subject.done.first).to be_a(CheckpointSubmissionPresenter::ForStudentRow) + expect(subject.done.first.submission[:state]).to eq(CheckpointSubmission::STATES[:done]) + expect(subject.done.first.submission[:grader_name]).to eq("#{instructor.first_name} #{instructor.last_name}") + end + end + + describe "column sorting" do + context "for no or rejected column" do + let(:rejected_student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Daz", last_name: "Dunder")).user } + let(:rejected_checkpoint_submission_1) { create_checkpoint_submission(rejected_student_1, challenge, cohort: cohort) } + let!(:rejected_submitted_challenge_answer_1) { create(:submitted_challenge_answer, cohort: cohort, user: rejected_student_1, challenge: challenge, checkpoint_submission: rejected_checkpoint_submission_1) } + + let(:rejected_student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Jen", last_name: "Juniper")).user } + let(:rejected_checkpoint_submission_2) { create_checkpoint_submission(rejected_student_2, challenge, cohort: cohort) } + let!(:rejected_submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: rejected_student_2, challenge: challenge, checkpoint_submission: rejected_checkpoint_submission_2) } + + let!(:no_submission_student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "e2estudent")).user } + let!(:no_submission_student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Other Student")).user } + + before do + rejected_checkpoint_submission.update(state: "rejected", grader: instructor, updated_at: 1.day.ago) + rejected_checkpoint_submission_2.update(state: "rejected", grader: instructor, updated_at: 1.week.ago) + rejected_checkpoint_submission_1.update(state: "rejected", grader: instructor, updated_at: 1.month.ago) + end + + it "sorts the rejected first by newest first" do + expect(subject.no_or_rejected.reject { |props| props.submission.nil? }. + map { |props| props.student.full_name }). + to eq([rejected_student.full_name, rejected_student_2.full_name, rejected_student_1.full_name]) + end + + it "sorts no submission students alphabetically and case-insensitively" do + expect(subject.no_or_rejected.select { |props| props.submission.nil? }. + map { |props| props.student.full_name }). + to eq([no_submission_student.full_name, no_submission_student_1.full_name, no_submission_student_2.full_name]) + end + end + + context "for needs review column" do + let(:needs_review_submission_student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Gee", last_name: "Gierson")).user } + let(:needs_review_submission_1) { create_checkpoint_submission(needs_review_submission_student_1, challenge, cohort: cohort, updated_at: 1.week.ago) } + let!(:needs_review_submission_submitted_challenge_answer_1) { create(:submitted_challenge_answer, cohort: cohort, user: needs_review_submission_student_1, challenge: challenge, checkpoint_submission: needs_review_submission_1) } + + let(:needs_review_submission_student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Lonny", last_name: "Lotterball")).user } + let(:needs_review_submission_2) { create_checkpoint_submission(needs_review_submission_student_2, challenge, cohort: cohort, updated_at: 1.month.ago) } + let!(:needs_review_submission_submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: needs_review_submission_student_2, challenge: challenge, checkpoint_submission: needs_review_submission_2) } + + it "sorts the oldest first" do + checkpoint_submission.update(updated_at: 1.day.ago) + + expect(subject.needs_review.map { |props| props.student.full_name }).to eq([needs_review_submission_student_2.full_name, needs_review_submission_student_1.full_name, student.full_name]) + end + end + + context "for done column" do + let(:done_student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "bab", last_name: "Baberson")).user } + let(:done_checkpoint_submission_1) { create_checkpoint_submission(done_student_1, challenge, cohort: cohort) } + let!(:done_submitted_challenge_answer_1) { create(:submitted_challenge_answer, cohort: cohort, user: done_student_1, challenge: challenge, checkpoint_submission: done_checkpoint_submission_1) } + + let(:done_student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "kiley", last_name: "Killerton")).user } + let(:done_checkpoint_submission_2) { create_checkpoint_submission(done_student_2, challenge, cohort: cohort) } + let!(:done_submitted_challenge_answer_2) { create(:submitted_challenge_answer, cohort: cohort, user: done_student_2, challenge: challenge, checkpoint_submission: done_checkpoint_submission_2) } + + before do + done_checkpoint_submission.update(state: "done", grader: instructor, updated_at: 1.day.ago) + done_checkpoint_submission_2.update(state: "done", grader: instructor, updated_at: 1.week.ago) + done_checkpoint_submission_1.update(state: "done", grader: instructor, updated_at: 1.month.ago) + end + + it "sorts the newest first" do + expect(subject.done.map { |props| props.student.full_name }).to eq([done_student.full_name, done_student_2.full_name, done_student_1.full_name]) + end + end + end + end +end diff --git a/scripts/spec/presenters/checkpoint_submission_presenter_spec.rb b/scripts/spec/presenters/checkpoint_submission_presenter_spec.rb new file mode 100644 index 0000000..6284f03 --- /dev/null +++ b/scripts/spec/presenters/checkpoint_submission_presenter_spec.rb @@ -0,0 +1,66 @@ +require "spec_helper" + +describe CheckpointSubmissionPresenter do + let(:cohort) { create(:cohort) } + let(:checkpoint) { create(:content_file, :checkpoint, path: "first_checkpoint.md") } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: checkpoint.standard.release) } + let(:challenge) { create(:challenge, content_file: checkpoint) } + let!(:first_student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:middle_student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:last_student) { create(:cohort_user, :student, cohort: cohort).user } + let(:checkpoint_submission) { create_checkpoint_submission(middle_student, challenge, cohort: cohort) } + + subject { described_class.new(checkpoint_submission: checkpoint_submission, content_file: checkpoint) } + + let!(:middle_submission) { create(:submitted_challenge_answer, challenge: challenge, checkpoint_submission: checkpoint_submission, user: middle_student) } + + it "sets the desired attributes" do + expect(subject).to have_attributes( + id: checkpoint_submission.id, + state: "needs_review", + title: checkpoint.title, + updated_at: checkpoint_submission.updated_at, + url: Rails.application.routes.url_helpers.cohort_checkpoint_submission_path( + cohort.id, checkpoint_submission.id + ) + ) + end + + context "a url_cohort_id is provided" do + let(:url_cohort) { create(:cohort) } + subject { described_class.new(checkpoint_submission: checkpoint_submission, content_file: checkpoint, url_cohort_id: url_cohort.id) } + + it "sets the url to use the given cohort id" do + expect(subject).to have_attributes( + id: checkpoint_submission.id, + state: "needs_review", + title: checkpoint.title, + updated_at: checkpoint_submission.updated_at, + url: Rails.application.routes.url_helpers.cohort_checkpoint_submission_path( + url_cohort.id, checkpoint_submission.id + ) + ) + end + end + + context "when the checkpoint submission has been rejected" do + before { checkpoint_submission.update(state: CheckpointSubmission::STATES[:rejected], grader_id: create(:user, :admin).id) } + it "should have a rejected state" do + expect(subject.state).to eq("rejected") + end + end + + context "when the checkpoint submission has been marked as done" do + let(:checkpoint_submission) { create_checkpoint_submission(middle_student, challenge, cohort: cohort, state: CheckpointSubmission::STATES[:done], grader_id: create(:user, :admin).id) } + it "should have a done state" do + expect(subject.state).to eq("done") + end + end + + context "when the checkpoint submission is in the state of 'started'" do + let(:checkpoint_submission) { create_checkpoint_submission(middle_student, challenge, cohort: cohort, state: CheckpointSubmission::STATES[:started], grader_id: create(:user, :admin).id) } + it "should have a started state" do + expect(subject.state).to eq("started") + end + end +end diff --git a/scripts/spec/presenters/content_file_presenter/for_curriculum_last_viewed_spec.rb b/scripts/spec/presenters/content_file_presenter/for_curriculum_last_viewed_spec.rb new file mode 100644 index 0000000..46eaa4c --- /dev/null +++ b/scripts/spec/presenters/content_file_presenter/for_curriculum_last_viewed_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe ContentFilePresenter::ForCurriculumLastViewed do + let!(:cohort) { create(:cohort) } + let!(:release) { create(:cohort_release, cohort: cohort).release } + let!(:standard) { create(:standard, release: release) } + let!(:content_file_1) { create(:content_file, standard: standard) } + let!(:content_file_2) { create(:content_file, standard: standard) } + let!(:content_file_3) { create(:content_file, standard: standard) } + + context "when you have no UserLastViewedStandardPath objects" do + subject { described_class.new(cohort: cohort, last_viewed_lesson_visit: nil, standards: [standard]) } + + it "has valid attributes" do + expect(subject.is_first_view).to eq(true) + expect(subject.block_id).to eq(release.block_id) + expect(subject.standard_name).to eq(standard.title) + expect(subject.content_file_title).to eq(content_file_1.title) + expect(subject.content_file_path).to eq(Rails.application.routes.url_helpers.content_file_path(cohort, release.block, content_file_1.path)) + end + end + + context "when you have UserLastViewedStandardPath objects" do + let(:lesson_visit) { create(:lesson_visit, standard_uid: standard.uid, cohort: cohort, block: release.block, content_file_path: content_file_2.path) } + subject { described_class.new(cohort: cohort, last_viewed_lesson_visit: lesson_visit, standards: [standard]) } + + it "has valid attributes" do + expect(subject.is_first_view).to eq(false) + expect(subject.block_id).to eq(release.block_id) + expect(subject.standard_name).to eq(standard.title) + expect(subject.content_file_title).to eq(content_file_2.title) + expect(subject.content_file_path).to eq(Rails.application.routes.url_helpers.content_file_path(cohort, release.block, content_file_2.path)) + end + end +end diff --git a/scripts/spec/presenters/content_file_presenter/for_show_spec.rb b/scripts/spec/presenters/content_file_presenter/for_show_spec.rb new file mode 100644 index 0000000..4eda358 --- /dev/null +++ b/scripts/spec/presenters/content_file_presenter/for_show_spec.rb @@ -0,0 +1,232 @@ +require "spec_helper" + +describe ContentFilePresenter::ForShow do + let!(:content_file) { create(:content_file, html: "asdf") } + let(:cohort) { create(:cohort_release, release: content_file.standard.release).cohort } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + + describe "initialize" do + describe "#batch_create_cohort_content_file_submitted_challenge_answers_path" do + let!(:content_file) { create(:content_file, :checkpoint) } + let(:challenge) { create(:challenge, content_file: content_file) } + + it "returns the url with the cohort param" do + presenter = described_class.new(content_file, cohort, student) + + expect(presenter.batch_create_cohort_content_file_submitted_challenge_answers_path).to eq( + batch_create_cohort_content_file_submitted_challenge_answers_path(content_file, cohort_id: cohort.id) + ) + end + end + + describe "#last_checkpoint_submitted_on" do + let!(:content_file) { create(:content_file, :checkpoint) } + let(:challenge) { create(:challenge, content_file: content_file) } + let!(:sca) { create(:submitted_challenge_answer, user: student, challenge: challenge, cohort: cohort) } + + it "returns creation date of last submission" do + presenter = described_class.new(content_file, cohort, student) + + expect(presenter.last_checkpoint_submitted_on).to be_within(1.second).of(sca.created_at) + end + end + + describe "#is_checkpoint_draft" do + let!(:content_file) { create(:content_file, :checkpoint) } + let(:challenge) { create(:challenge, content_file: content_file) } + let!(:sca) { create(:submitted_challenge_answer, user: student, challenge: challenge, cohort: cohort) } + + context "submission has a checkpoint submission association" do + it "returns false" do + sca.update(checkpoint_submission_id: create_checkpoint_submission(student, challenge, cohort: cohort).id) + presenter = described_class.new(content_file, cohort, student) + + expect(presenter.is_checkpoint_draft).to eq(false) + end + end + + context "submission's checkpoint submission is nil" do + let!(:content_file) { create(:content_file, :checkpoint) } + let(:challenge) { create(:challenge, content_file: content_file) } + let!(:sca) { create(:submitted_challenge_answer, user: student, challenge: challenge, cohort: cohort) } + + it "returns true" do + presenter = described_class.new(content_file, cohort, student) + + expect(presenter.is_checkpoint_draft).to eq(true) + end + end + end + + describe "#has_submitted_checkpoint" do + let!(:content_file) { create(:content_file, :checkpoint) } + let(:challenge) { create(:challenge, content_file: content_file) } + + context "has multiple submissions all without checkpoint submission association" do + let!(:non_checkpoint_submission_1) { create(:submitted_challenge_answer, user: student, challenge: challenge, cohort: cohort) } + let!(:non_checkpoint_submission_2) { create(:submitted_challenge_answer, user: student, challenge: challenge, cohort: cohort) } + let!(:non_checkpoint_submission_3) { create(:submitted_challenge_answer, user: student, challenge: challenge, cohort: cohort) } + let!(:non_checkpoint_submission_4) { create(:submitted_challenge_answer, user: student, challenge: challenge, cohort: cohort) } + + it "returns false" do + presenter = described_class.new(content_file, cohort, student) + + expect(presenter.has_submitted_checkpoint).to eq(false) + end + end + + context "has at least one submission that has a checkpoint submission association" do + let!(:old_checkpoint_submission) { create(:submitted_challenge_answer, user: student, challenge: challenge, checkpoint_submission: create_checkpoint_submission(student, challenge, cohort: cohort), cohort: cohort) } + + it "returns true" do + presenter = described_class.new(content_file, cohort, student) + + expect(presenter.has_submitted_checkpoint).to eq(true) + end + end + + context "has a checkpoint submission for a previous version of the same content file in the same block" do + let!(:old_checkpoint_submission) { create(:submitted_challenge_answer, user: student, challenge: challenge, checkpoint_submission: create_checkpoint_submission(student, challenge, cohort: cohort), cohort: cohort) } + let!(:new_content_file) { create(:content_file, uid: content_file.uid, standard: create(:standard, release: create(:release, block_id: content_file.standard.release.block_id))) } + + it "returns true" do + create(:cohort_release, release: new_content_file.standard.release, cohort: cohort) + presenter = described_class.new(new_content_file, cohort, student) + + expect(presenter.has_submitted_checkpoint).to eq(true) + end + end + end + + describe "#cohort_content_file_submitted_challenge_answers_path" do + it "yields the api v1 content files submitted challenge answers url for the content file" do + content_file_presenter = described_class.new(content_file, cohort, create(:user, :admin)) + expected_url = cohort_content_file_submitted_challenge_answers_path(cohort, content_file.id) + expect(content_file_presenter.cohort_content_file_submitted_challenge_answers_path).to eq(expected_url) + end + end + + describe "#content_file_github_url" do + let!(:content_file) { create(:content_file, :checkpoint) } + + it "sets the github_url correctly" do + presenter = described_class.new(content_file, cohort, student) + expect(presenter.github_url).to eq "#{content_file.standard.release.block.git_url}/blob/#{content_file.standard.release.branch_name}/#{content_file.path}" + end + end + + describe "user dependent variables" do + let(:challenge) { create(:challenge, content_file: content_file) } + + context "when user is an admin" do + let!(:sca) { create(:submitted_challenge_answer, user: student, challenge: challenge, cohort: cohort) } + let(:admin) { create(:user, :admin) } + let(:content_file_presenter) { described_class.new(content_file, cohort, admin) } + + it "assigns the student submissions url" do + expect(content_file_presenter.challenges_with_answers.to_json).to eq([ChallengeWithSubmittedChallengeAnswersPresenter.new(cohort, challenge, [], true, user_context: admin)].to_json) + end + + context "and is enrolled in the cohort" do + it "does not assign the student submissions url" do + admin.update(roles: []) + create(:cohort_user, :student, cohort: cohort, user: admin) + expect(content_file_presenter.challenges_with_answers.to_json).to eq([ChallengeWithSubmittedChallengeAnswersPresenter.new(cohort, challenge, [], true, user_context: admin)].to_json) + end + end + end + + context "when user is an instructor" do + let(:content_file_presenter) { described_class.new(content_file, cohort, instructor) } + + context "when the content file has a challenge" do + let!(:challenge) { create(:challenge, content_file: content_file, title: "HUZZAH") } + + context "when there are no submitted challenge answers for this cohort" do + it "assigns no submissions but confirms challenges exist" do + expect(content_file_presenter.challenges_with_answers.to_json).to eq([ChallengeWithSubmittedChallengeAnswersPresenter.new(cohort, challenge, [], true, user_context: instructor)].to_json) + end + end + + context "when there are submitted challenge answers for this cohort" do + it "assigns has_challenges and number_of_submissions" do + create(:submitted_challenge_answer, user: student, challenge: challenge, cohort: cohort) + expect(content_file_presenter.challenges_with_answers.to_json).to eq([ChallengeWithSubmittedChallengeAnswersPresenter.new(cohort, challenge, [], true, user_context: instructor)].to_json) + end + end + + context "and is enrolled in the cohort" do + it "does not assign the student submissions url" do + CohortUser.find_by(user: instructor).update(roles: []) + expect(content_file_presenter.challenges_with_answers.to_json).to eq([ChallengeWithSubmittedChallengeAnswersPresenter.new(cohort, challenge, [], true, user_context: instructor)].to_json) + end + end + end + + context "when the only submissions are from the admin" do + it "still has a submission count of 0" do + create(:submitted_challenge_answer, user: create(:user, :admin), challenge: challenge, cohort: cohort) + expect(content_file_presenter.challenges_with_answers.to_json).to eq([ChallengeWithSubmittedChallengeAnswersPresenter.new(cohort, challenge, [], true, user_context: instructor)].to_json) + end + end + end + + context "user is a student" do + let(:content_file_presenter) { described_class.new(content_file, cohort, student) } + + context "when a challenge exists for the content file" do + let!(:challenge) { create(:challenge, content_file: content_file) } + + it "yields formats for submitted challenge answers" do + create(:submitted_challenge_answer, user: student, challenge: challenge, status: "correct", cohort: cohort) + found_submission = SubmittedChallengeAnswerFinder.latest_for_user_id_content_file_for_cohort(student.id, challenge.content_file, cohort.id).find { |sca| sca.challenge_id == challenge.id } + + expect(content_file_presenter.challenges_with_answers.to_json).to eq([ChallengeWithSubmittedChallengeAnswersPresenter.new(cohort, challenge, [found_submission], false, user_context: student)].to_json) + end + + it "handles submitted challenge answers across releases" do + block = create(:block) + + release_1 = create(:release, block: block) + standard_1 = create(:standard, release: release_1) + content_file_1 = create(:content_file, standard: standard_1) + challenge_1 = create(:challenge, content_file: content_file_1, uid: "1234") + submitted_challenge_answer = create(:submitted_challenge_answer, challenge: challenge_1, user: student, cohort: cohort) + create(:cohort_release, release: release_1, cohort: cohort) + + release_2 = create(:release, block: block) + standard_2 = create(:standard, release: release_2) + content_file_2 = create(:content_file, standard: standard_2) + challenge_2 = create(:challenge, content_file: content_file_2, uid: "1234") + create(:cohort_release, release: release_2, cohort: cohort) + + content_file_presenter = ContentFilePresenter::ForShow.new(content_file_2, cohort, student) + + expect(content_file_presenter.challenges_with_answers.to_json).to eq([ChallengeWithSubmittedChallengeAnswersPresenter.new(cohort, challenge_2, [submitted_challenge_answer], false, user_context: student)].to_json) + end + + it "removes the answer and explanation from the challenge if the student's answer is incorrect" do + create(:submitted_challenge_answer, user: student, challenge: challenge, status: "incorrect", cohort: cohort) + found_submission = SubmittedChallengeAnswerFinder.latest_for_user_id_content_file_for_cohort(student.id, challenge.content_file, cohort.id).find { |sca| sca.challenge_id == challenge.id } + + expect(content_file_presenter.challenges_with_answers.to_json).to eq([ChallengeWithSubmittedChallengeAnswersPresenter.new(cohort, challenge, [found_submission], false, user_context: student)].to_json) + end + end + end + end + end + + describe "#checkpoint_submissions_overview_url" do + let(:content_file) { create(:content_file, :checkpoint) } + subject { described_class.new(content_file, cohort, student).checkpoint_submissions_overview_url } + + context "when content file is not a checkpoint" do + let(:content_file) { create(:content_file, :lesson) } + + it "returns nil" do + expect(subject).to be_nil + end + end + end +end diff --git a/scripts/spec/presenters/content_file_presenter/for_sidebar_spec.rb b/scripts/spec/presenters/content_file_presenter/for_sidebar_spec.rb new file mode 100644 index 0000000..0ae785f --- /dev/null +++ b/scripts/spec/presenters/content_file_presenter/for_sidebar_spec.rb @@ -0,0 +1,15 @@ +require "spec_helper" + +describe ContentFilePresenter::ForSidebar do + let(:cohort) { create(:cohort) } + let(:release) { create(:cohort_release, cohort: cohort).release } + let(:standard) { create(:standard, release: release) } + let(:content_file) { create(:content_file, standard: standard) } + subject { described_class.new(content_file: content_file, cohort_id: cohort.id) } + + it "has valid attributes" do + expect(subject.content_file).to eq(content_file) + expect(subject.title).to eq(content_file.title) + expect(subject.standard_content_file_path).to eq(Rails.application.routes.url_helpers.content_file_path(cohort, release.block, content_file.path)) + end +end diff --git a/scripts/spec/presenters/standard_presenter/for_checkpoint_submission_spec.rb b/scripts/spec/presenters/standard_presenter/for_checkpoint_submission_spec.rb new file mode 100644 index 0000000..351b39a --- /dev/null +++ b/scripts/spec/presenters/standard_presenter/for_checkpoint_submission_spec.rb @@ -0,0 +1,29 @@ +require "spec_helper" + +describe StandardPresenter::ForCheckpointSubmission do + let(:standard) { create(:standard) } + + subject { described_class.new(standard: standard, latest_performance: performance) } + + context "when there is a latest performance" do + let(:performance) { create(:performance, standard: standard) } + + it "should return a presenter with attributes for the standard" do + expect(subject.id).to eq(standard.id) + expect(subject.description).to eq(standard.description) + expect(subject.success_criteria).to eq(standard.success_criteria_as_html) + expect(subject.current_score).to eq(performance.score) + end + end + + context "when there is no latest performance" do + let(:performance) { nil } + + it "should return a presenter with attributes for the standard with a current score of nil" do + expect(subject.id).to eq(standard.id) + expect(subject.description).to eq(standard.description) + expect(subject.success_criteria).to eq(standard.success_criteria_as_html) + expect(subject.current_score).to be_nil + end + end +end diff --git a/scripts/spec/presenters/standard_presenter/for_standard_card_spec.rb b/scripts/spec/presenters/standard_presenter/for_standard_card_spec.rb new file mode 100644 index 0000000..5c50642 --- /dev/null +++ b/scripts/spec/presenters/standard_presenter/for_standard_card_spec.rb @@ -0,0 +1,151 @@ +require "spec_helper" + +describe StandardPresenter::ForStandardCard do + let(:cohort) { create(:cohort) } + let(:standard) { create(:standard) } + let!(:user) { create(:cohort_user, :student, cohort: cohort).user } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: standard.release) } + let(:user_performances) { [] } + let(:lesson_visits) { LessonVisit.where(user_id: user.id) } + + subject do + described_class.new( + standard: standard, + cohort: cohort, + lesson_visits: lesson_visits, + user_performances: user_performances, + current_user_id: user.id, + cohort_standard_progress_data: [], + instructor_or_admin: true + ) + end + + context "when the standard only has a checkpoint" do + let!(:checkpoint) { create(:content_file, :checkpoint, standard: standard, position: 1) } + + it "returns the path of the checkpoint" do + expect(subject.path).to eq( + Rails.application.routes.url_helpers.content_file_path( + cohort.id, + standard.release.block_id, + checkpoint.path + ) + ) + end + end + + context "when the standard has a lesson and a checkpoint" do + let!(:first_content_file) { create(:content_file, standard: standard, position: 0) } + + before do + create(:content_file, standard: standard, position: 1) + create(:content_file, :checkpoint, standard: standard, position: 2) + end + + it "should initialize and return attributes" do + expect(subject.description).to eq(standard.description) + expect(subject.path).to eq( + Rails.application.routes.url_helpers.content_file_path( + cohort.id, + standard.release.block_id, + first_content_file.path + ) + ) + expect(subject.title).to eq(standard.title) + expect(subject.id).to eq(standard.id) + end + + context "when no content file in the standard has been viewed by the current user" do + it "the default text and link" do + expect(subject.visited_by_user?(lesson_visits)).to eq(false) + end + end + + context "when a user has a performances for the standard" do + let!(:performance) { create(:performance, user: user, standard: standard, cohort: cohort, score: 1) } + let(:user_performances) { [performance] } + + it "sets the mastery score and class" do + expect(subject.mastery_score).to eq(1) + expect(subject.mastery_score_class).to eq("-score-1") + end + end + + context "when a content file has been viewed by the current user but no longer exists in the current release" do + let!(:second_content_file) { create(:content_file, standard: standard, position: 2, title: "Foobar") } + + it "returns the default text and link" do + create( + :lesson_visit, + cohort: cohort, + user: user, + block_id: standard.release.block_id, + standard_uid: standard.uid, + content_file_path: second_content_file.path + ) + + new_release = create(:release, block: standard.release.block) + new_standard = create(:standard, release: new_release, uid: standard.uid) + create(:content_file, standard: new_standard) + + cohort_release.update(release: new_release) + + result = described_class.new( + standard: new_standard, + cohort: cohort, + lesson_visits: LessonVisit.where(user_id: user.id), + user_performances: user_performances, + current_user_id: user.id, + cohort_standard_progress_data: [], + instructor_or_admin: true + ) + + expect(result.visited_by_user?(lesson_visits)).to eq(false) + expect(result.status_class).to eq("-is-unvisited") + end + end + + context "when a content file has been viewed by the current user" do + let!(:second_content_file) { create(:content_file, standard: standard, position: 2, title: "Foobar") } + before { create(:lesson_visit, cohort: cohort, user: user, block: standard.release.block, standard_uid: standard.uid, content_file_path: second_content_file.path) } + + it "returns information about the content file" do + expect(subject.visited_by_user?(lesson_visits)).to eq(true) + expect(subject.path).to eq( + Rails.application.routes.url_helpers.content_file_path( + cohort.id, + standard.release.block_id, + second_content_file.path + ) + ) + expect(subject.status_class).to eq("-is-current") + end + end + + describe "#last_visited_by_user?" do + context "when the standard is the most recently viewed by the current user" do + let!(:second_content_file) { create(:content_file, standard: standard, position: 2, title: "Foobar") } + before do + create(:lesson_visit, user: user, cohort: cohort, block: standard.release.block, standard_uid: standard.uid, content_file_path: first_content_file.path) + create(:lesson_visit, user: user, cohort: cohort, block: standard.release.block, standard_uid: standard.uid, content_file_path: second_content_file.path) + end + + it "returns true" do + expect(subject.last_visited_by_user?(lesson_visits)).to eq(true) + end + end + + context "when the standard is not the most recently viewed by the current user" do + let!(:second_content_file) { create(:content_file, standard: standard, position: 2, title: "Foobar") } + before do + create(:user_last_viewed_standard_path, user: user, block_id: standard.release.block_id, standard_uid: standard.uid, content_file_path: second_content_file.path) + create(:user_last_viewed_standard_path, user: user) + end + + it "returns false" do + expect(subject.last_visited_by_user?(lesson_visits)).to eq(false) + end + end + end + end +end diff --git a/scripts/spec/presenters/student_progress_presenter_spec.rb b/scripts/spec/presenters/student_progress_presenter_spec.rb new file mode 100644 index 0000000..7d551f9 --- /dev/null +++ b/scripts/spec/presenters/student_progress_presenter_spec.rb @@ -0,0 +1,186 @@ +require "spec_helper" + +describe StudentProgressPresenter do + let(:cohort) { create(:cohort) } + let(:block) { create(:block) } + let(:release) { create(:release, block: block) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + + let(:standard_1) { create(:standard, release: release) } + let(:standard_2) { create(:standard, release: release) } + let(:standard_3) { create(:standard, release: release) } + let(:standard_4) { create(:standard, release: release) } + let(:performance_as_a_zero_score) { create(:performance, user: student, updator: instructor, score: 0, block_id: block.id, standard: standard_1, cohort: cohort) } + let(:performance_1) { create(:performance, user: student, updator: instructor, score: 1, block_id: block.id, standard: standard_1, cohort: cohort) } + let(:performance_2) { create(:performance, user: student, updator: instructor, score: 2, block_id: block.id, standard: standard_2, cohort: cohort) } + let(:performance_3) { create(:performance, user: student, updator: instructor, score: 3, block_id: block.id, standard: standard_3, cohort: cohort) } + let(:performance_4) { create(:performance, user: student, updator: instructor, score: 3, block_id: block.id, standard: standard_4, cohort: cohort) } + let!(:activity) { create(:activity, cohort: cohort, name: "content_file.viewed", subject: create(:content_file), creator_id: student.id) } + let(:presenter) do + described_class.new( + student: student, + cohort: cohort, + performances: [performance_1, performance_2, performance_3, performance_4], + total_standards: 4, + progress_data: nil, + lesson_visits: nil, + include_totals: true + ) + end + + describe "#id" do + it "returns the student's id" do + expect(presenter.id).to eq(student.id) + end + end + + describe "#initials" do + it "returns the student's initials" do + expect(presenter.initials).to eq(student.initials) + end + end + + describe "#total_scored_standards" do + it "returns the total scored standards" do + expect(presenter.total_scored_standards).to eq(4) + end + end + + describe "#full_name" do + it "returns the student's full name" do + expect(presenter.full_name).to eq(student.full_name) + end + end + + describe "#email" do + it "returns the student's email" do + expect(presenter.email).to eq(student.email) + end + end + + describe "#latest_activity" do + it "returns the student's latest activity" do + expect(presenter.latest_activity).to include(activity.created_at.to_s) + end + end + + describe "#registration_date" do + it "returns the student's latest activity" do + expect(presenter.registration_date).to include(student.created_at.to_s) + end + end + + describe "#avatar" do + it "returns the student's avatar" do + expect(presenter.avatar).to eq(student.profile_image) + end + end + + describe "#performances_url" do + it "returns the url to the student's performances" do + expect(presenter.performances_url).to eq(submissions_dashboard_cohort_user_path(cohort, student)) + end + end + + describe "#score_percentages" do + it "returns the percentage of student's performances by score" do + expect(presenter.score_percentages).to eq(one: 25, two: 25, three: 50) + end + end + + describe "#progress_percentages" do + context "when cohort is in mastery mode" do + it "returns the percentage of student's performances compared to total standards" do + expect(presenter.progress_percentages).to eq(one: 25, two: 25, three: 50) + end + end + + context "when cohort is in percentage mode" do + let(:presenter) do + described_class.new( + student: student, + cohort: cohort, + performances: [], + total_standards: nil, + progress_data: progress_data, + lesson_visits: nil, + include_totals: include_totals + ) + end + let(:incorrect) { SubmittedChallengeAnswer::STATUSES[:incorrect] } + let(:correct) { SubmittedChallengeAnswer::STATUSES[:correct] } + let(:ungraded) { SubmittedChallengeAnswer::STATUSES[:ungraded] } + let(:failed) { SubmittedChallengeAnswer::STATUSES[:failed] } + + let!(:content_file_0) { create(:content_file, standard: standard_1, title: "Fizz", position: 0) } + let!(:challenge_0_0) { create(:challenge, content_file: content_file_0, position: 0, title: "Fizz Tastic") } + let!(:challenge_0_1) { create(:challenge, content_file: content_file_0, position: 1, title: "Fizzlin") } + + let!(:content_file_1) { create(:content_file, standard: standard_2, title: "Buzz", position: 1) } + let!(:challenge_1_0) { create(:challenge, content_file: content_file_1, position: 0, title: "Buzz Nonedrin") } + let!(:challenge_1_1) { create(:challenge, content_file: content_file_1, position: 1, title: "Buzzelzebub") } + + let!(:student_challenge_0_0_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge_0_0, status: correct) } + let!(:student_challenge_0_1_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge_0_1, status: correct) } + + let!(:student_challenge_1_0_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge_1_0, status: incorrect) } + let!(:student_challenge_1_1_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge_1_1, status: correct) } + + let!(:content_file_checkpoint) { create(:content_file, :checkpoint, standard: standard_1, title: "Bazz", position: 2) } + let!(:challenge_in_checkpoint) { create(:challenge, content_file: content_file_checkpoint, position: 0, points: 5, title: "Bazz Yetu") } + + let!(:student_challenge_2_0_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge_in_checkpoint, status: ungraded, points:4) } + + let!(:checkpoint_submission) do + checkpoint_submission = create(:checkpoint_submission, + content_file_uid: content_file_checkpoint.uid, + content_file_block_id: standard_1.release.block_id, + user_id: student.id, + correct_points: 4, + total_points: 5, + state: CheckpointSubmission::STATES[:done]) + end + + let(:progress_data) do + StandardSubmissionsService.new(standard: nil, + cohort: cohort, + cohort_releases: [release], + standard_uids: cohort.standard_uids, + all_checkpoints: CheckpointSubmission.where(id: checkpoint_submission.id)).all_standards + end + context "include totals set to true" do + let(:include_totals) {true} + it "returns the cohorts progress for all the students with totals" do + cohort.update(mode: Cohort::MODES[:percentage]) + + expect(presenter.progress_percentages).to eq(percent_completed: 50, + totals: {total_earned: 4, total_possible: 5, percent: 80}, + percent_completed_dasharray: "138.230075 138.230075", + completed_standards: [{title: standard_1.title, uid: standard_1.uid, id: standard_1.id}], + checkpoint_average: 80, + topic_averages: {}) + end + end + context "include totals set to false" do + let(:include_totals) {false} + it "returns the cohorts progress for all the students without totals" do + cohort.update(mode: Cohort::MODES[:percentage]) + + expect(presenter.progress_percentages).to eq(percent_completed: 50, + percent_completed_dasharray: "138.230075 138.230075", + completed_standards: [{title: standard_1.title, uid: standard_1.uid, id: standard_1.id}], + checkpoint_average: 80, + topic_averages: {}) + end + end + end + end + + describe "#average" do + it "returns the student's average of performances" do + expect(presenter.average).to eq("2.25") + end + end +end diff --git a/scripts/spec/presenters/submissions_dashboard_presenter_spec.rb b/scripts/spec/presenters/submissions_dashboard_presenter_spec.rb new file mode 100644 index 0000000..d4ceb59 --- /dev/null +++ b/scripts/spec/presenters/submissions_dashboard_presenter_spec.rb @@ -0,0 +1,68 @@ +require "spec_helper" + +describe SubmissionsDashboardPresenter do + let(:incorrect) { SubmittedChallengeAnswer::STATUSES[:incorrect] } + let!(:student) { create(:cohort_user, :student, cohort: cohort, user: create(:user)).user } + + let!(:cohort) { create(:cohort) } + let!(:release) { create(:release, block: create(:block, title: "Blocks-Test")) } + let!(:standard) { create(:standard, release: release) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release, position: 2) } + + let!(:content_file_checkpoint) { create(:content_file, :checkpoint, standard: standard, title: "Bazz", position: 0) } + let!(:challenge_in_checkpoint) { create(:challenge, content_file: content_file_checkpoint, position: 0, title: "Bazz Yetu") } + + let!(:checkpoint_submission) { create(:checkpoint_submission, + cohort_id: cohort.id, + content_file_block_id: release.block_id, + content_file_uid: content_file_checkpoint.uid, + user: student, + state: CheckpointSubmission::STATES[:needs_review]) } + let!(:student_checkpoint_sca) { create(:submitted_challenge_answer, + user: student, + challenge: challenge_in_checkpoint, + challenge_uid: challenge_in_checkpoint.uid, + created_at: 1.day.ago, + status: incorrect, + block_id: release.block_id, + checkpoint_submission_id: checkpoint_submission.id) } + + describe ".submission_data" do + context "mastery mode" do + it "yeilds block information with standards and the student's scores" do + results = described_class.submission_data(cohort: cohort, cohort_mode: Cohort::MODES[:mastery], student_ids: [student.id]) + expect(results[:cohort_mode]).to eq "Mastery" + expect(results[:blocks].length).to eq 1 + expect(results[:blocks][0][:title]).to eq release.block.title + expect(results[:blocks][0][:standards].length).to eq 1 + expect(results[:blocks][0][:standards][0][:id]).to eq standard.id + expect(results[:blocks][0][:standards][0][:student_performances][student.id]).to eq ( + {student_id: student.id, ungraded_submission_id: checkpoint_submission.id, ungraded_submission_state: CheckpointSubmission::STATES[:needs_review]} + ) + expect(results[:blocks][0][:standards][0][:content_files].length).to eq 1 + expect(results[:blocks][0][:standards][0][:content_files][0].id).to eq content_file_checkpoint.id + end + end + + context "percentage mode" do + let!(:visited_content_file) { create(:content_file, standard: standard, title: "CF", position: 1) } + let!(:lesson_visit) { create(:lesson_visit, cohort: cohort, block_id: release.block_id, content_file_uid: visited_content_file.uid, standard_uid: standard.uid, user_id: student.id, visit_count: 1) } + it "adds lesson visit information to the payload of data in mastery mode" do + results = described_class.submission_data(cohort: cohort, cohort_mode: Cohort::MODES[:percentage], student_ids: [student.id]) + expect(results[:cohort_mode]).to eq "Percentage" + expect(results[:blocks].length).to eq 1 + expect(results[:blocks][0][:title]).to eq release.block.title + expect(results[:blocks][0][:standards].length).to eq 1 + expect(results[:blocks][0][:standards][0][:id]).to eq standard.id + expect(results[:blocks][0][:standards][0][:student_performances][student.id]).to eq ( + {student_id: student.id, ungraded_submission_id: checkpoint_submission.id, ungraded_submission_state: CheckpointSubmission::STATES[:needs_review]} + ) + expect(results[:blocks][0][:standards][0][:content_files].length).to eq 1 + expect(results[:blocks][0][:standards][0][:content_files][0].id).to eq content_file_checkpoint.id + # here are added details for percentage + expect(results[:completion_progress][student.id][release.block.id][standard.uid]).to eq ({:total=>2, :completed=>1}) + end + + end + end +end diff --git a/scripts/spec/presenters/submitted_challenge_answer_presenter_spec.rb b/scripts/spec/presenters/submitted_challenge_answer_presenter_spec.rb new file mode 100644 index 0000000..6449946 --- /dev/null +++ b/scripts/spec/presenters/submitted_challenge_answer_presenter_spec.rb @@ -0,0 +1,130 @@ +require "spec_helper" + +describe SubmittedChallengeAnswerPresenter do + let(:cohort) { create(:cohort) } + + describe "#initialize" do + describe "#student_comment_url" do + it "sets student_comment_url to nil when no id is present" do + expect(described_class.new(SubmittedChallengeAnswer.new(cohort_id: cohort.id)).student_comment_url).to eq(nil) + end + + it "sets student_comment_url to the correct path when an id is present" do + submitted_challenge_answer = create(:submitted_challenge_answer, cohort: cohort) + expect(described_class.new(submitted_challenge_answer).student_comment_url).to eq(Rails.application.routes.url_helpers.cohort_user_challenge_path(cohort, submitted_challenge_answer.user, submitted_challenge_answer.challenge)) + end + end + + describe "#cancel_url" do + it "sets cancel_url to the correct path when an id is present and status is processing" do + submitted_challenge_answer = create(:submitted_challenge_answer, status: "processing", cohort: cohort) + expect(described_class.new(submitted_challenge_answer).cancel_url).to eq(Rails.application.routes.url_helpers.cancel_cohort_content_file_submitted_challenge_answer_path(cohort, submitted_challenge_answer.challenge.content_file, submitted_challenge_answer)) + end + end + + describe "#submitted_challenge_answer_url" do + it "sets submitted_challenge_answer_url to the correct path " do + submitted_challenge_answer = create(:submitted_challenge_answer, cohort: cohort) + expect(described_class.new(submitted_challenge_answer).submitted_challenge_answer_url).to eq(Rails.application.routes.url_helpers.cohort_content_file_submitted_challenge_answer_path(cohort, submitted_challenge_answer.challenge.content_file, submitted_challenge_answer)) + end + end + + describe "#created_at" do + it "sets the created_at" do + submitted_challenge_answer = create(:submitted_challenge_answer, created_at: Time.new(1993, 02, 24, 12, 0, 0, "+00:00"), cohort: cohort) + expect(described_class.new(submitted_challenge_answer).created_at).to eq(submitted_challenge_answer.created_at) + end + end + + describe "#set_explanation" do + it "sets explanation to nil when the submission is incorrect" do + submitted_challenge_answer = create(:submitted_challenge_answer, cohort: cohort, status: "incorrect") + expect(described_class.new(submitted_challenge_answer).challenge_explanation).to eq(nil) + end + + it "sets explanation when the submission is correct" do + submitted_challenge_answer = create(:submitted_challenge_answer, cohort: cohort, status: "correct") + expect(described_class.new(submitted_challenge_answer).challenge_explanation).to eq(submitted_challenge_answer.challenge.explanation) + end + + it "sets explanation when the submission is incorrect and is_instructor_or_admin is true" do + submitted_challenge_answer = create(:submitted_challenge_answer, cohort: cohort, status: "incorrect") + expect(described_class.new(submitted_challenge_answer, true).challenge_explanation).to eq(submitted_challenge_answer.challenge.explanation) + end + end + + describe "#incorrect_attempts" do + it "counts the number of incorrect answers for a given challenge by a given user" do + user = create(:cohort_user, :student, cohort: cohort).user + challenge = create(:challenge) + submitted_challenge_answer = create(:submitted_challenge_answer, user: user, challenge: challenge, status: :incorrect, cohort: cohort) + create(:submitted_challenge_answer, user: user, challenge: challenge, status: :incorrect) + create(:submitted_challenge_answer, user: user, challenge: challenge) + expect(described_class.new(submitted_challenge_answer).incorrect_attempts).to eq(2) + end + end + + describe "#hints" do + let(:challenge) { create(:challenge, hints: hints) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, challenge: challenge, cohort: cohort) } + + context "the challenge has no hints" do + let(:hints) { [] } + it "sets hints to an empty array" do + expect(described_class.new(submitted_challenge_answer).hints).to eq([]) + end + end + + context "the challenge has hints" do + let(:hints) { ["hint 1", "hint 2", "hint 3"] } + context "the student has not requested hints" do + it "sets hints to an empty array" do + expect(described_class.new(submitted_challenge_answer).hints).to eq([]) + end + end + + context "the student has requested hints" do + before { submitted_challenge_answer.update(hints_shown: 1) } + it "sets a subarray based on the # of hints to show" do + expect(described_class.new(submitted_challenge_answer).hints).to eq(challenge.hints[0...1]) + end + end + end + end + + describe "#grader_label" do + context "when submitted challenge answer has not been graded" do + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: :ungraded) } + + it "returns nil" do + expect(described_class.new(submitted_challenge_answer).grader_label).to eq(nil) + end + end + + context "when submitted challenge answer has been manually graded" do + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: :correct, manual_grader: create(:user), cohort: cohort) } + + it "returns a string that indicates who graded the submitted challenge answer" do + expect(described_class.new(submitted_challenge_answer).grader_label).to include("Graded by") + expect(described_class.new(submitted_challenge_answer).grader_label).to include(submitted_challenge_answer.manual_grader.full_name) + end + end + + context "when submitted challenge answer has been automatically graded" do + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort) } + + it "returns a string that indicates the submitted challenge answer was autograded" do + expect(described_class.new(submitted_challenge_answer).grader_label).to include("Autograded") + end + end + end + + describe "#last_graded_at" do + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer) } + + it "sets the graded_at variable" do + expect(described_class.new(submitted_challenge_answer).last_graded_at).to eq(submitted_challenge_answer.graded_at) + end + end + end +end diff --git a/scripts/spec/services/activity_aggregator_service_spec.rb b/scripts/spec/services/activity_aggregator_service_spec.rb new file mode 100644 index 0000000..6009552 --- /dev/null +++ b/scripts/spec/services/activity_aggregator_service_spec.rb @@ -0,0 +1,259 @@ +require "spec_helper" + +describe ActivityAggregatorService do + let!(:cohort) { create(:cohort) } + let!(:content_file) { create(:content_file) } + + let(:date) { Date.new(2017, 12, 15) } + let(:one_week_from_now) { date + 1.week } + + subject do + Timecop.travel(date) { described_class.new(cohort).execute } + end + + describe ":dates" do + describe "first date" do + context "when there are no activities" do + context "when the cohort has no start date" do + let(:month_ago) { date - 1.month } + before { cohort.update(starts_on: nil) } + it "it is one month ago" do + expect(subject[:dates][0]).to include( + month_name: month_ago.strftime("%B"), + name: month_ago.strftime(ActivityAggregatorService::DATE_NAME_STRFTIME), + day_of_week: month_ago.strftime("%a"), + day_of_month: month_ago.strftime("%d") + ) + end + end + + context "when the cohort has a start date" do + let(:starts_on) { date - 2.months } + before { cohort.update(starts_on: starts_on) } + it "is the cohort start date" do + expect(subject[:dates][0]).to include( + month_name: starts_on.strftime("%B"), + name: starts_on.strftime(ActivityAggregatorService::DATE_NAME_STRFTIME), + day_of_week: starts_on.strftime("%a"), + day_of_month: starts_on.strftime("%d") + ) + end + end + end + + context "when there are activities" do + context "when the cohort start date is before the earliest activity" do + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:activity) { create(:activity, :comment, cohort: cohort, creator: student, created_at: Date.new(2017, 11, 15)) } + before { cohort.update(starts_on: Date.new(2017, 11, 14)) } + it "is the date of the earliest activity" do + expect(subject[:dates][0]).to include( + month_name: cohort.starts_on.strftime("%B"), + name: cohort.starts_on.strftime(ActivityAggregatorService::DATE_NAME_STRFTIME), + day_of_week: cohort.starts_on.strftime("%a"), + day_of_month: cohort.starts_on.strftime("%d") + ) + end + end + + context "when the cohort start date is after the earliest activity" do + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:activity) { create(:activity, :comment, cohort: cohort, creator: student, created_at: Date.new(2017, 11, 15)) } + before { cohort.update(starts_on: Date.new(2017, 11, 16)) } + it "is the cohort start date" do + expect(subject[:dates][0]).to include( + month_name: cohort.starts_on.strftime("%B"), + name: activity.created_at.strftime(ActivityAggregatorService::DATE_NAME_STRFTIME), + day_of_week: activity.created_at.strftime("%a"), + day_of_month: activity.created_at.strftime("%d") + ) + end + end + end + end + + describe "last date" do + context "when the cohort has no end date" do + before { cohort.update(ends_on: nil) } + it "is today" do + expect(subject[:dates].last).to include( + month_name: one_week_from_now.strftime("%B"), + name: one_week_from_now.strftime(ActivityAggregatorService::DATE_NAME_STRFTIME), + day_of_week: one_week_from_now.strftime("%a"), + day_of_month: one_week_from_now.strftime("%d") + ) + end + end + + context "when the cohort has a end date" do + context "when the end date is after today" do + before { cohort.update(ends_on: Date.new(2018, 1, 15)) } + it "is today" do + expect(subject[:dates].last).to include( + month_name: one_week_from_now.strftime("%B"), + name: one_week_from_now.strftime(ActivityAggregatorService::DATE_NAME_STRFTIME), + day_of_week: one_week_from_now.strftime("%a"), + day_of_month: one_week_from_now.strftime("%d") + ) + end + end + + context "when there is an activity after the end date" do + before { cohort.update(ends_on: Date.new(2018, 1, 15)) } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:activity) { create(:activity, :comment, cohort: cohort, creator: student, created_at: Date.new(2019, 11, 15)) } + it "is activity created at date" do + expect(subject[:dates].last).to include( + month_name: activity.created_at.strftime("%B"), + name: activity.created_at.strftime(ActivityAggregatorService::DATE_NAME_STRFTIME), + day_of_week: activity.created_at.strftime("%a"), + day_of_month: activity.created_at.strftime("%d") + ) + end + end + + context "when the end date is before today" do + before { cohort.update(ends_on: Date.new(2017, 12, 14)) } + it "is the cohort end date" do + expect(subject[:dates].last).to include( + month_name: cohort.ends_on.strftime("%B"), + name: cohort.ends_on.strftime(ActivityAggregatorService::DATE_NAME_STRFTIME), + day_of_week: cohort.ends_on.strftime("%a"), + day_of_month: cohort.ends_on.strftime("%d") + ) + end + end + end + end + + describe ":is_weekend" do + it "returns true for weekend dates" do + expect(subject[:dates].map { |date| date[:is_weekend] }).to eq( + [false, false, false, true, true, false, false, false, false, false, true, true, false, false, false, false, false, true, true, false, false, false, false, false, true, true, false, false, false, false, false, true, true, false, false, false, false, false] + ) + end + end + end + + describe ":grouped_dates" do + before { cohort.update(starts_on: Date.new(2017, 11, 14), ends_on: Date.new(2017, 12, 14)) } + it "groups dates by month" do + expect(subject[:grouped_dates].first.first).to eq("November") + expect(subject[:grouped_dates].last.first).to eq("December") + end + end + + describe ":students" do + context "when there are no students" do + it "yields an empty array" do + expect(subject[:students]).to eq([]) + end + end + + context "when there are students" do + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + it "yields only students with their ids, full names, profile images, and initials" do + expect(subject[:students].length).to eq 1 + expect(subject[:students][0]).to include( + id: student.id, + full_name: student.full_name, + profile_image: student.profile_image, + initials: student.initials + ) + end + end + end + + describe ":activity_data" do + let!(:student) { create(:cohort_user, :student, cohort: cohort).user } + + context "when there is activity for an instructor" do + let!(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + before do + create(:activity, subject: content_file, creator: instructor, created_at: date, cohort: cohort, name: Activity::NAMES[:content_file_viewed]) + end + it "does not include the instructor's user id in the user hash keys" do + expect(subject[:activity_data].keys).to_not include(instructor.id) + end + end + + context "when the user has no activity" do + it "returns nothing" do + expect(subject[:activity_data]).to eq(student.id => {}) + end + end + + context "when the student has only viewed lessons" do + before do + 5.times do + create(:activity, subject: content_file, creator: student, created_at: date, cohort: cohort, name: Activity::NAMES[:content_file_viewed]) + end + end + + it "caps their daily activity score at 20" do + expect(subject[:activity_data]).to eq(student.id => { "20171214" => 20 }) + end + end + + context "when the student has interactions" do + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, user: student, challenge: create(:challenge, content_file: content_file)) } + + before do + 20.times do + create(:activity, subject: submitted_challenge_answer, creator: student, created_at: date, cohort: cohort, name: Activity::NAMES[:comment_created]) + end + end + + it "caps their daily activity score at 65" do + expect(subject[:activity_data]).to eq(student.id => { "20171214" => 65 }) + end + end + + context "when the student has submitted a checkpoint" do + before do + create(:activity, subject: content_file, creator: student, created_at: date, cohort: cohort, name: "checkpoint_submission.created") + end + + it "fulfills their day activity score" do + expect(subject[:activity_data]).to eq(student.id => { "20171214" => 100 }) + end + end + + context "when the student has exceeded the daily activity score maximum" do + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, user: student, challenge: create(:challenge, content_file: content_file)) } + + before do + create(:activity, subject: submitted_challenge_answer, creator: student, created_at: date, cohort: cohort, name: Activity::NAMES[:comment_created]) + create(:activity, subject: content_file, creator: student, created_at: date, cohort: cohort, name: "checkpoint_submission.created") + end + + it "returns a score of 100 for the day" do + expect(subject[:activity_data]).to eq(student.id => { "20171214" => 100 }) + end + end + end + + context "when there are numerous, well-engaged students in the cohort" do + let(:student_ids) { cohort.students.pluck(:id) } + + before do + 0.times do + user = create(:cohort_user, :student, cohort: cohort).user + + Date.new(2017, 11, 1).upto(Date.new(2017, 12, 31)) do |date| + described_class::ACTIVITY_NAMES.each do |name| + 5.times do + create(:activity, subject: content_file, creator: user, created_at: date, cohort: cohort, name: name) + end + end + end + end + end + + # TODO Should we re-enable this performance tests? + xit "does not slow to a crawl" do + expect(Benchmark.measure { subject }.real.round(5)).to be < 3 + end + end +end diff --git a/scripts/spec/services/assessment_service_spec.rb b/scripts/spec/services/assessment_service_spec.rb new file mode 100644 index 0000000..db72262 --- /dev/null +++ b/scripts/spec/services/assessment_service_spec.rb @@ -0,0 +1,335 @@ +require "spec_helper" + +describe AssessmentService do + describe "code runners" do + let(:faraday_resp) { OpenStruct.new(status: 200) } + + describe ".evaluate_code_snippet" do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:code_snippet], tests: "function () {return true;}", setup: "console.log(\"helloWorld\")", language: "javascript") } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: "processing", challenge_id: challenge.id, answer: "function repeats() {return true;}") } + + it "returns creates a javascript job" do + response = described_class.evaluate_code_snippet(challenge, submitted_challenge_answer, "some-callback-url") + expect(response.queue_name).to eq("javascript_evaluation") + end + + # Code assessments no longer use Faraday requests + xit "updates submitted challenge answer with borked status when non-200 is yielded" do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_return(OpenStruct.new(status: 400)) + response = described_class.evaluate_code_snippet(challenge, submitted_challenge_answer, "some-callback-url") + expect(response.status).to eq(400) + expect(submitted_challenge_answer.reload.status).to eq("failed") + expect(submitted_challenge_answer.reload.test_results).to eq("Connection failed") + end + + # Code assessments no longer use Faraday requests + xit "updates submitted challenge answer with borked status when Faraday::ConnectionFailed is raised" do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(Faraday::ConnectionFailed.new("bork")) + described_class.evaluate_code_snippet(challenge, submitted_challenge_answer, "some-callback-url") + expect(submitted_challenge_answer.reload.status).to eq("failed") + expect(submitted_challenge_answer.reload.test_results).to eq("Connection failed") + end + end + + describe ".evaluate_project" do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:project], upstream_repo_path: "https://gitlab.com/Galvanize-IT/js-native-array-methods/-/tree/project-challenge-test") } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: "processing", challenge_id: challenge.id, answer: "https://github.com/sperella/js-native-array-methods/tree/with_some_answers") } + context "project challenge" do + it "returns 200 if job created" do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_return(faraday_resp) + response = described_class.evaluate_project(submitted_challenge_answer, "some-callback-url") + expect(response.status).to eq(200) + end + + context "when a non-200 status is yielded" do + it "updates submitted challenge answer with borked status" do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_return(OpenStruct.new(status: 400)) + response = described_class.evaluate_project(submitted_challenge_answer, "some-callback-url") + expect(response.status).to eq(400) + expect(submitted_challenge_answer.reload.status).to eq("failed") + expect(submitted_challenge_answer.reload.test_results).to eq("Connection failed") + end + end + + context "when Faraday::ConnectionFailed is raised" do + it "updates submitted challenge answer with failed status" do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(Faraday::ConnectionFailed.new("bork")) + described_class.evaluate_project(submitted_challenge_answer, "some-callback-url") + expect(submitted_challenge_answer.reload.status).to eq("failed") + expect(submitted_challenge_answer.reload.test_results).to eq("Connection failed") + end + end + + context "when GithubUrl::Invalid is raised" do + it "updates submitted challenge answer with invalid fork status" do + allow(described_class).to receive(:get_repo_properties).and_raise(GithubUrl::Invalid) + described_class.evaluate_project(submitted_challenge_answer, "some-callback-url") + expect(submitted_challenge_answer.reload.status).to eq("invalid_fork") + expect(submitted_challenge_answer.reload.test_results).to eq("Unexpected URL. Please verify that you submitted a full URL.") + end + end + end + end + + describe ".evaluate_custom_snippet" do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:custom_snippet], docker_directory_path: "./spec/fixtures/docker-test-repo") } + let(:submitted_challenge_answer) { create(:submitted_challenge_answer, status: "processing", challenge_id: challenge.id, answer: "SpringApplication.run(DemoApplication.class, args);") } + + it "returns 200 if job created" do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_return(faraday_resp) + response = described_class.evaluate_custom_snippet(challenge, submitted_challenge_answer, "some-callback-url") + expect(response.status).to eq(200) + end + + context "when non-200 is yielded" do + it "updates submitted challenge answer with failed status" do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_return(OpenStruct.new(status: 400)) + response = described_class.evaluate_custom_snippet(challenge, submitted_challenge_answer, "some-callback-url") + expect(response.status).to eq(400) + expect(submitted_challenge_answer.reload.status).to eq("failed") + expect(submitted_challenge_answer.reload.test_results).to eq("Connection failed") + end + end + + context "when Faraday::ConnectionFailed is raised" do + it "updates submitted challenge answer with failed status" do + allow_any_instance_of(Faraday::Connection).to receive(:post).and_raise(Faraday::ConnectionFailed.new("bork")) + described_class.evaluate_custom_snippet(challenge, submitted_challenge_answer, "some-callback-url") + expect(submitted_challenge_answer.reload.status).to eq("failed") + expect(submitted_challenge_answer.reload.test_results).to eq("Connection failed") + end + end + end + end + + describe ".get_answer_status" do + context "project challenge" do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:project]) } + it "returns ungraded if any answer is given" do + answer_class = described_class.get_answer_status(challenge, "http://www.example.com") + expect(answer_class).to eq(:ungraded) + end + + it "returns incorrect if the answer is blank" do + answer_class = described_class.get_answer_status(challenge, " ") + expect(answer_class).to eq(:incorrect) + end + end + + context "custom-snippet challenge" do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:custom_snippet]) } + + it "returns :processing" do + answer_class = described_class.get_answer_status(challenge, "submitted_challenge_answer") + expect(answer_class).to eq(:processing) + end + end + + context "code-snippet challenge" do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:code_snippet]) } + it "returns :processing" do + answer_class = described_class.get_answer_status(challenge, "submitted_challenge_answer") + expect(answer_class).to eq(:processing) + end + end + + context "number challenge" do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:number], answer: challenge_answer) } + let(:challenge_answer) { "1" } + + context "when submitted answer is not a number or fraction" do + it "returns :incorrect if answer is incorrect" do + answer_class = described_class.get_answer_status(challenge, "cat5") + expect(answer_class).to eq(:incorrect) + end + end + + context "when submitted answer is a fraction" do + let(:challenge_answer) { "0.33" } + before { challenge.decimal = 2 } + + it "returns :incorrect if answer is incorrect" do + answer_class = described_class.get_answer_status(challenge, "1/4") + expect(answer_class).to eq(:incorrect) + end + + it "returns :correct if answer is correct" do + answer_class = described_class.get_answer_status(challenge, "1/3") + expect(answer_class).to eq(:correct) + end + end + + context "and has decimal specified" do + let(:challenge_answer) { "0.33" } + before { challenge.decimal = 2 } + + it "ignores leading and trailing zeros on challenge answer" do + expect(described_class.get_answer_status(challenge, "0.33")).to eq(:correct) + end + + it "ignores leading and trailing zeros on submitted answer" do + expect(described_class.get_answer_status(challenge, "0000.333")).to eq(:correct) + expect(described_class.get_answer_status(challenge, ".3330")).to eq(:correct) + end + + it "returns true if students answer is accurate to specified decimal with rounding" do + expect(described_class.get_answer_status(challenge, "0.333")).to eq(:correct) + expect(described_class.get_answer_status(challenge, "0.332")).to eq(:correct) + expect(described_class.get_answer_status(challenge, "0.325")).to eq(:correct) + expect(described_class.get_answer_status(challenge, "0.33")).to eq(:correct) + end + + it "returns false if students answer does not match to specified decimal" do + expect(described_class.get_answer_status(challenge, "0.3")).to eq(:incorrect) + expect(described_class.get_answer_status(challenge, "0.335")).to eq(:incorrect) + expect(described_class.get_answer_status(challenge, "0.322")).to eq(:incorrect) + end + end + + context "and does not have decimal specified" do + let(:challenge_answer) { "00.330" } + it "ignores leading and trailing zeros on challenge answer" do + expect(described_class.get_answer_status(challenge, "0.33")).to eq(:correct) + end + + it "ignores leading and trailing zeros on submitted answer" do + expect(described_class.get_answer_status(challenge, "0000.33")).to eq(:correct) + expect(described_class.get_answer_status(challenge, ".330")).to eq(:correct) + end + + it "returns true if students answer is an exact numerical match" do + expect(described_class.get_answer_status(challenge, "0.33")).to eq(:correct) + end + + it "returns false if student answer is not an exact numerical match" do + expect(described_class.get_answer_status(challenge, "0.3")).to eq(:incorrect) + end + end + end + + context Challenge::TYPES[:multiple_choice] do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:multiple_choice], answer: "option 1") } + it "returns :correct if answer is correct" do + answer_class = described_class.get_answer_status(challenge, "option 1") + expect(answer_class).to eq(:correct) + end + it "returns :incorrect if answer is incorrect" do + answer_class = described_class.get_answer_status(challenge, "option 2") + expect(answer_class).to eq(:incorrect) + end + end + + context Challenge::TYPES[:checkbox] do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:checkbox], answer: '["option 1", "option 2", "option 3"]') } + it "returns :correct if answer is correct" do + answer_class = described_class.get_answer_status(challenge, ["option 1", "option 2", "option 3"]) + expect(answer_class).to eq(:correct) + end + it "returns :incorrect if answer is incorrect" do + answer_class = described_class.get_answer_status(challenge, ["option 1", "option 2"]) + expect(answer_class).to eq(:incorrect) + end + end + + context Challenge::TYPES[:paragraph] do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:paragraph]) } + it "returns :ungraded if challenge is submitted but not gradable" do + answer_class = described_class.get_answer_status(challenge, "lots of text") + expect(answer_class).to eq(:ungraded) + end + end + + context Challenge::TYPES[:short_answer] do + context "string match" do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:short_answer], answer: "short answer text") } + it "returns :correct if challenge is correct" do + answer_class = described_class.get_answer_status(challenge, "short answer text") + expect(answer_class).to eq(:correct) + end + + it "returns :incorrect if challenge is incorrect" do + answer_class = described_class.get_answer_status(challenge, "short answers are for suckers") + expect(answer_class).to eq(:incorrect) + end + + it "returns correct if student's answer matches the correct answer but is a different case" do + answer_class = described_class.get_answer_status(challenge, "ShoRt AnsWer TexT") + expect(answer_class).to eq(:correct) + end + + it "trims whitespace on submitted answer" do + expect(described_class.get_answer_status(challenge, " short answer text ")).to eq(:correct) + end + end + + context "regex matching" do + context "has case option flag" do + let(:challenge) { create(:challenge, challenge_type: Challenge::TYPES[:short_answer], answer: '/\{\{\s*article\.publishedOn\s+\|\s+date:\'MM-DD-YYYY\'\s*\}\}/i') } + it "returns :correct if answer matches regex case" do + answer_class = described_class.get_answer_status(challenge, "{{article.publishedOn | date:'MM-DD-YYYY'}}") + expect(answer_class).to eq(:correct) + answer_class = described_class.get_answer_status(challenge, "{{ article.publishedOn | date:'MM-DD-YYYY' }}") + expect(answer_class).to eq(:correct) + answer_class = described_class.get_answer_status(challenge, "{{ article.publishedOn | date:'MM-DD-YYYY' }}") + expect(answer_class).to eq(:correct) + answer_class = described_class.get_answer_status(challenge, "{{article.PuBliSheDon | date:'Mm-dD-YyYy'}}") + expect(answer_class).to eq(:correct) + end + + it "returns :incorrect if answer does not regex match with case insensitivity" do + wrong_spacing = described_class.get_answer_status(challenge, "{{article.published On | date:'MM-DD-YYYY'}}") + expect(wrong_spacing).to eq(:incorrect) + wrong_quotes = described_class.get_answer_status(challenge, "{{article.publishedOn | date:\"MM-DD-YYYY\"}}") + expect(wrong_quotes).to eq(:incorrect) + garbage_matcher = described_class.get_answer_status(challenge, "garbage") + expect(garbage_matcher).to eq(:incorrect) + pipe_missing = described_class.get_answer_status(challenge, "{{article.publishedOn date:'MM-DD-YYYY'}}") + expect(pipe_missing).to eq(:incorrect) + end + + it "doesn't regex match if answer doesn't begin with '/'" do + no_slash = create(:challenge, challenge_type: Challenge::TYPES[:short_answer], answer: 'nope/\{\{\s*article\.publishedOn\s+\|\s+date:\'MM-DD-YYYY\'\s*\}\}/i') + answer_class = described_class.get_answer_status(no_slash, "{{article.publishedOn | date:'MM-DD-YYYY'}}") + expect(answer_class).to eq(:incorrect) + answer_class = described_class.get_answer_status(no_slash, 'nope/\{\{\s*article\.publishedOn\s+\|\s+date:\'MM-DD-YYYY\'\s*\}\}/i') + expect(answer_class).to eq(:correct) + end + end + + context "does not have case option flag" do + it "doesn't regex match if answer doesn't end match case" do + no_end_slash = create(:challenge, challenge_type: Challenge::TYPES[:short_answer], answer: '/\{\{\s*article\.publishedOn\s+\|\s+date:\'MM-DD-YYYY\'\s*\}\}/') + answer_class = described_class.get_answer_status(no_end_slash, "{{article.pUbLisheDon | date:'mM-Dd-YyYy'}}") + expect(answer_class).to eq(:incorrect) + end + end + end + end + end + + describe ".get_repo_properties" do + let(:repo_url) { "https://#{repo_host}/gSchool/fs-curriculum/tree/development/dumb_folder" } + + context "when the repo_host is github" do + let(:repo_host) { "github.com" } + it "returns a hash of repo properties from GithubUrl" do + expect(described_class.get_repo_properties(repo_url, "development")).to eq(org: "gSchool", + repo: "fs-curriculum", + branch: "development", + folder: "dumb_folder", + repo_host: repo_host) + end + end + + context "when the repo_host is non-github" do + let(:repo_host) { "sum.git.place" } + it "returns a hash of repo properties from GithubUrl" do + expect(described_class.get_repo_properties(repo_url, "master")).to eq(org: "gSchool", + repo: "fs-curriculum", + branch: "master", + folder: "dumb_folder", + repo_host: repo_host) + end + end + end +end diff --git a/scripts/spec/services/auto_assign_release_service_spec.rb b/scripts/spec/services/auto_assign_release_service_spec.rb new file mode 100644 index 0000000..1b3098d --- /dev/null +++ b/scripts/spec/services/auto_assign_release_service_spec.rb @@ -0,0 +1,27 @@ +require "spec_helper" + +describe AutoAssignReleaseService do + describe "::execute" do + let!(:block) { create(:block) } + let!(:old_release) { create(:release, block: block) } + let!(:new_release) { create(:release, block: block) } + + subject { described_class.execute(new_release) } + + context "when a cohort release is not set to use latest release" do + let!(:cohort_release) { create(:cohort_release, release: old_release, use_latest_release: false) } + + it "does not update the cohort release to point to the new release" do + expect { subject }.to_not(change { cohort_release.reload.release_id }) + end + end + + context "when a cohort release is set to use latest release" do + let!(:cohort_release) { create(:cohort_release, release: old_release, use_latest_release: true) } + + it "updates the cohort release to point to the new release" do + expect { subject }.to change { cohort_release.reload.release_id }.from(old_release.id).to(new_release.id) + end + end + end +end diff --git a/scripts/spec/services/checkpoint_submission_service_spec.rb b/scripts/spec/services/checkpoint_submission_service_spec.rb new file mode 100644 index 0000000..72050a3 --- /dev/null +++ b/scripts/spec/services/checkpoint_submission_service_spec.rb @@ -0,0 +1,135 @@ +require "spec_helper" + +describe CheckpointSubmissionService do + describe ".attempt_autoscore" do + let(:cohort) { create(:cohort) } + let(:standard) { create(:standard) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: standard.release) } + let(:content_file) { create(:content_file, :checkpoint, standard: standard, autoscore: true) } + let(:challenge) { create(:challenge, content_file: content_file, points: 4) } + let(:student) { create(:cohort_user, :student).user } + let(:checkpoint_submission) { create_checkpoint_submission(student, challenge, cohort: cohort) } + + context "when all challenges have been scored" do + it "sets the state to done" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :correct, points: 3) + expect do + CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore + end.to change { checkpoint_submission.reload.state }.from(CheckpointSubmission::STATES[:needs_review]).to(CheckpointSubmission::STATES[:done]) + expect(checkpoint_submission.total_points).to eq 4 + expect(checkpoint_submission.correct_points).to eq 3 + end + + it "calls through to NotificationService" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :correct) + expect(NotificationService).to receive(:checkpoint_submission_graded).with( + user: student, + cohort: cohort, + content_file: content_file, + checkpoint_submission: checkpoint_submission + ) + + CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore + end + + it "creates an Activity" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :correct) + + expect { CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore }.to change { Activity.count }.from(0).to(1) + new_activity = Activity.last + expect(new_activity.name).to eq(Activity::NAMES[:checkpoint_submission_evaluated]) + expect(new_activity.subject).to eq(checkpoint_submission) + expect(new_activity.creator).to eq(nil) + expect(new_activity.cohort).to eq(cohort) + end + + it "creates a performance with a score of 3 when all of the challenges are correct" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :correct) + expect do + CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore + end.to(change { Performance.count }.from(0).to(1)) + + performance = Performance.last + expect(performance.user_id).to eq student.id + expect(performance.standard_id).to eq standard.id + expect(performance.score).to eq 3 + expect(performance.checkpoint_submission_id).to eq checkpoint_submission.id + expect(performance.cohort_id).to eq cohort.id + expect(performance.challenge_completion).to eq "1/1" + end + + it "creates a performance with a score of 1 when all challenges are incorrect" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :incorrect) + expect do + CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore + end.to(change { Performance.count }.from(0).to(1)) + + performance = Performance.last + expect(performance.score).to eq 1 + expect(performance.challenge_completion).to eq "0/1" + end + + it "creates a performance with a score of 2 when some of the challenges are correct and some are incorrect" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :incorrect, points: 0) + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :correct, points: 2) + expect do + CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore + end.to(change { Performance.count }.from(0).to(1)) + + performance = Performance.last + expect(performance.score).to eq 2 + end + + it "does not create performance when an instructor has already scored the checkpoint" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :correct) + checkpoint_submission.update(grader: create(:cohort_user, :instructor, cohort: cohort).user) + expect do + expect do + CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore + end.to_not(change { Performance.count }) + end.to_not(change { checkpoint_submission.state }) + end + + it "does not create performance when the content file is not autoscore" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :correct) + expect do + expect do + CheckpointSubmissionService.new(checkpoint_submission, create(:content_file, :checkpoint, standard: standard, autoscore: false), cohort).attempt_autoscore + end.to_not(change { Performance.count }) + end.to_not(change { checkpoint_submission.state }) + end + + it "rolls back the creation of performances if the update to the checkpoint submission status fails" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :correct) + expect(checkpoint_submission).to receive(:update).and_raise("some exception") + expect do + expect do + expect do + CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore + end.to raise_error("some exception") + end.to_not(change { Performance.count }) + end.to_not(change { checkpoint_submission.state }) + end + end + + context "when scas do not have points assigned" do + it "does not include that sca in points totals" do + create(:submitted_challenge_answer, cohort: cohort, points: nil, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :correct) + + CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore + expect(checkpoint_submission.reload.correct_points).to eq 0 + end + end + + context "when challenges have not been scored yet" do + it "does not create performances when not all of the challenges have been scored (project & code snippets)" do + create(:submitted_challenge_answer, cohort: cohort, challenge: challenge, user: student, checkpoint_submission: checkpoint_submission, status: :processing) + expect do + expect do + CheckpointSubmissionService.new(checkpoint_submission, content_file, cohort).attempt_autoscore + end.to_not(change { Performance.count }) + end.to_not(change { checkpoint_submission.state }) + end + end + end +end diff --git a/scripts/spec/services/cohort_standard_progress_service_spec.rb b/scripts/spec/services/cohort_standard_progress_service_spec.rb new file mode 100644 index 0000000..c17db53 --- /dev/null +++ b/scripts/spec/services/cohort_standard_progress_service_spec.rb @@ -0,0 +1,216 @@ +require "spec_helper" + +describe CohortStandardProgressService do + let(:cohort) { create(:cohort) } + let(:standard) { create(:standard) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: standard.release) } + let(:content_file) { create(:content_file, standard: standard, autoscore: true) } + let!(:challenge) { create(:challenge, content_file: content_file) } + let(:student) { create(:cohort_user, :student).user } + let(:checkpoint_submissions) { nil } + + let!(:content_resource) { create(:content_file, content_file_type: ContentFile::TYPES[:resource], standard: standard, title: "Resource", position: 1) } + let!(:challenge_resource_1) { create(:challenge, content_file: content_resource, position: 0, title: "Buzz Nonedrin") } + let!(:checkpoints_for_submission_service) { CheckpointSubmission.where(id: checkpoint_submission&.id)} + let!(:include_totals) {false} + let!(:include_topics) {true} + + describe ".calculate" do + subject do + cohort_standard_progress = StandardSubmissionsService.new(standard: standard, cohort: cohort, cohort_releases: nil, student_ids: [student.id], all_checkpoints: checkpoints_for_submission_service).single + described_class.new(user_id: student.id, + lesson_visits: LessonVisit.where(user_id: student.id), + cohort_standard_progress: cohort_standard_progress, + standard: standard.id, checkpoint_submissions: checkpoint_submissions).calculate(include_totals: include_totals, include_topics: include_topics) + end + + context "when no things have been completed in standard" do + let!(:checkpoint_submission) { nil } + + it "returns a hash with no progress" do + expect(subject).to eq(percent_done: 0, remaining: 1, standard: {title: standard.title, uid: standard.uid, id: standard.id}, checkpoint_percent: nil) + end + end + + context "when some things have been completed in standard" do + let!(:content_file_2) { create(:content_file, standard: standard, autoscore: true) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2) } + let!(:student_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct], cohort: cohort) } + let!(:checkpoint_submission) { nil } + + it "returns a hash with some progress" do + expect(subject).to eq(percent_done: 50.0, remaining: 1, standard: {title: standard.title, uid: standard.uid, id: standard.id}, checkpoint_percent: nil) + end + + context "when a content file does not have a challenge or uid" do + let(:content_file_3) { build(:content_file, standard: standard, autoscore: true, uid: nil) } + + it "does not count that content file towards the progress" do + content_file_3.save(validate: false) + expect(subject).to eq( + percent_done: 50.0, + remaining: 1, + standard: {title: standard.title, uid: standard.uid, id: standard.id}, + checkpoint_percent: nil + ) + end + end + end + + context "when some things have been completed and checkpoint is present (needs review) in standard" do + let!(:content_file_2) { create(:content_file, standard: standard, autoscore: true) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2) } + let!(:student_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct]) } + let!(:content_file_3) { create(:content_file, :checkpoint, standard: standard, autoscore: true) } + let!(:challenge_3) { create(:challenge, content_file: content_file_3) } + let!(:checkpoint_submission) { create_checkpoint_submission(student, challenge_3, cohort: cohort, state: CheckpointSubmission::STATES[:needs_review]) } + + it "returns a hash with full progress" do + expect(subject).to eq( + percent_done: 100, + remaining: 0, + standard: {title: standard.title, uid: standard.uid, id: standard.id}, + checkpoint_percent: nil, + topic_averages: nil + ) + end + end + + context "when all things have been completed in standard" do + let!(:student_sca_1) { create(:submitted_challenge_answer, user: student, challenge: challenge, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct]) } + let!(:content_file_2) { create(:content_file, standard: standard, autoscore: true) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2) } + let!(:student_sca_2) { create(:submitted_challenge_answer, user: student, challenge: challenge_2, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct]) } + let!(:content_file_3) { create(:content_file, :checkpoint, standard: standard, autoscore: true) } + let!(:challenge_3) { create(:challenge, content_file: content_file_3) } + let!(:checkpoint_submission) { create_checkpoint_submission(student, challenge_3, cohort: cohort, state: CheckpointSubmission::STATES[:done], total_points: 2, correct_points: 1) } + + context "with checkpoint done but with incorrect challenges" do + let!(:student_sca_3) { create(:submitted_challenge_answer, user: student, challenge: challenge_3, status: SubmittedChallengeAnswer::GRADED_STATUSES[:incorrect], checkpoint_submission_id: checkpoint_submission.id) } + + it "returns a hash with progress data" do + expect(subject).to eq( + percent_done: 100, + remaining: 0, + standard: {title: standard.title, uid: standard.uid, id: standard.id}, + checkpoint_percent: 50.0, + topic_averages: {} + ) + end + + context "specifying checkpoint submissions" do + let(:checkpoint_submissions) { [checkpoint_submission] } + it "returns a hash with progress data" do + expect(subject).to eq( + percent_done: 100, + remaining: 0, + standard: {title: standard.title, uid: standard.uid, id: standard.id}, + checkpoint_percent: 50.0, + topic_averages: {} + ) + end + + context "when given checkpoint submissions with historical matches" do + let!(:earlier_checkpoint_submission) { create_checkpoint_submission(student, challenge_3, created_at: 1.day.ago, cohort: cohort, state: CheckpointSubmission::STATES[:done], total_points: 1, correct_points: 1) } + let!(:earlier_student_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge_3, status: SubmittedChallengeAnswer::GRADED_STATUSES[:incorrect], checkpoint_submission_id: earlier_checkpoint_submission.id) } + let!(:checkpoint_submissions) { [checkpoint_submission, earlier_checkpoint_submission] } + let!(:checkpoints_for_submission_service) { CheckpointSubmission.where(id: [checkpoint_submission.id, earlier_checkpoint_submission.id]) } + + before do + checkpoint_submission.update(state: "started") + end + + it "returns a hash with progress data for the earlier submission" do + expect(subject).to eq( + percent_done: 100, + remaining: 0, + standard: {title: standard.title, uid: standard.uid, id: standard.id}, + checkpoint_percent: 100, # matches earlier checkpoint submission + topic_averages: {} + ) + end + end + end + + context "include totals specified" do + let(:include_totals) {true} + it "returns a hash with progress data with totals" do + expect(subject).to eq( + percent_done: 100, + remaining: 0, + standard: {title: standard.title, uid: standard.uid, id: standard.id}, + checkpoint_percent: 50.0, + topic_averages: {}, + totals: {earned: 1, possible: 2} + ) + end + end + + context "include topics not specified" do + let(:include_totals) {true} + let(:include_topics) {false} + it "returns a hash with progress data, no topic averages" do + expect(subject).to eq( + percent_done: 100, + remaining: 0, + standard: {title: standard.title, uid: standard.uid, id: standard.id}, + checkpoint_percent: 50.0, + totals: {earned: 1, possible: 2}, + topic_averages: nil + ) + end + end + end + + context "with checkpoint done but with all correct challenges" do + let!(:student_sca_3) { create(:submitted_challenge_answer, user: student, challenge: challenge_3, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct], checkpoint_submission_id: checkpoint_submission.id) } + + it "returns a hash with progress data" do + checkpoint_submission.update(total_points: 2, correct_points: 2) + expect(subject).to eq( + percent_done: 100.00, + remaining: 0, + standard: {title: standard.title, uid: standard.uid, id: standard.id}, + checkpoint_percent: 100, + topic_averages: {} + ) + end + end + + context "with checkpoint and challenges with topics" do + let!(:block) { create(:block) } + let!(:release_2) { create(:release, block: block) } + let!(:cohort_2) { create(:cohort, mode: 'Percentage') } + let!(:cohort_2_release) { create(:cohort_release, release: release_2, cohort: cohort_2) } + let!(:standard_2) { create(:standard, release: release_2) } + let!(:content_file_2) { create(:content_file, standard: standard_2, autoscore: true, content_file_type: ContentFile::TYPES[:checkpoint]) } + let!(:student_2) { create(:cohort_user, :student).user } + let!(:challenge_2) { create(:challenge, content_file: content_file_2, topics: ['very neat', 'super cool'], release_id: standard_2.release.id, points: 5) } + let!(:checkpoint_submission_2) { create(:checkpoint_submission, content_file_uid: content_file_2.uid, content_file_block_id: block.id , cohort: cohort_2, user: student_2, state: CheckpointSubmission::STATES[:done], correct_points: 3, total_points: 5) } + let!(:student_sca) { create(:submitted_challenge_answer, checkpoint_submission: checkpoint_submission_2, user: student_2, cohort: cohort_2, challenge: challenge_2, created_at: 1.day.ago, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct], points: 3) } + + it "returns a hash with progress data including topics" do + cohort_standard_progress = StandardSubmissionsService.new(standard: standard_2, cohort: cohort_2, cohort_releases: [release_2], student_ids: [student_2.id], all_checkpoints: CheckpointSubmission.where(id: checkpoint_submission_2.id)).single + + expect( + described_class.new( + user_id: student_2.id, + lesson_visits: LessonVisit.where(user_id: student_2.id), + cohort_standard_progress: cohort_standard_progress, + standard: standard_2 + ).calculate(include_topics: true) + ).to eq( + percent_done: 100, + remaining: 0, + standard: {title: standard_2.title, uid: standard_2.uid, id: standard_2.id}, + checkpoint_percent: 60, + topic_averages: { + "very neat": { earned: 3, possible: 5 }, + "super cool": { earned: 3, possible: 5 } + } + ) + end + end + end + end +end diff --git a/scripts/spec/services/course_validator_spec.rb b/scripts/spec/services/course_validator_spec.rb new file mode 100644 index 0000000..f873719 --- /dev/null +++ b/scripts/spec/services/course_validator_spec.rb @@ -0,0 +1,221 @@ +require "spec_helper" + +describe CourseValidator do + + it "works when everything is valid" do + VCR.use_cassette("github-course-yaml-success") do + result = described_class.run(url: "https://github.com/gSchool/learn-course-files/blob/master/test/integration.yaml") + expect(result).to be_a_success + expect(result.value[:course]).to eq({ + default_unit_visibility: false, + sections: [ + { + title: "DSI Admissions Prep", + repos: [ + {url: "https://github.com/gSchool/dsi-intro-to-ds-stats"}, + {url: "https://github.com/gSchool/ds-sql-block"}, + ] + }, + { + title: "Advanced DSI Admissions Prep", + repos: [ + {url: "https://github.com/gSchool/ds-python-quizzes-block"}, + ] + } + ] + }) + end + end + + it "works when the course yaml file comes from gitlab sources" do + VCR.use_cassette("gitlab-course-yaml-success") do + result = described_class.run(url: "https://code.il2.dsop.io/csakamaki/p1-test/-/blob/master/course.yaml") + expect(result).to be_a_success + expect(result.value[:course]).to eq({ + default_unit_visibility: false, + sections: [ + { + title: "DSOP GitLab", + repos: [ + { url: "https://code.il2.dsop.io/csakamaki/p1-test" } + ] + }, + { + title: "gSchool GitHub", + repos: [ + { url: "https://github.com/gSchool/revacomm-test" } + ] + } + ] + }) + end + + end + + it "fetches from branches and fails when repos are missing in the yaml" do + VCR.use_cassette("gitlab-course-yaml-branch-failure") do + result = described_class.run(url: "https://code.il2.dsop.io/csakamaki/p1-test/-/blob/missingfile/course.yaml") + expect(result).to be_a_failure + expect(result.value.type).to eq :top_course_http_fetch_error + end + end + + describe "validations" do + let(:course_yaml) {""} + let(:json_payload) { {content: Base64.encode64(course_yaml)}.to_json } + subject do + task = described_class.new + params = { url: "stubbed" } + expect(task).to receive(:download_yaml).with(params).and_return( + SolidUseCase::Either.success(params.merge json: json_payload) + ) + allow(task).to receive(:validate_repos) {|params| SolidUseCase::Either.success(params)} + task.run(params) + end + + context "fails when parsing bad json" do + let(:json_payload) { {bad: "data"}.to_json } + it("fails") { expect(subject).to fail_with(:json_parse_error) } + end + + context "loading bad yaml" do + let(:course_yaml) { "[{]" } + it("fails") { expect(subject).to fail_with(:yaml_validation_error) } + end + + context "loading yaml but poorly formatted" do + let(:course_yaml) { + <<~YAML + --- + # bad move + --- + cOuRsE: + - SecTion: X + rePos: + - uRl: https://github.com/gSchool/blocks-test + YAML + } + it("fails") { expect(subject).to fail_with(:yaml_validation_error) } + end + + context "with an invalid key" do + let(:course_yaml) { + <<~YAML + --- + Course: + - Section: X + Repos: + - Url: https://github.com/gSchool/blocks-test + YAML + } + it "fails" do + expect(subject).to fail_with(:course_validation_error) + expect(subject.value.errors).to eq({ :invalid_key => ["repos"] }) + end + end + + context "with differently-cased keys" do + let(:course_yaml) { + <<~YAML + --- + cOuRsE: + - SecTion: X + rePos: + - uRl: https://github.com/gSchool/blocks-test + YAML + } + it("succeeds") { expect(subject).to be_a_success } + end + + context "with has no sections" do + let(:course_yaml) { + <<~YAML + --- + Course: + YAML + } + it "fails" do + expect(subject).to fail_with(:course_validation_error) + expect(subject.value.errors).to eq({ :empty_course => true }) + end + end + + context "with a section with no repos" do + let(:course_yaml) { + <<~YAML + --- + Course: + - Section: "No repos" + - Section: "With Repos" + Repos: + - Url: https://github.com/gSchool/blocks-test + YAML + } + it "fails" do + expect(subject).to fail_with(:course_validation_error) + expect(subject.value.errors).to eq({ :section_missing_repos => { title: "No repos", index: 0 } }) + end + end + + context "with duplicate repos in one section" do + let(:course_yaml) { + <<~YAML + --- + Course: + - Section: "Bad 1" + Repos: + - Url: https://github.com/gSchool/blocks-test + - Url: https://github.com/gSchool/blocks-test + YAML + } + it "fails" do + expect(subject).to fail_with(:course_validation_error) + expect(subject.value.errors).to eq({ :no_duplicate_repos => ['Duplicate github repos are not allowed'] }) + end + end + + context "with duplicate repos across multiple section" do + let(:course_yaml) { + <<~YAML + --- + Course: + - Section: "Good" + Repos: + - Url: https://github.com/gSchool/blocks-test + - Section: "Bad 2" + Repos: + - Url: https://github.com/gSchool/blocks-test + YAML + } + it "fails" do + expect(subject).to fail_with(:course_validation_error) + expect(subject.value.errors).to eq({ :no_duplicate_repos => ['Duplicate github repos are not allowed'] }) + end + end + + context "it succeeds when yaml is produced from .to_yaml" do + let(:course_yaml) { {Course: [{Section: "Section", Repos: [{Url: "https://github.com/gSchool/blocks-test"}]}]}.to_yaml } + it("succeeds") { expect(subject).to be_a_success } + end + + context "with a repo that do not point to master" + + context "with an invalid github url" do + let(:course_yaml) { + <<~YAML + --- + Course: + - Section: "ah" + Repos: + - Url: https://github.com/gSchool + YAML + } + it "fails" do + expect(subject).to fail_with(:course_validation_error) + expect(subject.value.errors).to eq({ "course" => { 0 => { + "repos" => { 0 => {"url" => ['is invalid']} } + } } }) + end + end + end +end diff --git a/scripts/spec/services/curriculum_progress_service_spec.rb b/scripts/spec/services/curriculum_progress_service_spec.rb new file mode 100644 index 0000000..6547543 --- /dev/null +++ b/scripts/spec/services/curriculum_progress_service_spec.rb @@ -0,0 +1,268 @@ +require "spec_helper" + +describe CurriculumProgressService do + describe ".execute_mastery" do + context "when the user has no performances" do + let(:release) { create(:standard).release } + subject { described_class.new(performances: []).execute_mastery } + + it "returns 0% progress" do + expect(subject).to eq( + percent_threes: 0.0, + percent_twos: 0.0, + percent_ones: 0.0, + completed_standards: [], + percent_unscored: 100.0, + score_3_dasharray: "0.0 276.46015", + score_2_dasharray: "0.0 276.46015", + score_1_dasharray: "0.0 276.46015", + score_2_dashoffset: "-0.0", + score_1_dashoffset: "-0.0" + ) + end + end + + context "when there are performances" do + let!(:standard_1) { create(:standard) } + let!(:standard_2) { create(:standard) } + let!(:standard_3) { create(:standard) } + let!(:performance_three) { create(:performance, standard: standard_2, score: 3) } + + subject do + described_class.new( + performances: [ + create(:performance, standard: standard_1, score: 2), + performance_three + ], + standards: [standard_1, standard_2, standard_3] + ).execute_mastery + end + + it "returns progress" do + expect(subject).to eq( + percent_threes: 33, + percent_twos: 33, + percent_ones: 0, + percent_unscored: 34, + completed_standards: [{completed_at: performance_three.created_at, title: "Some Standard"}], + score_1_dasharray: "0.0 276.46015", + score_1_dashoffset: "-184.30676666666662", + score_2_dasharray: "92.15338333333331 184.3067666666667", + score_2_dashoffset: "-92.15338333333331", + score_3_dasharray: "92.15338333333331 184.3067666666667" + ) + end + end + + context "#check_scores_for_accuracy" do + let!(:standard_1) { create(:standard) } + let!(:standard_2) { create(:standard) } + let!(:standard_3) { create(:standard) } + let!(:standard_4) { create(:standard) } + let!(:standard_5) { create(:standard) } + let!(:standard_6) { create(:standard) } + let!(:standard_7) { create(:standard) } + let!(:standard_8) { create(:standard) } + let!(:standard_9) { create(:standard) } + let!(:standard_10) { create(:standard) } + let!(:standard_11) { create(:standard) } + let!(:standard_12) { create(:standard) } + + subject do + described_class.new( + performances: [ + create(:performance, standard: standard_1, score: 2), + create(:performance, standard: standard_2, score: 3), + create(:performance, standard: standard_3, score: 1), + create(:performance, standard: standard_4, score: 1), + create(:performance, standard: standard_6, score: 2), + create(:performance, standard: standard_7, score: 3), + create(:performance, standard: standard_9, score: 1), + create(:performance, standard: standard_10, score: 3) + ], + standards: [ + standard_1, + standard_2, + standard_3, + standard_4, + standard_5, + standard_6, + standard_7, + standard_8, + standard_9, + standard_10, + standard_11, + standard_12, + ] + ).execute_mastery + end + + it "returns 100% progress" do + total = subject[:percent_threes] + subject[:percent_twos] + subject[:percent_ones] + subject[:percent_unscored] + expect(total).to eq(100) + end + end + end + + describe ".execute_completion" do + let(:cohort) { create(:cohort) } + let(:standard) { create(:standard) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: standard.release) } + let(:content_file) { create(:content_file, standard: standard, autoscore: true) } + let!(:challenge) { create(:challenge, content_file: content_file) } + let(:student) { create(:cohort_user, :student).user } + let(:include_totals) {false} + + context "when the user has completed contentfiles" do + let(:release) { create(:standard).release } + subject { described_class.new(completion_data: {}, standards: [standard]).execute_completion(include_totals: include_totals) } + + it "returns 0% progress" do + expect(subject).to eq( + percent_completed: 0.0, + percent_completed_dasharray: "0.0 276.46015", + completed_standards: [], + checkpoint_average: nil, + topic_averages: {} + ) + end + end + + context "when there are completed contentfiles" do + let!(:content_file_2) { create(:content_file, standard: standard, autoscore: true) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2) } + let!(:student_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge, created_at: 1.day.ago, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct]) } + + subject do + completion_data = StandardSubmissionsService.new(standard: standard, cohort: cohort, cohort_releases: [standard.release], student_ids: [student.id], all_checkpoints: []).all_standards + described_class.new( + completion_data: completion_data, + user_id: student.id, + standards: [standard] + ).execute_completion + end + + it "returns no progress as no checkpoints are done" do + expect(subject).to eq( + percent_completed: 0, + percent_completed_dasharray: "0.0 276.46015", + completed_standards: [], + checkpoint_average: nil, + topic_averages: {} + ) + end + end + + context "when there are completed checkpoints in percent mode it also rolls up topic averages" do + let!(:block) { create(:block) } + let!(:release_2) { create(:release, block: block) } + let!(:cohort_2) { create(:cohort, mode: 'Percentage') } + let!(:cohort_2_release) { create(:cohort_release, release: release_2, cohort: cohort_2) } + let!(:standard_2) { create(:standard, release: release_2) } + let!(:content_file_2) { create(:content_file, standard: standard_2, autoscore: true, content_file_type: ContentFile::TYPES[:checkpoint]) } + let!(:student_2) { create(:cohort_user, :student).user } + let!(:challenge_2) { create(:challenge, content_file: content_file_2, topics: ['very neat', 'super cool'], release_id: standard_2.release.id, points: 5) } + let!(:checkpoint_submission) { create(:checkpoint_submission, content_file_uid: content_file_2.uid, content_file_block_id: block.id , cohort: cohort_2, user: student_2, state: CheckpointSubmission::STATES[:done], correct_points: 3, total_points: 5) } + let!(:student_sca) { create(:submitted_challenge_answer, checkpoint_submission: checkpoint_submission, user: student_2, cohort: cohort_2, challenge: challenge_2, created_at: 1.day.ago, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct], points: 3) } + + subject do + completion_data = StandardSubmissionsService.new(standard: standard_2, cohort: cohort_2, cohort_releases: [release_2], student_ids: [student_2.id], all_checkpoints: [checkpoint_submission]).all_standards + described_class.new( + completion_data: completion_data, + user_id: student_2.id, + standards: [standard_2] + ).execute_completion(include_totals: include_totals) + end + + it "returns progress as checkpoints are done" do + expect(subject).to eq( + percent_completed: 100, + percent_completed_dasharray: "276.46015 0.0", + completed_standards: [{:title=>standard_2.title, :uid=>standard_2.uid, :id=>standard_2.id}], + checkpoint_average: 60, + topic_averages: { + "very neat": { earned: 3, possible: 5, average: 60 }, + "super cool": { earned: 3, possible: 5, average: 60 } + } + ) + end + + context "include totals is set to true" do + let(:include_totals) {true} + it "returns progress as checkpoints are done" do + expect(subject).to eq( + percent_completed: 100, + percent_completed_dasharray: "276.46015 0.0", + completed_standards: [{:title=>standard_2.title, :uid=>standard_2.uid, :id=>standard_2.id}], + checkpoint_average: 60, + topic_averages: { + "very neat": { earned: 3, possible: 5, average: 60 }, + "super cool": { earned: 3, possible: 5, average: 60 } + }, + totals: { total_earned: 3, total_possible: 5, percent: 60 } + ) + end + end + end + + context "when there are completed and pending contentfiles" do + let!(:content_file_2) { create(:content_file, standard: standard, autoscore: true) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2, topics: ['very neat', 'super cool'], release_id: standard.release.id) } + let!(:student_sca) { create(:submitted_challenge_answer, user: student, challenge: challenge, created_at: 1.day.ago, status: SubmittedChallengeAnswer::GRADED_STATUSES[:correct]) } + let!(:content_file_3) { create(:content_file, :checkpoint, standard: standard, autoscore: true) } + let!(:challenge_3) { create(:challenge, content_file: content_file_3, topics: ['very neat', 'super cool'], release_id: standard.release.id) } + let!(:checkpoint_submission) { create_checkpoint_submission(student, challenge_3, cohort: cohort, state: CheckpointSubmission::STATES[:needs_review], total_points: 2, correct_points: 1) } + + let!(:content_resource) { create(:content_file, content_file_type: ContentFile::TYPES[:resource], standard: standard, title: "Resource") } + let!(:challenge_resource_1) { create(:challenge, content_file: content_resource, title: "Buzz Nonedrin") } + + subject do + completion_data = StandardSubmissionsService.new(standard: standard, cohort: cohort, cohort_releases: [standard.release], student_ids: [student.id], all_checkpoints: CheckpointSubmission.where(id: checkpoint_submission.id)).all_standards + described_class.new( + completion_data: completion_data, + user_id: student.id, + standards: [standard] + ).execute_completion + end + + it "returns progress completed as the standard has a checkpoint submitted" do + expect(subject).to eq( + percent_completed: 100, + percent_completed_dasharray: "276.46015 0.0", + completed_standards: [{ title: standard.title, uid: standard.uid, id: standard.id }], + checkpoint_average: nil, + topic_averages: {} + ) + end + end + + context "when there are multiple standards" do + let(:standard_2) { create(:standard) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: standard_2.release) } + + let!(:content_file_2) { create(:content_file, :checkpoint, standard: standard_2, autoscore: true) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2) } + let!(:checkpoint_submission) { create_checkpoint_submission(student, challenge_2, cohort: cohort, state: CheckpointSubmission::STATES[:done], total_points: 2, correct_points: 2) } + + subject do + completion_data = StandardSubmissionsService.new(standard: nil, cohort: cohort, cohort_releases: [standard.release, standard_2.release], standard_uids: [standard.uid, standard_2.uid], student_ids: [student.id], all_checkpoints: CheckpointSubmission.where(id: checkpoint_submission.id)).all_standards + described_class.new( + completion_data: completion_data, + user_id: student.id, + standards: [standard, standard_2] + ).execute_completion + end + + it "returns partial completeness relative to a checkpoint submissions per standard" do + expect(subject).to eq( + percent_completed: 50, + percent_completed_dasharray: "138.230075 138.230075", + completed_standards: [{ title: standard_2.title, uid: standard_2.uid, id: standard_2.id }], + checkpoint_average: 100.0, + topic_averages: {} + ) + end + end + + end +end diff --git a/scripts/spec/services/download_repository_service_spec.rb b/scripts/spec/services/download_repository_service_spec.rb new file mode 100644 index 0000000..d08a6bc --- /dev/null +++ b/scripts/spec/services/download_repository_service_spec.rb @@ -0,0 +1,78 @@ +require "spec_helper" + +describe DownloadRepositoryService do + + context "when downloading from github" do + let(:block) { create(:block, org: "gSchool", origin: "github.com", repo_name: "blocks-test")} + let(:release) { create(:release, block: block)} + + subject { DownloadGithubRepositoryService.new(release: release, tmp_path: Rails.root.join("tmp", block.repo_name)).execute } + + describe ".execute" do + context "when provided an invalid github url" do + let(:block) { create(:block, org: "gSchool", origin: "github.com", repo_name: "does-not-exist")} + + it "returns an error" do + VCR.use_cassette("github-not-found") do + expect(subject[:errors]).to eq(["Could not download repository: 404 Not Found"]) + end + end + end + + context "when provided a valid github url" do + it "returns no errors" do + VCR.use_cassette("github-repo-success") do + expect(subject[:errors]).to eq([]) + expect(subject[:repository_path]).to include("gSchool-blocks-test") + expect(subject[:sha]).to eq("0711a9a1293ab2b9b77cf6280c1836c231c9c8d5") + end + end + end + + context "when provided a valid github url with a branch" do + let(:release) { create(:release, block: block, branch_name: "test-branch")} + + it "returns no errors" do + VCR.use_cassette("github-branch-success") do + expect(subject[:errors]).to eq([]) + expect(subject[:repository_path]).to include("gSchool-blocks-test") + expect(subject[:sha]).to eq("38ace18a4d5584d1cc0ec17e5e904c888293596b") + end + end + end + end + end + + context "when downloading from gitlab" do + let(:block) { create(:block, org: "csakamaki", origin: "code.il2.dsop.io", repo_name: "test-20200917")} + let(:release) { create(:release, block: block)} + + subject { DownloadGitlabRepositoryService.new(release: release, tmp_path: Rails.root.join("tmp", block.repo_name)).execute } + + describe ".execute" do + context "when provided an invalid gitlab url" do + let(:block) { create(:block, org: "not-found", origin: "code.il2.dsop.io", repo_name: "does-not-exist")} + + it "returns an error" do + VCR.use_cassette("gitlab-not-found") do + expect(subject[:errors]).to eq(["Could not download repository: 404 Not Found"]) + end + end + end + + context "when provided a valid gitlab url" do + it "returns no errors" do + VCR.use_cassette("gitlab-success") do + expect(subject[:errors]).to eq([]) + expect(subject[:repository_path]).to include("test-20200917") + expect(subject[:sha]).to eq("0d3ccb796757d84e99dcf43ef913821483a248a0") + end + end + end + end + end + + after do + FileUtils.remove_dir(Rails.root.join("tmp", block.repo_name)) + end +end diff --git a/scripts/spec/services/git_url_service_spec.rb b/scripts/spec/services/git_url_service_spec.rb new file mode 100644 index 0000000..8ec13b3 --- /dev/null +++ b/scripts/spec/services/git_url_service_spec.rb @@ -0,0 +1,208 @@ +require "spec_helper" + +describe GitUrlService do + subject { described_class.new(url: url) } + + describe "initialization" do + context "when a valid url is passed with a host" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum" } + + it "instantiates itself" do + expect { subject }.to_not raise_error + end + end + + context "when a valid url is passed without a protocol" do + let(:url) { "www.github.com/gSchool/fs-curriculum" } + + it "instantiates itself" do + expect { subject }.to_not raise_error + end + end + + context "given a github URL that does not include an organization" do + let(:url) { "http://www.github.com/" } + + it "indicates that the organization is missing" do + expect { subject }.to raise_error(described_class::Invalid, "Missing organization") + end + end + + context "given a github URL that does not include a repository" do + let(:url) { "http://www.github.com/gSchool" } + + it "indicates that the repository is missing" do + expect { subject }.to raise_error(described_class::Invalid, "Missing repository") + end + end + + context "given a blank URL" do + let(:url) { "" } + + it "indicates the a URL must be present" do + expect { subject }.to raise_error(URI::InvalidURIError) + end + end + + context "when a tree key is passed without a name" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum/tree" } + + it "returns the branch name given" do + expect { subject }.to raise_error(described_class::Invalid, "Missing branch") + end + end + + context "when a blob key is passed without a name" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum/blob" } + + it "returns the branch name given" do + expect { subject }.to raise_error(described_class::Invalid, "Missing branch") + end + end + + context "when a raw key is passed without a name" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum/raw" } + + it "returns the branch name given" do + expect { subject }.to raise_error(described_class::Invalid, "Missing branch") + end + end + end + + describe "#organization" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum" } + + it "returns the organization" do + expect(subject.organization).to eq("gSchool") + end + end + + describe "#repository" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum" } + + it "returns the repository" do + expect(subject.repository).to eq("fs-curriculum") + end + end + + describe "#branch" do + context "when a branch name is not given" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum" } + + it "returns master" do + expect(subject.branch).to eq("master") + end + end + + context "when a branch name is given" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum/tree/development" } + + it "returns the branch name given" do + expect(subject.branch).to eq("development") + end + end + + context "when a branch name is given with gitlab url syntax" do + let(:url) { "https://gitlab.galvanize.com/peteragrunde/courseyaml/-/tree/missingfile" } + + it "returns the branch name given" do + expect(subject.branch).to eq("missingfile") + end + end + + context "when a blob name is given" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum/blob/development" } + + it "returns the branch name given" do + expect(subject.branch).to eq("development") + end + end + + context "when a raw name is given" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum/raw/development" } + + it "returns the branch name given" do + expect(subject.branch).to eq("development") + end + end + + context "when a branch name is not in the url, and a default branch name is passed" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum" } + let(:default_branch_name) { "development" } + subject { described_class.new(url: url, default_branch: default_branch_name) } + + it "returns the default branch name provided" do + expect(subject.branch).to eq(default_branch_name) + end + end + end + + describe "#path" do + context "for a master branch" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum/blob/master/foo/bar.md" } + + it "returns the path" do + expect(subject.path).to eq("foo/bar.md") + end + end + + context "for a non-master branch" do + let(:url) { "http://www.github.com/gSchool/fs-curriculum/tree/development/foo/bar.md" } + + it "returns the path" do + expect(subject.path).to eq("foo/bar.md") + end + end + end + + describe "subgroup gitlab url" do + context "for a project with a branch" do + let(:url) { "https://code.il2.dsop.io/tron/products/learn-lms/microservices/forge/-/tree/keycloak-auth" } + + it "gets the correct repo" do + expect(subject.repository).to eq("forge") + end + + it "gets the correct org" do + expect(subject.organization).to eq("tron") + end + + it "gets the correct branch" do + expect(subject.branch).to eq("keycloak-auth") + end + + it "gets an empty path" do + expect(subject.path).to eq("") + end + + it "gets the correct full path" do + expect(subject.full_path).to eq("tron/products/learn-lms/microservices/forge") + end + end + + context "for a project with a file specified" do + let(:url) { "https://code.il2.dsop.io/tron/products/learn-lms/microservices/forge/-/blob/master/config/environment.rb" } + + it "returns a full path without file information" do + expect(subject.full_path).to eq("tron/products/learn-lms/microservices/forge") + end + + it "gets the correct path" do + expect(subject.path).to eq("config/environment.rb") + end + end + + context "for a project with just a repository" do + let(:url) { "https://code.il2.dsop.io/tron/products/learn-lms/microservices/forge" } + + it "returns a full path without file information" do + expect(subject.full_path).to eq("tron/products/learn-lms/microservices/forge") + end + + it "gets the correct path" do + expect(subject.path).to eq("") + end + end + end +end + diff --git a/scripts/spec/services/mastery_average_service_spec.rb b/scripts/spec/services/mastery_average_service_spec.rb new file mode 100644 index 0000000..172e16e --- /dev/null +++ b/scripts/spec/services/mastery_average_service_spec.rb @@ -0,0 +1,23 @@ +require "spec_helper" + +describe MasteryAverageService do + subject { described_class.execute(performance_scores) } + + describe ".execute" do + context "when provided no scores" do + let(:performance_scores) { [] } + + it "returns N/A" do + expect(subject).to eq "N/A" + end + end + + context "when provided scores" do + let(:performance_scores) { [1, 1, 2, 3, 3, 3] } + + it "returns the mastery average" do + expect(subject).to eq 2.17 + end + end + end +end diff --git a/scripts/spec/services/notification_service_spec.rb b/scripts/spec/services/notification_service_spec.rb new file mode 100644 index 0000000..fa279a3 --- /dev/null +++ b/scripts/spec/services/notification_service_spec.rb @@ -0,0 +1,122 @@ +require "spec_helper" + +describe NotificationService do + describe ".checkpoint_submission_graded" do + let(:cohort) { create(:cohort) } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:content_file) { create(:content_file, :checkpoint) } + + subject do + described_class.checkpoint_submission_graded( + user: student, + grader: instructor, + cohort: cohort, + content_file: content_file, + checkpoint_submission: checkpoint_submission + ) + end + + context "when the cohort is percentage mode" do + let(:challenge_1) { create(:challenge, content_file: content_file) } + let!(:checkpoint_submission) { create(:checkpoint_submission, user_id: student.id, content_file_uid: content_file.uid, content_file_block_id: content_file.standard.release.block_id , cohort: cohort, state: CheckpointSubmission::STATES[:done], correct_points: 3, total_points: 5) } + + it "calculates percentage when challenge_completion is not set on the performance" do + cohort.update(mode: Cohort::MODES[:percentage]) + create(:performance, user: student, checkpoint_submission: checkpoint_submission, score: 2, challenge_completion: nil) + expect_any_instance_of(User).to receive(:notify).with( + tagline: "Checkpoint Scored", + title: "#{instructor.full_name} scored #{content_file.standard.release.block.title} - #{content_file.standard.title} - Checkpoint", + url: cohort_checkpoint_submission_path(cohort.id, checkpoint_submission.id), + description: "Your score is 3/5 points." + ) + + subject + end + end + + context "when the checkpoint submission is marked done" do + let(:checkpoint_submission) { create(:checkpoint_submission, state: CheckpointSubmission::STATES[:done], user_id: student.id, content_file_uid: content_file.uid, content_file_block_id: content_file.standard.release.block_id) } + + context "when no grader is passed" do + let(:instructor) { nil } + + it "returns the specific autoscore tagline" do + expect_any_instance_of(User).to receive(:notify).with( + tagline: "Checkpoint Autoscored", + title: "#{content_file.standard.release.block.title} - #{content_file.standard.title} - Checkpoint", + url: cohort_checkpoint_submission_path(cohort.id, checkpoint_submission.id), + description: "" + ) + + subject + end + end + + context "when there are multiple performances for the checkpoint submission" do + before do + create(:performance, user: student, checkpoint_submission: checkpoint_submission, score: 1) + create(:performance, user: student, checkpoint_submission: checkpoint_submission, score: 2, created_at: DateTime.yesterday) + end + + it "notifies the user" do + expect_any_instance_of(User).to receive(:notify).with( + tagline: "Checkpoint Scored", + title: "#{instructor.full_name} scored #{content_file.standard.release.block.title} - #{content_file.standard.title} - Checkpoint", + url: cohort_checkpoint_submission_path(cohort.id, checkpoint_submission.id), + description: "You received a 1." + ) + + subject + end + end + + context "when there is not a performance for the checkpoint submission" do + it "notifies the user" do + expect_any_instance_of(User).to receive(:notify).with( + tagline: "Checkpoint Scored", + title: "#{instructor.full_name} scored #{content_file.standard.release.block.title} - #{content_file.standard.title} - Checkpoint", + url: cohort_checkpoint_submission_path(cohort.id, checkpoint_submission.id), + description: "" + ) + + subject + end + end + end + + context "when the checkpoint submission is rejected" do + let(:checkpoint_submission) { create(:checkpoint_submission, user_id: student.id, content_file_uid: content_file.uid, content_file_block_id: content_file.standard.release.block_id) } + + before { checkpoint_submission.update(state: CheckpointSubmission::STATES[:rejected], grader: instructor) } + + context "when there is an associated comment" do + let!(:comment) { create(:activity, subject: checkpoint_submission, content: "No good!", cohort: cohort, creator: instructor, name: Activity::NAMES[:comment_created]) } + + it "notifies the user" do + expect_any_instance_of(User).to receive(:notify).with( + tagline: "Checkpoint Rejected", + title: "#{instructor.full_name} rejected #{content_file.standard.release.block.title} - #{content_file.standard.title} - Checkpoint", + url: cohort_checkpoint_submission_path(cohort.id, checkpoint_submission.id), + description: "\"No good!\"" + ) + + subject + end + end + + context "when there is not an associated comment" do + it "notifies the user" do + expect_any_instance_of(User).to receive(:notify).with( + tagline: "Checkpoint Rejected", + title: "#{instructor.full_name} rejected #{content_file.standard.release.block.title} - #{content_file.standard.title} - Checkpoint", + url: cohort_checkpoint_submission_path(cohort.id, checkpoint_submission.id), + description: "Please resubmit." + ) + + subject + end + end + end + end +end diff --git a/scripts/spec/services/platform_one_auth_resolver_service_spec.rb b/scripts/spec/services/platform_one_auth_resolver_service_spec.rb new file mode 100644 index 0000000..cf0f3f2 --- /dev/null +++ b/scripts/spec/services/platform_one_auth_resolver_service_spec.rb @@ -0,0 +1,88 @@ +require "spec_helper" + +describe PlatformOneAuthResolverService do + describe "resolves a user" do + + payload = { + exp: Time.now.to_i + 18000000, + iat: Time.now.to_i, + auth_time: Time.now.to_i, + jti: "e4600f88-1b42-4e01-ac01-8646d3cc4df6", + iss: "https://login.dsop.io/auth/realms/baby-yoda", + aud: "il4_191f836b-ec50-4819-ba10-1afaa5b99600_mission-widow", + sub: "2cba6ea8-8451-4952-80d6-c7de379d5b7e", + typ: "ID", + given_name: "Steve", + family_name: "Wozniak", + email: "the_woz@test.com" + } + + token = JWT.encode(payload, nil, 'none') + + subject { PlatformOneAuthResolverService.new(token) } + + context "the user does not exist" do + it "creates a new user" do + subject.find_or_create_user + + new_user = User.find_by(email: "the_woz@test.com"); + expect(new_user.first_name).to eq("Steve") + expect(new_user.last_name).to eq("Wozniak") + expect(new_user.email).to eq("the_woz@test.com") + expect(new_user.roles).to eq([]) + end + end + + context "the user exists" do + it "finds the user and sets its values" do + create(:user, email: "the_woz@test.com") + + subject.find_or_create_user + + found_user = User.find_by(email: "the_woz@test.com"); + expect(found_user.first_name).to eq("Steve") + expect(found_user.last_name).to eq("Wozniak") + expect(found_user.email).to eq("the_woz@test.com") + expect(found_user.roles).to eq([]) + end + end + + end + + describe "for a token with a missing email" do + payload = { + exp: Time.now.to_i + 18000000, + iat: Time.now.to_i, + auth_time: Time.now.to_i, + jti: "e4600f88-1b42-4e01-ac01-8646d3cc4df6", + iss: "https://login.dsop.io/auth/realms/baby-yoda", + aud: "il4_191f836b-ec50-4819-ba10-1afaa5b99600_mission-widow", + sub: "2cba6ea8-8451-4952-80d6-c7de379d5b7e", + typ: "ID", + given_name: "Steve", + family_name: "Wozniak", + email: "" + } + + token = JWT.encode(payload, nil, 'none') + + subject { PlatformOneAuthResolverService.new(token) } + + it "generates an error" do + expect { subject.find_or_create_user }.to raise_error(PlatformOneAuthResolverService::CreateUserError) + end + end + + describe "for an empty token" do + payload = {} + + token = JWT.encode(payload, nil, 'none') + + subject { PlatformOneAuthResolverService.new(token) } + + it "generates an error" do + expect { subject.find_or_create_user }.to raise_error(PlatformOneAuthResolverService::CreateUserError) + end + end + +end diff --git a/scripts/spec/services/preview_content_file_service_spec.rb b/scripts/spec/services/preview_content_file_service_spec.rb new file mode 100644 index 0000000..b9e636c --- /dev/null +++ b/scripts/spec/services/preview_content_file_service_spec.rb @@ -0,0 +1,24 @@ +require "spec_helper" + +describe PreviewContentFileService do + let(:cohort) { create(:cohort, sandbox: true) } + let!(:block) { create(:block, title: "preview") } + let!(:release) { create(:release, block: block) } + + before do + allow_any_instance_of(DownloadS3RepositoryService).to( + receive(:execute).and_return(errors: [], repository_path: Rails.root.join("spec", "fixtures", "preview-content-file", "preview.md").to_s, sha: "preview") + ) + end + + it "creates a content file for a sandbox for preview" do + expect { + PreviewContentFileService.new(sandbox_id: cohort.id, s3_key: "Key").execute + }.to change { Release.count }.by(1) + .and change { Standard.count }.by(1) + .and change { ContentFile.count }.by(1) + .and change { Challenge.count } + .and change { Section.count }.by(1) + .and change { CohortRelease.count }.by(1) + end +end diff --git a/scripts/spec/services/release_destroyer_service_spec.rb b/scripts/spec/services/release_destroyer_service_spec.rb new file mode 100644 index 0000000..5e51444 --- /dev/null +++ b/scripts/spec/services/release_destroyer_service_spec.rb @@ -0,0 +1,45 @@ +require "spec_helper" + +describe ReleaseDestroyerService do + let!(:cohort) { create(:cohort) } + let!(:preview_block) { create(:block, repo_name: "preview") } + let!(:release) { create(:release, preview_cohort_id: cohort.id, block: preview_block) } + let!(:standard) { create(:standard, release: release) } + let!(:section) { create(:section) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release, section: section) } + let!(:content_file) { create(:content_file, :checkpoint, standard: standard, autoscore: true) } + let!(:challenge) { create(:challenge, content_file: content_file, points: 4) } + let!(:submitted_challenge_answer) { create(:submitted_challenge_answer, cohort: cohort, challenge: challenge)} + let!(:checkpoint_submission) { create_checkpoint_submission(create(:user), challenge, cohort: cohort) } + let!(:perf) { create(:performance, checkpoint_submission: checkpoint_submission) } + + let!(:ignore_cohort) { create(:cohort) } + let!(:release_2) { create(:release, preview_cohort_id: ignore_cohort.id, block: preview_block) } + let!(:standard_2) { create(:standard, release: release_2) } + let!(:section_2) { create(:section) } + let!(:cohort_release_2) { create(:cohort_release, cohort: ignore_cohort, release: release_2, section: section_2) } + let!(:content_file_2) { create(:content_file, :checkpoint, standard: standard_2, autoscore: true) } + let!(:challenge_2) { create(:challenge, content_file: content_file_2, points: 4) } + let!(:checkpoint_submission_2) { create_checkpoint_submission(create(:user), challenge_2, cohort: ignore_cohort) } + + it "destroys all the items built or attached to that release" do + expect { + ReleaseDestroyerService.new(release_id: release.id, cohort_id: cohort.id).remove_release_from_existence + }.to change { Performance.count }.by(-1) + .and change { SubmittedChallengeAnswer.count }.by(-1) + .and change { CheckpointSubmission.count }.by(-1) + .and change { Challenge.count }.by(-1) + .and change { ContentFile.count }.by(-1) + .and change { Standard.count }.by(-1) + .and change { Section.count }.by(-1) + .and change { CohortRelease.count }.by(-1) + .and change { Release.count }.by(-1) + end + + it "does nothing unless the release is in preview mode" do + live_release = create(:release) + expect { + ReleaseDestroyerService.new(release_id: live_release.id).remove_release_from_existence + }.to_not change { Release.count } + end +end diff --git a/scripts/spec/services/resync_course_service_spec.rb b/scripts/spec/services/resync_course_service_spec.rb new file mode 100644 index 0000000..091b076 --- /dev/null +++ b/scripts/spec/services/resync_course_service_spec.rb @@ -0,0 +1,200 @@ +require "spec_helper" + +describe ResyncCourseService do + let(:cohort_1) { create(:cohort) } + let(:block_1) { create(:block, repo_name: 'ds-sql-block', org: "gSchool", origin: "github.com") } + let(:block_2) { create(:block, repo_name: 'ds-python', org: "gSchool", origin: "github.com") } + + let!(:cohort_release_1) do + create(:cohort_release, release: create(:release, block: block_1, state: 'success'), cohort: cohort_1) + end + let!(:release_2) { create(:release, block: block_2, state: 'success') } + + let(:sections) do + [{ + title: "Mock Section", + repos: [ + {url: "https://github.com/gSchool/ds-sql-block"}, + {url: "https://github.com/gSchool/ds-python"}, + ] + }] + end + + let(:params) { { cohort_id: cohort_1.id, sections: sections } } + + subject do + described_class.run(params) + end + + it "creates a cohort release" do + expect(Block.count).to eq(2) + expect(Release.count).to eq(2) + expect(CohortRelease.count).to eq(1) + + expect(subject).to be_a_success + + expect(Section.count).to eq(1) + expect(Section.last.title).to eq("Mock Section") + expect(Section.last.cohort_id).to eq(cohort_1.id) + expect(Section.last.position).to eq(1) + expect(Block.count).to eq(2) + expect(Release.count).to eq(2) + expect(CohortRelease.count).to eq(2) + expect(CohortRelease.first.cohort_id).to eq(cohort_1.id) + expect(CohortRelease.first.section_id).to eq(Section.last.id) + expect(CohortRelease.last.use_latest_release).to eq(true) + + expect(CohortRelease.all.order("position ASC").map(&:id)).to eq([ + cohort_release_1.id, + CohortRelease.last.id + ]) + end + + context "multiple sections" do + let(:sections) do + [ + { + title: "Mock Section 1", + repos: [ + {url: "https://github.com/gSchool/ds-python"}, + {url: "https://github.com/gSchool/ds-python-again"}, + ] + }, + { + title: "Mock Section 2", + repos: [ + {url: "https://github.com/gSchool/ds-sql-block"}, + {url: "https://github.com/gSchool/ds-sql-block-once-more"}, + ] + } + ] + end + let!(:block_3) { create(:block, repo_name: 'ds-sql-block-once-more', org: "gSchool", origin: "github.com") } + let!(:block_4) { create(:block, repo_name: 'ds-python-again', org: "gSchool", origin: "github.com") } + let!(:release_3) { create(:release, block: block_3, state: 'success') } + let!(:release_4) { create(:release, block: block_4, state: 'success') } + + it "creates positioned sections" do + expect(Block.count).to eq(4) + expect(Release.count).to eq(4) + expect(CohortRelease.count).to eq(1) + + expect(subject).to be_a_success + + expect(Section.count).to eq(2) + expect(Section.first.title).to eq("Mock Section 1") + expect(Section.first.cohort_id).to eq(cohort_1.id) + expect(Section.first.position).to eq(1) + expect(Section.last.title).to eq("Mock Section 2") + expect(Section.last.cohort_id).to eq(cohort_1.id) + expect(Section.last.position).to eq(2) + + expect(CohortRelease.count).to eq(4) + end + end + + describe "default_unit_visibility from course yaml" do + let!(:standard_1) { create(:standard, release: cohort_release_1.release) } + let!(:standard_2) { create(:standard, release: release_2) } + + it "creates content visibility objects for each standard when true" do + params.merge!(course_url: "url", default_unit_visibility: true) + + expect(subject).to be_a_success + + expect(ContentVisibility.count).to eq(2) + expect(ContentVisibility.first.content_uid).to eq(standard_1.uid) + expect(ContentVisibility.last.content_uid).to eq(standard_2.uid) + end + + it "does not create content visibility objects for each standard when non existant" do + expect(subject).to be_a_success + + expect(ContentVisibility.count).to eq(0) + end + end + + it "sets position on cohort releases" do + cohort_release_1.update(position: 234) + expect(subject).to be_a_success + + expect(CohortRelease.count).to eq(2) + + expect(CohortRelease.first.id).to eq(cohort_release_1.id) + expect(CohortRelease.first.position).to eq(1) + expect(CohortRelease.first.release.block.repo_name).to eq("ds-sql-block") + + expect(CohortRelease.last.position).to eq(2) + expect(CohortRelease.last.release.block.repo_name).to eq("ds-python") + end + + it "removes cohort releases not listed in config" do + sections[0][:repos].shift + + expect(Block.count).to eq(2) + expect(Release.count).to eq(2) + expect(CohortRelease.count).to eq(1) + + expect(subject).to be_a_success + + expect(Block.count).to eq(2) + expect(Release.count).to eq(2) + expect(CohortRelease.count).to eq(1) + + expect(CohortRelease.first.id).to_not eq(cohort_release_1.id) + expect(CohortRelease.first.cohort_id).to eq(cohort_1.id) + + expect(CohortRelease.last.id).to_not eq(cohort_release_1.id) + expect(CohortRelease.last.cohort_id).to eq(cohort_1.id) + end + + it "doesn't grab failed releases" do + Release.update_all(created_at: 1.day.ago) + unseen_release = create(:release, block: block_2, state: 'failure') + + expect(subject).to be_a_success + + expect(CohortRelease.count).to eq(2) + + expect(CohortRelease.first.release_id).to_not eq(unseen_release.id) + expect(CohortRelease.last.release_id).to_not eq(unseen_release.id) + end + + it "persists feature branches across course resyncs" do + CohortRelease.destroy_all + + feature1_cohort_release = create(:cohort_release, release: create(:release, block: block_1, state: 'success', branch_name: "feature"), cohort: cohort_1) + feature2_cohort_release = create(:cohort_release, release: create(:release, block: block_2, state: 'success', branch_name: "feature"), cohort: cohort_1) + + expect(subject).to be_a_success + expect(CohortRelease.count).to eq(2) + expect(CohortRelease.first.release_id).to eq(feature1_cohort_release.release_id) + expect(CohortRelease.last.release_id).to eq(feature2_cohort_release.release_id) + + expect(CohortRelease.first.release.branch_name).to eq('feature') + expect(CohortRelease.last.release.branch_name).to eq('feature') + end + + it "is idempotent" do + result_1 = described_class.run({ cohort_id: cohort_1.id, sections: sections }) + expect(result_1).to be_a_success + + sections_titles = Section.all.map(&:title) + blocks = Block.all + releases = Release.all + cohort_releases = CohortRelease.all + + expect(sections_titles.count).to eq(1) + expect(blocks.count).to eq(2) + expect(releases.count).to eq(2) + expect(cohort_releases.count).to eq(2) + + result_2 = described_class.run({ cohort_id: cohort_1.id, sections: sections }) + expect(result_2).to be_a_success + + expect(sections_titles).to eq(Section.all.map(&:title)) + expect(Block.all).to eq(blocks) + expect(Release.all).to eq(releases) + expect(CohortRelease.all).to eq(cohort_releases) + end +end diff --git a/scripts/spec/services/s3_asset_uploader_service_spec.rb b/scripts/spec/services/s3_asset_uploader_service_spec.rb new file mode 100644 index 0000000..cc50e9c --- /dev/null +++ b/scripts/spec/services/s3_asset_uploader_service_spec.rb @@ -0,0 +1,18 @@ +require "spec_helper" + +describe S3AssetUploaderService do + subject { described_class.new } + + describe "#find_or_create_content" do + let(:mock_aws_object) { double("aws object") } + let(:image_text) { "image" } + + context "when passed valid file contents and path" do + it "finds the image in S3 and returns the public URL" do + expect(Rails.application.config.aws).to receive_message_chain(:client, :head_object) { mock_aws_object } + + expect(subject.find_or_create_content(full_path: "bar.jpg")).to eq("http://some-s3-url/content/.jpg") + end + end + end +end diff --git a/scripts/spec/services/standard_submissions_service_spec.rb b/scripts/spec/services/standard_submissions_service_spec.rb new file mode 100644 index 0000000..cde5538 --- /dev/null +++ b/scripts/spec/services/standard_submissions_service_spec.rb @@ -0,0 +1,483 @@ +require "spec_helper" + +describe StandardSubmissionsService do + let!(:cohort) { create(:cohort) } + let(:instructor) { create(:cohort_user, :instructor, cohort: cohort).user } + let(:student) { create(:cohort_user, :student, cohort: cohort).user } + let!(:release) { create(:release) } + let!(:standard) { create(:standard, release: release) } + let!(:cohort_release) { create(:cohort_release, cohort: cohort, release: release) } + + let!(:content_file_0) { create(:content_file, standard: standard, title: "Fizz", position: 0) } + let!(:challenge_0_0) { create(:challenge, content_file: content_file_0, position: 0, title: "Fizz Tastic") } + let!(:challenge_0_1) { create(:challenge, content_file: content_file_0, position: 1, title: "Fizzlin") } + + let!(:content_file_1) { create(:content_file, standard: standard, title: "Buzz", position: 1) } + let!(:challenge_1_0) { create(:challenge, content_file: content_file_1, position: 0, title: "Buzz Nonedrin") } + let!(:challenge_1_1) { create(:challenge, content_file: content_file_1, position: 1, title: "Buzzelzebub") } + + let!(:content_file_hidden) { create(:content_file, standard: standard, title: "Hidden", position: 2) } + let!(:challenge_1_0) { create(:challenge, content_file: content_file_1, position: 0, title: "Buzz Nonedrin") } + let!(:challenge_1_1) { create(:challenge, content_file: content_file_1, position: 1, title: "Buzzelzebub") } + let!(:content_visibility) { create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_hidden.uid) } + + let!(:content_resource) { create(:content_file, content_file_type: ContentFile::TYPES[:resource], standard: standard, title: "Resource", position: 1) } + let!(:challenge_resource_1) { create(:challenge, content_file: content_resource, position: 0, title: "Buzz Nonedrin") } + let!(:challenge_resource_2) { create(:challenge, content_file: content_resource, position: 1, title: "Buzzelzebub") } + + let!(:content_instructor) { create(:content_file, content_file_type: ContentFile::TYPES[:instructor], standard: standard, title: "Instructor", position: 3) } + let!(:content_instructor_1) { create(:challenge, content_file: content_instructor, position: 0, title: "Buzz Nonedrin") } + let!(:content_instructor_2) { create(:challenge, content_file: content_instructor, position: 1, title: "Buzzelzebub") } + + let!(:content_file_checkpoint) { create(:content_file, :checkpoint, standard: standard, title: "Bazz", position: 2) } + let!(:challenge_in_checkpoint) { create(:challenge, content_file: content_file_checkpoint, position: 0, title: "Bazz Yetu") } + + before do + sign_in(student) + end + + describe "#single" do + describe "content file information" do + it "yields lesson and challenge data" do + results = StandardSubmissionsService.new(cohort: cohort, standard: standard, all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: student.instructor_or_admin?(cohort.id)).single + + expect(results.length).to eq 3 + first_content_file = results[0].deep_symbolize_keys + expect(first_content_file[:id]).to eq content_file_0.id + expect(first_content_file[:uid]).to eq content_file_0.uid + expect(first_content_file[:title]).to eq content_file_0.title + expect(first_content_file[:content_file_type]).to eq content_file_0.content_file_type + expect(first_content_file[:challenges].length).to eq 2 + expect(first_content_file[:challenges][0][:title]).to eq challenge_0_0.title + expect(first_content_file[:challenges][0][:id]).to eq challenge_0_0.id + expect(first_content_file[:challenges][1][:title]).to eq challenge_0_1.title + expect(first_content_file[:challenges][1][:id]).to eq challenge_0_1.id + expect(first_content_file[:challenges][1][:uid]).to eq challenge_0_1.uid + + second_content_file = results[1].deep_symbolize_keys + expect(second_content_file[:id]).to eq content_file_1.id + expect(second_content_file[:uid]).to eq content_file_1.uid + expect(second_content_file[:title]).to eq content_file_1.title + expect(second_content_file[:content_file_type]).to eq content_file_1.content_file_type + expect(second_content_file[:challenges].length).to eq 2 + expect(second_content_file[:challenges][0][:title]).to eq challenge_1_0.title + expect(second_content_file[:challenges][0][:id]).to eq challenge_1_0.id + expect(second_content_file[:challenges][0][:id]).to eq challenge_1_0.id + expect(second_content_file[:challenges][1][:title]).to eq challenge_1_1.title + expect(second_content_file[:challenges][1][:id]).to eq challenge_1_1.id + expect(second_content_file[:challenges][1][:uid]).to eq challenge_1_1.uid + + third_content_file = results[2].deep_symbolize_keys + expect(third_content_file[:id]).to eq content_file_checkpoint.id + expect(third_content_file[:uid]).to eq content_file_checkpoint.uid + expect(third_content_file[:title]).to eq content_file_checkpoint.title + expect(third_content_file[:content_file_type]).to eq content_file_checkpoint.content_file_type + expect(third_content_file[:challenges].length).to eq 1 + expect(third_content_file[:challenges][0][:title]).to eq challenge_in_checkpoint.title + expect(third_content_file[:challenges][0][:id]).to eq challenge_in_checkpoint.id + expect(third_content_file[:challenges][0][:uid]).to eq challenge_in_checkpoint.uid + end + + it "yields hidden content files so they show up on submissions tracker" do + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_1.uid) + results = StandardSubmissionsService.new(cohort: cohort, standard: standard, all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: student.instructor_or_admin?(cohort.id)).single(show_hidden: true) + + expect(results.length).to eq 4 + expect(results.map {|cf| cf[:id]}).to include(content_file_1.id) + end + + context "when as an instructor" do + before do + sign_in(instructor) + end + + it "yields instructor content files" do + results = StandardSubmissionsService.new(cohort: cohort, standard: standard, all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: instructor.instructor_or_admin?(cohort.id)).single(show_hidden: true) + + expect(results.length).to eq 5 + expect(results.map {|cf| cf[:id]}).to include(content_instructor.id) + end + end + end + + describe "student submission information" do + let!(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user } + let!(:student_2) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user } + + let(:incorrect) { SubmittedChallengeAnswer::STATUSES[:incorrect] } + let(:correct) { SubmittedChallengeAnswer::STATUSES[:correct] } + let(:ungraded) { SubmittedChallengeAnswer::STATUSES[:ungraded] } + let(:failed) { SubmittedChallengeAnswer::STATUSES[:failed] } + + let!(:student_1_challenge_0_0_old_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_0, created_at: 1.day.ago, status: incorrect, cohort: cohort) } + let!(:student_1_challenge_0_0_latest_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_0, status: correct, cohort: cohort) } + let!(:student_2_challenge_0_0_sca) { create(:submitted_challenge_answer, user: student_2, challenge: challenge_0_0, status: failed, cohort: cohort) } + + let!(:student_1_challenge_0_1_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_1, status: ungraded, cohort: cohort) } + + # no 0_1 submission for student 2, test absence of submission still creates empty object + + it "yields latest student submission information for each challenge" do + results = StandardSubmissionsService.new(cohort: cohort, standard: standard, all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: student.instructor_or_admin?(cohort.id)).single + + first_content_file = results[0].deep_symbolize_keys + first_challenges = first_content_file[:challenges] + expect(first_challenges[0][:student_submissions][student_1.id][:status]).to eq correct + expect(first_challenges[0][:student_submissions][student_2.id][:status]).to eq failed + + expect(first_challenges[1][:student_submissions][student_1.id][:status]).to eq ungraded + expect(first_challenges[1][:student_submissions][student_2.id][:status]).to eq "n/a" + end + + it "yields a single student's submission information when given" do + results = StandardSubmissionsService.new(cohort: cohort, standard: standard, student_ids: [student_1.id], all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: student.instructor_or_admin?(cohort.id)).single + + first_content_file = results[0].deep_symbolize_keys + first_challenges = first_content_file[:challenges] + expect(first_challenges[0][:student_submissions].keys.length).to eq 1 + expect(first_challenges[0][:student_submissions][student_1.id][:status]).to eq correct + + expect(first_challenges[1][:student_submissions].keys.length).to eq 1 + expect(first_challenges[1][:student_submissions][student_1.id][:status]).to eq ungraded + end + + it "yields nil when single student doesn't have a latest checkpoint submission" do + results = StandardSubmissionsService.new(cohort: cohort, standard: standard, student_ids: [student_1.id], all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: student.instructor_or_admin?(cohort.id)).single + + last_content_file = results.last.deep_symbolize_keys + expect(last_content_file).to have_key :checkpoint_submissions + expect(last_content_file[:checkpoint_submissions][student_1.id]).to eq nil + end + + it "does not leak submissions standards which share uids but not blocks" do + cohort_release.destroy + + copy_release = create(:release) + copy_standard = create(:standard, release: copy_release, uid: standard.uid) + copy_content_file = create(:content_file, standard: copy_standard, uid: content_file_0.uid, title: "Fizz", position: 0) + create(:challenge, content_file: copy_content_file, uid: challenge_0_0.uid, position: 0, title: "Fizz Tastic") + create(:cohort_release, cohort: cohort, release: copy_release) + + results = StandardSubmissionsService.new(cohort: cohort, standard: copy_standard, all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: student.instructor_or_admin?(cohort.id)).single + + first_content_file = results[0].deep_symbolize_keys + first_challenges = first_content_file[:challenges] + expect(first_challenges[0][:student_submissions][student_1.id][:status]).to eq "n/a" + expect(first_challenges[0][:student_submissions][student_2.id][:status]).to eq "n/a" + end + + it "yields the student's latest checkpoint submission" do + checkpoint_1 = create(:checkpoint_submission, + content_file_uid: content_file_checkpoint.uid, + content_file_block_id: release.block_id, + user_id: student_1.id, + cohort_id: cohort.id, + state: CheckpointSubmission::STATES[:done], + created_at: 1.day.ago) + checkpoint_2 = create(:checkpoint_submission, + content_file_uid: content_file_checkpoint.uid, + content_file_block_id: release.block_id, + user_id: student_1.id, + cohort_id: cohort.id, + state: CheckpointSubmission::STATES[:needs_review], + created_at: 1.hour.ago) + results = StandardSubmissionsService.new(cohort: cohort, standard: standard, student_ids: [student_1.id], all_checkpoints: CheckpointSubmission.where(id: [checkpoint_1.id, checkpoint_2]), instructor_or_admin: student.instructor_or_admin?(cohort.id)).single + + last_content_file = results.last.deep_symbolize_keys + sub = last_content_file[:checkpoint_submissions][student_1.id] + expect(sub[:state]).to eq(CheckpointSubmission::STATES[:needs_review]) + expect(sub[:id]).to eq(checkpoint_2.id) + expect(sub[:created_at]).to be_a ActiveSupport::TimeWithZone + end + + it "yields submission states for the checkpoint's submissions, not the latest submissions" do # this is to account for drafts & code snippet submissions + checkpoint_challenge = create(:challenge, content_file: content_file_checkpoint, position: 1, title: "ChackPoint Chlng") + checkpoint_submission = create(:checkpoint_submission, + content_file_uid: content_file_checkpoint.uid, + content_file_block_id: release.block_id, + user_id: student_1.id, + state: CheckpointSubmission::STATES[:needs_review], + cohort_id: cohort.id, + created_at: 1.day.ago) + + create(:submitted_challenge_answer, user: student_1, challenge: checkpoint_challenge, challenge_uid: checkpoint_challenge.uid, created_at: 1.day.ago, status: incorrect, checkpoint_submission: checkpoint_submission) + create(:submitted_challenge_answer, user: student_1, challenge: checkpoint_challenge, challenge_uid: checkpoint_challenge.uid, status: correct) + + results = StandardSubmissionsService.new(cohort: cohort, standard: standard, student_ids: [student_1.id], all_checkpoints: CheckpointSubmission.where(id: [checkpoint_submission.id]), instructor_or_admin: student.instructor_or_admin?(cohort.id)).single + last_content_file = results.last.deep_symbolize_keys + expect(last_content_file[:challenges].last[:student_submissions][student_1.id][:status]).to eq incorrect + end + end + end + + describe "#all_standards" do + describe "student submission information" do + let!(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user } + + let(:incorrect) { SubmittedChallengeAnswer::STATUSES[:incorrect] } + let(:correct) { SubmittedChallengeAnswer::STATUSES[:correct] } + let(:ungraded) { SubmittedChallengeAnswer::STATUSES[:ungraded] } + let(:failed) { SubmittedChallengeAnswer::STATUSES[:failed] } + + let!(:student_1_challenge_0_0_old_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_0, created_at: 1.day.ago, status: incorrect, cohort: cohort) } + let!(:student_1_challenge_0_0_latest_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_0, status: correct, cohort: cohort) } + let!(:student_1_challenge_0_1_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_1, status: ungraded, cohort: cohort) } + + it "yields hash of latest student submission information for the cohort" do + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release], student_ids: [student_1.id], standard_uids: [standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: student.instructor_or_admin?(cohort.id)).all_standards + + expect(results.keys).to eq([release.id]) + expect(results[release.id].keys).to eq([standard.id]) + + expect(results[release.id][standard.id].size).to eq(3) + + first_content_file = results[release.id][standard.id][0].deep_symbolize_keys + first_challenges = first_content_file[:challenges] + expect(first_challenges[0][:student_submissions][student_1.id][:status]).to eq correct + expect(first_challenges[1][:student_submissions][student_1.id][:status]).to eq ungraded + end + + context "when as an instructor" do + before do + sign_in(instructor) + end + + it "has instructor content file information" do + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release], student_ids: [student_1.id], standard_uids: [standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: instructor.instructor_or_admin?(cohort.id)).all_standards + expect(results[release.id][standard.id].size).to eq(4) + expect(results[release.id][standard.id].map {|cf| cf[:id]}).to include(content_instructor.id) + end + end + + describe "hidden content" do + let!(:hidden_standard) { create(:standard, release: release) } # release is already attached to cohort, but we won't pass this one in, no need to make a content visibility + + let!(:content_file_hidden) { create(:content_file, standard: hidden_standard, title: "Legends of the Hidden Temple", content_file_type: ContentFile::TYPES[:checkpoint]) } + let!(:challenge_hidden) { create(:challenge, content_file: content_file_hidden, position: 0, title: "Literally stack a 3 piece monkey puzzle omg how is this hard") } + let!(:checkpoint_submission) do + create(:checkpoint_submission, + content_file_uid: content_file_hidden.uid, + content_file_block_id: release.block_id, + user_id: student_1.id, + state: CheckpointSubmission::STATES[:needs_review]) + end + + it "does not yield standard status when a standard is hidden" do + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release], student_ids: [student_1.id], standard_uids: [standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil)).all_standards + + expect(results.keys).to eq([release.id]) + expect(results[release.id].keys).to eq([standard.id]) + end + + it "does not yield a standard id if its content files are all hidden" do + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_0.uid) + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_1.uid) + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_resource.uid) + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_checkpoint.uid) + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release], student_ids: [student_1.id], standard_uids: [standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil)).all_standards + expect(results[release.id].keys).to eq([]) + end + end + + it "does not leak submissions for standards which share uids but not blocks" do + cohort_release.destroy + + copy_release = create(:release) + copy_standard = create(:standard, release: copy_release, uid: standard.uid) + copy_content_file = create(:content_file, standard: copy_standard, uid: content_file_0.uid, title: "Fizz", position: 0) + create(:challenge, content_file: copy_content_file, uid: challenge_0_0.uid, position: 0, title: "Fizz Tastic") + create(:cohort_release, cohort: cohort, release: copy_release) + + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [copy_release], student_ids: [student_1.id], standard_uids: [copy_standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil)).all_standards + + expect(results.keys).to eq([copy_release.id]) + expect(results[copy_release.id].keys).to eq([copy_standard.id]) + + first_content_file = results[copy_release.id][copy_standard.id][0].deep_symbolize_keys + first_challenges = first_content_file[:challenges] + expect(first_challenges[0][:student_submissions][student_1.id][:status]).to eq "n/a" + end + + it "yields submissions across blocks with identical standard, challenge, and content file uids" do + copy_release = create(:release) + copy_standard = create(:standard, release: copy_release, uid: standard.uid) + copy_content_file = create(:content_file, standard: copy_standard, uid: content_file_0.uid, title: "Fizz", position: 0) + create(:challenge, content_file: copy_content_file, uid: challenge_0_0.uid, position: 0, title: "Fizz Tastic") + create(:cohort_release, cohort: cohort, release: copy_release) + + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release, copy_release], student_ids: [student_1.id], standard_uids: [standard.uid, copy_standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil)).all_standards + + expect(results.keys).to eq([release.id, copy_release.id]) + expect(results[release.id].keys).to eq([standard.id]) + + expect(results[release.id][standard.id].size).to eq(3) + + first_content_file = results[release.id][standard.id][0].deep_symbolize_keys + first_challenges = first_content_file[:challenges] + expect(first_challenges[0][:student_submissions][student_1.id][:status]).to eq correct + expect(first_challenges[1][:student_submissions][student_1.id][:status]).to eq ungraded + + copy_content_file = results[copy_release.id][copy_standard.id][0].deep_symbolize_keys + copy_challenges = copy_content_file[:challenges] + expect(copy_challenges[0][:student_submissions][student_1.id][:status]).to eq "n/a" + end + end + + context "when challenge exists across multiple standards in different blocks" do + let(:another_release) { create(:release) } + let!(:another_cohort_release) { create(:cohort_release, cohort: cohort, release: another_release) } + let(:another_standard) { create(:standard, release: another_release) } + let!(:another_content_file) { create(:content_file, standard: another_standard) } + let!(:another_challenge) { create(:challenge, content_file: another_content_file, position: 0, uid: challenge_0_0.uid) } + let!(:student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user } + let!(:another_student_sca) { create(:submitted_challenge_answer, user: student, challenge: another_challenge, status: SubmittedChallengeAnswer::STATUSES[:correct], points: 1, cohort: cohort) } + + it "scopes the submitted challenge answer to the challenge" do + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release, another_release], student_ids: [student.id], standard_uids: [standard.uid, another_standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil)).all_standards + + expect(results.keys).to eq([release.id, another_release.id]) + expect(results[another_release.id].keys).to eq([another_standard.id]) + + expect(results[another_release.id][another_standard.id].size).to eq(1) + + another_first_content_file = results[another_release.id][another_standard.id][0].deep_symbolize_keys + another_first_challenge = another_first_content_file[:challenges][0] + expect(another_first_challenge[:student_submissions][student.id][:status]).to eq(SubmittedChallengeAnswer::STATUSES[:correct]) + expect(another_first_challenge[:student_submissions][student.id][:total_points]).to eq(1) + expect(another_first_challenge[:student_submissions][student.id][:correct_points]).to eq(1) + + first_content_file = results[release.id][standard.id][0].deep_symbolize_keys + first_challenge = first_content_file[:challenges][0] + expect(first_challenge[:student_submissions][student.id][:status]).to eq("n/a") + end + end + end + + describe "#stat_standards" do + describe "limited student submission information" do + let!(:student_1) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user } + + let(:incorrect) { SubmittedChallengeAnswer::STATUSES[:incorrect] } + let(:correct) { SubmittedChallengeAnswer::STATUSES[:correct] } + let(:ungraded) { SubmittedChallengeAnswer::STATUSES[:ungraded] } + let(:failed) { SubmittedChallengeAnswer::STATUSES[:failed] } + + let!(:student_1_challenge_0_0_old_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_0, created_at: 1.day.ago, status: incorrect, cohort: cohort) } + let!(:student_1_challenge_0_0_latest_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_0, status: correct, cohort: cohort) } + let!(:student_1_challenge_0_1_sca) { create(:submitted_challenge_answer, user: student_1, challenge: challenge_0_1, status: ungraded, cohort: cohort) } + + it "yields only the checkpoint content file when present" do + cs = create(:checkpoint_submission, + content_file_uid: content_file_checkpoint.uid, + content_file_block_id: release.block_id, + user_id: student_1.id, + state: CheckpointSubmission::STATES[:done], + total_points: 3, + correct_points: 2) + create(:submitted_challenge_answer, checkpoint_submission: cs, challenge: challenge_in_checkpoint, user: student_1, points: 1) + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release], student_ids: [student_1.id], standard_uids: [standard.uid], all_checkpoints: CheckpointSubmission.all, instructor_or_admin: student.instructor_or_admin?(cohort.id)).stat_standards + + expect(results.keys).to eq([release.id]) + expect(results[release.id].keys).to eq([standard.id]) + + expect(results[release.id][standard.id].size).to eq(1) + + checkpoint_content_file = results[release.id][standard.id][0].deep_symbolize_keys + expect(checkpoint_content_file[:uid]).to eq content_file_checkpoint.uid + expect(checkpoint_content_file[:content_file_type]).to eq "checkpoint" + expect(checkpoint_content_file[:challenges]).to eq [] + + checkpoints = checkpoint_content_file[:checkpoint_submissions] + expect(checkpoints[student_1.id][:id]).to eq cs.id + expect(checkpoints[student_1.id][:state]).to eq cs.state + expect(checkpoints[student_1.id][:total_possible_points]).to eq cs.total_points + expect(checkpoints[student_1.id][:points_earned]).to eq cs.correct_points + end + + context "when as an instructor" do + before do + sign_in(instructor) + end + + it "has instructor content file information" do + create(:content_visibility, content_type: "ContentFile", cohort: cohort, content_uid: content_file_checkpoint.uid) + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release], student_ids: [student_1.id], standard_uids: [standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil), instructor_or_admin: instructor.instructor_or_admin?(cohort.id)).stat_standards + expect(results[release.id][standard.id].size).to eq(3) + expect(results[release.id][standard.id].map {|cf| cf[:uid]}).to include(content_instructor.uid) + end + end + + describe "hidden content" do + let!(:hidden_standard) { create(:standard, release: release) } # release is already attached to cohort, but we won't pass this one in, no need to make a content visibility + + let!(:content_file_hidden) { create(:content_file, standard: hidden_standard, title: "Legends of the Hidden Temple", content_file_type: ContentFile::TYPES[:checkpoint]) } + let!(:challenge_hidden) { create(:challenge, content_file: content_file_hidden, position: 0, title: "Literally stack a 3 piece monkey puzzle omg how is this hard") } + let!(:checkpoint_submission) do + create(:checkpoint_submission, + content_file_uid: content_file_hidden.uid, + content_file_block_id: release.block_id, + user_id: student_1.id, + state: CheckpointSubmission::STATES[:needs_review]) + end + + it "does not yield standard status when a standard is hidden" do + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release], student_ids: [student_1.id], standard_uids: [standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil)).stat_standards + + expect(results.keys).to eq([release.id]) + expect(results[release.id].keys).to eq([standard.id]) + end + + it "does not yield a standard id if its content files are all hidden" do + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_0.uid) + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_1.uid) + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_resource.uid) + create(:content_visibility, cohort_id: cohort.id, content_type: "ContentFile", content_uid: content_file_checkpoint.uid) + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release], student_ids: [student_1.id], standard_uids: [standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil)).stat_standards + expect(results[release.id].keys).to eq([]) + end + end + + it "does not leak submissions for standards which share uids but not blocks" do + cohort_release.destroy + + copy_release = create(:release) + copy_standard = create(:standard, release: copy_release, uid: standard.uid) + copy_content_file = create(:content_file, standard: copy_standard, uid: content_file_0.uid, title: "Fizz", position: 0) + create(:challenge, content_file: copy_content_file, uid: challenge_0_0.uid, position: 0, title: "Fizz Tastic") + create(:cohort_release, cohort: cohort, release: copy_release) + + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [copy_release], student_ids: [student_1.id], standard_uids: [copy_standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil)).stat_standards + + expect(results.keys).to eq([copy_release.id]) + expect(results[copy_release.id].keys).to eq([copy_standard.id]) + + first_content_file = results[copy_release.id][copy_standard.id][0].deep_symbolize_keys + first_challenges = first_content_file[:challenges] + expect(first_challenges[0][:student_submissions][student_1.id][:status]).to eq "n/a" + end + end + + context "when challenge exists across multiple standards in different blocks" do + let(:another_release) { create(:release) } + let!(:another_cohort_release) { create(:cohort_release, cohort: cohort, release: another_release) } + let(:another_standard) { create(:standard, release: another_release) } + let!(:another_content_file) { create(:content_file, standard: another_standard) } + let!(:another_challenge) { create(:challenge, content_file: another_content_file, position: 0, uid: challenge_0_0.uid) } + let!(:student) { create(:cohort_user, :student, cohort: cohort, user: create(:user, first_name: "Abe", last_name: "Abbot")).user } + let!(:another_student_sca) { create(:submitted_challenge_answer, user: student, challenge: another_challenge, status: SubmittedChallengeAnswer::STATUSES[:correct], points: 1, cohort: cohort) } + + it "scopes the submitted challenge answer to the challenge" do + results = StandardSubmissionsService.new(cohort: cohort, standard: nil, cohort_releases: [release, another_release], student_ids: [student.id], standard_uids: [standard.uid, another_standard.uid], all_checkpoints: CheckpointSubmission.where(id: nil)).stat_standards + + expect(results.keys).to eq([release.id, another_release.id]) + expect(results[another_release.id].keys).to eq([another_standard.id]) + + expect(results[another_release.id][another_standard.id].size).to eq(1) + + another_first_content_file = results[another_release.id][another_standard.id][0].deep_symbolize_keys + another_first_challenge = another_first_content_file[:challenges][0] + expect(another_first_challenge[:student_submissions][student.id][:status]).to eq(SubmittedChallengeAnswer::STATUSES[:correct]) + end + end + end +end diff --git a/scripts/spec/spec_helper.rb b/scripts/spec/spec_helper.rb new file mode 100644 index 0000000..333bf7d --- /dev/null +++ b/scripts/spec/spec_helper.rb @@ -0,0 +1,76 @@ +require 'simplecov' +SimpleCov.start + +ENV["RAILS_ENV"] ||= "test" +require File.expand_path("../../config/environment", __FILE__) +abort("The Rails environment is running in production mode!") if Rails.env.production? +require "spec_helper" +require "rspec/rails" +require "webmock/rspec" +require "vcr" +require "pundit/rspec" +require 'solid_use_case' +require 'solid_use_case/rspec_matchers' +require 'support/json_spec_matchers' + +Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } + +VCR.configure do |c| + c.cassette_library_dir = "spec/fixtures/vcr_cassettes" + c.hook_into :webmock + c.ignore_localhost = true +end + +Rails.application.routes.default_url_options[:host] = "test.host" + +WebMock.disable_net_connect!(allow_localhost: true) + +ActiveRecord::Migration.maintain_test_schema! + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end + +RSpec.configure do |config| + config.include FactoryBot::Syntax::Methods + config.include ObjectCreationMethods + config.include ActiveJob::TestHelper + config.include Rails.application.routes.url_helpers + config.include ActionView::TestCase::Behavior, file_path: %r{spec/presenters} + config.include(SolidUseCase::RSpecMatchers) + + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + config.use_transactional_fixtures = true + config.around(:each, use_transactional_fixtures: false) do |example| + self.use_transactional_tests = false + example.run + self.use_transactional_tests = true + end + + config.infer_spec_type_from_file_location! + + config.filter_rails_from_backtrace! + + config.expect_with :rspec do |expectations| + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + config.mock_with :rspec do |mocks| + mocks.verify_partial_doubles = true + end + + config.shared_context_metadata_behavior = :apply_to_host_groups + + config.before(:each) do + allow(Octokit::Client).to receive(:new).and_return(Mocktokit::Client.new) + end +end + +def sign_in(user) + allow_any_instance_of(ApplicationController).to receive(:current_user) { user } + allow_any_instance_of(Api::ApplicationController).to receive(:current_user) { user } +end diff --git a/scripts/spec/support/json_spec_matchers.rb b/scripts/spec/support/json_spec_matchers.rb new file mode 100644 index 0000000..89ccf99 --- /dev/null +++ b/scripts/spec/support/json_spec_matchers.rb @@ -0,0 +1,76 @@ +module JsonSpec + module Helpers + def parse_json(json, path = nil) + json = json.to_json unless json.is_a?(String) + ruby = MultiJson.decode("[#{json}]").first + value_at_json_path(ruby, path.to_s) + rescue MultiJson::DecodeError + MultiJson.decode(json) + end + end + + module Matchers + def include_partial_json(json = nil) + IncludePartialJson.new(json) + end + + class IncludePartialJson < IncludeJson + attr_accessor :actual, :expected + + def matches?(actual_json) + raise "Expected included JSON not provided" if @expected_json.nil? + + @actual_json = actual_json + + @actual = parse_json(actual_json, @path) + @expected = exclude_keys(parse_json(@expected_json)) + relevant = find_relevant(actual, expected) + relevant == expected + end + + def find_relevant(actual, expected) + if actual.is_a?(Hash) && expected.is_a?(Hash) + actual.each do |key, value| + if expected.key?(key) + if value.is_a?(Hash) || value.is_a?(Array) + actual[key] = find_relevant(actual[key], expected[key]) + end + else + actual.delete(key) + end + end + elsif actual.is_a?(Array) && expected.is_a?(Array) + actual.each_with_index do |item, index| + if item.is_a?(Hash) || item.is_a?(Array) + actual[index] = find_relevant(actual[index], expected[index]) + end + end + end + actual + end + + def diffable? + true + end + + def failure_message + message_with_path("Expected to partially include\n#{@expected_json}") + end + alias failure_message_for_should failure_message + + def failure_message_when_negated + message_with_path("Expected to not partially include\n#{@expected_json}") + end + alias failure_message_for_should_not failure_message_when_negated + + def description + message_with_path("include partial JSON") + end + end + end +end + +JsonSpec.configure do + exclude_keys # exclude no keys +end + diff --git a/scripts/spec/support/mocktokit.rb b/scripts/spec/support/mocktokit.rb new file mode 100644 index 0000000..06fbc64 --- /dev/null +++ b/scripts/spec/support/mocktokit.rb @@ -0,0 +1,31 @@ +module Mocktokit + class Client + def branch(*_args) + nil + end + + def repository(org_name) + org = org_name.split("/")[0] + name = org_name.split("/")[1] + + { + name: name, + organization: { + login: org + } + } + end + + def repository?(*_args) + true + end + + def content(*_args) + nil + end + + def contents(*_args) + nil + end + end +end diff --git a/scripts/spec/support/object_creation_methods.rb b/scripts/spec/support/object_creation_methods.rb new file mode 100644 index 0000000..8b8f405 --- /dev/null +++ b/scripts/spec/support/object_creation_methods.rb @@ -0,0 +1,21 @@ +module ObjectCreationMethods + def create_content_file(content_file_params = {}) + content_file = create(:content_file, content_file_params) + release = content_file.standard.release + + cohort_release = create(:cohort_release, release: release) + + [cohort_release.cohort, release.block, content_file] + end + + def create_checkpoint_submission(user, challenge, checkpoint_submission_params = {}) + create( + :checkpoint_submission, + { + user_id: user.id, + content_file_uid: challenge.content_file.uid, + content_file_block_id: challenge.content_file.standard.release.block_id + }.merge(checkpoint_submission_params) + ) + end +end diff --git a/tsconfig.json b/scripts/tsconfig.json similarity index 100% rename from tsconfig.json rename to scripts/tsconfig.json diff --git a/vendor/assets/javascripts/bootstrap-datepicker.js b/scripts/vendor/assets/javascripts/bootstrap-datepicker.js similarity index 100% rename from vendor/assets/javascripts/bootstrap-datepicker.js rename to scripts/vendor/assets/javascripts/bootstrap-datepicker.js diff --git a/vendor/assets/javascripts/hopscotch.js b/scripts/vendor/assets/javascripts/hopscotch.js similarity index 100% rename from vendor/assets/javascripts/hopscotch.js rename to scripts/vendor/assets/javascripts/hopscotch.js diff --git a/vendor/assets/javascripts/react-tooltip.js b/scripts/vendor/assets/javascripts/react-tooltip.js similarity index 100% rename from vendor/assets/javascripts/react-tooltip.js rename to scripts/vendor/assets/javascripts/react-tooltip.js diff --git a/vendor/assets/stylesheets/bootstrap-datepicker.css b/scripts/vendor/assets/stylesheets/bootstrap-datepicker.css similarity index 100% rename from vendor/assets/stylesheets/bootstrap-datepicker.css rename to scripts/vendor/assets/stylesheets/bootstrap-datepicker.css diff --git a/vendor/assets/stylesheets/hopscotch.css b/scripts/vendor/assets/stylesheets/hopscotch.css similarity index 100% rename from vendor/assets/stylesheets/hopscotch.css rename to scripts/vendor/assets/stylesheets/hopscotch.css diff --git a/yarn.lock b/scripts/yarn.lock similarity index 100% rename from yarn.lock rename to scripts/yarn.lock -- GitLab From 4232f279d79a4eb27b6644cd6dafcf76920bde21 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 5 Nov 2020 16:48:46 -1000 Subject: [PATCH 187/287] Removed a byebug --- scripts/app/controllers/api/v1/users_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/app/controllers/api/v1/users_controller.rb b/scripts/app/controllers/api/v1/users_controller.rb index 1712099..245729e 100644 --- a/scripts/app/controllers/api/v1/users_controller.rb +++ b/scripts/app/controllers/api/v1/users_controller.rb @@ -32,7 +32,7 @@ class Api::V1::UsersController < Api::ApplicationController render_method_not_allowed_response and return unless request.post? authorize(current_user, :learn_cli_metadata?) - byebug + @api_interaction_metadata = { cli_benchmark: user_params.as_json } -- GitLab From 937f8a90df288133473f7743861980deb6e96c8c Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Thu, 5 Nov 2020 20:00:13 -1000 Subject: [PATCH 188/287] Change entrypoint --- .Dockerfile.swp | Bin 20480 -> 0 bytes Dockerfile | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 .Dockerfile.swp diff --git a/.Dockerfile.swp b/.Dockerfile.swp deleted file mode 100644 index e38d85e67f1c1580477d7e6a7572442646f48eab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20480 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 I#zBmK1JXg1W&i*H diff --git a/Dockerfile b/Dockerfile index 26c4aa2..b80680a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -189,4 +189,4 @@ RUN bundle exec rake assets:clean ENV PATH /app/node_modules/.bin:$PATH # Set the entry point. -ENTRYPOINT ["./entrypoint-server.sh"] +ENTRYPOINT ["/app/entrypoint-server.sh"] -- GitLab From 32545b53bc5fa169e84f7a89d3168931f8e37187 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 17:20:47 -1000 Subject: [PATCH 189/287] updating gitignore Former-commit-id: c32130ac1719b05890f935cd88aa3c14e133832f --- .gitignore | 30 ++++---- Dockerfile | 77 ++++++++++++------- scripts/bundles/bundle.tar.gz.REMOVED.git-id | 1 + .../node_modules.tar.gz.REMOVED.git-id | 1 + .../bundles/redis-cli.tar.gz.REMOVED.git-id | 1 + 5 files changed, 65 insertions(+), 45 deletions(-) create mode 100644 scripts/bundles/bundle.tar.gz.REMOVED.git-id create mode 100644 scripts/bundles/node_modules.tar.gz.REMOVED.git-id create mode 100644 scripts/bundles/redis-cli.tar.gz.REMOVED.git-id diff --git a/.gitignore b/.gitignore index 4f0f814..77aa186 100644 --- a/.gitignore +++ b/.gitignore @@ -5,26 +5,22 @@ # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. -/.bundle +/scripts/.bundle # Ignore all logfiles and tempfiles. -/log/* -/tmp/* - -/node_modules +/scripts/log/* +/scripts/tmp/* .byebug_history .DS_Store .env -.idea/* -/public/packs-test -/node_modules +.idea +/scripts/public/packs-test +node_modules .rspec_status -.idea/* -.vscode/* -/public/packs -/public/packs-test -/node_modules +.vscode +/scripts/public/packs +/scripts/public/packs-test yarn-debug.log* .yarn-integrity yarn-error.log @@ -33,8 +29,8 @@ tags .solargraph.yml .generators .rakeTasks -/coverage -/public/assets/ -!/public/assets/images +/scripts/coverage +/scritps/public/assets/ +!/scripts/public/assets/images log.txt -sidekiq.log \ No newline at end of file +sidekiq.log diff --git a/Dockerfile b/Dockerfile index b80680a..f5aaf35 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,35 +13,49 @@ FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} as builder USER 0 # Install what we can via dnf -RUN dnf update -y && dnf install -y \ - autoconf \ - automake \ - bzip2 \ - curl \ - diffutils \ - gcc-c++ \ - libedit \ - libffi-devel \ - libtool \ - make \ - openssl-devel \ - patch \ - readline \ - zlib \ - zlib-devel +#RUN dnf update -y && dnf install -y \ +# autoconf \ +# automake \ +# bzip2 \ +# curl \ +# diffutils \ +# gcc-c++ \ +# libedit \ +# libffi-devel \ +# libtool \ +# make \ +# openssl-devel \ +# patch \ +# readline \ +# zlib \ +# zlib-devel # Uncomment to test in Platform 1 -RUN curl -O https://download.redis.io/releases/redis-6.0.9.tar.gz +#RUN curl -O https://download.redis.io/releases/redis-6.0.9.tar.gz # Build redis from source #COPY redis-6.0.9.tar.gz . -RUN tar xzf redis-6.0.9.tar.gz -WORKDIR redis-6.0.9/deps -RUN make hiredis jemalloc linenoise lua -WORKDIR .. -RUN make -RUN make install -WORKDIR .. +#RUN tar xzf redis-6.0.9.tar.gz +#WORKDIR redis-6.0.9/deps +#RUN make hiredis jemalloc linenoise lua +#WORKDIR .. +#RUN make +#RUN make install + +# Switch to app directory. +WORKDIR /app + +# Redis. +COPY ./scripts/bundles/redis-cli.tar.gz . +RUN tar xzf redis-cli.tar.gz + +# Unzip the bundle directory. +COPY ./scripts/bundles/bundle.tar.gz . +RUN tar xzf bundle.tar.gz + +# Unzip the node_modules. +COPY ./scripts/bundles/node_modules.tar.gz . +RUN tar xzf node_modules.tar.gz # Stage 2: Setup the Image. FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} @@ -64,7 +78,13 @@ RUN dnf update -y && dnf install -y \ gcc-c++ # Redis CLI Binary. -COPY --from=builder /usr/local/bin/redis-cli /usr/local/bin/redis-cli +COPY --from=builder /app/redis-cli /usr/local/bin/redis-cli + +# copy the bundler fules. +COPY --from=builder /app/bundle /usr/local/bundle + +# Copy the node_modules +COPY --from=builder /app/node_modules /app/node_modules ## Patch Binary. #COPY --from=builder /usr/bin/patch /usr/bin/patch @@ -171,13 +191,13 @@ USER 1001 ENV RAILS_ENV production # Install Rails Dependecies. -RUN gem install bundler:2.1.4 +#RUN gem install bundler:2.1.4 # Run the bundle install -RUN bundle install +#RUN bundle install ## Run Yarn Install -RUN yarn install +#RUN yarn install # Precompile assets RUN bundle exec rake assets:precompile @@ -190,3 +210,4 @@ ENV PATH /app/node_modules/.bin:$PATH # Set the entry point. ENTRYPOINT ["/app/entrypoint-server.sh"] +#CMD tail -f /dev/null diff --git a/scripts/bundles/bundle.tar.gz.REMOVED.git-id b/scripts/bundles/bundle.tar.gz.REMOVED.git-id new file mode 100644 index 0000000..4b90411 --- /dev/null +++ b/scripts/bundles/bundle.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +e949587cc2c87f431a7d318d7b8e4853a8b1859d \ No newline at end of file diff --git a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id new file mode 100644 index 0000000..d3c29b5 --- /dev/null +++ b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +67f211bd5a4a466daeead95c9d7accd1047c9492 \ No newline at end of file diff --git a/scripts/bundles/redis-cli.tar.gz.REMOVED.git-id b/scripts/bundles/redis-cli.tar.gz.REMOVED.git-id new file mode 100644 index 0000000..07b1028 --- /dev/null +++ b/scripts/bundles/redis-cli.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +e694dfedcbaeaf8927abdba4868d90eb0857e619 \ No newline at end of file -- GitLab From 8a0f36af71cc4186fd9541e340dcba4f1f67fcf8 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 19:53:54 -1000 Subject: [PATCH 190/287] adding node.js and yarn binaries Former-commit-id: 41845377c895e658f64b7cf504bb3a37eb868384 --- Dockerfile | 146 ++---------------- scripts/.nvmrc | 2 +- ...e-v14.15.0-linux-x64.tar.gz.REMOVED.git-id | 1 + .../yarn-v1.22.5.tar.gz.REMOVED.git-id | 1 + 4 files changed, 15 insertions(+), 135 deletions(-) create mode 100644 scripts/bundles/node-v14.15.0-linux-x64.tar.gz.REMOVED.git-id create mode 100644 scripts/bundles/yarn-v1.22.5.tar.gz.REMOVED.git-id diff --git a/Dockerfile b/Dockerfile index f5aaf35..779f350 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,36 +12,6 @@ ARG BASE_TAG=2.6.6.212 FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} as builder USER 0 -# Install what we can via dnf -#RUN dnf update -y && dnf install -y \ -# autoconf \ -# automake \ -# bzip2 \ -# curl \ -# diffutils \ -# gcc-c++ \ -# libedit \ -# libffi-devel \ -# libtool \ -# make \ -# openssl-devel \ -# patch \ -# readline \ -# zlib \ -# zlib-devel - -# Uncomment to test in Platform 1 -#RUN curl -O https://download.redis.io/releases/redis-6.0.9.tar.gz - -# Build redis from source -#COPY redis-6.0.9.tar.gz . -#RUN tar xzf redis-6.0.9.tar.gz -#WORKDIR redis-6.0.9/deps -#RUN make hiredis jemalloc linenoise lua -#WORKDIR .. -#RUN make -#RUN make install - # Switch to app directory. WORKDIR /app @@ -57,6 +27,11 @@ RUN tar xzf bundle.tar.gz COPY ./scripts/bundles/node_modules.tar.gz . RUN tar xzf node_modules.tar.gz +# Node.js +RUN echo node-v14.15.0-linux-x64.tar.gz +COPY ./scripts/bundles/node-v14.15.0-linux-x64.tar.gz . +RUN tar xzf node-v14.15.0-linux-x64.tar.gz + # Stage 2: Setup the Image. FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 @@ -81,101 +56,13 @@ RUN dnf update -y && dnf install -y \ COPY --from=builder /app/redis-cli /usr/local/bin/redis-cli # copy the bundler fules. -COPY --from=builder /app/bundle /usr/local/bundle +COPY --from=builder /app/bundle /usr/local/bundle # Copy the node_modules COPY --from=builder /app/node_modules /app/node_modules -## Patch Binary. -#COPY --from=builder /usr/bin/patch /usr/bin/patch -# -## Make Binary. -#COPY --from=builder /usr/bin/make /usr/bin/make -# -## Git Binaries. -#COPY --from=builder /usr/bin/git* /usr/bin/ -# -## Yarn Binaries. -#COPY --from=builder /usr/share/yarn /usr/share/yarn -#RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarn -#RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarnpkg -# -## libz Dependencies. -#COPY --from=builder /usr/lib64/libz* /usr/lib64/ -# -## Postgres Dependencies -#COPY --from=builder /usr/bin/pkgconf /usr/bin/pkgconf -#COPY --from=builder /usr/bin/pg_config /usr/bin/pg_config -#COPY --from=builder /usr/lib64/libpq* /usr/lib64/ -#COPY --from=builder /usr/lib64/libpkgconf* /usr/lib64/ -#COPY --from=builder /usr/include/libpq /usr/include/libpq -#COPY --from=builder /usr/include/libpq* /usr/include/ -#COPY --from=builder /usr/include/pg* /usr/include/ -#COPY --from=builder /usr/include/pgsql /usr/include/pgsql -#COPY --from=builder /usr/include/postgres* /usr/include/ -# -## libxml2 Dependencies. -#COPY --from=builder /usr/lib64/libxml* /usr/lib64/ -#COPY --from=builder /usr/lib64/liblz* /usr/lib64/ -#COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ -#COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ -#COPY --from=builder /usr/include/lzma /usr/include/lzma -#COPY --from=builder /usr/include/zlib* /usr/include/ -#COPY --from=builder /usr/include/zconf* /usr/include/ -#COPY --from=builder /usr/include/libxml2 /usr/include/libxml2 -#COPY --from=builder /usr/lib64/xml2Conf.sh /usr/lib64/xml2Conf.sh -# -## libxslt Dependencies. -#COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libexslt* /usr/lib64/ -#COPY --from=builder /usr/include/libxslt /usr/include/libxslt -#COPY --from=builder /usr/include/libexslt /usr/include/libexslt -#COPY --from=builder /usr/include/gcrypt* /usr/include/ -#COPY --from=builder /usr/include/gpg* /usr/include/ -#COPY --from=builder /usr/lib64/xsltConf.sh /usr/lib64/xsltConf.sh - -## GCC Dependencies. -#COPY --from=builder /usr/bin/gcc* /usr/bin/ -#COPY --from=builder /usr/lib/gcc /usr/lib/gcc -#COPY --from=builder /usr/bin/as /usr/bin/as -#COPY --from=builder /usr/bin/ld /usr/bin/ld -#COPY --from=builder /usr/libexec/gcc /usr/libexec/gcc -#COPY --from=builder /usr/lib64/libmpc* /usr/lib64/ -#COPY --from=builder /usr/lib64/libopcodes* /usr/lib64/ -#COPY --from=builder /usr/lib64/libbfd* /usr/lib64/ -#COPY --from=builder /usr/lib64/libc.so* /usr/lib64/ -#COPY --from=builder /usr/lib64/libc_nonshared* /usr/lib64/ -#COPY --from=builder /usr/lib64/libcrypt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libgomp* /usr/lib64/ -#COPY --from=builder /usr/lib64/libgpg* /usr/lib64/ -#COPY --from=builder /usr/lib64/libgcrypt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libgcc* /usr/lib64/ -#COPY --from=builder /usr/lib64/crt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ -#COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ -#COPY --from=builder /usr/lib64/libmcheck* /usr/lib64/ -#COPY --from=builder /usr/lib64/Mcrt1* /usr/lib64/ -#COPY --from=builder /usr/lib64/Scrt1* /usr/lib64/ -#COPY --from=builder /usr/lib64/gcrt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libanl* /usr/lib64/ -#COPY --from=builder /usr/lib64/libdl* /usr/lib64/ -#COPY --from=builder /usr/lib64/libg* /usr/lib64/ -#COPY --from=builder /usr/lib64/libisl* /usr/lib64/ -#COPY --from=builder /usr/lib64/liblzma* /usr/lib64/ -#COPY --from=builder /usr/lib64/libmvec* /usr/lib64/ -#COPY --from=builder /usr/lib64/libpthread* /usr/lib64/ -#COPY --from=builder /usr/lib64/libresolv* /usr/lib64/ -#COPY --from=builder /usr/lib64/librt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libthread_db* /usr/lib64/ -#COPY --from=builder /usr/lib64/libutil* /usr/lib64/ -#COPY --from=builder /usr/lib64/libz* /usr/lib64/ -#COPY --from=builder /usr/lib64/rcrt* /usr/lib64/ -#COPY --from=builder /usr/include /usr/include - -# Install Node -RUN curl -L -O https://raw.githubusercontent.com/tj/n/master/bin/n -COPY ./scripts/.nvmrc . -RUN bash n auto +# Copy node.js +COPY --from=builder /app/node-v14.15.0-linux-x64 /app/nodejs # Setup our environment WORKDIR /app @@ -187,17 +74,11 @@ RUN chown -R 1001 . # Become the ruby user USER 1001 -# Set the Rails environment variable. +# Set the Rails and node environment variable. ENV RAILS_ENV production - -# Install Rails Dependecies. -#RUN gem install bundler:2.1.4 - -# Run the bundle install -#RUN bundle install - -## Run Yarn Install -#RUN yarn install +ENV NODE_HOME /app/nodejs +ENV PATH /app/node_modules/.bin:$PATH +ENV PATH /app/nodejs/bin:$PATH # Precompile assets RUN bundle exec rake assets:precompile @@ -205,9 +86,6 @@ RUN bundle exec rake assets:precompile # Asset Clean RUN bundle exec rake assets:clean -# Add the node_modules to the path. -ENV PATH /app/node_modules/.bin:$PATH - # Set the entry point. ENTRYPOINT ["/app/entrypoint-server.sh"] #CMD tail -f /dev/null diff --git a/scripts/.nvmrc b/scripts/.nvmrc index 9306ff9..c107424 100644 --- a/scripts/.nvmrc +++ b/scripts/.nvmrc @@ -1 +1 @@ -14.8.0 +14.15.0 diff --git a/scripts/bundles/node-v14.15.0-linux-x64.tar.gz.REMOVED.git-id b/scripts/bundles/node-v14.15.0-linux-x64.tar.gz.REMOVED.git-id new file mode 100644 index 0000000..7152095 --- /dev/null +++ b/scripts/bundles/node-v14.15.0-linux-x64.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +f4d256209259d79fe547f7bbe8245e52e356d0a8 \ No newline at end of file diff --git a/scripts/bundles/yarn-v1.22.5.tar.gz.REMOVED.git-id b/scripts/bundles/yarn-v1.22.5.tar.gz.REMOVED.git-id new file mode 100644 index 0000000..89957a7 --- /dev/null +++ b/scripts/bundles/yarn-v1.22.5.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +2ffdf471020c0266a0c89ca2c125833d251e5181 \ No newline at end of file -- GitLab From bee1137dd8fa9276be9832f18f862e5153238bf4 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 19:59:26 -1000 Subject: [PATCH 191/287] changing node versions Former-commit-id: 420932defc348522db60b60c26b4621f1adcca9f --- Dockerfile | 8 ++++---- scripts/.nvmrc | 2 +- .../bundles/node-v14.15.0-linux-x64.tar.gz.REMOVED.git-id | 1 - .../bundles/node-v14.8.0-linux-x64.tar.gz.REMOVED.git-id | 1 + 4 files changed, 6 insertions(+), 6 deletions(-) delete mode 100644 scripts/bundles/node-v14.15.0-linux-x64.tar.gz.REMOVED.git-id create mode 100644 scripts/bundles/node-v14.8.0-linux-x64.tar.gz.REMOVED.git-id diff --git a/Dockerfile b/Dockerfile index 779f350..4c098f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,9 +28,9 @@ COPY ./scripts/bundles/node_modules.tar.gz . RUN tar xzf node_modules.tar.gz # Node.js -RUN echo node-v14.15.0-linux-x64.tar.gz -COPY ./scripts/bundles/node-v14.15.0-linux-x64.tar.gz . -RUN tar xzf node-v14.15.0-linux-x64.tar.gz +RUN echo node-v14.8.0-linux-x64.tar.gz +COPY ./scripts/bundles/node-v14.8.0-linux-x64.tar.gz . +RUN tar xzf node-v14.8.0-linux-x64.tar.gz # Stage 2: Setup the Image. FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} @@ -62,7 +62,7 @@ COPY --from=builder /app/bundle /usr/local/bundle COPY --from=builder /app/node_modules /app/node_modules # Copy node.js -COPY --from=builder /app/node-v14.15.0-linux-x64 /app/nodejs +COPY --from=builder /app/node-v14.8.0-linux-x64 /app/nodejs # Setup our environment WORKDIR /app diff --git a/scripts/.nvmrc b/scripts/.nvmrc index c107424..a34fffa 100644 --- a/scripts/.nvmrc +++ b/scripts/.nvmrc @@ -1 +1 @@ -14.15.0 +14.8.0 \ No newline at end of file diff --git a/scripts/bundles/node-v14.15.0-linux-x64.tar.gz.REMOVED.git-id b/scripts/bundles/node-v14.15.0-linux-x64.tar.gz.REMOVED.git-id deleted file mode 100644 index 7152095..0000000 --- a/scripts/bundles/node-v14.15.0-linux-x64.tar.gz.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -f4d256209259d79fe547f7bbe8245e52e356d0a8 \ No newline at end of file diff --git a/scripts/bundles/node-v14.8.0-linux-x64.tar.gz.REMOVED.git-id b/scripts/bundles/node-v14.8.0-linux-x64.tar.gz.REMOVED.git-id new file mode 100644 index 0000000..937094b --- /dev/null +++ b/scripts/bundles/node-v14.8.0-linux-x64.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +eda96a7b40acb7ed21dabc8f89fb50627cea38b6 \ No newline at end of file -- GitLab From 683f9417034c1ae22238b89eed00f1097ce4829b Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 20:16:44 -1000 Subject: [PATCH 192/287] got nodejs install working Former-commit-id: 96ab458364230101f4346f00fde151f26d5cdcda --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 4c098f3..e7c650a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -79,6 +79,7 @@ ENV RAILS_ENV production ENV NODE_HOME /app/nodejs ENV PATH /app/node_modules/.bin:$PATH ENV PATH /app/nodejs/bin:$PATH +RUN ln -s /app/nodejs/bin/node /app/nodejs/bin/nodejs # Precompile assets RUN bundle exec rake assets:precompile -- GitLab From c7f9de5962e36c205d445e1e97a6f1fa63e02096 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 20:30:40 -1000 Subject: [PATCH 193/287] getting yarn to work. Former-commit-id: 9030c0dc6277bef23e78972b7989e6147bf96152 --- Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e7c650a..9af19a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,10 +28,13 @@ COPY ./scripts/bundles/node_modules.tar.gz . RUN tar xzf node_modules.tar.gz # Node.js -RUN echo node-v14.8.0-linux-x64.tar.gz COPY ./scripts/bundles/node-v14.8.0-linux-x64.tar.gz . RUN tar xzf node-v14.8.0-linux-x64.tar.gz +# Yarn +COPY ./scripts/bundles/yarn-v1.22.5.tar.gz . +RUN tar xzf yarn-v1.22.5.tar.gz + # Stage 2: Setup the Image. FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 @@ -64,6 +67,9 @@ COPY --from=builder /app/node_modules /app/node_modules # Copy node.js COPY --from=builder /app/node-v14.8.0-linux-x64 /app/nodejs +# Copy yarn. +COPY --from=builder /app/yarn-v1.22.5 /app/yarn + # Setup our environment WORKDIR /app ADD ./scripts . @@ -78,6 +84,7 @@ USER 1001 ENV RAILS_ENV production ENV NODE_HOME /app/nodejs ENV PATH /app/node_modules/.bin:$PATH +ENV PATH /app/yarn/bin:$PATH ENV PATH /app/nodejs/bin:$PATH RUN ln -s /app/nodejs/bin/node /app/nodejs/bin/nodejs -- GitLab From 11f9bfd74b8a4d0b822d7fe0d6c689f98173744e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 20:48:30 -1000 Subject: [PATCH 194/287] got yarn working Former-commit-id: f07534b26304e8d538afa48661b7887166a9b566 --- Dockerfile | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9af19a7..bdacc9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,15 +39,11 @@ RUN tar xzf yarn-v1.22.5.tar.gz FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 -RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo -RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg - RUN dnf update -y && dnf install -y \ curl \ make \ patch \ git \ - yarn \ zlib-devel \ libpq-devel \ libxml2-devel \ @@ -58,7 +54,7 @@ RUN dnf update -y && dnf install -y \ # Redis CLI Binary. COPY --from=builder /app/redis-cli /usr/local/bin/redis-cli -# copy the bundler fules. +# Copy the bundler fules. COPY --from=builder /app/bundle /usr/local/bundle # Copy the node_modules -- GitLab From 6c63811ad0d7fab88339512915181ad367cb6021 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 20:49:00 -1000 Subject: [PATCH 195/287] cleanup Former-commit-id: 1f3350903ef6e501d4005f54503fdf21335cd72e --- Dockerfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bdacc9a..1726227 100644 --- a/Dockerfile +++ b/Dockerfile @@ -92,4 +92,3 @@ RUN bundle exec rake assets:clean # Set the entry point. ENTRYPOINT ["/app/entrypoint-server.sh"] -#CMD tail -f /dev/null -- GitLab From d5a070af2d3ba2507865b7d79f3f5d86958428bb Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 21:20:45 -1000 Subject: [PATCH 196/287] converting to single stage docker build Former-commit-id: 73ef8c222a746e6bb6ce7d17728f756464d45b23 --- Dockerfile | 72 +++++++++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 41 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1726227..a77ed3f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,37 +8,11 @@ ARG BASE_REGISTRY=registry.il2.dsop.io ARG BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 ARG BASE_TAG=2.6.6.212 -# Stage 1: Build redis from source. -FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} as builder -USER 0 - -# Switch to app directory. -WORKDIR /app - -# Redis. -COPY ./scripts/bundles/redis-cli.tar.gz . -RUN tar xzf redis-cli.tar.gz - -# Unzip the bundle directory. -COPY ./scripts/bundles/bundle.tar.gz . -RUN tar xzf bundle.tar.gz - -# Unzip the node_modules. -COPY ./scripts/bundles/node_modules.tar.gz . -RUN tar xzf node_modules.tar.gz - -# Node.js -COPY ./scripts/bundles/node-v14.8.0-linux-x64.tar.gz . -RUN tar xzf node-v14.8.0-linux-x64.tar.gz - -# Yarn -COPY ./scripts/bundles/yarn-v1.22.5.tar.gz . -RUN tar xzf yarn-v1.22.5.tar.gz - -# Stage 2: Setup the Image. +# Setup the Image. FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 +# Install required libs. RUN dnf update -y && dnf install -y \ curl \ make \ @@ -51,24 +25,40 @@ RUN dnf update -y && dnf install -y \ gcc \ gcc-c++ -# Redis CLI Binary. -COPY --from=builder /app/redis-cli /usr/local/bin/redis-cli +# Setup our environment +WORKDIR /app +ADD ./scripts . + +# Switch to the binary directory. +WORKDIR /app/bundles -# Copy the bundler fules. -COPY --from=builder /app/bundle /usr/local/bundle +# Redis. +RUN tar xzf redis-cli.tar.gz +RUN rm redis-cli.tar.gz +RUN mv redis-cli /usr/local/bin/redis-cli + +# Unzip the bundle directory. +RUN tar xzf bundle.tar.gz +RUN rm bundle.tar.gz +RUN mv bundle /usr/local/bundle -# Copy the node_modules -COPY --from=builder /app/node_modules /app/node_modules +# Unzip the node_modules. +RUN tar xzf node_modules.tar.gz +RUN rm node_modules.tar.gz +RUN mv node_modules /app/node_modules -# Copy node.js -COPY --from=builder /app/node-v14.8.0-linux-x64 /app/nodejs +# Node.js +RUN tar xzf node-v14.8.0-linux-x64.tar.gz +RUN rm node-v14.8.0-linux-x64.tar.gz +RUN mv node-v14.8.0-linux-x64 /app/nodejs -# Copy yarn. -COPY --from=builder /app/yarn-v1.22.5 /app/yarn +# Yarn +RUN tar xzf yarn-v1.22.5.tar.gz +RUN rm yarn-v1.22.5.tar.gz +RUN mv yarn-v1.22.5 /app/yarn -# Setup our environment +# Switch back to app directory. WORKDIR /app -ADD ./scripts . # Add write permissions. RUN chown -R 1001 . @@ -91,4 +81,4 @@ RUN bundle exec rake assets:precompile RUN bundle exec rake assets:clean # Set the entry point. -ENTRYPOINT ["/app/entrypoint-server.sh"] +ENTRYPOINT ["/app/entrypoint-server.sh"] \ No newline at end of file -- GitLab From 0a14d3ad8dfb152722f4dac5323c57554dc99cb4 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 21:43:15 -1000 Subject: [PATCH 197/287] fixing location of bundle directory Former-commit-id: 607763bdd3d00ef7defe312249bf1f53c0a4a7d4 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a77ed3f..df342f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,7 +40,7 @@ RUN mv redis-cli /usr/local/bin/redis-cli # Unzip the bundle directory. RUN tar xzf bundle.tar.gz RUN rm bundle.tar.gz -RUN mv bundle /usr/local/bundle +RUN mv bundle /usr/local/ # Unzip the node_modules. RUN tar xzf node_modules.tar.gz -- GitLab From a293ac586664ce7761f652139e86a10b33398ee3 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 22:03:34 -1000 Subject: [PATCH 198/287] changes for ironbank Former-commit-id: 9dfd0ad19fb257a80f22c0c29811c5e6a1cb946a --- Dockerfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index df342f1..b6f6b78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ ### Iron Bank Settings -# ARG BASE_REGISTRY=registry1.dsop.io -# ARG BASE_IMAGE=ironbank/opensource/ruby/ruby26 -# ARG BASE_TAG=2.6.6 +ARG BASE_REGISTRY=registry1.dsop.io +ARG BASE_IMAGE=ironbank/opensource/ruby/ruby26 +ARG BASE_TAG=2.6.6 ### Platform 1 Pipeline Settings -ARG BASE_REGISTRY=registry.il2.dsop.io -ARG BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 -ARG BASE_TAG=2.6.6.212 +#ARG BASE_REGISTRY=registry.il2.dsop.io +#ARG BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 +#ARG BASE_TAG=2.6.6.212 # Setup the Image. FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} @@ -81,4 +81,4 @@ RUN bundle exec rake assets:precompile RUN bundle exec rake assets:clean # Set the entry point. -ENTRYPOINT ["/app/entrypoint-server.sh"] \ No newline at end of file +ENTRYPOINT ["/app/entrypoint-server.sh"] -- GitLab From b4ec915a2deb13c8e8a2781b85dd5a4598771ec7 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 6 Nov 2020 22:12:00 -1000 Subject: [PATCH 199/287] moving gitlab-ci file to scripts Former-commit-id: 451cb64de5930a2bb0ae2b88a42fbba8732da63a --- Dockerfile.old | 169 ----------------------- .gitlab-ci.yml => scripts/.gitlab-ci.yml | 0 2 files changed, 169 deletions(-) delete mode 100644 Dockerfile.old rename .gitlab-ci.yml => scripts/.gitlab-ci.yml (100%) diff --git a/Dockerfile.old b/Dockerfile.old deleted file mode 100644 index 613216e..0000000 --- a/Dockerfile.old +++ /dev/null @@ -1,169 +0,0 @@ -# Stage 1: Install libararies -FROM centos:8 as builder -USER 0 - -## Setup the yarn repo. -#RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo -#RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg - -## Install the required packages. -RUN dnf update -y && \ - dnf install -y \ - redis -# make \ -# patch \ -# git \ -# yarn \ -# zlib-devel \ -# libpq-devel \ -# libxml2-devel \ -# libxslt-devel \ -# gcc - -# Stage 2: Setup the Image. -FROM registry.il2.dsop.io/platform-one/devops/pipeline-templates/ironbank/ruby26:2.6.6.212 -USER 0 - -RUN curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | tee /etc/yum.repos.d/yarn.repo -RUN rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg - -RUN dnf update -y && \ - dnf install -y \ - make \ - patch \ - git \ - yarn \ - zlib-devel \ - libpq-devel \ - libxml2-devel \ - libxslt-devel \ - gcc \ - gcc-c++ - -# Redis CLI Binary. -COPY --from=builder /usr/bin/redis-cli /usr/bin/redis-cli - -## Patch Binary. -#COPY --from=builder /usr/bin/patch /usr/bin/patch -# -## Make Binary. -#COPY --from=builder /usr/bin/make /usr/bin/make -# -## Git Binaries. -#COPY --from=builder /usr/bin/git* /usr/bin/ -# -## Yarn Binaries. -#COPY --from=builder /usr/share/yarn /usr/share/yarn -#RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarn -#RUN ln -s /usr/share/yarn/bin/yarn /usr/bin/yarnpkg -# -## libz Dependencies. -#COPY --from=builder /usr/lib64/libz* /usr/lib64/ -# -## Postgres Dependencies -#COPY --from=builder /usr/bin/pkgconf /usr/bin/pkgconf -#COPY --from=builder /usr/bin/pg_config /usr/bin/pg_config -#COPY --from=builder /usr/lib64/libpq* /usr/lib64/ -#COPY --from=builder /usr/lib64/libpkgconf* /usr/lib64/ -#COPY --from=builder /usr/include/libpq /usr/include/libpq -#COPY --from=builder /usr/include/libpq* /usr/include/ -#COPY --from=builder /usr/include/pg* /usr/include/ -#COPY --from=builder /usr/include/pgsql /usr/include/pgsql -#COPY --from=builder /usr/include/postgres* /usr/include/ -# -## libxml2 Dependencies. -#COPY --from=builder /usr/lib64/libxml* /usr/lib64/ -#COPY --from=builder /usr/lib64/liblz* /usr/lib64/ -#COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ -#COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ -#COPY --from=builder /usr/include/lzma /usr/include/lzma -#COPY --from=builder /usr/include/zlib* /usr/include/ -#COPY --from=builder /usr/include/zconf* /usr/include/ -#COPY --from=builder /usr/include/libxml2 /usr/include/libxml2 -#COPY --from=builder /usr/lib64/xml2Conf.sh /usr/lib64/xml2Conf.sh -# -## libxslt Dependencies. -#COPY --from=builder /usr/lib64/libxslt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libexslt* /usr/lib64/ -#COPY --from=builder /usr/include/libxslt /usr/include/libxslt -#COPY --from=builder /usr/include/libexslt /usr/include/libexslt -#COPY --from=builder /usr/include/gcrypt* /usr/include/ -#COPY --from=builder /usr/include/gpg* /usr/include/ -#COPY --from=builder /usr/lib64/xsltConf.sh /usr/lib64/xsltConf.sh - -## GCC Dependencies. -#COPY --from=builder /usr/bin/gcc* /usr/bin/ -#COPY --from=builder /usr/lib/gcc /usr/lib/gcc -#COPY --from=builder /usr/bin/as /usr/bin/as -#COPY --from=builder /usr/bin/ld /usr/bin/ld -#COPY --from=builder /usr/libexec/gcc /usr/libexec/gcc -#COPY --from=builder /usr/lib64/libmpc* /usr/lib64/ -#COPY --from=builder /usr/lib64/libopcodes* /usr/lib64/ -#COPY --from=builder /usr/lib64/libbfd* /usr/lib64/ -#COPY --from=builder /usr/lib64/libc.so* /usr/lib64/ -#COPY --from=builder /usr/lib64/libc_nonshared* /usr/lib64/ -#COPY --from=builder /usr/lib64/libcrypt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libgomp* /usr/lib64/ -#COPY --from=builder /usr/lib64/libgpg* /usr/lib64/ -#COPY --from=builder /usr/lib64/libgcrypt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libgcc* /usr/lib64/ -#COPY --from=builder /usr/lib64/crt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libm-2* /usr/lib64/ -#COPY --from=builder /usr/lib64/libm.so* /usr/lib64/ -#COPY --from=builder /usr/lib64/libmcheck* /usr/lib64/ -#COPY --from=builder /usr/lib64/Mcrt1* /usr/lib64/ -#COPY --from=builder /usr/lib64/Scrt1* /usr/lib64/ -#COPY --from=builder /usr/lib64/gcrt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libanl* /usr/lib64/ -#COPY --from=builder /usr/lib64/libdl* /usr/lib64/ -#COPY --from=builder /usr/lib64/libg* /usr/lib64/ -#COPY --from=builder /usr/lib64/libisl* /usr/lib64/ -#COPY --from=builder /usr/lib64/liblzma* /usr/lib64/ -#COPY --from=builder /usr/lib64/libmvec* /usr/lib64/ -#COPY --from=builder /usr/lib64/libpthread* /usr/lib64/ -#COPY --from=builder /usr/lib64/libresolv* /usr/lib64/ -#COPY --from=builder /usr/lib64/librt* /usr/lib64/ -#COPY --from=builder /usr/lib64/libthread_db* /usr/lib64/ -#COPY --from=builder /usr/lib64/libutil* /usr/lib64/ -#COPY --from=builder /usr/lib64/libz* /usr/lib64/ -#COPY --from=builder /usr/lib64/rcrt* /usr/lib64/ -#COPY --from=builder /usr/include /usr/include - -# Install Node -ADD .nvmrc . -RUN curl -L https://raw.githubusercontent.com/tj/n/master/bin/n -o n -RUN bash n auto - -# Setup our environment -WORKDIR /usr/src/app -ADD . . - -# Add write permissions. -RUN chown -R 1001 . - -# Become the ruby user -USER 1001 - -# Set the Rails environment variable. -ENV RAILS_ENV production - -# Install Rails Dependecies. -RUN gem install bundler:2.1.4 - -# Run the bundle install -RUN bundle install - -## Run Yarn Install -RUN yarn install - -# Precompile assets -RUN bundle exec rake assets:precompile - -# Asset Clean -RUN bundle exec rake assets:clean - -# Add the node_modules to the path. -ENV PATH /usr/src/app/node_modules/.bin:$PATH - -# Set the entry point. -ENTRYPOINT ["./entrypoint-server.sh"] \ No newline at end of file diff --git a/.gitlab-ci.yml b/scripts/.gitlab-ci.yml similarity index 100% rename from .gitlab-ci.yml rename to scripts/.gitlab-ci.yml -- GitLab From 2a664324d916c94563d373757a295ad62103d78c Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 19 Nov 2020 17:28:56 -1000 Subject: [PATCH 200/287] Add /api/v1/uploads path Former-commit-id: 3c4762a41ccba856afbcbc48c993c61333ebb6e7 --- scripts/app/assets/images/mobile-logo.png | Bin 0 -> 24976 bytes scripts/app/assets/images/p1-learn-lockup.png | Bin 0 -> 35627 bytes .../stylesheets/components/_footer.scss | 4 +- .../components/_primary-navigation.scss | 4 +- .../controllers/api/v1/uploads_controller.rb | 40 ++++++++++++++++++ scripts/config/routes.rb | 1 + 6 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 scripts/app/assets/images/mobile-logo.png create mode 100644 scripts/app/assets/images/p1-learn-lockup.png create mode 100644 scripts/app/controllers/api/v1/uploads_controller.rb diff --git a/scripts/app/assets/images/mobile-logo.png b/scripts/app/assets/images/mobile-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..af1f4aaeee4eff038288cda1873cc51995b3ca41 GIT binary patch literal 24976 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 footer { > .logo { background: none left center no-repeat; - background-image: image-url("svg/mobile-logo.svg"); + background-image: image-url("mobile-logo.png"); background-size: contain; height: 34px; margin: 0 1rem 0 0; min-width: 40px; @media screen and (min-width: $tablet-breakpoint) { - background-image: image-url("svg/g-learn-lockup.svg"); + background-image: image-url("p1-learn-lockup.png"); margin: 0 1.5rem 0 0; min-width: 150px; } diff --git a/scripts/app/assets/stylesheets/components/_primary-navigation.scss b/scripts/app/assets/stylesheets/components/_primary-navigation.scss index 42fc382..52e60f6 100644 --- a/scripts/app/assets/stylesheets/components/_primary-navigation.scss +++ b/scripts/app/assets/stylesheets/components/_primary-navigation.scss @@ -25,13 +25,13 @@ > .logo { background: none left center no-repeat; - background-image: image-url("svg/mobile-logo.svg"); + background-image: image-url("mobile-logo.png"); background-size: contain; height: 34px; margin: 0 1rem; min-width: 40px; @media screen and (min-width: $tablet-breakpoint) { - background-image: image-url("svg/g-learn-lockup.svg"); + background-image: image-url("p1-learn-lockup.png"); margin: 0 1.5rem; min-width: 236px; } diff --git a/scripts/app/controllers/api/v1/uploads_controller.rb b/scripts/app/controllers/api/v1/uploads_controller.rb new file mode 100644 index 0000000..f5e5fd3 --- /dev/null +++ b/scripts/app/controllers/api/v1/uploads_controller.rb @@ -0,0 +1,40 @@ +class Api::V1::UploadsController < Api::ApplicationController + def create + + render_method_not_allowed_response and return unless request.post? + + authorize(current_user, :regenerate_api_token?) + + @full_path = params[:full_path] + # @md5_name = data_path_md5 + @md5_name = SecureRandom.hex + @directory = params[:directory] || "content" + + if (@full_path.nil?) + json_response(error: "Parameter full_path not set") + return + end + + # Upload to s3 bucket + begin + file_content = request.body.read + aws_image_object = Rails.application.config.aws.object(content_key) + aws_image_object.put(body: file_content) + # aws_image_object.public_url + rescue StandardError => e + raise(Error, e) + end + + json_response(controller: "Uploaded file #{@full_path}") + end + + def content_key + @content_key ||= "#{@directory}/#{@md5_name}.#{@full_path}" + end + + # def data_path_md5 + # stdout, stderr, _status = Open3.capture3("openssl md5 '#{@full_path}'") + # stderr.blank? ? stdout.split("=").last.strip! : nil + # end + +end diff --git a/scripts/config/routes.rb b/scripts/config/routes.rb index b453d74..161d592 100644 --- a/scripts/config/routes.rb +++ b/scripts/config/routes.rb @@ -18,6 +18,7 @@ Rails.application.routes.draw do resources :releases, only: %i[create] do match "release_polling", via: :all, to: "release_polling" end + resources :uploads, only: %i[create] # resources :cohorts, only: %i[create] resources :cohorts, module: "cohorts", only: %i[create] do resources :users, only: %i[index create update destroy] -- GitLab From bb2c35ea3b0a22ddef49f47a9df4a7f3fd1f43f8 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 19 Nov 2020 21:50:20 -1000 Subject: [PATCH 201/287] Fixed api/v1/upload behavior Former-commit-id: 1a0d675f852407928d94a805ce845084359d4cd9 --- .../controllers/api/v1/uploads_controller.rb | 23 +++---------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/scripts/app/controllers/api/v1/uploads_controller.rb b/scripts/app/controllers/api/v1/uploads_controller.rb index f5e5fd3..10d31c7 100644 --- a/scripts/app/controllers/api/v1/uploads_controller.rb +++ b/scripts/app/controllers/api/v1/uploads_controller.rb @@ -6,9 +6,6 @@ class Api::V1::UploadsController < Api::ApplicationController authorize(current_user, :regenerate_api_token?) @full_path = params[:full_path] - # @md5_name = data_path_md5 - @md5_name = SecureRandom.hex - @directory = params[:directory] || "content" if (@full_path.nil?) json_response(error: "Parameter full_path not set") @@ -16,25 +13,11 @@ class Api::V1::UploadsController < Api::ApplicationController end # Upload to s3 bucket - begin - file_content = request.body.read - aws_image_object = Rails.application.config.aws.object(content_key) - aws_image_object.put(body: file_content) - # aws_image_object.public_url - rescue StandardError => e - raise(Error, e) - end + file_content = request.body.read + aws_image_object = Rails.application.config.aws.object(@full_path) + aws_image_object.put(body: file_content) json_response(controller: "Uploaded file #{@full_path}") end - def content_key - @content_key ||= "#{@directory}/#{@md5_name}.#{@full_path}" - end - - # def data_path_md5 - # stdout, stderr, _status = Open3.capture3("openssl md5 '#{@full_path}'") - # stderr.blank? ? stdout.split("=").last.strip! : nil - # end - end -- GitLab From 440523ca4412b5eb14928aedbcf585876356306e Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 19 Nov 2020 22:01:39 -1000 Subject: [PATCH 202/287] Revert icons Former-commit-id: 8db914ea1f780ffd7c68471cd3a350fb50c79076 --- scripts/app/assets/stylesheets/components/_footer.scss | 4 ++-- .../assets/stylesheets/components/_primary-navigation.scss | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/app/assets/stylesheets/components/_footer.scss b/scripts/app/assets/stylesheets/components/_footer.scss index 0e7e8d3..6ca2484 100644 --- a/scripts/app/assets/stylesheets/components/_footer.scss +++ b/scripts/app/assets/stylesheets/components/_footer.scss @@ -52,13 +52,13 @@ body > footer { > .logo { background: none left center no-repeat; - background-image: image-url("mobile-logo.png"); + background-image: image-url("svg/mobile-logo.svg"); background-size: contain; height: 34px; margin: 0 1rem 0 0; min-width: 40px; @media screen and (min-width: $tablet-breakpoint) { - background-image: image-url("p1-learn-lockup.png"); + background-image: image-url("svg/g-learn-lockup.svg"); margin: 0 1.5rem 0 0; min-width: 150px; } diff --git a/scripts/app/assets/stylesheets/components/_primary-navigation.scss b/scripts/app/assets/stylesheets/components/_primary-navigation.scss index 52e60f6..42fc382 100644 --- a/scripts/app/assets/stylesheets/components/_primary-navigation.scss +++ b/scripts/app/assets/stylesheets/components/_primary-navigation.scss @@ -25,13 +25,13 @@ > .logo { background: none left center no-repeat; - background-image: image-url("mobile-logo.png"); + background-image: image-url("svg/mobile-logo.svg"); background-size: contain; height: 34px; margin: 0 1rem; min-width: 40px; @media screen and (min-width: $tablet-breakpoint) { - background-image: image-url("p1-learn-lockup.png"); + background-image: image-url("svg/g-learn-lockup.svg"); margin: 0 1.5rem; min-width: 236px; } -- GitLab From e5c964e68467ba68cb19e43670dc6bea25b42452 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Thu, 19 Nov 2020 22:45:10 -1000 Subject: [PATCH 203/287] Change to init preview block Former-commit-id: 9d134b4e319ddeb041c89461da38f5ba52ecdfc7 --- scripts/app/controllers/api/v1/users_controller.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/app/controllers/api/v1/users_controller.rb b/scripts/app/controllers/api/v1/users_controller.rb index 245729e..4cc51b9 100644 --- a/scripts/app/controllers/api/v1/users_controller.rb +++ b/scripts/app/controllers/api/v1/users_controller.rb @@ -12,6 +12,8 @@ class Api::V1::UsersController < Api::ApplicationController render_method_not_allowed_response and return unless request.get? authorize(current_user, :learn_cli_credentials?) + + Block.find_or_create_by(title: "preview", repo_name: "preview") render json: { user_id: current_user.id.to_s, -- GitLab From 426a24257ab422b6b5a1d9ce35b8cd4c024f7852 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 4 Dec 2020 10:09:18 -1000 Subject: [PATCH 204/287] adding logging Former-commit-id: 40f09dabcfd56bdf5c46d226e8921935d7aae681 --- scripts/config/environment.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/config/environment.rb b/scripts/config/environment.rb index 426333b..ff8d4c2 100644 --- a/scripts/config/environment.rb +++ b/scripts/config/environment.rb @@ -3,3 +3,9 @@ require_relative 'application' # Initialize the Rails application. Rails.application.initialize! + +puts "==========================================" +puts "Host: #{Rails.configuration.database_configuration[Rails.env]["host"]}" +puts "Database: #{Rails.configuration.database_configuration[Rails.env]["database"]}" +puts "User: #{Rails.configuration.database_configuration[Rails.env]["username"]}" +puts "==========================================" \ No newline at end of file -- GitLab From 6ef3466f9c872f436b8545d6f9e8c9bf8c4553c4 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 7 Dec 2020 11:49:58 -1000 Subject: [PATCH 205/287] adding admin users to seed data Former-commit-id: 379dffeac1fc992b311ffe5986b3cf8898425a09 --- scripts/db/seeds.rb | 16 ++++++++++++++-- scripts/entrypoint-server.sh | 1 + 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/db/seeds.rb b/scripts/db/seeds.rb index ab31218..910d17b 100644 --- a/scripts/db/seeds.rb +++ b/scripts/db/seeds.rb @@ -12,8 +12,6 @@ # CreateReleaseJob.perform_now(pending_release_id: release.id) # end -Block.find_or_create_by(title: "preview", repo_name: "preview") - # # these values are specific to galvanize-auth-staging values # cohort = Cohort.create(uid: "9871fef341f9ef250b", # name: "Story Testing", @@ -117,3 +115,17 @@ Block.find_or_create_by(title: "preview", repo_name: "preview") # }) # end # end + +Block.find_or_create_by(title: "preview", repo_name: "preview") + +admin_users = [ + "muranaka@revacomm.com", + "csakamaki@revacomm.com", + "dcong@revacomm.com" +] +roles = ["forge.admin", "forge.blocks_manager"] + +admin_users.each do |email| + user = User.find_or_create_by(email: email) + user.update(roles: roles) +end \ No newline at end of file diff --git a/scripts/entrypoint-server.sh b/scripts/entrypoint-server.sh index 3df1bbc..4c83c42 100755 --- a/scripts/entrypoint-server.sh +++ b/scripts/entrypoint-server.sh @@ -2,4 +2,5 @@ rm -f tmp/pids/server.pid bundle exec rails db:migrate +bundle exec rails db:seed RAILS_ENV=production rails server -b 0.0.0.0 -p 3000 -- GitLab From 6d9768898b97aeea99b56cdd0ad1df7bc5d0ac46 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 7 Dec 2020 12:16:24 -1000 Subject: [PATCH 206/287] fixing derrins name Former-commit-id: e9215b8408d1c98dc7d5b3273ff6438a2b49299a --- scripts/db/seeds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/db/seeds.rb b/scripts/db/seeds.rb index 910d17b..cdada57 100644 --- a/scripts/db/seeds.rb +++ b/scripts/db/seeds.rb @@ -121,7 +121,7 @@ Block.find_or_create_by(title: "preview", repo_name: "preview") admin_users = [ "muranaka@revacomm.com", "csakamaki@revacomm.com", - "dcong@revacomm.com" + "dchong@revacomm.com" ] roles = ["forge.admin", "forge.blocks_manager"] -- GitLab From a660862c0528470ed63e67c65d8917f3187907fd Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 7 Dec 2020 12:18:24 -1000 Subject: [PATCH 207/287] adding third role. Former-commit-id: eb79ec03cce2f36b0e1a1fce3cc5124a6747a174 --- scripts/db/seeds.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/db/seeds.rb b/scripts/db/seeds.rb index cdada57..702cef8 100644 --- a/scripts/db/seeds.rb +++ b/scripts/db/seeds.rb @@ -123,7 +123,11 @@ admin_users = [ "csakamaki@revacomm.com", "dchong@revacomm.com" ] -roles = ["forge.admin", "forge.blocks_manager"] +roles = [ + "forge.admin", + "forge.blocks_manager", + "auth.product_admin" +] admin_users.each do |email| user = User.find_or_create_by(email: email) -- GitLab From e90e49f385494a816af692d60b4b32abc7ebb644 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 7 Dec 2020 12:24:47 -1000 Subject: [PATCH 208/287] fixing Former-commit-id: 0741431ec1145952db86c79e92d37fbe19b5bcbc --- scripts/db/seeds.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/db/seeds.rb b/scripts/db/seeds.rb index 702cef8..b006a85 100644 --- a/scripts/db/seeds.rb +++ b/scripts/db/seeds.rb @@ -132,4 +132,6 @@ roles = [ admin_users.each do |email| user = User.find_or_create_by(email: email) user.update(roles: roles) -end \ No newline at end of file +end + +User.delete_by(email: "dcong@revacomm.com") \ No newline at end of file -- GitLab From 8f20310e4771eaace58c36f12f6602b0a903661f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 7 Dec 2020 12:48:45 -1000 Subject: [PATCH 209/287] removing cleanup Former-commit-id: 0093f0d77b8d40d85a32fc973f4a014fdc3eded9 --- scripts/db/seeds.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/db/seeds.rb b/scripts/db/seeds.rb index b006a85..702cef8 100644 --- a/scripts/db/seeds.rb +++ b/scripts/db/seeds.rb @@ -132,6 +132,4 @@ roles = [ admin_users.each do |email| user = User.find_or_create_by(email: email) user.update(roles: roles) -end - -User.delete_by(email: "dcong@revacomm.com") \ No newline at end of file +end \ No newline at end of file -- GitLab From aeabd691470263b89d698d59b9e2df4e02cf964b Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 8 Dec 2020 11:32:47 -1000 Subject: [PATCH 210/287] adding sleep to entrypoint script Former-commit-id: f91ff9dcdca4f9e63def2f8c07cbaac4239b9cdf --- scripts/entrypoint-server.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/entrypoint-server.sh b/scripts/entrypoint-server.sh index 4c83c42..5938f0e 100755 --- a/scripts/entrypoint-server.sh +++ b/scripts/entrypoint-server.sh @@ -1,5 +1,7 @@ #!/bin/sh +sleep 5 + rm -f tmp/pids/server.pid bundle exec rails db:migrate bundle exec rails db:seed -- GitLab From 08cccda9a650dd720703e185da15f567ffab46e0 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 8 Dec 2020 15:57:05 -1000 Subject: [PATCH 211/287] debugging Former-commit-id: b464e8d9371ea4cbd82450f772f53e8ca6fc6806 --- scripts/app/services/platform_one_auth_resolver_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/app/services/platform_one_auth_resolver_service.rb b/scripts/app/services/platform_one_auth_resolver_service.rb index f6bde0d..aac063b 100644 --- a/scripts/app/services/platform_one_auth_resolver_service.rb +++ b/scripts/app/services/platform_one_auth_resolver_service.rb @@ -7,6 +7,7 @@ class PlatformOneAuthResolverService def initialize(jwt_token) @payload = JWT.decode(jwt_token, nil, false)[0] + puts @payload @uid = @payload["sub"] @email = @payload["email"] @first_name = @payload["given_name"] -- GitLab From 906cda95096852b6577400dea567b82c7b662ced Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 8 Dec 2020 16:16:40 -1000 Subject: [PATCH 212/287] removing debug printing Former-commit-id: 8bd479da815da16bd7ba9f4eabe3ab7fbf8c1d70 --- .../app/services/platform_one_auth_resolver_service.rb | 1 - scripts/config/environment.rb | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/app/services/platform_one_auth_resolver_service.rb b/scripts/app/services/platform_one_auth_resolver_service.rb index aac063b..f6bde0d 100644 --- a/scripts/app/services/platform_one_auth_resolver_service.rb +++ b/scripts/app/services/platform_one_auth_resolver_service.rb @@ -7,7 +7,6 @@ class PlatformOneAuthResolverService def initialize(jwt_token) @payload = JWT.decode(jwt_token, nil, false)[0] - puts @payload @uid = @payload["sub"] @email = @payload["email"] @first_name = @payload["given_name"] diff --git a/scripts/config/environment.rb b/scripts/config/environment.rb index ff8d4c2..45506d9 100644 --- a/scripts/config/environment.rb +++ b/scripts/config/environment.rb @@ -4,8 +4,8 @@ require_relative 'application' # Initialize the Rails application. Rails.application.initialize! -puts "==========================================" -puts "Host: #{Rails.configuration.database_configuration[Rails.env]["host"]}" -puts "Database: #{Rails.configuration.database_configuration[Rails.env]["database"]}" -puts "User: #{Rails.configuration.database_configuration[Rails.env]["username"]}" -puts "==========================================" \ No newline at end of file +# puts "==========================================" +# puts "Host: #{Rails.configuration.database_configuration[Rails.env]["host"]}" +# puts "Database: #{Rails.configuration.database_configuration[Rails.env]["database"]}" +# puts "User: #{Rails.configuration.database_configuration[Rails.env]["username"]}" +# puts "==========================================" \ No newline at end of file -- GitLab From 71ca2cdd6669684dbfbf92dbb579035f71b5cb1c Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 8 Dec 2020 16:40:39 -1000 Subject: [PATCH 213/287] increasing timeout Former-commit-id: b84362d7b57c3df6edbbe82e77541cbc721c8deb --- scripts/entrypoint-server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/entrypoint-server.sh b/scripts/entrypoint-server.sh index 5938f0e..b64943d 100755 --- a/scripts/entrypoint-server.sh +++ b/scripts/entrypoint-server.sh @@ -1,6 +1,6 @@ #!/bin/sh -sleep 5 +sleep 10 rm -f tmp/pids/server.pid bundle exec rails db:migrate -- GitLab From b5eebfa9579c3d895325a2fca326b626bfe3267a Mon Sep 17 00:00:00 2001 From: Joshua Eason Date: Thu, 10 Dec 2020 16:40:43 -0700 Subject: [PATCH 214/287] Converting to hardening manifest Former-commit-id: 97a456d61cb4d7e97378d5d25dcae8cd4c4bdaa4 --- Dockerfile | 10 -------- Jenkinsfile | 14 ----------- LICENSE | 1 + README.md | 2 +- download.json | 13 ---------- hardening_manifest.yaml | 54 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 56 insertions(+), 38 deletions(-) delete mode 100644 Jenkinsfile create mode 100644 LICENSE delete mode 100644 download.json create mode 100644 hardening_manifest.yaml diff --git a/Dockerfile b/Dockerfile index b6f6b78..b56cce9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,3 @@ -### Iron Bank Settings -ARG BASE_REGISTRY=registry1.dsop.io -ARG BASE_IMAGE=ironbank/opensource/ruby/ruby26 -ARG BASE_TAG=2.6.6 - -### Platform 1 Pipeline Settings -#ARG BASE_REGISTRY=registry.il2.dsop.io -#ARG BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 -#ARG BASE_TAG=2.6.6.212 - # Setup the Image. FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index ec2a93f..0000000 --- a/Jenkinsfile +++ /dev/null @@ -1,14 +0,0 @@ -@Library('DCCSCR@master') _ -dccscrPipeline(version: "0.1.0") - -/* The above format is required for all production submissions. -* The version will be the image tag associated with this container for this branch. -* -* You may limit the job to the build stage or the scanning stage only: -* dccscrPipeline(version: "1.4.2", scan: false) -* dccscrPipeline(version: "1.0.0", build: false) -* -* You may also use a different branch of the jenkins-shared-library. -* Only do this if you know what you are doing: -* @Library('DCCSCR@feature1_branch') _ */ - diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e9db0f7 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +Please add a license \ No newline at end of file diff --git a/README.md b/README.md index 288603e..a901b4b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # forge -forge \ No newline at end of file +Please provide a detailed README \ No newline at end of file diff --git a/download.json b/download.json deleted file mode 100644 index 39b3156..0000000 --- a/download.json +++ /dev/null @@ -1,13 +0,0 @@ -{ "resources": - [ - { - "url" : "https://download.redis.io/releases/redis-6.0.9.tar.gz", - "filename": "redis-6.0.9.tar.gz", - "validation": - { - "type": "sha256", - "value": "dc2bdcf81c620e9f09cfd12e85d3bc631c897b2db7a55218fd8a65eaa37f86dd" - } - } - ] -} diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml new file mode 100644 index 0000000..953d282 --- /dev/null +++ b/hardening_manifest.yaml @@ -0,0 +1,54 @@ +--- +apiVersion: v1 + +# The repository name in registry1, excluding /ironbank/ +name: "forge" + +# List of tags to push for the repository in registry1 +# The most specific version should be the first tag and will be shown +# on ironbank.dsop.io +tags: +- "0.1.0" +- "latest" + +# Build args passed to Dockerfile ARGs +args: + BASE_IMAGE: "ironbank/opensource/ruby/ruby26" + BASE_TAG: "2.6.6.212" + +# Docker image labels +labels: + org.opencontainers.image.title: "forge" + ## Human-readable description of the software packaged in the image + # org.opencontainers.image.description: "FIXME" + ## License(s) under which contained software is distributed + # org.opencontainers.image.licenses: "FIXME" + ## URL to find more information on the image + # org.opencontainers.image.url: "FIXME" + ## Name of the distributing entity, organization or individual + # org.opencontainers.image.vendor: "FIXME" + org.opencontainers.image.version: "0.1.0" + ## Keywords to help with search (ex. "cicd,gitops,golang") + # mil.dso.ironbank.image.keywords: "FIXME" + ## This value can be "opensource" or "commercial" + # mil.dso.ironbank.image.type: "FIXME" + ## Product the image belongs to for grouping multiple images + # mil.dso.ironbank.product.name: "FIXME" + +# List of resources to make available to the offline build context +resources: +- url: https://download.redis.io/releases/redis-6.0.9.tar.gz + filename: redis-6.0.9.tar.gz + validation: + type: sha256 + value: dc2bdcf81c620e9f09cfd12e85d3bc631c897b2db7a55218fd8a65eaa37f86dd + +# List of project maintainers +maintainers: +- email: "dchong@revacomm.com" + # The name of the current container owner + name: "Derrin Chong" + username: "dchong" +- name: "Joshua Eason" + username: "jeason" + cht_member: true -- GitLab From 73a09788606d320617f77d36ae7d19cbab85b004 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 15 Dec 2020 10:22:52 -1000 Subject: [PATCH 215/287] Updating from ironbank Former-commit-id: 4ba070e3ee01f782b7c0704124a709d0ace7b2b4 --- Dockerfile | 6 ++- README.md | 110 +++++++++++++++++++++++++++++++++++++++- hardening_manifest.yaml | 25 ++++----- 3 files changed, 124 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index b56cce9..8c04504 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,8 @@ -# Setup the Image. +# Platform 1 Pipeline Settings +ARG BASE_REGISTRY=registry.il2.dsop.io +ARG BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 +ARG BASE_TAG=2.6.6.212 + FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 diff --git a/README.md b/README.md index a901b4b..9098182 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,109 @@ -# forge +Forge +===== -Please provide a detailed README \ No newline at end of file +## Standards +See the [software-team-standards](https://www.github.com/Galvanize-IT/software-team-standards) for git aliases. + +## Dependency Setup + +[install Homebrew](http://brew.sh/) if you don't already have it. + +Install rbenv and get the right version of ruby (the version here may change, so check the Gemfile for the correct +version. ) + +- `brew install rbenv` +- `rbenv install 2.6.6` + +Install our data stores: + +- `brew install postgresql` +- `brew install redis` +- `brew services start redis` + +To simplify your postgres installation, once you've installed it with homebrew, it's recommended that you use the +[Postgres.app](http://postgresapp.com/), which is an easy download and install. This allows you to not need to specify a +username/password in your `database.yml`. + + +## Installation + +You will need a [Github access token](https://github.com/settings/tokens). Replace the `YOUR_GENERATED_PERSONAL_ACCESS_TOKEN` with your token. + +The token will need **Full control of private repositories** + +- `git clone git@github.com:Galvanize-IT/forge.git && cd forge` +- `rbenv local 2.6.6` +- `gem install bundler` +- `bundle config --local GITHUB__COM YOUR_GENERATED_PERSONAL_ACCESS_TOKEN:x-oauth-basic` +- `bundle` +- `yarn install` + +## Data + +- `rake db:create` +- `rake db:migrate` + +## Auth + +Set up [Auth](https://github.com/Galvanize-IT/auth) before running Forge. + +## Configuration + +Ask a co-developer for their .env example. + +### Sidekiq + +As an admin, you can view Sidekiq jobs at `/admin/sidekiq`. + +## Starting the server + +`heroku local -p 3003` + +or: +`rails s -p 3003` +`sidekiq` +`bin/webpack-dev-server` + +## Deployment + +PRs are automatically deployed to their own one-off environments. To test webhook-related Auth features, be sure to update the webhooks for Forge at `auth-staging.herokuapp.com` to point to your review enviroment. + +Sign in to your review environment using your credentials for `auth-staging.herokuapp.com`, or `dev+auth@galvanize.com:password`. + +`master` is automatically deployed to `staging` once CI passes. + +Migrations are run as part of the deployment release phase. + +Note: `galvanize-forge` will need to have access to any repos used for project challenges. + +## Mailer previews + +To preview our mailers, go to `http://localhost:3003/rails/mailers`. + +## End-to-end Tests (e2e) + +The [Forge e2e suite](https://github.com/Galvanize-IT/forge-e2e) is run on every deploy to staging, and the results are posted to `#forge_devops`. + +## Code Generators + +The `ts_routes` gem generates typesafe routes for use in TypeScript. To update them: + +`rake ts:routes` + +### API Documentation + +The specs for the API use a specific DSL that allows the tests to generate our API documentation. You can then run the +integration specs and generate the documentation. The documentation can be committed if you’ve made changes to the API, +otherwise they can be disregard. + +Run `rails spec:api:docs` to generate the documentation files and then browse to `/api/docs` to see what the +documentation looks like. + +## Linters + +We use `rubocop` and `eslint`. + +You can use overcommit to automatically run our linters in a pre-commit hook: (`gem install overcommit; overcommit --install`). +Note: If you want to disable it inline (i.e. for a WIP commit), use `OVERCOMMIT_DISABLE=1 git commit -m "wip"`. + +You can also run the linters manually with auto-correction, along with the test suite, by running `bash lintspec.sh`. diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index 953d282..beb8cd9 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -20,35 +20,32 @@ args: labels: org.opencontainers.image.title: "forge" ## Human-readable description of the software packaged in the image - # org.opencontainers.image.description: "FIXME" + #org.opencontainers.image.description: "FIXME" ## License(s) under which contained software is distributed # org.opencontainers.image.licenses: "FIXME" ## URL to find more information on the image # org.opencontainers.image.url: "FIXME" ## Name of the distributing entity, organization or individual - # org.opencontainers.image.vendor: "FIXME" + org.opencontainers.image.vendor: "Galvanize" org.opencontainers.image.version: "0.1.0" ## Keywords to help with search (ex. "cicd,gitops,golang") - # mil.dso.ironbank.image.keywords: "FIXME" + mil.dso.ironbank.image.keywords: "learn,lms,galvanize" ## This value can be "opensource" or "commercial" - # mil.dso.ironbank.image.type: "FIXME" + mil.dso.ironbank.image.type: "commercial" ## Product the image belongs to for grouping multiple images - # mil.dso.ironbank.product.name: "FIXME" - -# List of resources to make available to the offline build context -resources: -- url: https://download.redis.io/releases/redis-6.0.9.tar.gz - filename: redis-6.0.9.tar.gz - validation: - type: sha256 - value: dc2bdcf81c620e9f09cfd12e85d3bc631c897b2db7a55218fd8a65eaa37f86dd + mil.dso.ironbank.product.name: "Learn" # List of project maintainers maintainers: - email: "dchong@revacomm.com" - # The name of the current container owner name: "Derrin Chong" username: "dchong" +- email: "muranaka@revacomm.com" + name: "Michael Uranaka" + username: "muranaka" +- email: "csakamaki@revacomm.com" + name: "Charlie Sakamaki" + username: "csakamaki" - name: "Joshua Eason" username: "jeason" cht_member: true -- GitLab From b2168e64ea86eea3dd5b62b10c2e44eb12fc1026 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Sun, 20 Dec 2020 22:41:40 -1000 Subject: [PATCH 216/287] updating registry Former-commit-id: 333fde25ce43213479f4f3c63f6bdd2c69761861 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 8c04504..1489bcb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Platform 1 Pipeline Settings -ARG BASE_REGISTRY=registry.il2.dsop.io +ARG BASE_REGISTRY=registry.il2.dso.mil ARG BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 ARG BASE_TAG=2.6.6.212 -- GitLab From e274b9c7c881a1d91458dd0d31c4d11e4dcb445c Mon Sep 17 00:00:00 2001 From: Derrin Chong Date: Tue, 22 Dec 2020 02:12:58 +0000 Subject: [PATCH 217/287] Fill in the blanks Former-commit-id: 044ef843741cb456c03fd4ee1fe76c7fcf33ef3c --- hardening_manifest.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index beb8cd9..5286f6d 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -20,7 +20,7 @@ args: labels: org.opencontainers.image.title: "forge" ## Human-readable description of the software packaged in the image - #org.opencontainers.image.description: "FIXME" + org.opencontainers.image.description: "Galvanize is a technology ecosystem for learners, entrepreneurs, startups, and established companies that meets the needs of the rapidly changing digital world. Our goal is to transform individuals and teams through effective education and community programs Delivering exceptional outcomes Coalescing and nurturing a community of innovators anchored by our campuses" ## License(s) under which contained software is distributed # org.opencontainers.image.licenses: "FIXME" ## URL to find more information on the image @@ -29,7 +29,7 @@ labels: org.opencontainers.image.vendor: "Galvanize" org.opencontainers.image.version: "0.1.0" ## Keywords to help with search (ex. "cicd,gitops,golang") - mil.dso.ironbank.image.keywords: "learn,lms,galvanize" + mil.dso.ironbank.image.keywords: "learn,lms,galvanize,online,remote,school,learning" ## This value can be "opensource" or "commercial" mil.dso.ironbank.image.type: "commercial" ## Product the image belongs to for grouping multiple images -- GitLab From 25375d6a5c48b6602640a8fa170fcd0dea875bdd Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Wed, 23 Dec 2020 15:13:30 -1000 Subject: [PATCH 218/287] Fix domain for test Former-commit-id: 30204853ef50caccc9515a5f51a481360d80b2b0 --- scripts/spec/services/download_repository_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/spec/services/download_repository_service_spec.rb b/scripts/spec/services/download_repository_service_spec.rb index d08a6bc..2ef54a8 100644 --- a/scripts/spec/services/download_repository_service_spec.rb +++ b/scripts/spec/services/download_repository_service_spec.rb @@ -44,7 +44,7 @@ describe DownloadRepositoryService do end context "when downloading from gitlab" do - let(:block) { create(:block, org: "csakamaki", origin: "code.il2.dsop.io", repo_name: "test-20200917")} + let(:block) { create(:block, org: "csakamaki", origin: "code.il2.dso.mil", repo_name: "test-20200917")} let(:release) { create(:release, block: block)} subject { DownloadGitlabRepositoryService.new(release: release, tmp_path: Rails.root.join("tmp", block.repo_name)).execute } -- GitLab From d90919f7547f4134c40f59243709ab64f1cc3151 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Wed, 23 Dec 2020 18:18:43 -1000 Subject: [PATCH 219/287] Updates for new dso.mil domain Former-commit-id: fb677beff2add260a03630bbc9f19bfbd19ef0c0 --- scripts/Gemfile | 2 +- scripts/config/secrets.yml | 2 +- scripts/doc/api.md | 2 +- scripts/serviceentry.yaml | 2 +- .../controllers/sessions_controller_spec.rb | 2 +- .../gitlab-course-yaml-branch-failure.yml | 18 +-- .../gitlab-course-yaml-success.yml | 104 +++++++++--------- .../vcr_cassettes/gitlab-not-found.yml | 12 +- .../fixtures/vcr_cassettes/gitlab-success.yml | 24 ++-- .../spec/services/course_validator_spec.rb | 8 +- .../download_repository_service_spec.rb | 2 +- scripts/spec/services/git_url_service_spec.rb | 6 +- ...platform_one_auth_resolver_service_spec.rb | 4 +- 13 files changed, 93 insertions(+), 95 deletions(-) diff --git a/scripts/Gemfile b/scripts/Gemfile index d937d75..1a86e24 100644 --- a/scripts/Gemfile +++ b/scripts/Gemfile @@ -53,7 +53,7 @@ gem "solid_use_case" # file processing ### We need to pass git credentials into the docker build command so we can clone the block-parser -# gem "block_parser", :git => "https://code.il2.dsop.io/tron/products/learn-lms/ruby-gems/block-parser.git" +# gem "block_parser", :git => "https://code.il2.dso.mil/tron/products/learn-lms/ruby-gems/block-parser.git" gem "block_parser", path: "./gems/block-parser" # Reduces boot times through caching; required in config/boot.rb diff --git a/scripts/config/secrets.yml b/scripts/config/secrets.yml index a3a095e..c989a87 100644 --- a/scripts/config/secrets.yml +++ b/scripts/config/secrets.yml @@ -32,7 +32,7 @@ shared: github_com: type: github token: <%= ENV['GITHUB_COM_TOKEN'] %> - code_il2_dsop_io: + code_il2_dso_mil: type: gitlab token: <%= ENV['GITLAB_DSOP_IL2_TOKEN'] %> gitlab_com: diff --git a/scripts/doc/api.md b/scripts/doc/api.md index 7631cd6..8f251a2 100644 --- a/scripts/doc/api.md +++ b/scripts/doc/api.md @@ -8,7 +8,7 @@ Future work might add the ability to publish curriculum and manage cohort creati Authentication ============== -Each Learn admin and instructor account will have a token that can be used to access the API. The token can be found in the UI under the account dropdown, or by going directly to https://learn.apps.il2.dsop.io/api_token. The token must be passed in the header of API requests as `X-LEARN-API-TOKEN: Bearer {token}` +Each Learn admin and instructor account will have a token that can be used to access the API. The token can be found in the UI under the account dropdown, or by going directly to https://learn.dso.mil/api_token. The token must be passed in the header of API requests as `X-LEARN-API-TOKEN: Bearer {token}` The authorization scope for each token will match the user’s permissions. For example, if a user is allowed to update a cohort’s visibility in the UI, that user’s token will be allowed to make API requests to do the same. diff --git a/scripts/serviceentry.yaml b/scripts/serviceentry.yaml index 9b4e97c..3cb48be 100644 --- a/scripts/serviceentry.yaml +++ b/scripts/serviceentry.yaml @@ -5,7 +5,7 @@ metadata: namespace: galvanize-learn spec: hosts: - - code.il2.dsop.io + - code.il2.dso.mil - google.com location: MESH_EXTERNAL ports: diff --git a/scripts/spec/controllers/sessions_controller_spec.rb b/scripts/spec/controllers/sessions_controller_spec.rb index 4dab6eb..91eeb73 100644 --- a/scripts/spec/controllers/sessions_controller_spec.rb +++ b/scripts/spec/controllers/sessions_controller_spec.rb @@ -32,7 +32,7 @@ describe SessionsController do iat: Time.now.to_i, auth_time: Time.now.to_i, jti: "e4600f88-1b42-4e01-ac01-8646d3cc4df6", - iss: "https://login.dsop.io/auth/realms/baby-yoda", + iss: "https://login.dso.mil/auth/realms/baby-yoda", aud: "il4_191f836b-ec50-4819-ba10-1afaa5b99600_mission-widow", sub: "2cba6ea8-8451-4952-80d6-c7de379d5b7e", typ: "ID", diff --git a/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-branch-failure.yml b/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-branch-failure.yml index bc1ecf6..8787042 100644 --- a/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-branch-failure.yml +++ b/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-branch-failure.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects?search=p1-test + uri: https://code.il2.dso.mil/api/v4/projects?search=p1-test body: encoding: US-ASCII string: '' @@ -29,8 +29,8 @@ http_interactions: Etag: - W/"905a13fb3b949a706442a5f1b4b540d6" Link: - - ; - rel="first", ; + - ; + rel="first", ; rel="last" Vary: - Origin @@ -63,14 +63,14 @@ http_interactions: body: encoding: UTF-8 string: '[{"id":702,"description":"","name":"p1-test","name_with_namespace":"Charlie - Sakamaki / p1-test","path":"p1-test","path_with_namespace":"csakamaki/p1-test","created_at":"2020-09-23T19:53:49.310Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dsop.io:csakamaki/p1-test.git","http_url_to_repo":"https://code.il2.dsop.io/csakamaki/p1-test.git","web_url":"https://code.il2.dsop.io/csakamaki/p1-test","readme_url":null,"avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-24T00:07:57.336Z","namespace":{"id":288,"name":"Charlie - Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"_links":{"self":"https://code.il2.dsop.io/api/v4/projects/702","issues":"https://code.il2.dsop.io/api/v4/projects/702/issues","merge_requests":"https://code.il2.dsop.io/api/v4/projects/702/merge_requests","repo_branches":"https://code.il2.dsop.io/api/v4/projects/702/repository/branches","labels":"https://code.il2.dsop.io/api/v4/projects/702/labels","events":"https://code.il2.dsop.io/api/v4/projects/702/events","members":"https://code.il2.dsop.io/api/v4/projects/702/members"},"empty_repo":false,"archived":false,"visibility":"private","owner":{"id":238,"name":"Charlie - Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-07T20:50:13.933Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' - http_version: + Sakamaki / p1-test","path":"p1-test","path_with_namespace":"csakamaki/p1-test","created_at":"2020-09-23T19:53:49.310Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dso.mil:csakamaki/p1-test.git","http_url_to_repo":"https://code.il2.dso.mil/csakamaki/p1-test.git","web_url":"https://code.il2.dso.mil/csakamaki/p1-test","readme_url":null,"avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-24T00:07:57.336Z","namespace":{"id":288,"name":"Charlie + Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dso.mil/csakamaki"},"_links":{"self":"https://code.il2.dso.mil/api/v4/projects/702","issues":"https://code.il2.dso.mil/api/v4/projects/702/issues","merge_requests":"https://code.il2.dso.mil/api/v4/projects/702/merge_requests","repo_branches":"https://code.il2.dso.mil/api/v4/projects/702/repository/branches","labels":"https://code.il2.dso.mil/api/v4/projects/702/labels","events":"https://code.il2.dso.mil/api/v4/projects/702/events","members":"https://code.il2.dso.mil/api/v4/projects/702/members"},"empty_repo":false,"archived":false,"visibility":"private","owner":{"id":238,"name":"Charlie + Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dso.mil/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-07T20:50:13.933Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' + http_version: recorded_at: Tue, 06 Oct 2020 07:44:25 GMT - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects/702/repository/files/course.yaml?ref=missingfile + uri: https://code.il2.dso.mil/api/v4/projects/702/repository/files/course.yaml?ref=missingfile body: encoding: US-ASCII string: '' @@ -113,6 +113,6 @@ http_interactions: body: encoding: UTF-8 string: '{"message":"404 Commit Not Found"}' - http_version: + http_version: recorded_at: Tue, 06 Oct 2020 07:44:25 GMT recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-success.yml b/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-success.yml index 549e280..a84944c 100644 --- a/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-success.yml +++ b/scripts/spec/fixtures/vcr_cassettes/gitlab-course-yaml-success.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects?search=p1-test + uri: https://code.il2.dso.mil/api/v4/projects?search=p1-test body: encoding: US-ASCII string: '' @@ -22,15 +22,13 @@ http_interactions: headers: Cache-Control: - max-age=0, private, must-revalidate - Content-Length: - - '3280' Content-Type: - application/json Etag: - - W/"905a13fb3b949a706442a5f1b4b540d6" + - W/"81be7f29b8b3092ab074fadd48e9b3f2" Link: - - ; - rel="first", ; + - ; + rel="first", ; rel="last" Vary: - Origin @@ -47,30 +45,31 @@ http_interactions: X-Prev-Page: - '' X-Request-Id: - - O1E5nWd8Oa5 + - 8Xlos2Tb412 X-Runtime: - - '0.103785' + - '0.072129' X-Total: - '1' X-Total-Pages: - '1' Date: - - Tue, 06 Oct 2020 07:37:19 GMT + - Thu, 24 Dec 2020 04:03:11 GMT X-Envoy-Upstream-Service-Time: - - '108' + - '74' Server: - istio-envoy + Transfer-Encoding: + - chunked body: encoding: UTF-8 string: '[{"id":702,"description":"","name":"p1-test","name_with_namespace":"Charlie - Sakamaki / p1-test","path":"p1-test","path_with_namespace":"csakamaki/p1-test","created_at":"2020-09-23T19:53:49.310Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dsop.io:csakamaki/p1-test.git","http_url_to_repo":"https://code.il2.dsop.io/csakamaki/p1-test.git","web_url":"https://code.il2.dsop.io/csakamaki/p1-test","readme_url":null,"avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-24T00:07:57.336Z","namespace":{"id":288,"name":"Charlie - Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"_links":{"self":"https://code.il2.dsop.io/api/v4/projects/702","issues":"https://code.il2.dsop.io/api/v4/projects/702/issues","merge_requests":"https://code.il2.dsop.io/api/v4/projects/702/merge_requests","repo_branches":"https://code.il2.dsop.io/api/v4/projects/702/repository/branches","labels":"https://code.il2.dsop.io/api/v4/projects/702/labels","events":"https://code.il2.dsop.io/api/v4/projects/702/events","members":"https://code.il2.dsop.io/api/v4/projects/702/members"},"empty_repo":false,"archived":false,"visibility":"private","owner":{"id":238,"name":"Charlie - Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-07T20:50:13.933Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' - http_version: - recorded_at: Tue, 06 Oct 2020 07:37:19 GMT + Sakamaki / p1-test","path":"p1-test","path_with_namespace":"csakamaki/p1-test","created_at":"2020-09-23T19:53:49.310Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dso.mil:csakamaki/p1-test.git","http_url_to_repo":"https://code.il2.dso.mil/csakamaki/p1-test.git","web_url":"https://code.il2.dso.mil/csakamaki/p1-test","readme_url":null,"avatar_url":null,"forks_count":0,"star_count":0,"last_activity_at":"2020-12-24T02:53:59.799Z","namespace":{"id":288,"name":"Charlie + Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dso.mil/csakamaki"},"_links":{"self":"https://code.il2.dso.mil/api/v4/projects/702","issues":"https://code.il2.dso.mil/api/v4/projects/702/issues","merge_requests":"https://code.il2.dso.mil/api/v4/projects/702/merge_requests","repo_branches":"https://code.il2.dso.mil/api/v4/projects/702/repository/branches","labels":"https://code.il2.dso.mil/api/v4/projects/702/labels","events":"https://code.il2.dso.mil/api/v4/projects/702/events","members":"https://code.il2.dso.mil/api/v4/projects/702/members"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"private","owner":{"id":238,"name":"Charlie + Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dso.mil/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-11-19T00:50:12.208Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"ci_forward_deployment_enabled":true,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' + recorded_at: Thu, 24 Dec 2020 04:03:11 GMT - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects/702/repository/files/course.yaml?ref=master + uri: https://code.il2.dso.mil/api/v4/projects/702/repository/files/course.yaml?ref=master body: encoding: US-ASCII string: '' @@ -90,12 +89,10 @@ http_interactions: headers: Cache-Control: - max-age=0, private, must-revalidate - Content-Length: - - '648' Content-Type: - application/json Etag: - - W/"58ce3aa47e9466c129ffaf1a1f8ab1fe" + - W/"46686ce61776a9ce9a5cbf67a8f117c6" Vary: - Origin X-Content-Type-Options: @@ -103,11 +100,11 @@ http_interactions: X-Frame-Options: - SAMEORIGIN X-Gitlab-Blob-Id: - - 696c1f44c7233b2fd1c27c43c2b1106cc06f0eb3 + - 48654c43a0644a1f46d9cb7772317cc9408ffb25 X-Gitlab-Commit-Id: - - c114c7afbf65344d93f697f34060ca901b3035df + - 55ce2e91da39472f9ae9faf77c4025a872c55c74 X-Gitlab-Content-Sha256: - - d537f06b2b5bc5e1e1980872aac58766d2a57578d2f565e53b007debf05feb9f + - 365879dd4054b10e3c12e2e2d30b3ec988f01017896d7a46bfe9dc24c5832ec3 X-Gitlab-Encoding: - base64 X-Gitlab-File-Name: @@ -115,29 +112,30 @@ http_interactions: X-Gitlab-File-Path: - course.yaml X-Gitlab-Last-Commit-Id: - - ac0da165cce031bf056866c3629dc7f221748c8f + - 55ce2e91da39472f9ae9faf77c4025a872c55c74 X-Gitlab-Ref: - master X-Gitlab-Size: - '211' X-Request-Id: - - UlX7wrEymh2 + - eAMVwDv2Qw1 X-Runtime: - - '0.052112' + - '0.036504' Date: - - Tue, 06 Oct 2020 07:37:19 GMT + - Thu, 24 Dec 2020 04:03:12 GMT + Content-Length: + - '648' X-Envoy-Upstream-Service-Time: - - '54' + - '38' Server: - istio-envoy body: encoding: UTF-8 - string: '{"file_name":"course.yaml","file_path":"course.yaml","size":211,"encoding":"base64","content_sha256":"d537f06b2b5bc5e1e1980872aac58766d2a57578d2f565e53b007debf05feb9f","ref":"master","blob_id":"696c1f44c7233b2fd1c27c43c2b1106cc06f0eb3","commit_id":"c114c7afbf65344d93f697f34060ca901b3035df","last_commit_id":"ac0da165cce031bf056866c3629dc7f221748c8f","content":"LS0tCiAgQ291cnNlOgogICAgLSBTZWN0aW9uOiBEU09QIEdpdExhYgogICAgICBSZXBvczoKICAgICAgICAtIFVSTDogaHR0cHM6Ly9jb2RlLmlsMi5kc29wLmlvL2NzYWthbWFraS9wMS10ZXN0CiAgICAtIFNlY3Rpb246IGdTY2hvb2wgR2l0SHViCiAgICAgIFJlcG9zOgogICAgICAgIC0gVVJMOiBodHRwczovL2dpdGh1Yi5jb20vZ1NjaG9vbC9yZXZhY29tbS10ZXN0Cg=="}' - http_version: - recorded_at: Tue, 06 Oct 2020 07:37:19 GMT + string: '{"file_name":"course.yaml","file_path":"course.yaml","size":211,"encoding":"base64","content_sha256":"365879dd4054b10e3c12e2e2d30b3ec988f01017896d7a46bfe9dc24c5832ec3","ref":"master","blob_id":"48654c43a0644a1f46d9cb7772317cc9408ffb25","commit_id":"55ce2e91da39472f9ae9faf77c4025a872c55c74","last_commit_id":"55ce2e91da39472f9ae9faf77c4025a872c55c74","content":"LS0tCiAgQ291cnNlOgogICAgLSBTZWN0aW9uOiBEU09QIEdpdExhYgogICAgICBSZXBvczoKICAgICAgICAtIFVSTDogaHR0cHM6Ly9jb2RlLmlsMi5kc28ubWlsL2NzYWthbWFraS9wMS10ZXN0CiAgICAtIFNlY3Rpb246IGdTY2hvb2wgR2l0SHViCiAgICAgIFJlcG9zOgogICAgICAgIC0gVVJMOiBodHRwczovL2dpdGh1Yi5jb20vZ1NjaG9vbC9yZXZhY29tbS10ZXN0Cg=="}' + recorded_at: Thu, 24 Dec 2020 04:03:12 GMT - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects?search=p1-test + uri: https://code.il2.dso.mil/api/v4/projects?search=p1-test body: encoding: US-ASCII string: '' @@ -157,15 +155,13 @@ http_interactions: headers: Cache-Control: - max-age=0, private, must-revalidate - Content-Length: - - '3280' Content-Type: - application/json Etag: - - W/"905a13fb3b949a706442a5f1b4b540d6" + - W/"81be7f29b8b3092ab074fadd48e9b3f2" Link: - - ; - rel="first", ; + - ; + rel="first", ; rel="last" Vary: - Origin @@ -182,27 +178,28 @@ http_interactions: X-Prev-Page: - '' X-Request-Id: - - kFr194XN4I8 + - ybXkfLYyKN1 X-Runtime: - - '0.065975' + - '0.067555' X-Total: - '1' X-Total-Pages: - '1' Date: - - Tue, 06 Oct 2020 07:37:19 GMT + - Thu, 24 Dec 2020 04:03:12 GMT X-Envoy-Upstream-Service-Time: - - '68' + - '69' Server: - istio-envoy + Transfer-Encoding: + - chunked body: encoding: UTF-8 string: '[{"id":702,"description":"","name":"p1-test","name_with_namespace":"Charlie - Sakamaki / p1-test","path":"p1-test","path_with_namespace":"csakamaki/p1-test","created_at":"2020-09-23T19:53:49.310Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dsop.io:csakamaki/p1-test.git","http_url_to_repo":"https://code.il2.dsop.io/csakamaki/p1-test.git","web_url":"https://code.il2.dsop.io/csakamaki/p1-test","readme_url":null,"avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-24T00:07:57.336Z","namespace":{"id":288,"name":"Charlie - Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"_links":{"self":"https://code.il2.dsop.io/api/v4/projects/702","issues":"https://code.il2.dsop.io/api/v4/projects/702/issues","merge_requests":"https://code.il2.dsop.io/api/v4/projects/702/merge_requests","repo_branches":"https://code.il2.dsop.io/api/v4/projects/702/repository/branches","labels":"https://code.il2.dsop.io/api/v4/projects/702/labels","events":"https://code.il2.dsop.io/api/v4/projects/702/events","members":"https://code.il2.dsop.io/api/v4/projects/702/members"},"empty_repo":false,"archived":false,"visibility":"private","owner":{"id":238,"name":"Charlie - Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-07T20:50:13.933Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' - http_version: - recorded_at: Tue, 06 Oct 2020 07:37:20 GMT + Sakamaki / p1-test","path":"p1-test","path_with_namespace":"csakamaki/p1-test","created_at":"2020-09-23T19:53:49.310Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dso.mil:csakamaki/p1-test.git","http_url_to_repo":"https://code.il2.dso.mil/csakamaki/p1-test.git","web_url":"https://code.il2.dso.mil/csakamaki/p1-test","readme_url":null,"avatar_url":null,"forks_count":0,"star_count":0,"last_activity_at":"2020-12-24T02:53:59.799Z","namespace":{"id":288,"name":"Charlie + Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dso.mil/csakamaki"},"_links":{"self":"https://code.il2.dso.mil/api/v4/projects/702","issues":"https://code.il2.dso.mil/api/v4/projects/702/issues","merge_requests":"https://code.il2.dso.mil/api/v4/projects/702/merge_requests","repo_branches":"https://code.il2.dso.mil/api/v4/projects/702/repository/branches","labels":"https://code.il2.dso.mil/api/v4/projects/702/labels","events":"https://code.il2.dso.mil/api/v4/projects/702/events","members":"https://code.il2.dso.mil/api/v4/projects/702/members"},"packages_enabled":true,"empty_repo":false,"archived":false,"visibility":"private","owner":{"id":238,"name":"Charlie + Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dso.mil/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-11-19T00:50:12.208Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"private","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"ci_forward_deployment_enabled":true,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"allow_merge_on_skipped_pipeline":null,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' + recorded_at: Thu, 24 Dec 2020 04:03:12 GMT - request: method: head uri: https://api.github.com/repos/gSchool/revacomm-test @@ -214,13 +211,15 @@ http_interactions: - token 86f4c1b3eb7417dc81fe87466c5179106a6d213c User-Agent: - Galvanize Forge + Accept: + - "*/*" response: status: code: 200 message: OK headers: Date: - - Tue, 06 Oct 2020 07:37:21 GMT + - Thu, 24 Dec 2020 04:03:13 GMT Content-Type: - application/json; charset=utf-8 Content-Length: @@ -236,11 +235,11 @@ http_interactions: - Accept-Encoding - Accept-Encoding, Accept, X-Requested-With Etag: - - '"2575197bdf1788abeea33fa22b34b5e4e00fe10bb9a22582fbd0a22e3ae43d14"' + - '"8be52508cc022f98cb8fae642ff9da872b8bbe22765f95db5fe31d62d2fc1286"' Last-Modified: - Wed, 23 Sep 2020 20:21:08 GMT X-Oauth-Scopes: - - repo + - repo, workflow X-Accepted-Oauth-Scopes: - repo X-Github-Media-Type: @@ -250,7 +249,7 @@ http_interactions: X-Ratelimit-Remaining: - '4999' X-Ratelimit-Reset: - - '1601973441' + - '1608786193' X-Ratelimit-Used: - '1' Access-Control-Expose-Headers: @@ -272,10 +271,9 @@ http_interactions: Content-Security-Policy: - default-src 'none' X-Github-Request-Id: - - D9CF:6749:3F7741:4BD406:5F7C1EB0 + - CBEA:439C:161B825:1ABDF59:5FE41301 body: encoding: UTF-8 string: '' - http_version: - recorded_at: Tue, 06 Oct 2020 07:37:21 GMT -recorded_with: VCR 4.0.0 + recorded_at: Thu, 24 Dec 2020 04:03:13 GMT +recorded_with: VCR 6.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/gitlab-not-found.yml b/scripts/spec/fixtures/vcr_cassettes/gitlab-not-found.yml index 7d3fbbb..01d3667 100644 --- a/scripts/spec/fixtures/vcr_cassettes/gitlab-not-found.yml +++ b/scripts/spec/fixtures/vcr_cassettes/gitlab-not-found.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects?membership=true&search=does-not-exist + uri: https://code.il2.dso.mil/api/v4/projects?membership=true&search=does-not-exist body: encoding: US-ASCII string: '' @@ -29,8 +29,8 @@ http_interactions: Etag: - W/"4f53cda18c2baa0c0354bb5f9a3ecbe5" Link: - - ; - rel="first", ; + - ; + rel="first", ; rel="last" Vary: - Origin @@ -63,11 +63,11 @@ http_interactions: body: encoding: UTF-8 string: "[]" - http_version: + http_version: recorded_at: Tue, 06 Oct 2020 07:49:47 GMT - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects//repository/branches/master + uri: https://code.il2.dso.mil/api/v4/projects//repository/branches/master body: encoding: US-ASCII string: '' @@ -106,6 +106,6 @@ http_interactions: body: encoding: UTF-8 string: '{"error":"404 Not Found"}' - http_version: + http_version: recorded_at: Tue, 06 Oct 2020 07:49:47 GMT recorded_with: VCR 4.0.0 diff --git a/scripts/spec/fixtures/vcr_cassettes/gitlab-success.yml b/scripts/spec/fixtures/vcr_cassettes/gitlab-success.yml index c701ecf..1b66453 100644 --- a/scripts/spec/fixtures/vcr_cassettes/gitlab-success.yml +++ b/scripts/spec/fixtures/vcr_cassettes/gitlab-success.yml @@ -2,7 +2,7 @@ http_interactions: - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects?membership=true&search=test-20200917 + uri: https://code.il2.dso.mil/api/v4/projects?membership=true&search=test-20200917 body: encoding: US-ASCII string: '' @@ -29,8 +29,8 @@ http_interactions: Etag: - W/"94af059ec19294995f506264a0cbc893" Link: - - ; - rel="first", ; + - ; + rel="first", ; rel="last" Vary: - Origin @@ -63,14 +63,14 @@ http_interactions: body: encoding: UTF-8 string: '[{"id":599,"description":"","name":"test-20200917","name_with_namespace":"Charlie - Sakamaki / test-20200917","path":"test-20200917","path_with_namespace":"csakamaki/test-20200917","created_at":"2020-09-18T20:08:10.651Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dsop.io:csakamaki/test-20200917.git","http_url_to_repo":"https://code.il2.dsop.io/csakamaki/test-20200917.git","web_url":"https://code.il2.dsop.io/csakamaki/test-20200917","readme_url":"https://code.il2.dsop.io/csakamaki/test-20200917/-/blob/master/README.md","avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-30T19:14:19.395Z","namespace":{"id":288,"name":"Charlie - Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"_links":{"self":"https://code.il2.dsop.io/api/v4/projects/599","issues":"https://code.il2.dsop.io/api/v4/projects/599/issues","merge_requests":"https://code.il2.dsop.io/api/v4/projects/599/merge_requests","repo_branches":"https://code.il2.dsop.io/api/v4/projects/599/repository/branches","labels":"https://code.il2.dsop.io/api/v4/projects/599/labels","events":"https://code.il2.dsop.io/api/v4/projects/599/events","members":"https://code.il2.dsop.io/api/v4/projects/599/members"},"empty_repo":false,"archived":false,"visibility":"public","owner":{"id":238,"name":"Charlie - Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dsop.io/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-09T20:50:21.090Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' - http_version: + Sakamaki / test-20200917","path":"test-20200917","path_with_namespace":"csakamaki/test-20200917","created_at":"2020-09-18T20:08:10.651Z","default_branch":"master","tag_list":[],"ssh_url_to_repo":"git@code.il2.dso.mil:csakamaki/test-20200917.git","http_url_to_repo":"https://code.il2.dso.mil/csakamaki/test-20200917.git","web_url":"https://code.il2.dso.mil/csakamaki/test-20200917","readme_url":"https://code.il2.dso.mil/csakamaki/test-20200917/-/blob/master/README.md","avatar_url":null,"star_count":0,"forks_count":0,"last_activity_at":"2020-09-30T19:14:19.395Z","namespace":{"id":288,"name":"Charlie + Sakamaki","path":"csakamaki","kind":"user","full_path":"csakamaki","parent_id":null,"avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dso.mil/csakamaki"},"_links":{"self":"https://code.il2.dso.mil/api/v4/projects/599","issues":"https://code.il2.dso.mil/api/v4/projects/599/issues","merge_requests":"https://code.il2.dso.mil/api/v4/projects/599/merge_requests","repo_branches":"https://code.il2.dso.mil/api/v4/projects/599/repository/branches","labels":"https://code.il2.dso.mil/api/v4/projects/599/labels","events":"https://code.il2.dso.mil/api/v4/projects/599/events","members":"https://code.il2.dso.mil/api/v4/projects/599/members"},"empty_repo":false,"archived":false,"visibility":"public","owner":{"id":238,"name":"Charlie + Sakamaki","username":"csakamaki","state":"active","avatar_url":"https://secure.gravatar.com/avatar/ed30ad3597897901ccbd315e731bf100?s=80\u0026d=identicon","web_url":"https://code.il2.dso.mil/csakamaki"},"resolve_outdated_diff_discussions":false,"container_registry_enabled":true,"container_expiration_policy":{"cadence":"7d","enabled":true,"keep_n":null,"older_than":null,"name_regex":null,"name_regex_keep":null,"next_run_at":"2020-10-09T20:50:21.090Z"},"issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"jobs_enabled":true,"snippets_enabled":true,"can_create_merge_request_in":true,"issues_access_level":"enabled","repository_access_level":"enabled","merge_requests_access_level":"enabled","forking_access_level":"enabled","wiki_access_level":"enabled","builds_access_level":"enabled","snippets_access_level":"enabled","pages_access_level":"enabled","emails_disabled":null,"shared_runners_enabled":true,"lfs_enabled":true,"creator_id":238,"import_status":"none","open_issues_count":0,"ci_default_git_depth":50,"public_jobs":true,"build_timeout":3600,"auto_cancel_pending_pipelines":"enabled","build_coverage_regex":null,"ci_config_path":"","shared_with_groups":[],"only_allow_merge_if_pipeline_succeeds":false,"request_access_enabled":true,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":true,"printing_merge_request_link_enabled":true,"merge_method":"merge","suggestion_commit_message":null,"auto_devops_enabled":false,"auto_devops_deploy_strategy":"continuous","autoclose_referenced_issues":true,"approvals_before_merge":0,"mirror":false,"external_authorization_classification_label":null,"packages_enabled":true,"service_desk_enabled":false,"service_desk_address":null,"marked_for_deletion_at":null,"marked_for_deletion_on":null,"permissions":{"project_access":{"access_level":40,"notification_level":3},"group_access":null}}]' + http_version: recorded_at: Tue, 06 Oct 2020 07:55:51 GMT - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects/599/repository/branches/master + uri: https://code.il2.dso.mil/api/v4/projects/599/repository/branches/master body: encoding: US-ASCII string: '' @@ -116,12 +116,12 @@ http_interactions: encoding: UTF-8 string: '{"name":"master","commit":{"id":"0d3ccb796757d84e99dcf43ef913821483a248a0","short_id":"0d3ccb79","created_at":"2020-09-30T09:12:44.000-10:00","parent_ids":["1ff934599cfdc0f21efe3d524609aec8c73b0632"],"title":"add python-simple","message":"add python-simple\n","author_name":"Charlie Sakamaki","author_email":"csakamaki@revacomm.com","authored_date":"2020-09-30T09:12:44.000-10:00","committer_name":"Charlie - Sakamaki","committer_email":"csakamaki@revacomm.com","committed_date":"2020-09-30T09:12:44.000-10:00","web_url":"https://code.il2.dsop.io/csakamaki/test-20200917/-/commit/0d3ccb796757d84e99dcf43ef913821483a248a0"},"merged":false,"protected":true,"developers_can_push":false,"developers_can_merge":false,"can_push":true,"default":true,"web_url":"https://code.il2.dsop.io/csakamaki/test-20200917/-/tree/master"}' - http_version: + Sakamaki","committer_email":"csakamaki@revacomm.com","committed_date":"2020-09-30T09:12:44.000-10:00","web_url":"https://code.il2.dso.mil/csakamaki/test-20200917/-/commit/0d3ccb796757d84e99dcf43ef913821483a248a0"},"merged":false,"protected":true,"developers_can_push":false,"developers_can_merge":false,"can_push":true,"default":true,"web_url":"https://code.il2.dso.mil/csakamaki/test-20200917/-/tree/master"}' + http_version: recorded_at: Tue, 06 Oct 2020 07:55:52 GMT - request: method: get - uri: https://code.il2.dsop.io/api/v4/projects/599/repository/archive.zip + uri: https://code.il2.dso.mil/api/v4/projects/599/repository/archive.zip body: encoding: US-ASCII string: '' @@ -173,6 +173,6 @@ http_interactions: encoding: ASCII-8BIT string: !binary |- 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Y291cnNlZmlsZXMvVVQFAAGs2HRfUEsDBAoAAAAAAJaZPlEAAAAAAAAAAAAAAABRAAkAdGVzdC0yMDIwMDkxNy1tYXN0ZXItMGQzY2NiNzk2NzU3ZDg0ZTk5ZGNmNDNlZjkxMzgyMTQ4M2EyNDhhMC9jb3Vyc2VmaWxlcy9naXRodWIvVVQFAAGs2HRfUEsDBAoAAAAIAJaZPlHw+naYXQAAAHUAAABcAAkAdGVzdC0yMDIwMDkxNy1tYXN0ZXItMGQzY2NiNzk2NzU3ZDg0ZTk5ZGNmNDNlZjkxMzgyMTQ4M2EyNDhhMC9jb3Vyc2VmaWxlcy9naXRodWIvY291cnNlLnlhbWxVVAUAAazYdF81ijsOgCAQRHtOMRdYie22tlYYD6BmIyQiBFbP7ydazZs3Y4jIAF06ShW+CSAMsmhIO/+A9h0AJzlV/srzHF3P8Kq5srVrUH/MzZKidXJOd8bOT2ULYlWqUvmkuQBQSwMECgAAAAAAlpk+UQAAAAAAAAAAAAAAAFEACQB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvdXJzZWZpbGVzL2dpdGxhYi9VVAUAAazYdF9QSwMECgAAAAgAlpk+Uc9Kmm1jAAAAdQAAAFwACQB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvdXJzZWZpbGVzL2dpdGxhYi9jb3Vyc2UueWFtbFVUBQABrNh0XzWKOw4CMQwF+5ziXcBJNg3CLS3VIg4QZS0RLeAIe+/PR2w3o5lARAE46fYy4Q8BhIs07/rkHTD9AjDLUOO/fM/rfGbc3IdxSk0Xif1e4mI6YtfUrK71UdeeXMyp5JLzcTqEN1BLAwQKAAAACACWmT5RoKr04p4BAAAfAwAATgAJAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvcHl0aG9uLXNpbXBsZS5tZFVUBQABrNh0X2VTTXObMBC961dsh+kMzgCTxDZ2fE2bX5BLT0aGxWgqJKKPafn33Q3Y2KkuSG/fvt23QkmSwLe6k1qjOaMQDxDGAQ9Q2wZzb9QwYCBQS3OO8kyBYQydNeuiJFQ1B2jbsm1f1o95Wb5s8o3c7/J9e9rkcl0/l9t694QnpgYVNGX/stHBm3I+wFs0dVDWwOtSPUm4nY+IniNC/MBWGQQJ7YVcM7eBqrfNcXsMqkd/LKsC3jtcSL6zUTcQ5G/ONbE/oQPpQRqQ7hx7NCGDAV1rXQ+BMkktagunEbZgKSBZJoM+6qAGPX5yHHo6MqfMGDCEhOjMZ9Dgn5lQXFygafLFyYQNWtbYWd2gE6KqqmmYIoFmMjryeK42OnTIrFvFO4UJDlTD38qpfrAuQC/VdR+NCswTotbSe3infXoBCz69So+rgwBa1AwwnnrU7YzxonHxMEGRdfofMH1+zOBpt7+h8OKsgoqgCz8/otQpN1LcXVhKMqsM+APfYbuCByhXX63OvmbgLzk3chrmYpVb/V956me+nrsiX2vcyV7R5T38A1BLAwQKAAAACACWmT5RE381dIEDAAAjCAAATgAJAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvcmV2YWNvbW0tcXVpei5tZFVUBQABrNh0X5VVWW/bRhB+318xUh4sFZEiioJMO2mD1iiKtOhDDtQPRREvl0NyoxWX3kOyU+S/d5ZXJB+NQ0DQcnbmm+ub4Tvc8Qu93cKv21rpW0R46+Vnxp7BSJRcKawKZOwHcLc1nsPWKydrhTNRaimQ5DI7B56tkhxFssowX61Pl0mc5iJb5mdnmPJkHdScdIrs33q0TuoKZjP4s8OCix6r1rJy9hyWwUDXUtD57zFFV/Pqdvwc6FgJrJ3navwPhRgeGF13mOyy5K6JE3QOorUCaeFdl+Lr3gKrbDZY9UJdh1fLZsAh19rtkRvY8srnXDhvZFX0mI1KrbhDUL4SJQg63lWoACuS1kZahEwW0nEFzvDK5tpseVOEQzzrTZ5qbrJHfB6E3kfaywhzj4Z9l8sDuM58EN1QalWjzfrKfUWxoE3BK/m5USD8gSQWqHmaekhguVdDAILXPJVKOkkqrjTaFyXkBm0J5IrqhtRTSwXLoDAYOoiirLTSxVGUh2GxTnZAUNYoPsBYUaLYpPqmoyrmcZ4kyBenSbQSC0zW0eIMlxFHXEVRLB6m6ntUKNzAfug7QJTlxkmuPgqDmXTnVCh/yOT4PkuJppI4QxR1JRLTlNL70GhucGDqiR34u+OKLF8z9iTykmv4TWcgGiZgFt4vudqA9gaoG5sg+AUb1ylhgNJ6Q96D+A/EGqQDaoJq7IIK9RxhG1KCD3yDXYtoNImeGAoAF946vUVDqQjfSH5Wqk+Pp3qH7AnkfVLg34rwTiiPkPwedx4hjs5wZitZ1+hISOQrPC/o4hPfcSuMrF3HqQXHJI0z4lDMVyjWqYhXeb5cxNEpX5/F0VdO/T6YBvhmvHvHgWYGqapVWAbOtE2xjuaClsJH72VGfLpcLKJk9ld0QLHVQxQzkpYT7TFaTw2HXViNLbwd8GmhDgNOa7Jp7Wg8/3+m0RQKLLXKQjWvrq4+WTZ4aR28b8AnU/iXvXjR53Tk6VXtw5jfOCip1z+NxuxLgDpyfOSnk1vCqjunO9rOYX/QWP5ITq69NDg5oXLKk+m8vXjZgB5gtva9xFFmts8hw9CXlCDagD/QHJ88HwrYZMOAHukm476QbRnHd9SgedoQJsclmc6dnmfE4jleTx4q/rSx/jJljH53ou/ivb+l2RsoeUZfTPoQ019JcDQU7fzt0dJEDNvkDey12YTr0YD0zeX6H1BLAQIAAAoAAAAAAJaZPlEAAAAAAAAAAAAAAAA+AAkAAAAAAAAAEAAAAAAAAAB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL1VUBQABrNh0X1BLAQIAAAoAAAAIAJaZPlGiBqPVnwUAAMIPAABeAAkAAAAAAAEAAAAAAGUAAAB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL1B5dGhvbi1Db2RlLVNuaXBwZXQtQ2hhbGxlbmdlLm1kVVQFAAGs2HRfUEsBAgAACgAAAAAAlpk+UXS5yyUNAAAADQAAAEcACQAAAAAAAQAAAAAAiQYAAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvUkVBRE1FLm1kVVQFAAGs2HRfUEsBAgAACgAAAAgAlpk+USQ2wkzSAAAAiAEAAEsACQAAAAAAAQAAAAAABAcAAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvY2hlY2twb2ludC5tZFVUBQABrNh0X1BLAQIAAAoAAAAIAJaZPlE+YPWdeAEAANwCAABJAAkAAAAAAAEAAAAAAEgIAAB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvbmZpZy55YW1sVVQFAAGs2HRfUEsBAgAACgAAAAAAlpk+UQAAAAAAAAAAAAAAAEoACQAAAAAAAAAQAAAAMAoAAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvY291cnNlZmlsZXMvVVQFAAGs2HRfUEsBAgAACgAAAAAAlpk+UQAAAAAAAAAAAAAAAFEACQAAAAAAAAAQAAAAoQoAAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvY291cnNlZmlsZXMvZ2l0aHViL1VUBQABrNh0X1BLAQIAAAoAAAAIAJaZPlHw+naYXQAAAHUAAABcAAkAAAAAAAEAAAAAABkLAAB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvdXJzZWZpbGVzL2dpdGh1Yi9jb3Vyc2UueWFtbFVUBQABrNh0X1BLAQIAAAoAAAAAAJaZPlEAAAAAAAAAAAAAAABRAAkAAAAAAAAAEAAAAPkLAAB0ZXN0LTIwMjAwOTE3LW1hc3Rlci0wZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEwL2NvdXJzZWZpbGVzL2dpdGxhYi9VVAUAAazYdF9QSwECAAAKAAAACACWmT5Rz0qabWMAAAB1AAAAXAAJAAAAAAABAAAAAABxDAAAdGVzdC0yMDIwMDkxNy1tYXN0ZXItMGQzY2NiNzk2NzU3ZDg0ZTk5ZGNmNDNlZjkxMzgyMTQ4M2EyNDhhMC9jb3Vyc2VmaWxlcy9naXRsYWIvY291cnNlLnlhbWxVVAUAAazYdF9QSwECAAAKAAAACACWmT5RoKr04p4BAAAfAwAATgAJAAAAAAABAAAAAABXDQAAdGVzdC0yMDIwMDkxNy1tYXN0ZXItMGQzY2NiNzk2NzU3ZDg0ZTk5ZGNmNDNlZjkxMzgyMTQ4M2EyNDhhMC9weXRob24tc2ltcGxlLm1kVVQFAAGs2HRfUEsBAgAACgAAAAgAlpk+URN/NXSBAwAAIwgAAE4ACQAAAAAAAQAAAAAAag8AAHRlc3QtMjAyMDA5MTctbWFzdGVyLTBkM2NjYjc5Njc1N2Q4NGU5OWRjZjQzZWY5MTM4MjE0ODNhMjQ4YTAvcmV2YWNvbW0tcXVpei5tZFVUBQABrNh0X1BLBQYAAAAADAAMAEsGAABgEwAAKAAwZDNjY2I3OTY3NTdkODRlOTlkY2Y0M2VmOTEzODIxNDgzYTI0OGEw - http_version: + http_version: recorded_at: Tue, 06 Oct 2020 07:55:52 GMT recorded_with: VCR 4.0.0 diff --git a/scripts/spec/services/course_validator_spec.rb b/scripts/spec/services/course_validator_spec.rb index f873719..3abaad2 100644 --- a/scripts/spec/services/course_validator_spec.rb +++ b/scripts/spec/services/course_validator_spec.rb @@ -28,8 +28,8 @@ describe CourseValidator do end it "works when the course yaml file comes from gitlab sources" do - VCR.use_cassette("gitlab-course-yaml-success") do - result = described_class.run(url: "https://code.il2.dsop.io/csakamaki/p1-test/-/blob/master/course.yaml") + VCR.use_cassette("gitlab-course-yaml-success" ) do + result = described_class.run(url: "https://code.il2.dso.mil/csakamaki/p1-test/-/blob/master/course.yaml") expect(result).to be_a_success expect(result.value[:course]).to eq({ default_unit_visibility: false, @@ -37,7 +37,7 @@ describe CourseValidator do { title: "DSOP GitLab", repos: [ - { url: "https://code.il2.dsop.io/csakamaki/p1-test" } + { url: "https://code.il2.dso.mil/csakamaki/p1-test" } ] }, { @@ -54,7 +54,7 @@ describe CourseValidator do it "fetches from branches and fails when repos are missing in the yaml" do VCR.use_cassette("gitlab-course-yaml-branch-failure") do - result = described_class.run(url: "https://code.il2.dsop.io/csakamaki/p1-test/-/blob/missingfile/course.yaml") + result = described_class.run(url: "https://code.il2.dso.mil/csakamaki/p1-test/-/blob/missingfile/course.yaml") expect(result).to be_a_failure expect(result.value.type).to eq :top_course_http_fetch_error end diff --git a/scripts/spec/services/download_repository_service_spec.rb b/scripts/spec/services/download_repository_service_spec.rb index 2ef54a8..95f0558 100644 --- a/scripts/spec/services/download_repository_service_spec.rb +++ b/scripts/spec/services/download_repository_service_spec.rb @@ -51,7 +51,7 @@ describe DownloadRepositoryService do describe ".execute" do context "when provided an invalid gitlab url" do - let(:block) { create(:block, org: "not-found", origin: "code.il2.dsop.io", repo_name: "does-not-exist")} + let(:block) { create(:block, org: "not-found", origin: "code.il2.dso.mil", repo_name: "does-not-exist")} it "returns an error" do VCR.use_cassette("gitlab-not-found") do diff --git a/scripts/spec/services/git_url_service_spec.rb b/scripts/spec/services/git_url_service_spec.rb index 8ec13b3..f886208 100644 --- a/scripts/spec/services/git_url_service_spec.rb +++ b/scripts/spec/services/git_url_service_spec.rb @@ -157,7 +157,7 @@ describe GitUrlService do describe "subgroup gitlab url" do context "for a project with a branch" do - let(:url) { "https://code.il2.dsop.io/tron/products/learn-lms/microservices/forge/-/tree/keycloak-auth" } + let(:url) { "https://code.il2.dso.mil/tron/products/learn-lms/microservices/forge/-/tree/keycloak-auth" } it "gets the correct repo" do expect(subject.repository).to eq("forge") @@ -181,7 +181,7 @@ describe GitUrlService do end context "for a project with a file specified" do - let(:url) { "https://code.il2.dsop.io/tron/products/learn-lms/microservices/forge/-/blob/master/config/environment.rb" } + let(:url) { "https://code.il2.dso.mil/tron/products/learn-lms/microservices/forge/-/blob/master/config/environment.rb" } it "returns a full path without file information" do expect(subject.full_path).to eq("tron/products/learn-lms/microservices/forge") @@ -193,7 +193,7 @@ describe GitUrlService do end context "for a project with just a repository" do - let(:url) { "https://code.il2.dsop.io/tron/products/learn-lms/microservices/forge" } + let(:url) { "https://code.il2.dso.mil/tron/products/learn-lms/microservices/forge" } it "returns a full path without file information" do expect(subject.full_path).to eq("tron/products/learn-lms/microservices/forge") diff --git a/scripts/spec/services/platform_one_auth_resolver_service_spec.rb b/scripts/spec/services/platform_one_auth_resolver_service_spec.rb index cf0f3f2..d84b723 100644 --- a/scripts/spec/services/platform_one_auth_resolver_service_spec.rb +++ b/scripts/spec/services/platform_one_auth_resolver_service_spec.rb @@ -8,7 +8,7 @@ describe PlatformOneAuthResolverService do iat: Time.now.to_i, auth_time: Time.now.to_i, jti: "e4600f88-1b42-4e01-ac01-8646d3cc4df6", - iss: "https://login.dsop.io/auth/realms/baby-yoda", + iss: "https://login.dso.mil/auth/realms/baby-yoda", aud: "il4_191f836b-ec50-4819-ba10-1afaa5b99600_mission-widow", sub: "2cba6ea8-8451-4952-80d6-c7de379d5b7e", typ: "ID", @@ -55,7 +55,7 @@ describe PlatformOneAuthResolverService do iat: Time.now.to_i, auth_time: Time.now.to_i, jti: "e4600f88-1b42-4e01-ac01-8646d3cc4df6", - iss: "https://login.dsop.io/auth/realms/baby-yoda", + iss: "https://login.dso.mil/auth/realms/baby-yoda", aud: "il4_191f836b-ec50-4819-ba10-1afaa5b99600_mission-widow", sub: "2cba6ea8-8451-4952-80d6-c7de379d5b7e", typ: "ID", -- GitLab From e41ee121fa78a22e5bef4a2c0450a08a40a337ea Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 30 Dec 2020 10:40:43 -1000 Subject: [PATCH 220/287] updating secrets to match minio credentials Former-commit-id: e774b49cd8bb743d4aca0407dbcf4c1c1ff0b0e5 --- scripts/.env.example | 6 +++++- scripts/app.json | 2 +- scripts/config/secrets.yml | 9 +++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/scripts/.env.example b/scripts/.env.example index 2ad408f..7759360 100644 --- a/scripts/.env.example +++ b/scripts/.env.example @@ -10,4 +10,8 @@ STANDARD_EVENT_UIDS="40f3bb48-1d23-4105-a22a-7f07b90bd1e6,cc22ef7e-d9c9-4e3d-ae9 S3_REGION=us-gov-west-1 accesskey= secretkey= -appS3BucketUUID= \ No newline at end of file +appS3BucketUUID= +S3_BUCKET= +host= +port= +protocol= \ No newline at end of file diff --git a/scripts/app.json b/scripts/app.json index 8384ccf..d825267 100644 --- a/scripts/app.json +++ b/scripts/app.json @@ -79,7 +79,7 @@ "MIXPANEL_PROJECT_TOKEN": { "required": true }, - "PROTOCOL": { + "FORGE_PROTOCOL": { "required": true }, "RACK_ENV": { diff --git a/scripts/config/secrets.yml b/scripts/config/secrets.yml index c989a87..1743ee0 100644 --- a/scripts/config/secrets.yml +++ b/scripts/config/secrets.yml @@ -10,8 +10,9 @@ shared: auth_webhook_token: <%= ENV["AUTH_WEBHOOK_TOKEN"] %> github_token: <%= ENV["GITHUB_COM_TOKEN"] %> mixpanel_project_token: <%= ENV["MIXPANEL_PROJECT_TOKEN"] %> - protocol: <%= ENV['PROTOCOL'] || "http://" %> - s3_bucket_name: <%= ENV["APP_S3_BUCKET"] || "temp" %> + protocol: <%= ENV['FORGE_PROTOCOL'] || "http://" %> + s3_bucket_name: <%= ENV["S3_BUCKET"] ? (ENV["appS3BucketUUID"] ? "#{ENV["S3_BUCKET"]}-#{ENV["appS3BucketUUID"]}" : ENV["S3_BUCKET"]) : "temp" %> + s3_bucket_uuid: <% ENV["appS3BucketUUID"] %>> secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> sendgrid_password: <%= ENV['SENDGRID_PASSWORD'] %> sendgrid_username: <%= ENV['SENDGRID_USERNAME'] %> @@ -24,10 +25,10 @@ shared: glearn_access_key_id: <%= ENV['accesskey'] || "temp" %> glearn_secret_access_key: <%= ENV['secretkey'] || "temp" %> glearn_key_prefix: <%= ENV['GLEARN_KEY_PREFIX'] || "forge" %> - glearn_bucket_name: <%= ENV["APP_S3_BUCKET"] || "temp" %> + glearn_bucket_name: <%= ENV["S3_BUCKET"] ? (ENV["appS3BucketUUID"] ? "#{ENV["S3_BUCKET"]}-#{ENV["appS3BucketUUID"]}" : ENV["S3_BUCKET"]) : "temp" %> s3_region: <%= ENV["S3_REGION"] || "us-gov-west-1" %> dev_notify_slack_url: <%= ENV['DEV_NOTIFY_SLACK_URL'] %> - minio_endpoint_url: <%= ENV["MINIO_ENDPOINT_URL"] %> + minio_endpoint_url: <%= (ENV["protocol"] && ENV["host"] && ENV["port"]) ? "#{ENV["protocol"]}://#{ENV["host"]}:#{ENV["port"]}" : nil %> git_download_tokens: github_com: type: github -- GitLab From 1de5e9180f090ca306e332074c9accc2aaf9eb68 Mon Sep 17 00:00:00 2001 From: Charlie Sakamaki Date: Wed, 30 Dec 2020 10:55:46 -1000 Subject: [PATCH 221/287] Add delay for intermittent failing tests Former-commit-id: 3d936bb779763ec07c957ea9302a274c9bc370b4 --- scripts/spec/features/cohorts/users/challenges_feature_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/spec/features/cohorts/users/challenges_feature_spec.rb b/scripts/spec/features/cohorts/users/challenges_feature_spec.rb index 8db44db..5a0c787 100644 --- a/scripts/spec/features/cohorts/users/challenges_feature_spec.rb +++ b/scripts/spec/features/cohorts/users/challenges_feature_spec.rb @@ -126,6 +126,7 @@ describe "Challenges" do within("#challenge_#{challenge_2.id}") do within(".challenge-grading-buttons") do find(".incorrect-status").click + sleep 1 within(".status-section") do find(".incorrect-check") end @@ -140,7 +141,7 @@ describe "Challenges" do within("#challenge_#{challenge_2.id}") do within(".challenge-grading-buttons") do find(".correct-status").click - + sleep 1 within(".status-section") do find(".correct-check") end -- GitLab From 7fb9d84e21e04dadde4e3592ab3090f06740fdf5 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 20 Jan 2021 20:06:58 -1000 Subject: [PATCH 222/287] adding license Former-commit-id: 6579d2a4b2b6582cf5455b99ef4f0172d12cdd88 --- LICENSE | 1 - 1 file changed, 1 deletion(-) diff --git a/LICENSE b/LICENSE index e9db0f7..e69de29 100644 --- a/LICENSE +++ b/LICENSE @@ -1 +0,0 @@ -Please add a license \ No newline at end of file -- GitLab From ee43706af998e23674e2fc604e005f6eebbda0ca Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 20 Jan 2021 20:18:30 -1000 Subject: [PATCH 223/287] adding license Former-commit-id: 0007f2f5395208e3cfa0aeed6fcf423c9aaff88f --- LICENSE | 27 +++++++++++++++++++++++++++ hardening_manifest.yaml | 2 +- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index e69de29..6b1e578 100644 --- a/LICENSE +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright 2021. Galvanize Inc. Galvanize Inc. proprietary and confidential. Do not distribute. + +A commercial license will be required to run an instance (or any copy or derivative thereof) of this software and the +learning management system (the "Software"). Purchase of a license will be facilitated by contacting +legal@galvanize.com. Use of the Software is prohibited without a commercial license. By way of example, but not +limitation, without a commercial license, you may not use the Software in any way or at any time, either directly or +indirectly, including but not limited to, you may not at any time, attempt, directly or indirectly, or allow any third +party to attempt to (i) copy, modify, or create derivative works of the Software, in whole or in part; (ii) use, rent, +lease, lend, sell, license, sublicense, assign, distribute, publish, transfer, or otherwise make available the Software; +(iii) reverse engineer, disassemble, decompile, decode, adapt, or otherwise attempt to derive or gain access to any +software component of the Software, in whole or in part; (iv) remove any proprietary notices from the Software; +or (v) use the Software in any manner or for any purpose that infringes, misappropriates, or otherwise violates any +intellectual property right or other right of any person, or that violates any applicable law. THE SOFTWARE IS PROVIDED +"AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL GALVANIZE, THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +FURTHER, IN NO EVENT WILL GALVANIZE, THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE UNDER OR IN CONNECTION WITH THIS +AGREEMENT UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT +LIABILITY, AND OTHERWISE, FOR ANY: (a) CONSEQUENTIAL, INCIDENTAL, INDIRECT, EXEMPLARY, SPECIAL, ENHANCED, OR PUNITIVE +DAMAGES; (b) INCREASED COSTS, DIMINUTION IN VALUE OR LOST BUSINESS, PRODUCTION, REVENUES, OR PROFITS; (c) LOSS OF +GOODWILL OR REPUTATION; (d) USE, INABILITY TO USE, LOSS, INTERRUPTION, DELAY OR RECOVERY OF ANY DATA, OR BREACH OF +DATA OR SYSTEM SECURITY; (e) COST OF REPLACEMENT GOODS OR SERVICES; OR (f) ANY DIRECT DAMAGES, INCLUDING ANY LIABILITY +ARISING OUT OF OR RELATED TO THIS AGREEMENT UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT, +TORT (INCLUDING NEGLIGENCE), OR STRICT LIABILITY, IN EACH CASE REGARDLESS OF WHETHER GALVANIZE, THE AUTHORS OR +COPYRIGHT HOLDERS WAS ADVISED OF THE POSSIBILITY OF SUCH LOSSES OR DAMAGES OR SUCH LOSSES OR DAMAGES WERE OTHERWISE +FORESEEABLE. \ No newline at end of file diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index 5286f6d..a3c8ba9 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -22,7 +22,7 @@ labels: ## Human-readable description of the software packaged in the image org.opencontainers.image.description: "Galvanize is a technology ecosystem for learners, entrepreneurs, startups, and established companies that meets the needs of the rapidly changing digital world. Our goal is to transform individuals and teams through effective education and community programs Delivering exceptional outcomes Coalescing and nurturing a community of innovators anchored by our campuses" ## License(s) under which contained software is distributed - # org.opencontainers.image.licenses: "FIXME" + org.opencontainers.image.licenses: "proprietary" ## URL to find more information on the image # org.opencontainers.image.url: "FIXME" ## Name of the distributing entity, organization or individual -- GitLab From fb94f7cb7e70167f7e323f6d167722c8f8046338 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 20 Jan 2021 20:25:47 -1000 Subject: [PATCH 224/287] filling in rest of params. Former-commit-id: c931b9523e771f4a62131ee6bfc5cfd0ec106c8c --- hardening_manifest.yaml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index a3c8ba9..62189ba 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -19,20 +19,13 @@ args: # Docker image labels labels: org.opencontainers.image.title: "forge" - ## Human-readable description of the software packaged in the image org.opencontainers.image.description: "Galvanize is a technology ecosystem for learners, entrepreneurs, startups, and established companies that meets the needs of the rapidly changing digital world. Our goal is to transform individuals and teams through effective education and community programs Delivering exceptional outcomes Coalescing and nurturing a community of innovators anchored by our campuses" - ## License(s) under which contained software is distributed - org.opencontainers.image.licenses: "proprietary" - ## URL to find more information on the image - # org.opencontainers.image.url: "FIXME" - ## Name of the distributing entity, organization or individual + org.opencontainers.image.licenses: "proprietary" + org.opencontainers.image.url: "https://www.galvanize.com" org.opencontainers.image.vendor: "Galvanize" org.opencontainers.image.version: "0.1.0" - ## Keywords to help with search (ex. "cicd,gitops,golang") mil.dso.ironbank.image.keywords: "learn,lms,galvanize,online,remote,school,learning" - ## This value can be "opensource" or "commercial" mil.dso.ironbank.image.type: "commercial" - ## Product the image belongs to for grouping multiple images mil.dso.ironbank.product.name: "Learn" # List of project maintainers -- GitLab From 8a81e4398d2336083eac79000097a1fc9b6c4421 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 20 Jan 2021 20:29:42 -1000 Subject: [PATCH 225/287] fixing spacing Former-commit-id: a44c631272ffd9b0f6dc95cdd8ac797f04436568 --- hardening_manifest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index 62189ba..c7a2652 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -21,7 +21,7 @@ labels: org.opencontainers.image.title: "forge" org.opencontainers.image.description: "Galvanize is a technology ecosystem for learners, entrepreneurs, startups, and established companies that meets the needs of the rapidly changing digital world. Our goal is to transform individuals and teams through effective education and community programs Delivering exceptional outcomes Coalescing and nurturing a community of innovators anchored by our campuses" org.opencontainers.image.licenses: "proprietary" - org.opencontainers.image.url: "https://www.galvanize.com" + org.opencontainers.image.url: "https://www.galvanize.com" org.opencontainers.image.vendor: "Galvanize" org.opencontainers.image.version: "0.1.0" mil.dso.ironbank.image.keywords: "learn,lms,galvanize,online,remote,school,learning" -- GitLab From 9bc91f9e07104b164deb47583225b51d3f95b4f5 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 21 Jan 2021 10:18:50 -1000 Subject: [PATCH 226/287] fixing for ironbank Former-commit-id: cbd2592cfe7db19cc20c7f86ce3e4bba9a27e5fd --- Dockerfile | 5 ----- hardening_manifest.yaml | 6 +++--- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1489bcb..98565aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,3 @@ -# Platform 1 Pipeline Settings -ARG BASE_REGISTRY=registry.il2.dso.mil -ARG BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 -ARG BASE_TAG=2.6.6.212 - FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index c7a2652..87f83e3 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -2,7 +2,7 @@ apiVersion: v1 # The repository name in registry1, excluding /ironbank/ -name: "forge" +name: "galvanize/galvanize/forge" # List of tags to push for the repository in registry1 # The most specific version should be the first tag and will be shown @@ -13,8 +13,8 @@ tags: # Build args passed to Dockerfile ARGs args: - BASE_IMAGE: "ironbank/opensource/ruby/ruby26" - BASE_TAG: "2.6.6.212" + BASE_IMAGE: "opensource/ruby/ruby26" + BASE_TAG: "2.6.6" # Docker image labels labels: -- GitLab From dddd15e72cc77aca92deba14a2e77287f93f87b0 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 21 Jan 2021 16:50:35 -1000 Subject: [PATCH 227/287] updating node modules and node version Former-commit-id: c4c081c6e5521e3869c54b9a3671097f381fc60f --- Dockerfile | 6 +- scripts/.nvmrc | 2 +- ...e-v14.15.4-linux-x64.tar.gz.REMOVED.git-id | 1 + ...de-v14.8.0-linux-x64.tar.gz.REMOVED.git-id | 1 - scripts/package.json | 68 +- scripts/yarn.lock | 620 +++++++++++------- 6 files changed, 405 insertions(+), 293 deletions(-) create mode 100644 scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id delete mode 100644 scripts/bundles/node-v14.8.0-linux-x64.tar.gz.REMOVED.git-id diff --git a/Dockerfile b/Dockerfile index 98565aa..817917c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,9 +37,9 @@ RUN rm node_modules.tar.gz RUN mv node_modules /app/node_modules # Node.js -RUN tar xzf node-v14.8.0-linux-x64.tar.gz -RUN rm node-v14.8.0-linux-x64.tar.gz -RUN mv node-v14.8.0-linux-x64 /app/nodejs +RUN tar xzf node-v14.15.4-linux-x64.tar.gz +RUN rm node-v14.15.4-linux-x64.tar.gz +RUN mv node-v14.15.4-linux-x64 /app/nodejs # Yarn RUN tar xzf yarn-v1.22.5.tar.gz diff --git a/scripts/.nvmrc b/scripts/.nvmrc index a34fffa..47266f8 100644 --- a/scripts/.nvmrc +++ b/scripts/.nvmrc @@ -1 +1 @@ -14.8.0 \ No newline at end of file +14.15.4 \ No newline at end of file diff --git a/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id b/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id new file mode 100644 index 0000000..e1ab22e --- /dev/null +++ b/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +95016bc120595a9214116f158624cf9ea07e7cb8 \ No newline at end of file diff --git a/scripts/bundles/node-v14.8.0-linux-x64.tar.gz.REMOVED.git-id b/scripts/bundles/node-v14.8.0-linux-x64.tar.gz.REMOVED.git-id deleted file mode 100644 index 937094b..0000000 --- a/scripts/bundles/node-v14.8.0-linux-x64.tar.gz.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -eda96a7b40acb7ed21dabc8f89fb50627cea38b6 \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index 099c822..b6d54dd 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -16,11 +16,14 @@ "url": "https://github.com/Galvanize-IT/forge/issues" }, "homepage": "https://github.com/Galvanize-IT/forge#readme", + "engines": { + "node": "14.15.4" + }, "dependencies": { "@rails/webpacker": "5.2.1", "@types/react-paginate": "^6.2.1", - "@types/react-pdf": "^4.0.6", - "@vimeo/player": "^2.14.0", + "@types/react-pdf": "^5.0.1", + "@vimeo/player": "^2.15.0", "babel-preset-react": "^6.24.1", "brace": "^0.11.1", "browserify": "^16.5.2", @@ -28,59 +31,56 @@ "dropzone": "^5.7.2", "highlightjs": "^9.16.2", "immutability-helper": "^3.1.1", - "lodash-es": "^4.17.15", - "marked": "^1.2.0", - "moment": "2.29.1", - "moment-timezone": "0.5.31", + "lodash-es": "^4.17.20", + "marked": "^1.2.7", + "moment": "^2.29.1", + "moment-timezone": "^0.5.32", "objectify-array": "^2.1.0", "popper.js": "^1.16.1", "prop-types": "^15.7.2", "rc-slider": "Galvanize-IT/slider#c569ca3b11979aced8306e6c02f37466cb7cd365", - "react": "^16.13.1", + "react": "^17.0.1", "react-addons-css-transition-group": "^15.6.2", "react-addons-test-utils": "^15.6.2", "react-beautiful-dnd": "^13.0.0", - "react-copy-to-clipboard": "^5.0.2", - "react-datepicker": "^3.2.2", - "react-dom": "^16.13.1", + "react-copy-to-clipboard": "^5.0.3", + "react-datepicker": "^3.4.1", + "react-dom": "^17.0.1", "react-json-pretty": "^2.2.0", - "react-paginate": "^6.5.0", + "react-paginate": "^7.0.0", "react-pdf": "^4.2.0", "react-scroll-sync": "^0.8.0", "react-sizeme": "^2.6.12", - "react-textarea-autosize": "^8.2.0", - "react-tooltip": "^4.2.10", + "react-textarea-autosize": "^8.3.0", + "react-tooltip": "^4.2.13", "react_ujs": "^2.6.1", "reactify": "^1.1.1", - "ts-loader": "8.0.4", - "typescript": "^4.0.3", + "ts-loader": "^8.0.14", + "typescript": "^4.1.3", "unfetch": "^4.2.0" }, "devDependencies": { "@types/lodash-es": "^4.17.3", - "@types/node-fetch": "^2.5.7", + "@types/node-fetch": "^2.5.8", "@types/rc-slider": "^8.6.6", - "@types/react": "^16.9.51", + "@types/react": "^17.0.0", "@types/react-addons-css-transition-group": "^15.0.5", "@types/react-beautiful-dnd": "^13.0.0", - "@types/react-copy-to-clipboard": "^4.3.0", - "@types/react-datepicker": "^3.1.1", - "@types/react-dom": "^16.9.8", + "@types/react-copy-to-clipboard": "^5.0.0", + "@types/react-datepicker": "^3.1.3", + "@types/react-dom": "^17.0.0", "@types/react-textarea-autosize": "^4.3.5", - "@types/vimeo__player": "^2.9.1", - "eslint": "7.10.0", - "eslint-config-airbnb": "18.2.0", - "eslint-plugin-import": "2.22.1", - "eslint-plugin-jsx-a11y": "6.3.1", - "eslint-plugin-promise": "4.2.1", - "eslint-plugin-react": "7.21.3", - "eslint-plugin-standard": "4.0.1", - "postcss-flexbugs-fixes": "^4.2.1", - "postcss-import": "^12.0.1", + "@types/vimeo__player": "^2.10.0", + "eslint": "^7.18.0", + "eslint-config-airbnb": "^18.2.1", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-react": "^7.22.0", + "eslint-plugin-standard": "^5.0.0", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-import": "^14.0.0", "postcss-preset-env": "^6.7.0", - "webpack-dev-server": "^3.11.0" - }, - "engines": { - "node": "14.8.0" + "webpack-dev-server": "^3.11.2" } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index f8fc261..0d818ba 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -863,10 +863,10 @@ resolved "https://registry.yarnpkg.com/@csstools/convert-colors/-/convert-colors-1.4.0.tgz#ad495dc41b12e75d588c6db8b9834f08fa131eb7" integrity sha512-5a6wqoJV/xEdbRNKVo6I4hO3VjyDq//8q2f9I6PBAvMesJHFauXDorcNCsr9RzvsZnaWi5NYCcfyqP1QeFHFbw== -"@eslint/eslintrc@^0.1.3": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.1.3.tgz#7d1a2b2358552cc04834c0979bd4275362e37085" - integrity sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA== +"@eslint/eslintrc@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.3.0.tgz#d736d6963d7003b6514e6324bec9c602ac340318" + integrity sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg== dependencies: ajv "^6.12.4" debug "^4.1.1" @@ -875,7 +875,7 @@ ignore "^4.0.6" import-fresh "^3.2.1" js-yaml "^3.13.1" - lodash "^4.17.19" + lodash "^4.17.20" minimatch "^3.0.4" strip-json-comments "^3.1.1" @@ -964,10 +964,10 @@ resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== -"@types/node-fetch@^2.5.7": - version "2.5.7" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c" - integrity sha512-o2WVNf5UhWRkxlf6eq+jMZDu7kjgpgJfl4xVNlvryc95O/6F2ld8ztKX+qu+Rjyet93WAWm5LjeX9H5FGkODvw== +"@types/node-fetch@^2.5.8": + version "2.5.8" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.8.tgz#e199c835d234c7eb0846f6618012e558544ee2fb" + integrity sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw== dependencies: "@types/node" "*" form-data "^3.0.0" @@ -1027,26 +1027,26 @@ dependencies: "@types/react" "*" -"@types/react-copy-to-clipboard@^4.3.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-4.3.0.tgz#8e07becb4f11cfced4bd36038cb5bdf5c2658be5" - integrity sha512-iideNPRyroENqsOFh1i2Dv3zkviYS9r/9qD9Uh3Z9NNoAAqqa2x53i7iGndGNnJFIo20wIu7Hgh77tx1io8bgw== +"@types/react-copy-to-clipboard@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.0.tgz#38b035ca0c28334d3e0efaf3f319b81eea9690cd" + integrity sha512-faUg6Kx3Dfv0MBIcs+xzIptlRtjEVSaNjqyC14YAp4UwSiTHghnKtBOt9ERRTZZJfoJgnw10tomVaqG86GzdAw== dependencies: "@types/react" "*" -"@types/react-datepicker@^3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-3.1.1.tgz#bbb5e4561f657ca8e3d6829dd99b91a98c0bb74c" - integrity sha512-vwNrgaIMJThvvwmtnA8jSVVJZ0FNgljQrq1jDA4MtYJIDmVmd9NNrFaXf9u2JqR2nS+8Kvi8OVs/tnAbUqZhHw== +"@types/react-datepicker@^3.1.3": + version "3.1.3" + resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-3.1.3.tgz#4525475c451091477de27ed52c029a9043ec6b11" + integrity sha512-Gca56Pa8hpy3eO2naRLdC63iflER7JXxgdHTGJ6yF5DjvhQoirRPqCJerWLq6TOWd+sEsZWwMm17Q17YRVLFtw== dependencies: "@types/react" "*" date-fns "^2.0.1" popper.js "^1.14.1" -"@types/react-dom@^16.9.8": - version "16.9.8" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" - integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== +"@types/react-dom@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.0.tgz#b3b691eb956c4b3401777ee67b900cb28415d95a" + integrity sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g== dependencies: "@types/react" "*" @@ -1057,10 +1057,10 @@ dependencies: "@types/react" "*" -"@types/react-pdf@^4.0.6": - version "4.0.6" - resolved "https://registry.yarnpkg.com/@types/react-pdf/-/react-pdf-4.0.6.tgz#cc96357d87ca72e21d8d4caec5956c62a3990abc" - integrity sha512-ZmtUA31L5AaF9PilB8cJ3PuGOHIiyWcHnCir7KOux1cDjfVH6VxiN/j7CcyX98U9hwlyN81bkqxeNWNuEc0a4w== +"@types/react-pdf@^5.0.1": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/react-pdf/-/react-pdf-5.0.1.tgz#bfa1a2e052dabe76c2fcc30162e1d3e33a4c2abe" + integrity sha512-iDpGh45UAyLx4udkzJy0lxMeCLBrAfO08fEL4FKfeSXXvBTnax60ueTTysJ4XY0XqEJn5gYDuMH93nlJhAjFoQ== dependencies: "@types/pdfjs-dist" "*" "@types/react" "*" @@ -1079,23 +1079,23 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/react@^16.9.51": - version "16.9.51" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.51.tgz#f8aa51ffa9996f1387f63686696d9b59713d2b60" - integrity sha512-lQa12IyO+DMlnSZ3+AGHRUiUcpK47aakMMoBG8f7HGxJT8Yfe+WE128HIXaHOHVPReAW0oDS3KAI0JI2DDe1PQ== +"@types/react@^17.0.0": + version "17.0.0" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8" + integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw== dependencies: "@types/prop-types" "*" csstype "^3.0.2" -"@types/vimeo__player@^2.9.1": - version "2.9.1" - resolved "https://registry.yarnpkg.com/@types/vimeo__player/-/vimeo__player-2.9.1.tgz#4de8583753a1480a1b5dbc6dcf5ec21c55c67e1a" - integrity sha512-L6XHWenPkN+WHFWmo/fhA70kTQnNUxOs3bQS3nF/FK3kv+UzEVRqPEkxJNUZWExwrhOKaTKzplZHmxYwmr0SJA== +"@types/vimeo__player@^2.10.0": + version "2.10.0" + resolved "https://registry.yarnpkg.com/@types/vimeo__player/-/vimeo__player-2.10.0.tgz#dee8934c59d0a01a4252e6fb143c8807d3235502" + integrity sha512-uhKJDgEfQiEhKHw9bP7+wZSvXTyN4iw4Yb+7Omo76jCJZp1NZkWfZYaaobXrkiuFT6k2ld2eqaXKSpvqfmDUPQ== -"@vimeo/player@^2.14.0": - version "2.14.0" - resolved "https://registry.yarnpkg.com/@vimeo/player/-/player-2.14.0.tgz#0d7826162ab072505e2eadb221ab9c8f79507a0d" - integrity sha512-hTeROVnkcyboRjetPi9tasryrjDPVsmr/73clwbYsBpSk8k5q1ygFyvkWXQShv9Rc+hvXM5RJXPd7i7Wh7dqcA== +"@vimeo/player@^2.15.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@vimeo/player/-/player-2.15.0.tgz#c1037706f0a95abf87f6aa960d058e11efe6510e" + integrity sha512-PH+H7G7pIUbrw8AHeigOc13cUoe8E78y4XCVqSP7pLFj/kQm0EvHQGBO3224Qq7a0XMEmNR6kH+n+8o0GslWCg== dependencies: native-promise-only "0.8.1" weakmap-polyfill "2.0.1" @@ -1280,7 +1280,7 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-jsx@^5.2.0: +acorn-jsx@^5.2.0, acorn-jsx@^5.3.1: version "5.3.1" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b" integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng== @@ -1362,6 +1362,16 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.0.3.tgz#13ae747eff125cafb230ac504b2406cf371eece2" + integrity sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -1412,7 +1422,7 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansi-styles@^4.1.0: +ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -1492,6 +1502,17 @@ array-includes@^3.1.1: es-abstract "^1.17.0" is-string "^1.0.5" +array-includes@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.2.tgz#a8db03e0b88c8c6aeddc49cb132f9bcab4ebf9c8" + integrity sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + es-abstract "^1.18.0-next.1" + get-intrinsic "^1.0.1" + is-string "^1.0.5" + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -1561,10 +1582,10 @@ ast-types@0.9.6: version "0.9.6" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== async-each@^1.0.1: version "1.0.3" @@ -1618,12 +1639,12 @@ aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" -axe-core@^3.5.4: - version "3.5.5" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.5.tgz#84315073b53fa3c0c51676c588d59da09a192227" - integrity sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q== +axe-core@^4.0.2: + version "4.1.1" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.1.1.tgz#70a7855888e287f7add66002211a423937063eaf" + integrity sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ== -axobject-query@^2.1.2: +axobject-query@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== @@ -2136,6 +2157,14 @@ cached-path-relative@^1.0.0, cached-path-relative@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" +call-bind@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + caller-callsite@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" @@ -2214,7 +2243,7 @@ chalk@^1.1.1: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.0, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" dependencies: @@ -2222,7 +2251,7 @@ chalk@^2.0, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== @@ -2481,10 +2510,10 @@ concat-stream@^1.5.0, concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@ readable-stream "^2.2.2" typedarray "^0.0.6" -confusing-browser-globals@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.9.tgz#72bc13b483c0276801681871d4898516f8f54fdd" - integrity sha512-KbS1Y0jMtyPgIxjO7ZzMAuUpAKMt1SzCL9fsrKsX6b0zJPTaT0SiSPmewwVZg9UAO83HVIlEhZF84LIjZ0lmAw== +confusing-browser-globals@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" + integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== connect-history-api-fallback@^1.6.0: version "1.6.0" @@ -2915,7 +2944,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: +debug@^3.1.1, debug@^3.2.6: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" dependencies: @@ -3229,6 +3258,11 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + emoji-regex@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.0.0.tgz#48a2309cc8a1d2e9d23bc6a67c39b63032e76ea4" @@ -3361,22 +3395,22 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" -eslint-config-airbnb-base@^14.2.0: - version "14.2.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz#fe89c24b3f9dc8008c9c0d0d88c28f95ed65e9c4" - integrity sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q== +eslint-config-airbnb-base@^14.2.1: + version "14.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz#8a2eb38455dc5a312550193b319cdaeef042cd1e" + integrity sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA== dependencies: - confusing-browser-globals "^1.0.9" - object.assign "^4.1.0" + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" object.entries "^1.1.2" -eslint-config-airbnb@18.2.0: - version "18.2.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz#8a82168713effce8fc08e10896a63f1235499dcd" - integrity sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg== +eslint-config-airbnb@^18.2.1: + version "18.2.1" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.2.1.tgz#b7fe2b42f9f8173e825b73c8014b592e449c98d9" + integrity sha512-glZNDEZ36VdlZWoxn/bUR1r/sdFKPd1mHPbqUtkctgNG4yT2DLLtJ3D+yCV+jzZCc2V1nBVkmdknOJBZ5Hc0fg== dependencies: - eslint-config-airbnb-base "^14.2.0" - object.assign "^4.1.0" + eslint-config-airbnb-base "^14.2.1" + object.assign "^4.1.2" object.entries "^1.1.2" eslint-import-resolver-node@^0.3.4: @@ -3395,7 +3429,7 @@ eslint-module-utils@^2.6.0: debug "^2.6.9" pkg-dir "^2.0.0" -eslint-plugin-import@2.22.1: +eslint-plugin-import@^2.22.1: version "2.22.1" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.1.tgz#0896c7e6a0cf44109a2d97b95903c2bb689d7702" integrity sha512-8K7JjINHOpH64ozkAhpT3sd+FswIZTfMZTjdx052pnWrgRCVfp8op9tbjpAk3DdUeI/Ba4C8OjdC0r90erHEOw== @@ -3414,49 +3448,49 @@ eslint-plugin-import@2.22.1: resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-plugin-jsx-a11y@6.3.1: - version "6.3.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz#99ef7e97f567cc6a5b8dd5ab95a94a67058a2660" - integrity sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g== +eslint-plugin-jsx-a11y@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.4.1.tgz#a2d84caa49756942f42f1ffab9002436391718fd" + integrity sha512-0rGPJBbwHoGNPU73/QCLP/vveMlM1b1Z9PponxO87jfr6tuH5ligXbDT6nHSSzBC8ovX2Z+BQu7Bk5D/Xgq9zg== dependencies: - "@babel/runtime" "^7.10.2" + "@babel/runtime" "^7.11.2" aria-query "^4.2.2" array-includes "^3.1.1" ast-types-flow "^0.0.7" - axe-core "^3.5.4" - axobject-query "^2.1.2" + axe-core "^4.0.2" + axobject-query "^2.2.0" damerau-levenshtein "^1.0.6" emoji-regex "^9.0.0" has "^1.0.3" - jsx-ast-utils "^2.4.1" + jsx-ast-utils "^3.1.0" language-tags "^1.0.5" -eslint-plugin-promise@4.2.1: +eslint-plugin-promise@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== -eslint-plugin-react@7.21.3: - version "7.21.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.21.3.tgz#71655d2af5155b19285ec929dd2cdc67a4470b52" - integrity sha512-OI4GwTCqyIb4ipaOEGLWdaOHCXZZydStAsBEPB2e1ZfNM37bojpgO1BoOQbFb0eLVz3QLDx7b+6kYcrxCuJfhw== +eslint-plugin-react@^7.22.0: + version "7.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.22.0.tgz#3d1c542d1d3169c45421c1215d9470e341707269" + integrity sha512-p30tuX3VS+NWv9nQot9xIGAHBXR0+xJVaZriEsHoJrASGCJZDJ8JLNM0YqKqI0AKm6Uxaa1VUHoNEibxRCMQHA== dependencies: array-includes "^3.1.1" array.prototype.flatmap "^1.2.3" doctrine "^2.1.0" has "^1.0.3" - jsx-ast-utils "^2.4.1" + jsx-ast-utils "^2.4.1 || ^3.0.0" object.entries "^1.1.2" object.fromentries "^2.0.2" object.values "^1.1.1" prop-types "^15.7.2" - resolve "^1.17.0" + resolve "^1.18.1" string.prototype.matchall "^4.0.2" -eslint-plugin-standard@4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz#ff0519f7ffaff114f76d1bd7c3996eef0f6e20b4" - integrity sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ== +eslint-plugin-standard@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-standard/-/eslint-plugin-standard-5.0.0.tgz#c43f6925d669f177db46f095ea30be95476b1ee4" + integrity sha512-eSIXPc9wBM4BrniMzJRBm2uoVuXz2EPa+NXPk2+itrVt+r5SbKFERx/IgrK/HmfjddyKVz2f+j+7gBRvu19xLg== eslint-scope@^4.0.3: version "4.0.3" @@ -3486,13 +3520,18 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== -eslint@7.10.0: - version "7.10.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.10.0.tgz#494edb3e4750fb791133ca379e786a8f648c72b9" - integrity sha512-BDVffmqWl7JJXqCjAK6lWtcQThZB/aP1HXSH1JKwGwv0LQEdvpR7qzNrUT487RM39B5goWuboFad5ovMBmD8yA== +eslint-visitor-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" + integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== + +eslint@^7.18.0: + version "7.18.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67" + integrity sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ== dependencies: "@babel/code-frame" "^7.0.0" - "@eslint/eslintrc" "^0.1.3" + "@eslint/eslintrc" "^0.3.0" ajv "^6.10.0" chalk "^4.0.0" cross-spawn "^7.0.2" @@ -3501,11 +3540,11 @@ eslint@7.10.0: enquirer "^2.3.5" eslint-scope "^5.1.1" eslint-utils "^2.1.0" - eslint-visitor-keys "^1.3.0" - espree "^7.3.0" + eslint-visitor-keys "^2.0.0" + espree "^7.3.1" esquery "^1.2.0" esutils "^2.0.2" - file-entry-cache "^5.0.1" + file-entry-cache "^6.0.0" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" globals "^12.1.0" @@ -3516,7 +3555,7 @@ eslint@7.10.0: js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" - lodash "^4.17.19" + lodash "^4.17.20" minimatch "^3.0.4" natural-compare "^1.4.0" optionator "^0.9.1" @@ -3525,7 +3564,7 @@ eslint@7.10.0: semver "^7.2.1" strip-ansi "^6.0.0" strip-json-comments "^3.1.0" - table "^5.2.3" + table "^6.0.4" text-table "^0.2.0" v8-compile-cache "^2.0.3" @@ -3538,6 +3577,15 @@ espree@^7.3.0: acorn-jsx "^5.2.0" eslint-visitor-keys "^1.3.0" +espree@^7.3.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" + integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g== + dependencies: + acorn "^7.4.0" + acorn-jsx "^5.3.1" + eslint-visitor-keys "^1.3.0" + esprima-fb@13001.1001.0-dev-harmony-fb: version "13001.1001.0-dev-harmony-fb" resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-13001.1001.0-dev-harmony-fb.tgz#633acdb40d9bd4db8a1c1d68c06a942959fad2b0" @@ -3739,13 +3787,7 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -faye-websocket@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" - dependencies: - websocket-driver ">=0.5.1" - -faye-websocket@~0.11.1: +faye-websocket@^0.11.3: version "0.11.3" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" integrity sha512-D2y4bovYpzziGgbHYtGCMjlJM36vAl/y+xUyn1C+FVx8szd1E+86KwVw6XvYSzOP8iMpm1X0I4xJD+QtUb36OA== @@ -3757,12 +3799,12 @@ figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== +file-entry-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.0.tgz#7921a89c391c6d93efec2169ac6bf300c527ea0a" + integrity sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA== dependencies: - flat-cache "^2.0.1" + flat-cache "^3.0.4" file-loader@^6.0.0: version "6.1.0" @@ -3875,25 +3917,24 @@ findup-sync@^3.0.0: micromatch "^3.0.4" resolve-dir "^1.0.1" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + flatted "^3.1.0" + rimraf "^3.0.2" flatted@^3.0.4: version "3.1.0" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.0.tgz#a5d06b4a8b01e3a63771daa5cb7a1903e2e57067" integrity sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA== +flatted@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469" + integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA== + flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" @@ -4054,6 +4095,15 @@ get-caller-file@^2.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-intrinsic@^1.0.1, get-intrinsic@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.0.2.tgz#6820da226e50b24894e08859469dc68361545d49" + integrity sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -4407,6 +4457,11 @@ http-errors@~1.7.2: version "0.4.10" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" +http-parser-js@>=0.5.1: + version "0.5.3" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" + integrity sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg== + http-proxy-middleware@0.19.1: version "0.19.1" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" @@ -4706,6 +4761,13 @@ is-color-stop@^1.0.0: rgb-regex "^1.0.1" rgba-regex "^1.0.0" +is-core-module@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" + integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ== + dependencies: + has "^1.0.3" + is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -4772,6 +4834,11 @@ is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -4967,6 +5034,11 @@ json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" @@ -4986,9 +5058,10 @@ json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" -json3@^3.3.2: +json3@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.3.tgz#7fc10e375fc5ae42c4705a5cc0aa6f62be305b81" + integrity sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA== json5@^1.0.1: version "1.0.1" @@ -5038,13 +5111,13 @@ jstransform@^10.1.0: esprima-fb "13001.1001.0-dev-harmony-fb" source-map "0.1.31" -jsx-ast-utils@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" - integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w== +"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82" + integrity sha512-EIsmt3O3ljsU6sot/J4E1zDRxfBNrhjyf/OKjlydwgEimQuznlM4Wv7U+ueONJMyEn1WRE0K8dhi3dVAXYT24Q== dependencies: - array-includes "^3.1.1" - object.assign "^4.1.0" + array-includes "^3.1.2" + object.assign "^4.1.2" killable@^1.0.1: version "1.0.1" @@ -5156,7 +5229,7 @@ loader-utils@^1.0.0, loader-utils@^1.2.3, loader-utils@^1.4.0: emojis-list "^3.0.0" json5 "^1.0.1" -loader-utils@^1.0.2, loader-utils@^1.1.0: +loader-utils@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" dependencies: @@ -5195,10 +5268,10 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" - integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== +lodash-es@^4.17.20: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.20.tgz#29f6332eefc60e849f869c264bc71126ad61e8f7" + integrity sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA== lodash._getnative@^3.0.0: version "3.9.1" @@ -5264,7 +5337,7 @@ lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4, lodash@~4.17.10 version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" -lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.5: +lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -5346,10 +5419,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.0.tgz#7221ce2395fa6cf6d722e6f2871a32d3513c85ca" - integrity sha512-tiRxakgbNPBr301ihe/785NntvYyhxlqcL3YaC8CaxJQh7kiaEtrN9B/eK2I2943Yjkh5gw25chYFDQhOMCwMA== +marked@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.7.tgz#6e14b595581d2319cdcf033a24caaf41455a01fb" + integrity sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA== md5.js@^1.3.4: version "1.3.5" @@ -5629,22 +5702,22 @@ module-deps@^6.2.3: through2 "^2.0.0" xtend "^4.0.0" -moment-timezone@0.5.31: - version "0.5.31" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.31.tgz#9c40d8c5026f0c7ab46eda3d63e49c155148de05" - integrity sha512-+GgHNg8xRhMXfEbv81iDtrVeTcWt0kWmTEY1XQK14dICTXnWJnT0dxdlPspwqF3keKMVPXwayEsk1DI0AA/jdA== +moment-timezone@^0.5.32: + version "0.5.32" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.32.tgz#db7677cc3cc680fd30303ebd90b0da1ca0dfecc2" + integrity sha512-Z8QNyuQHQAmWucp8Knmgei8YNo28aLjJq6Ma+jy1ZSpSk5nyfRT8xgUbSQvD2+2UajISfenndwvFuH3NGS+nvA== dependencies: moment ">= 2.9.0" -moment@2.29.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== - "moment@>= 2.9.0": version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" +moment@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -5978,6 +6051,16 @@ object.assign@^4.1.0, object.assign@^4.1.1: has-symbols "^1.0.1" object-keys "^1.1.1" +object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + object.entries@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" @@ -6576,6 +6659,11 @@ postcss-flexbugs-fixes@^4.2.1: dependencies: postcss "^7.0.26" +postcss-flexbugs-fixes@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz#2028e145313074fc9abe276cb7ca14e5401eb49d" + integrity sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ== + postcss-focus-visible@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" @@ -6622,6 +6710,15 @@ postcss-import@^12.0.1: read-cache "^1.0.0" resolve "^1.1.7" +postcss-import@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.0.0.tgz#3ed1dadac5a16650bde3f4cdea6633b9c3c78296" + integrity sha512-gFDDzXhqr9ELmnLHgCC3TbGfA6Dm/YMb/UN8/f7Uuq4fL7VTk2vOIj6hwINEwbokEmp123bLD7a5m+E+KIetRg== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + postcss-initial@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" @@ -7014,7 +7111,7 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3, postcss-value-parser@^ version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" -postcss-value-parser@^4.1.0: +postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -7313,18 +7410,18 @@ react-beautiful-dnd@^13.0.0: redux "^4.0.4" use-memo-one "^1.1.1" -react-copy-to-clipboard@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.2.tgz#d82a437e081e68dfca3761fbd57dbf2abdda1316" - integrity sha512-/2t5mLMMPuN5GmdXo6TebFa8IoFxZ+KTDDqYhcDm0PhkgEzSxVvIX26G20s1EB02A4h2UZgwtfymZ3lGJm0OLg== +react-copy-to-clipboard@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.3.tgz#2a0623b1115a1d8c84144e9434d3342b5af41ab4" + integrity sha512-9S3j+m+UxDZOM0Qb8mhnT/rMR0NGSrj9A/073yz2DSxPMYhmYFBMYIdI2X4o8AjOjyFsSNxDRnCX6s/gRxpriw== dependencies: copy-to-clipboard "^3" prop-types "^15.5.8" -react-datepicker@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-3.2.2.tgz#cd5351ab1bba0b34412dd11db3169801f8f8b2ef" - integrity sha512-/3D6hfhXcCNCbO8LICuQeoNDItWFyitGo+aLcsi0tAyJLtCInamYRwPIXhsEF+N6/qWim1yNyr71mqjj4YEBmg== +react-datepicker@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-3.4.1.tgz#3c8e8989f1ab31a767c17170a2d1318aa3c3b9ef" + integrity sha512-ASyVb7UmVx1vzeITidD7Cr/EXRXhKyjjbSkBndPc1MipYq4rqQ3eMFgvRQzpsXc3JmIMFgICm7nmN6Otc1GE/Q== dependencies: classnames "^2.2.6" date-fns "^2.0.1" @@ -7332,15 +7429,14 @@ react-datepicker@^3.2.2: react-onclickoutside "^6.9.0" react-popper "^1.3.4" -react-dom@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.13.1.tgz#c1bd37331a0486c078ee54c4740720993b2e0e7f" - integrity sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag== +react-dom@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz#1de2560474ec9f0e334285662ede52dbc5426fc6" + integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.19.1" + scheduler "^0.20.1" react-is@^16.7.0, react-is@^16.8.1: version "16.9.0" @@ -7367,10 +7463,10 @@ react-onclickoutside@^6.9.0: resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz#a54bc317ae8cf6131a5d78acea55a11067f37a1f" integrity sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A== -react-paginate@^6.5.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/react-paginate/-/react-paginate-6.5.0.tgz#b9baf53627b115cfd688afa048776aa45bffda19" - integrity sha512-H7xSi9jyiJzgfaj+2nNhQcjZfwzJ/Mxb64V2RiyDctjZyCWojwsaGwMqhLBpQ58iAuMVtBMRQ7ECqMcUKG9QSQ== +react-paginate@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/react-paginate/-/react-paginate-7.0.0.tgz#af206ef92a2b6ef87646855eb1612157254ad0c0" + integrity sha512-mzPwHGJfSs79JBGX2V0v/FfQp3yWdz0XRrB9JvsUbJdsxqCt4osk1O669+K8VPQ0Lh9v0lJsnLLoJwnsgdJFng== dependencies: prop-types "^15.6.1" @@ -7425,10 +7521,10 @@ react-sizeme@^2.6.12: shallowequal "^1.1.0" throttle-debounce "^2.1.0" -react-textarea-autosize@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.2.0.tgz#fae38653f5ec172a855fd5fffb39e466d56aebdb" - integrity sha512-grajUlVbkx6VdtSxCgzloUIphIZF5bKr21OYMceWPKkniy7H0mRAT/AXPrRtObAe+zUePnNlBwUc4ivVjUGIjw== +react-textarea-autosize@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.0.tgz#e6e2fd186d9f61bb80ac6e2dcb4c55504f93c2fa" + integrity sha512-3GLWFAan2pbwBeoeNDoqGmSbrShORtgWfaWX0RJDivsUrpShh01saRM5RU/i4Zmf+whpBVEY5cA90Eq8Ub1N3w== dependencies: "@babel/runtime" "^7.10.2" use-composed-ref "^1.0.0" @@ -7441,10 +7537,10 @@ react-tools@~0.13.0: commoner "^0.10.0" jstransform "^10.1.0" -react-tooltip@^4.2.10: - version "4.2.10" - resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.10.tgz#ed1a1acd388940c96f4b6309f4fd4dcce5e01bdc" - integrity sha512-D7ZLx6/QwpUl0SZRek3IZy/HWpsEEp0v3562tcT8IwZgu8IgV7hY5ZzniTkHyRcuL+IQnljpjj7A7zCgl2+T3w== +react-tooltip@^4.2.13: + version "4.2.13" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.13.tgz#908db8a41dc10ae2ae9cc1864746cde939aaab0f" + integrity sha512-iAZ02wSxChLWb7Vnu0zeQMyAo/jiGHrwFNILWaR3pCKaFVRjKcv/B6TBI4+Xd66xLXVzLngwJ91Tf5D+mqAqVA== dependencies: prop-types "^15.7.2" uuid "^7.0.3" @@ -7459,14 +7555,13 @@ react-transition-group@^1.2.0: prop-types "^15.5.6" warning "^3.0.0" -react@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@^17.0.1: + version "17.0.1" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz#6e0600416bd57574e3f86d92edba3d9008726127" + integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" - prop-types "^15.6.2" react_ujs@^2.6.0: version "2.6.0" @@ -7713,6 +7808,11 @@ require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -7762,6 +7862,14 @@ resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.3.2, resolve@^1.4. dependencies: path-parse "^1.0.6" +resolve@^1.18.1: + version "1.19.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c" + integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg== + dependencies: + is-core-module "^2.1.0" + path-parse "^1.0.6" + ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -7785,13 +7893,6 @@ rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: dependencies: glob "^7.1.3" -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -7855,10 +7956,10 @@ sax@^1.2.4, sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" -scheduler@^0.19.1: - version "0.19.1" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.19.1.tgz#4f3e2ed2c1a7d65681f4c854fa8c5a1ccb40f196" - integrity sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA== +scheduler@^0.20.1: + version "0.20.1" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz#da0b907e24026b01181ecbc75efdc7f27b5a000c" + integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -7900,7 +8001,7 @@ select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" -selfsigned@^1.10.7: +selfsigned@^1.10.8: version "1.10.8" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.8.tgz#0d17208b7d12c33f8eac85c41835f27fc3d81a30" integrity sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w== @@ -7926,6 +8027,13 @@ semver@^7.2.1: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +semver@^7.3.4: + version "7.3.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" + integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== + dependencies: + lru-cache "^6.0.0" + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -8088,14 +8196,14 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" snapdragon-node@^2.0.1: version "2.1.1" @@ -8124,26 +8232,26 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -sockjs-client@1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.4.0.tgz#c9f2568e19c8fd8173b4997ea3420e0bb306c7d5" - integrity sha512-5zaLyO8/nri5cua0VtOrFXBPK1jbL4+1cebT/mmKA1E1ZXOvJrII75bPu0l0k843G/+iAbhEqzyKr0w/eCCj7g== +sockjs-client@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.5.0.tgz#2f8ff5d4b659e0d092f7aba0b7c386bd2aa20add" + integrity sha512-8Dt3BDi4FYNrCFGTL/HtwVzkARrENdwOUf1ZoW/9p3M8lZdFT35jVdrHza+qgxuG9H3/shR4cuX/X9umUrjP8Q== dependencies: - debug "^3.2.5" + debug "^3.2.6" eventsource "^1.0.7" - faye-websocket "~0.11.1" - inherits "^2.0.3" - json3 "^3.3.2" - url-parse "^1.4.3" + faye-websocket "^0.11.3" + inherits "^2.0.4" + json3 "^3.3.3" + url-parse "^1.4.7" -sockjs@0.3.20: - version "0.3.20" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" - integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== +sockjs@^0.3.21: + version "0.3.21" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.21.tgz#b34ffb98e796930b60a0cfa11904d6a339a7d417" + integrity sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw== dependencies: - faye-websocket "^0.10.0" + faye-websocket "^0.11.3" uuid "^3.4.0" - websocket-driver "0.6.5" + websocket-driver "^0.7.4" sort-keys@^1.0.0: version "1.1.2" @@ -8386,6 +8494,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + string.prototype.matchall@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz#48bb510326fb9fdeb6a33ceaa81a6ea04ef7648e" @@ -8564,15 +8681,15 @@ syntax-error@^1.1.1: dependencies: acorn-node "^1.2.0" -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== +table@^6.0.4: + version "6.0.7" + resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34" + integrity sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g== dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" + ajv "^7.0.2" + lodash "^4.17.20" + slice-ansi "^4.0.0" + string-width "^4.2.0" tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" @@ -8782,16 +8899,16 @@ ts-essentials@^2.0.3: resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== -ts-loader@8.0.4: - version "8.0.4" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.4.tgz#02b9c91fbcfdb3114d8b1e98a3829265270eee7a" - integrity sha512-5u8KF1SW8eCUb/Ff7At81e3wznPmT/27fvaGRO9CziVy+6NlPVRvrzSox4OwU0/e6OflOUB32Err4VquysCSAQ== +ts-loader@^8.0.14: + version "8.0.14" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.14.tgz#e46ac1f8dcb88808d0b1335d2eae65b74bd78fe8" + integrity sha512-Jt/hHlUnApOZjnSjTmZ+AbD5BGlQFx3f1D0nYuNKwz0JJnuDGHJas6az+FlWKwwRTu+26GXpv249A8UAnYUpqA== dependencies: - chalk "^2.3.0" + chalk "^4.1.0" enhanced-resolve "^4.0.0" - loader-utils "^1.0.2" + loader-utils "^2.0.0" micromatch "^4.0.0" - semver "^6.0.0" + semver "^7.3.4" ts-pnp@^1.1.6: version "1.2.0" @@ -8859,10 +8976,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.3.tgz#153bbd468ef07725c1df9c77e8b453f8d36abba5" - integrity sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg== +typescript@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" + integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== umd@^3.0.0: version "3.0.3" @@ -8969,7 +9086,7 @@ urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" -url-parse@^1.4.3: +url-parse@^1.4.3, url-parse@^1.4.7: version "1.4.7" resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" dependencies: @@ -9177,10 +9294,10 @@ webpack-dev-middleware@^3.7.2: range-parser "^1.2.1" webpack-log "^2.0.0" -webpack-dev-server@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" - integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== +webpack-dev-server@^3.11.2: + version "3.11.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.2.tgz#695ebced76a4929f0d5de7fd73fafe185fe33708" + integrity sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ== dependencies: ansi-html "0.0.7" bonjour "^3.5.0" @@ -9202,11 +9319,11 @@ webpack-dev-server@^3.11.0: p-retry "^3.0.1" portfinder "^1.0.26" schema-utils "^1.0.0" - selfsigned "^1.10.7" + selfsigned "^1.10.8" semver "^6.3.0" serve-index "^1.9.1" - sockjs "0.3.20" - sockjs-client "1.4.0" + sockjs "^0.3.21" + sockjs-client "^1.5.0" spdy "^4.0.2" strip-ansi "^3.0.1" supports-color "^6.1.0" @@ -9260,13 +9377,6 @@ webpack@^4.44.1: watchpack "^1.7.4" webpack-sources "^1.4.1" -websocket-driver@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" - integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= - dependencies: - websocket-extensions ">=0.1.1" - websocket-driver@>=0.5.1: version "0.7.3" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" @@ -9275,6 +9385,15 @@ websocket-driver@>=0.5.1: safe-buffer ">=5.1.0" websocket-extensions ">=0.1.1" +websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + websocket-extensions@>=0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" @@ -9335,13 +9454,6 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - ws@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" -- GitLab From cd65c9ce733bcd24038581831aa974252c961c5f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 21 Jan 2021 17:57:50 -1000 Subject: [PATCH 228/287] updating node modules Former-commit-id: 32c502be26c2a8389e1a171a1779b9fdaecb606c --- .gitignore | 1 + scripts/bundles/node_modules.tar.gz.REMOVED.git-id | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 77aa186..13fbbd3 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ tags !/scripts/public/assets/images log.txt sidekiq.log +Dockerfile.packages diff --git a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id index d3c29b5..f31166f 100644 --- a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id +++ b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id @@ -1 +1 @@ -67f211bd5a4a466daeead95c9d7accd1047c9492 \ No newline at end of file +0e7ff92b037bcac0e65982b90a202c51c7d1703f \ No newline at end of file -- GitLab From 3c7c3fbfeb3e78fd0ff900359fda3f4aed17ee83 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 21 Jan 2021 19:41:29 -1000 Subject: [PATCH 229/287] downgrading some modules that break the build Former-commit-id: 5fbbef71ec0228721b8d57fdab22d03036e79933 --- .../bundles/node_modules.tar.gz.REMOVED.git-id | 2 +- scripts/package.json | 12 ++++++------ scripts/yarn.lock | 16 +--------------- 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id index f31166f..4e9c14f 100644 --- a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id +++ b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id @@ -1 +1 @@ -0e7ff92b037bcac0e65982b90a202c51c7d1703f \ No newline at end of file +3766c04acbeedd18f840987086baad4f7ac75904 \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index b6d54dd..0057077 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -39,13 +39,13 @@ "popper.js": "^1.16.1", "prop-types": "^15.7.2", "rc-slider": "Galvanize-IT/slider#c569ca3b11979aced8306e6c02f37466cb7cd365", - "react": "^17.0.1", + "react": "^16.14.0", "react-addons-css-transition-group": "^15.6.2", "react-addons-test-utils": "^15.6.2", "react-beautiful-dnd": "^13.0.0", "react-copy-to-clipboard": "^5.0.3", "react-datepicker": "^3.4.1", - "react-dom": "^17.0.1", + "react-dom": "^16.14.0", "react-json-pretty": "^2.2.0", "react-paginate": "^7.0.0", "react-pdf": "^4.2.0", @@ -63,12 +63,12 @@ "@types/lodash-es": "^4.17.3", "@types/node-fetch": "^2.5.8", "@types/rc-slider": "^8.6.6", - "@types/react": "^17.0.0", + "@types/react": "^16.9.56", "@types/react-addons-css-transition-group": "^15.0.5", "@types/react-beautiful-dnd": "^13.0.0", "@types/react-copy-to-clipboard": "^5.0.0", "@types/react-datepicker": "^3.1.3", - "@types/react-dom": "^17.0.0", + "@types/react-dom": "^16.9.9", "@types/react-textarea-autosize": "^4.3.5", "@types/vimeo__player": "^2.10.0", "eslint": "^7.18.0", @@ -78,8 +78,8 @@ "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react": "^7.22.0", "eslint-plugin-standard": "^5.0.0", - "postcss-flexbugs-fixes": "^5.0.2", - "postcss-import": "^14.0.0", + "postcss-flexbugs-fixes": "^4.2.1", + "postcss-import": "^12.0.1", "postcss-preset-env": "^6.7.0", "webpack-dev-server": "^3.11.2" } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 0d818ba..c271a6d 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -6659,11 +6659,6 @@ postcss-flexbugs-fixes@^4.2.1: dependencies: postcss "^7.0.26" -postcss-flexbugs-fixes@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz#2028e145313074fc9abe276cb7ca14e5401eb49d" - integrity sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ== - postcss-focus-visible@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz#477d107113ade6024b14128317ade2bd1e17046e" @@ -6710,15 +6705,6 @@ postcss-import@^12.0.1: read-cache "^1.0.0" resolve "^1.1.7" -postcss-import@^14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.0.0.tgz#3ed1dadac5a16650bde3f4cdea6633b9c3c78296" - integrity sha512-gFDDzXhqr9ELmnLHgCC3TbGfA6Dm/YMb/UN8/f7Uuq4fL7VTk2vOIj6hwINEwbokEmp123bLD7a5m+E+KIetRg== - dependencies: - postcss-value-parser "^4.0.0" - read-cache "^1.0.0" - resolve "^1.1.7" - postcss-initial@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-3.0.2.tgz#f018563694b3c16ae8eaabe3c585ac6319637b2d" @@ -7111,7 +7097,7 @@ postcss-value-parser@^3.0.0, postcss-value-parser@^3.2.3, postcss-value-parser@^ version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" -postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0: +postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== -- GitLab From 0bff57988246ba24a1008d93055b127419308f9b Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 22 Jan 2021 15:46:11 -1000 Subject: [PATCH 230/287] updaing dependencies Former-commit-id: d153fdc4bbd87691ac7f835b0a8c7a8e6943cf66 --- .gitignore | 1 + scripts/Gemfile.lock | 650 ++++++++++-------- scripts/bundles/bundle.tar.gz.REMOVED.git-id | 2 +- .../node_modules.tar.gz.REMOVED.git-id | 2 +- scripts/gems/block-parser/Gemfile.lock | 101 +-- scripts/package.json | 8 +- 6 files changed, 440 insertions(+), 324 deletions(-) diff --git a/.gitignore b/.gitignore index 13fbbd3..d313f3c 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ tags log.txt sidekiq.log Dockerfile.packages +repackage.sh diff --git a/scripts/Gemfile.lock b/scripts/Gemfile.lock index 12b55c6..1fc26f0 100644 --- a/scripts/Gemfile.lock +++ b/scripts/Gemfile.lock @@ -16,61 +16,65 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (6.0.3.4) - actionpack (= 6.0.3.4) + actioncable (6.1.1) + actionpack (= 6.1.1) + activesupport (= 6.1.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.0.3.4) - actionpack (= 6.0.3.4) - activejob (= 6.0.3.4) - activerecord (= 6.0.3.4) - activestorage (= 6.0.3.4) - activesupport (= 6.0.3.4) + actionmailbox (6.1.1) + actionpack (= 6.1.1) + activejob (= 6.1.1) + activerecord (= 6.1.1) + activestorage (= 6.1.1) + activesupport (= 6.1.1) mail (>= 2.7.1) - actionmailer (6.0.3.4) - actionpack (= 6.0.3.4) - actionview (= 6.0.3.4) - activejob (= 6.0.3.4) + actionmailer (6.1.1) + actionpack (= 6.1.1) + actionview (= 6.1.1) + activejob (= 6.1.1) + activesupport (= 6.1.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.0.3.4) - actionview (= 6.0.3.4) - activesupport (= 6.0.3.4) - rack (~> 2.0, >= 2.0.8) + actionpack (6.1.1) + actionview (= 6.1.1) + activesupport (= 6.1.1) + rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.3.4) - actionpack (= 6.0.3.4) - activerecord (= 6.0.3.4) - activestorage (= 6.0.3.4) - activesupport (= 6.0.3.4) + actiontext (6.1.1) + actionpack (= 6.1.1) + activerecord (= 6.1.1) + activestorage (= 6.1.1) + activesupport (= 6.1.1) nokogiri (>= 1.8.5) - actionview (6.0.3.4) - activesupport (= 6.0.3.4) + actionview (6.1.1) + activesupport (= 6.1.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.3.4) - activesupport (= 6.0.3.4) + activejob (6.1.1) + activesupport (= 6.1.1) globalid (>= 0.3.6) - activemodel (6.0.3.4) - activesupport (= 6.0.3.4) - activerecord (6.0.3.4) - activemodel (= 6.0.3.4) - activesupport (= 6.0.3.4) - activestorage (6.0.3.4) - actionpack (= 6.0.3.4) - activejob (= 6.0.3.4) - activerecord (= 6.0.3.4) + activemodel (6.1.1) + activesupport (= 6.1.1) + activerecord (6.1.1) + activemodel (= 6.1.1) + activesupport (= 6.1.1) + activestorage (6.1.1) + actionpack (= 6.1.1) + activejob (= 6.1.1) + activerecord (= 6.1.1) + activesupport (= 6.1.1) marcel (~> 0.3.1) - activesupport (6.0.3.4) + mimemagic (~> 0.3.2) + activesupport (6.1.1) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) analytics-ruby (2.2.8) @@ -78,85 +82,97 @@ GEM kramdown railties ast (2.4.1) - autoprefixer-rails (10.0.1.0) + autoprefixer-rails (10.2.0.0) execjs aws-eventstream (1.1.0) - aws-partitions (1.382.0) + aws-partitions (1.418.0) aws-sdk (3.0.1) aws-sdk-resources (~> 3) - aws-sdk-accessanalyzer (1.13.0) + aws-sdk-accessanalyzer (1.14.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-acm (1.38.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-acmpca (1.30.0) + aws-sdk-acmpca (1.32.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-alexaforbusiness (1.43.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-amplify (1.26.0) + aws-sdk-amplify (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-amplifybackend (1.1.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-apigateway (1.55.0) + aws-sdk-apigateway (1.58.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-apigatewaymanagementapi (1.19.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-apigatewayv2 (1.29.0) + aws-sdk-apigatewayv2 (1.30.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-appconfig (1.12.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-appflow (1.2.0) + aws-sdk-appflow (1.4.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-applicationautoscaling (1.48.0) + aws-sdk-appintegrationsservice (1.0.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-applicationautoscaling (1.49.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-applicationdiscoveryservice (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-applicationinsights (1.15.0) + aws-sdk-applicationinsights (1.16.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-appmesh (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-appmesh (1.31.0) + aws-sdk-appregistry (1.3.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-appstream (1.48.0) + aws-sdk-appstream (1.49.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-appsync (1.36.0) + aws-sdk-appsync (1.37.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-athena (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) + aws-sdk-auditmanager (1.1.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) aws-sdk-augmentedairuntime (1.10.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-autoscaling (1.47.0) + aws-sdk-autoscaling (1.53.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-autoscalingplans (1.28.0) + aws-sdk-autoscalingplans (1.29.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-backup (1.23.0) + aws-sdk-backup (1.25.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-batch (1.39.0) + aws-sdk-batch (1.43.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-braket (1.4.0) + aws-sdk-braket (1.5.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-budgets (1.36.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-chime (1.37.0) + aws-sdk-chime (1.40.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-cloud9 (1.29.0) @@ -165,40 +181,40 @@ GEM aws-sdk-clouddirectory (1.29.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudformation (1.44.0) + aws-sdk-cloudformation (1.46.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudfront (1.43.0) + aws-sdk-cloudfront (1.47.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-cloudhsm (1.27.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudhsmv2 (1.30.0) + aws-sdk-cloudhsmv2 (1.31.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudsearch (1.26.0) + aws-sdk-cloudsearch (1.27.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-cloudsearchdomain (1.22.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudtrail (1.29.0) + aws-sdk-cloudtrail (1.31.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudwatch (1.45.0) + aws-sdk-cloudwatch (1.47.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudwatchevents (1.38.0) + aws-sdk-cloudwatchevents (1.40.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-cloudwatchlogs (1.38.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-codeartifact (1.4.0) + aws-sdk-codeartifact (1.6.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-codebuild (1.63.0) + aws-sdk-codebuild (1.65.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-codecommit (1.40.0) @@ -210,49 +226,52 @@ GEM aws-sdk-codeguruprofiler (1.12.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-codegurureviewer (1.13.0) + aws-sdk-codegurureviewer (1.14.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-codepipeline (1.37.0) + aws-sdk-codepipeline (1.39.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-codestar (1.27.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-codestarconnections (1.11.0) + aws-sdk-codestarconnections (1.12.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-codestarnotifications (1.8.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-cognitoidentity (1.27.0) + aws-sdk-cognitoidentity (1.29.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-cognitoidentityprovider (1.47.0) + aws-sdk-cognitoidentityprovider (1.48.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-cognitosync (1.24.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-comprehend (1.41.0) + aws-sdk-comprehend (1.42.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-comprehendmedical (1.23.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-computeoptimizer (1.9.0) + aws-sdk-computeoptimizer (1.11.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-configservice (1.55.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-configservice (1.53.0) + aws-sdk-connect (1.38.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-connect (1.34.0) + aws-sdk-connectcontactlens (1.0.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-connectparticipant (1.8.0) + aws-sdk-connectparticipant (1.9.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-core (3.109.1) + aws-sdk-core (3.111.2) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) @@ -260,10 +279,13 @@ GEM aws-sdk-costandusagereportservice (1.28.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-costexplorer (1.52.0) + aws-sdk-costexplorer (1.56.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-databasemigrationservice (1.45.0) + aws-sdk-customerprofiles (1.1.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-databasemigrationservice (1.50.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-dataexchange (1.10.0) @@ -272,7 +294,7 @@ GEM aws-sdk-datapipeline (1.24.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-datasync (1.27.0) + aws-sdk-datasync (1.28.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-dax (1.27.0) @@ -284,49 +306,55 @@ GEM aws-sdk-devicefarm (1.39.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) + aws-sdk-devopsguru (1.2.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) aws-sdk-directconnect (1.37.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-directoryservice (1.34.0) + aws-sdk-directoryservice (1.37.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-dlm (1.35.0) + aws-sdk-dlm (1.37.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-docdb (1.25.0) + aws-sdk-docdb (1.27.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-dynamodb (1.55.0) + aws-sdk-dynamodb (1.58.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-dynamodbstreams (1.26.0) + aws-sdk-dynamodbstreams (1.27.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-ebs (1.11.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-ec2 (1.200.0) + aws-sdk-ec2 (1.221.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-ec2instanceconnect (1.11.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-ecr (1.39.0) + aws-sdk-ecr (1.40.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-ecrpublic (1.0.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-ecs (1.70.0) + aws-sdk-ecs (1.73.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-efs (1.36.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-eks (1.45.0) + aws-sdk-eks (1.46.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticache (1.44.0) + aws-sdk-elasticache (1.50.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticbeanstalk (1.38.0) + aws-sdk-elasticbeanstalk (1.40.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-elasticinference (1.10.0) @@ -335,64 +363,76 @@ GEM aws-sdk-elasticloadbalancing (1.29.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticloadbalancingv2 (1.53.0) + aws-sdk-elasticloadbalancingv2 (1.56.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticsearchservice (1.43.0) + aws-sdk-elasticsearchservice (1.46.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-elastictranscoder (1.27.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-emr (1.39.0) + aws-sdk-emr (1.40.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-eventbridge (1.16.0) + aws-sdk-emrcontainers (1.0.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-eventbridge (1.18.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-firehose (1.35.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-fms (1.32.0) + aws-sdk-fms (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-forecastqueryservice (1.10.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-forecastservice (1.11.0) + aws-sdk-forecastservice (1.14.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-frauddetector (1.13.0) + aws-sdk-frauddetector (1.15.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-fsx (1.31.0) + aws-sdk-fsx (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-gamelift (1.38.0) + aws-sdk-gamelift (1.39.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-glacier (1.35.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-globalaccelerator (1.23.0) + aws-sdk-globalaccelerator (1.27.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-glue (1.82.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-glue (1.75.0) + aws-sdk-gluedatabrew (1.0.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-greengrass (1.37.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) + aws-sdk-greengrassv2 (1.1.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) aws-sdk-groundstation (1.15.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-guardduty (1.42.0) + aws-sdk-guardduty (1.43.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-health (1.31.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-honeycode (1.3.0) + aws-sdk-healthlake (1.1.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-honeycode (1.4.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-iam (1.46.0) @@ -401,7 +441,7 @@ GEM aws-sdk-identitystore (1.3.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-imagebuilder (1.15.0) + aws-sdk-imagebuilder (1.17.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-importexport (1.24.0) @@ -410,7 +450,7 @@ GEM aws-sdk-inspector (1.32.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-iot (1.59.0) + aws-sdk-iot (1.64.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-iot1clickdevicesservice (1.26.0) @@ -419,37 +459,46 @@ GEM aws-sdk-iot1clickprojects (1.26.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-iotanalytics (1.34.0) + aws-sdk-iotanalytics (1.36.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-iotdataplane (1.26.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) + aws-sdk-iotdeviceadvisor (1.0.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) aws-sdk-iotevents (1.20.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-ioteventsdata (1.13.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) + aws-sdk-iotfleethub (1.0.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) aws-sdk-iotjobsdataplane (1.25.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-iotsecuretunneling (1.8.0) + aws-sdk-iotsecuretunneling (1.9.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-iotsitewise (1.12.0) + aws-sdk-iotsitewise (1.16.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-iotthingsgraph (1.12.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) + aws-sdk-iotwireless (1.1.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) aws-sdk-ivs (1.5.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-kafka (1.29.0) + aws-sdk-kafka (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-kendra (1.14.0) + aws-sdk-kendra (1.20.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-kinesis (1.30.0) @@ -458,7 +507,7 @@ GEM aws-sdk-kinesisanalytics (1.29.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisanalyticsv2 (1.23.0) + aws-sdk-kinesisanalyticsv2 (1.24.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-kinesisvideo (1.30.0) @@ -473,28 +522,40 @@ GEM aws-sdk-kinesisvideosignalingchannels (1.8.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-kms (1.39.0) + aws-sdk-kms (1.41.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-lakeformation (1.11.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-lambda (1.51.0) + aws-sdk-lambda (1.57.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-lambdapreview (1.24.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-lex (1.32.0) + aws-sdk-lex (1.33.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-lexmodelbuildingservice (1.42.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-lexmodelsv2 (1.0.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-lexmodelbuildingservice (1.39.0) + aws-sdk-lexruntimev2 (1.0.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-licensemanager (1.20.0) + aws-sdk-licensemanager (1.23.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-lightsail (1.39.0) + aws-sdk-lightsail (1.41.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-locationservice (1.0.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-lookoutforvision (1.0.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-machinelearning (1.25.0) @@ -503,31 +564,31 @@ GEM aws-sdk-macie (1.25.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-macie2 (1.13.0) + aws-sdk-macie2 (1.19.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-managedblockchain (1.17.0) + aws-sdk-managedblockchain (1.18.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-marketplacecatalog (1.9.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplacecommerceanalytics (1.29.0) + aws-sdk-marketplacecommerceanalytics (1.30.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-marketplaceentitlementservice (1.24.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplacemetering (1.31.0) + aws-sdk-marketplacemetering (1.32.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-mediaconnect (1.28.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-mediaconvert (1.58.0) + aws-sdk-mediaconvert (1.61.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-medialive (1.56.0) + aws-sdk-medialive (1.61.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-mediapackage (1.36.0) @@ -542,7 +603,7 @@ GEM aws-sdk-mediastoredata (1.27.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-mediatailor (1.32.0) + aws-sdk-mediatailor (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-migrationhub (1.29.0) @@ -554,16 +615,22 @@ GEM aws-sdk-mobile (1.24.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-mq (1.33.0) + aws-sdk-mq (1.34.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-mturk (1.27.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-neptune (1.30.0) + aws-sdk-mwaa (1.0.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-networkmanager (1.8.0) + aws-sdk-neptune (1.32.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-networkfirewall (1.0.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-networkmanager (1.9.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-opsworks (1.30.0) @@ -572,25 +639,25 @@ GEM aws-sdk-opsworkscm (1.40.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-organizations (1.52.0) + aws-sdk-organizations (1.55.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-outposts (1.10.0) + aws-sdk-outposts (1.13.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-personalize (1.19.0) + aws-sdk-personalize (1.20.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-personalizeevents (1.14.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-personalizeruntime (1.18.0) + aws-sdk-personalizeruntime (1.20.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-pi (1.24.0) + aws-sdk-pi (1.25.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-pinpoint (1.47.0) + aws-sdk-pinpoint (1.48.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-pinpointemail (1.24.0) @@ -599,31 +666,34 @@ GEM aws-sdk-pinpointsmsvoice (1.21.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-polly (1.37.0) + aws-sdk-polly (1.38.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-pricing (1.24.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) + aws-sdk-prometheusservice (1.1.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) aws-sdk-qldb (1.11.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-qldbsession (1.9.0) + aws-sdk-qldbsession (1.10.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-quicksight (1.33.0) + aws-sdk-quicksight (1.39.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-ram (1.22.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-rds (1.104.0) + aws-sdk-rds (1.111.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-rdsdataservice (1.23.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-redshift (1.50.0) + aws-sdk-redshift (1.53.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-redshiftdataapiservice (1.2.0) @@ -632,30 +702,34 @@ GEM aws-sdk-rekognition (1.47.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-resourcegroups (1.32.0) + aws-sdk-resourcegroups (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-resourcegroupstaggingapi (1.34.0) + aws-sdk-resourcegroupstaggingapi (1.35.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-resources (3.84.0) + aws-sdk-resources (3.94.0) aws-sdk-accessanalyzer (~> 1) aws-sdk-acm (~> 1) aws-sdk-acmpca (~> 1) aws-sdk-alexaforbusiness (~> 1) aws-sdk-amplify (~> 1) + aws-sdk-amplifybackend (~> 1) aws-sdk-apigateway (~> 1) aws-sdk-apigatewaymanagementapi (~> 1) aws-sdk-apigatewayv2 (~> 1) aws-sdk-appconfig (~> 1) aws-sdk-appflow (~> 1) + aws-sdk-appintegrationsservice (~> 1) aws-sdk-applicationautoscaling (~> 1) aws-sdk-applicationdiscoveryservice (~> 1) aws-sdk-applicationinsights (~> 1) aws-sdk-appmesh (~> 1) + aws-sdk-appregistry (~> 1) aws-sdk-appstream (~> 1) aws-sdk-appsync (~> 1) aws-sdk-athena (~> 1) + aws-sdk-auditmanager (~> 1) aws-sdk-augmentedairuntime (~> 1) aws-sdk-autoscaling (~> 1) aws-sdk-autoscalingplans (~> 1) @@ -694,9 +768,11 @@ GEM aws-sdk-computeoptimizer (~> 1) aws-sdk-configservice (~> 1) aws-sdk-connect (~> 1) + aws-sdk-connectcontactlens (~> 1) aws-sdk-connectparticipant (~> 1) aws-sdk-costandusagereportservice (~> 1) aws-sdk-costexplorer (~> 1) + aws-sdk-customerprofiles (~> 1) aws-sdk-databasemigrationservice (~> 1) aws-sdk-dataexchange (~> 1) aws-sdk-datapipeline (~> 1) @@ -704,6 +780,7 @@ GEM aws-sdk-dax (~> 1) aws-sdk-detective (~> 1) aws-sdk-devicefarm (~> 1) + aws-sdk-devopsguru (~> 1) aws-sdk-directconnect (~> 1) aws-sdk-directoryservice (~> 1) aws-sdk-dlm (~> 1) @@ -714,6 +791,7 @@ GEM aws-sdk-ec2 (~> 1) aws-sdk-ec2instanceconnect (~> 1) aws-sdk-ecr (~> 1) + aws-sdk-ecrpublic (~> 1) aws-sdk-ecs (~> 1) aws-sdk-efs (~> 1) aws-sdk-eks (~> 1) @@ -725,6 +803,7 @@ GEM aws-sdk-elasticsearchservice (~> 1) aws-sdk-elastictranscoder (~> 1) aws-sdk-emr (~> 1) + aws-sdk-emrcontainers (~> 1) aws-sdk-eventbridge (~> 1) aws-sdk-firehose (~> 1) aws-sdk-fms (~> 1) @@ -736,10 +815,13 @@ GEM aws-sdk-glacier (~> 1) aws-sdk-globalaccelerator (~> 1) aws-sdk-glue (~> 1) + aws-sdk-gluedatabrew (~> 1) aws-sdk-greengrass (~> 1) + aws-sdk-greengrassv2 (~> 1) aws-sdk-groundstation (~> 1) aws-sdk-guardduty (~> 1) aws-sdk-health (~> 1) + aws-sdk-healthlake (~> 1) aws-sdk-honeycode (~> 1) aws-sdk-iam (~> 1) aws-sdk-identitystore (~> 1) @@ -751,12 +833,15 @@ GEM aws-sdk-iot1clickprojects (~> 1) aws-sdk-iotanalytics (~> 1) aws-sdk-iotdataplane (~> 1) + aws-sdk-iotdeviceadvisor (~> 1) aws-sdk-iotevents (~> 1) aws-sdk-ioteventsdata (~> 1) + aws-sdk-iotfleethub (~> 1) aws-sdk-iotjobsdataplane (~> 1) aws-sdk-iotsecuretunneling (~> 1) aws-sdk-iotsitewise (~> 1) aws-sdk-iotthingsgraph (~> 1) + aws-sdk-iotwireless (~> 1) aws-sdk-ivs (~> 1) aws-sdk-kafka (~> 1) aws-sdk-kendra (~> 1) @@ -773,8 +858,12 @@ GEM aws-sdk-lambdapreview (~> 1) aws-sdk-lex (~> 1) aws-sdk-lexmodelbuildingservice (~> 1) + aws-sdk-lexmodelsv2 (~> 1) + aws-sdk-lexruntimev2 (~> 1) aws-sdk-licensemanager (~> 1) aws-sdk-lightsail (~> 1) + aws-sdk-locationservice (~> 1) + aws-sdk-lookoutforvision (~> 1) aws-sdk-machinelearning (~> 1) aws-sdk-macie (~> 1) aws-sdk-macie2 (~> 1) @@ -796,7 +885,9 @@ GEM aws-sdk-mobile (~> 1) aws-sdk-mq (~> 1) aws-sdk-mturk (~> 1) + aws-sdk-mwaa (~> 1) aws-sdk-neptune (~> 1) + aws-sdk-networkfirewall (~> 1) aws-sdk-networkmanager (~> 1) aws-sdk-opsworks (~> 1) aws-sdk-opsworkscm (~> 1) @@ -811,6 +902,7 @@ GEM aws-sdk-pinpointsmsvoice (~> 1) aws-sdk-polly (~> 1) aws-sdk-pricing (~> 1) + aws-sdk-prometheusservice (~> 1) aws-sdk-qldb (~> 1) aws-sdk-qldbsession (~> 1) aws-sdk-quicksight (~> 1) @@ -830,6 +922,8 @@ GEM aws-sdk-s3control (~> 1) aws-sdk-s3outposts (~> 1) aws-sdk-sagemaker (~> 1) + aws-sdk-sagemakeredgemanager (~> 1) + aws-sdk-sagemakerfeaturestoreruntime (~> 1) aws-sdk-sagemakerruntime (~> 1) aws-sdk-savingsplans (~> 1) aws-sdk-schemas (~> 1) @@ -866,38 +960,45 @@ GEM aws-sdk-waf (~> 1) aws-sdk-wafregional (~> 1) aws-sdk-wafv2 (~> 1) + aws-sdk-wellarchitected (~> 1) aws-sdk-workdocs (~> 1) aws-sdk-worklink (~> 1) aws-sdk-workmail (~> 1) aws-sdk-workmailmessageflow (~> 1) aws-sdk-workspaces (~> 1) aws-sdk-xray (~> 1) - aws-sdk-robomaker (1.30.0) + aws-sdk-robomaker (1.31.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-route53 (1.44.0) + aws-sdk-route53 (1.45.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-route53domains (1.28.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-route53resolver (1.21.0) + aws-sdk-route53resolver (1.22.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.83.0) + aws-sdk-s3 (1.87.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sdk-s3control (1.24.0) + aws-sdk-s3control (1.26.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-s3outposts (1.0.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemaker (1.70.0) + aws-sdk-sagemaker (1.75.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-sagemakeredgemanager (1.0.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) + aws-sdk-sagemakerfeaturestoreruntime (1.0.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemakerruntime (1.27.0) + aws-sdk-sagemakerruntime (1.28.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-savingsplans (1.12.0) @@ -909,31 +1010,31 @@ GEM aws-sdk-secretsmanager (1.43.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-securityhub (1.35.0) + aws-sdk-securityhub (1.38.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-serverlessapplicationrepository (1.32.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-servicecatalog (1.50.0) + aws-sdk-servicecatalog (1.57.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-servicediscovery (1.31.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-servicequotas (1.11.0) + aws-sdk-servicequotas (1.12.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-ses (1.36.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-sesv2 (1.13.0) + aws-sdk-sesv2 (1.14.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-shield (1.32.0) + aws-sdk-shield (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-signer (1.26.0) + aws-sdk-signer (1.27.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-simpledb (1.24.0) @@ -945,25 +1046,25 @@ GEM aws-sdk-snowball (1.35.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-sns (1.33.0) + aws-sdk-sns (1.37.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-sqs (1.34.0) + aws-sdk-sqs (1.35.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-ssm (1.94.0) + aws-sdk-ssm (1.103.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-ssoadmin (1.3.0) + aws-sdk-ssoadmin (1.4.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-ssooidc (1.8.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-states (1.36.0) + aws-sdk-states (1.37.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-storagegateway (1.50.0) + aws-sdk-storagegateway (1.52.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-support (1.28.0) @@ -972,28 +1073,28 @@ GEM aws-sdk-swf (1.25.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-synthetics (1.9.0) + aws-sdk-synthetics (1.10.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-textract (1.21.0) + aws-sdk-textract (1.22.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-timestreamquery (1.1.0) + aws-sdk-timestreamquery (1.2.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-timestreamwrite (1.1.0) + aws-sdk-timestreamwrite (1.2.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-transcribeservice (1.50.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-transcribestreamingservice (1.22.0) + aws-sdk-transcribestreamingservice (1.24.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-transfer (1.28.0) + aws-sdk-transfer (1.29.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-translate (1.28.0) + aws-sdk-translate (1.29.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-waf (1.36.0) @@ -1005,22 +1106,25 @@ GEM aws-sdk-wafv2 (1.14.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) + aws-sdk-wellarchitected (1.0.0) + aws-sdk-core (~> 3, >= 3.109.0) + aws-sigv4 (~> 1.1) aws-sdk-workdocs (1.28.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-worklink (1.21.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-workmail (1.32.0) + aws-sdk-workmail (1.33.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sdk-workmailmessageflow (1.9.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-workspaces (1.48.0) + aws-sdk-workspaces (1.49.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) - aws-sdk-xray (1.34.0) + aws-sdk-xray (1.35.0) aws-sdk-core (~> 3, >= 3.109.0) aws-sigv4 (~> 1.1) aws-sigv2 (1.0.1) @@ -1033,22 +1137,22 @@ GEM barnes (0.0.8) multi_json (~> 1) statsd-ruby (~> 1.1) - better_errors (2.8.3) + better_errors (2.9.1) coderay (>= 1.0.0) erubi (>= 1.0.0) rack (>= 0.9.0) - binding_of_caller (0.8.0) + binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) - bootsnap (1.4.8) + bootsnap (1.5.1) msgpack (~> 1.0) - bootstrap (4.5.2) + bootstrap (4.5.3) autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) - browser (5.1.0) + browser (5.3.0) builder (3.2.4) byebug (11.1.3) - capybara (3.33.0) + capybara (3.34.0) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) @@ -1058,43 +1162,47 @@ GEM xpath (~> 3.2) childprocess (3.0.0) coderay (1.1.3) - commonmarker (0.21.0) + commonmarker (0.21.1) ruby-enum (~> 0.5) - concurrent-ruby (1.1.7) + concurrent-ruby (1.1.8) connection_pool (2.2.3) - crack (0.4.4) + crack (0.4.5) + rexml crass (1.0.6) database_cleaner (1.8.5) - debug_inspector (0.0.3) + debug_inspector (1.0.0) deterministic (0.6.0) diff-lcs (1.4.4) - docile (1.3.2) + docile (1.3.5) dotenv (2.7.6) dotenv-rails (2.7.6) dotenv (= 2.7.6) railties (>= 3.2) - erubi (1.9.0) + erubi (1.10.0) execjs (2.7.0) factory_bot (6.1.0) activesupport (>= 5.0.0) factory_bot_rails (6.1.0) factory_bot (~> 6.1.0) railties (>= 5.0.0) - faraday (1.0.1) + faraday (1.3.0) + faraday-net_http (~> 1.0) multipart-post (>= 1.2, < 3) - ffi (1.13.1) + ruby2_keywords + faraday-net_http (1.0.1) + ffi (1.14.2) flamegraph (0.9.5) - font-awesome-rails (4.7.0.5) - railties (>= 3.2, < 6.1) + font-awesome-rails (4.7.0.6) + railties (>= 3.2, < 6.2) foreman (0.87.2) - github-markup (3.0.4) + github-markup (3.0.5) github_url (0.2.1) - gitlab (4.16.1) - httparty (~> 0.14, >= 0.14.0) + gitlab (4.17.0) + httparty (~> 0.18) terminal-table (~> 1.5, >= 1.5.1) globalid (0.4.2) activesupport (>= 4.2.0) - haml (5.2.0) + haml (5.2.1) temple (>= 0.8.0) tilt hashdiff (1.0.1) @@ -1102,7 +1210,7 @@ GEM httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.8.5) + i18n (1.8.7) concurrent-ruby (~> 1.0) iniparse (1.5.0) jmespath (1.4.0) @@ -1110,9 +1218,8 @@ GEM rails-dom-testing (>= 1, < 3) railties (>= 4.2.0) thor (>= 0.14, < 2.0) - js-routes (1.4.9) + js-routes (1.4.14) railties (>= 4) - sprockets-rails json_spec (1.1.5) multi_json (~> 1.0) rspec (>= 2.0, < 4.0) @@ -1123,10 +1230,10 @@ GEM addressable (~> 2.7) letter_opener (1.7.0) launchy (~> 2.2) - listen (3.2.1) + listen (3.4.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.7.0) + loofah (2.9.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) @@ -1135,64 +1242,66 @@ GEM mimemagic (~> 0.3.2) mathjax-rails (2.6.1) railties (>= 3.0) - memory_profiler (0.9.14) + memory_profiler (1.0.0) method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) + mime-types-data (3.2020.1104) mimemagic (0.3.5) mini_mime (1.0.2) - mini_portile2 (2.4.0) - minitest (5.14.2) + mini_portile2 (2.5.0) + minitest (5.14.3) msgpack (1.3.3) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) mustache (1.1.1) nio4r (2.5.4) - nokogiri (1.10.10) - mini_portile2 (~> 2.4.0) - octokit (4.18.0) + nokogiri (1.11.1) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) + octokit (4.20.0) faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) overcommit (0.57.0) childprocess (>= 0.6.3, < 5) iniparse (~> 1.4) - pagy (3.8.3) - parallel (1.19.2) - parser (2.7.2.0) + pagy (3.10.0) + parallel (1.20.1) + parser (3.0.0.0) ast (~> 2.4.1) pg (1.2.3) popper_js (1.16.0) - psych (3.2.0) + psych (3.3.0) public_suffix (4.0.6) - puma (5.0.2) + puma (5.1.1) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) + racc (1.5.2) rack (2.2.3) rack-attack (6.3.1) rack (>= 1.0, < 3) - rack-mini-profiler (2.1.0) + rack-mini-profiler (2.3.0) rack (>= 1.2.0) rack-proxy (0.6.5) rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.0.3.4) - actioncable (= 6.0.3.4) - actionmailbox (= 6.0.3.4) - actionmailer (= 6.0.3.4) - actionpack (= 6.0.3.4) - actiontext (= 6.0.3.4) - actionview (= 6.0.3.4) - activejob (= 6.0.3.4) - activemodel (= 6.0.3.4) - activerecord (= 6.0.3.4) - activestorage (= 6.0.3.4) - activesupport (= 6.0.3.4) - bundler (>= 1.3.0) - railties (= 6.0.3.4) + rails (6.1.1) + actioncable (= 6.1.1) + actionmailbox (= 6.1.1) + actionmailer (= 6.1.1) + actionpack (= 6.1.1) + actiontext (= 6.1.1) + actionview (= 6.1.1) + activejob (= 6.1.1) + activemodel (= 6.1.1) + activerecord (= 6.1.1) + activestorage (= 6.1.1) + activesupport (= 6.1.1) + bundler (>= 1.15.0) + railties (= 6.1.1) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -1203,14 +1312,14 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (6.0.3.4) - actionpack (= 6.0.3.4) - activesupport (= 6.0.3.4) + railties (6.1.1) + actionpack (= 6.1.1) + activesupport (= 6.1.1) method_source rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) + thor (~> 1.0) rainbow (3.0.0) - rake (13.0.1) + rake (13.0.3) rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) @@ -1220,51 +1329,52 @@ GEM execjs railties (>= 3.2) tilt - redcarpet (3.5.0) - redis (4.2.2) + redcarpet (3.5.1) + redis (4.2.5) regexp_parser (1.8.2) rexml (3.2.4) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.3) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.2) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-rails (4.0.1) + rspec-support (~> 3.10.0) + rspec-rails (4.0.2) actionpack (>= 4.2) activesupport (>= 4.2) railties (>= 4.2) - rspec-core (~> 3.9) - rspec-expectations (~> 3.9) - rspec-mocks (~> 3.9) - rspec-support (~> 3.9) - rspec-support (3.9.3) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.10.1) rspec_api_documentation (6.1.0) activesupport (>= 3.0.0) mustache (~> 1.0, >= 0.99.4) rspec (~> 3.0) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.93.1) + rubocop (1.8.1) parallel (~> 1.10) - parser (>= 2.7.1.5) + parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8) + regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 0.6.0) + rubocop-ast (>= 1.2.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.8.0) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.4.0) parser (>= 2.7.1.5) ruby-enum (0.8.0) i18n - ruby-progressbar (1.10.1) + ruby-progressbar (1.11.0) + ruby2_keywords (0.0.4) rubyzip (2.3.0) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) @@ -1279,22 +1389,24 @@ GEM sawyer (0.8.2) addressable (>= 2.3.5) faraday (> 0.8, < 2.0) - scout_apm (2.6.9) + scout_apm (4.0.3) parser selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) - semantic_range (2.3.0) - shoulda-matchers (4.4.1) + semantic_range (2.3.1) + shoulda-matchers (4.5.1) activesupport (>= 4.2.0) - sidekiq (6.1.2) + sidekiq (6.1.3) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) - simplecov (0.19.0) + simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) + simplecov_json_formatter (0.1.2) solid_use_case (2.2.0) deterministic (~> 0.6.0) sprockets (4.0.2) @@ -1304,25 +1416,23 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - stackprof (0.2.15) - statsd-ruby (1.4.0) + stackprof (0.2.16) + statsd-ruby (1.5.0) temple (0.8.2) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - thor (1.0.1) - thread_safe (0.3.6) + terminal-table (1.6.0) + thor (1.1.0) tilt (2.0.10) timecop (0.9.2) ts_routes (1.0.3) railties (>= 4.0) - tzinfo (1.2.7) - thread_safe (~> 0.1) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) uglifier (4.2.0) execjs (>= 0.3.0, < 3) underscore-rails (1.8.3) - unicode-display_width (1.7.0) + unicode-display_width (2.0.0) vcr (6.0.0) - webmock (3.9.2) + webmock (3.11.1) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -1336,7 +1446,7 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.4.0) + zeitwerk (2.4.2) zip-zip (0.3) rubyzip (>= 1.0.0) diff --git a/scripts/bundles/bundle.tar.gz.REMOVED.git-id b/scripts/bundles/bundle.tar.gz.REMOVED.git-id index 4b90411..4d2c500 100644 --- a/scripts/bundles/bundle.tar.gz.REMOVED.git-id +++ b/scripts/bundles/bundle.tar.gz.REMOVED.git-id @@ -1 +1 @@ -e949587cc2c87f431a7d318d7b8e4853a8b1859d \ No newline at end of file +200756010d7b37199ed54c0795a6c377cb33f8aa \ No newline at end of file diff --git a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id index 4e9c14f..f47e9e9 100644 --- a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id +++ b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id @@ -1 +1 @@ -3766c04acbeedd18f840987086baad4f7ac75904 \ No newline at end of file +9d9fcd20003a816854393a6e7ac24b19b3a0b904 \ No newline at end of file diff --git a/scripts/gems/block-parser/Gemfile.lock b/scripts/gems/block-parser/Gemfile.lock index 3d84f82..aaece8f 100644 --- a/scripts/gems/block-parser/Gemfile.lock +++ b/scripts/gems/block-parser/Gemfile.lock @@ -16,95 +16,100 @@ PATH GEM remote: https://rubygems.org/ specs: - activemodel (6.0.3.4) - activesupport (= 6.0.3.4) - activesupport (6.0.3.4) + activemodel (6.1.1) + activesupport (= 6.1.1) + activesupport (6.1.1) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) ast (2.4.1) byebug (11.1.3) - commonmarker (0.21.0) + commonmarker (0.21.1) ruby-enum (~> 0.5) - concurrent-ruby (1.1.7) + concurrent-ruby (1.1.8) diff-lcs (1.4.4) - faraday (1.0.1) + faraday (1.3.0) + faraday-net_http (~> 1.0) multipart-post (>= 1.2, < 3) - github-markup (3.0.4) + ruby2_keywords + faraday-net_http (1.0.1) + github-markup (3.0.5) github_url (0.2.1) - gitlab (4.16.1) - httparty (~> 0.14, >= 0.14.0) + gitlab (4.17.0) + httparty (~> 0.18) terminal-table (~> 1.5, >= 1.5.1) httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.8.5) + i18n (1.8.7) concurrent-ruby (~> 1.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) - mini_portile2 (2.4.0) - minitest (5.14.2) + mime-types-data (3.2020.1104) + mini_portile2 (2.5.0) + minitest (5.14.3) multi_xml (0.6.0) multipart-post (2.1.1) - nokogiri (1.10.10) - mini_portile2 (~> 2.4.0) - octokit (4.18.0) + nokogiri (1.11.1) + mini_portile2 (~> 2.5.0) + racc (~> 1.4) + octokit (4.20.0) faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) - parallel (1.19.2) - parser (2.7.2.0) + parallel (1.20.1) + parser (3.0.0.0) ast (~> 2.4.1) - psych (3.2.0) + psych (3.3.0) public_suffix (4.0.6) + racc (1.5.2) rainbow (3.0.0) - rake (13.0.1) - redcarpet (3.5.0) - regexp_parser (1.8.2) + rake (13.0.3) + redcarpet (3.5.1) + regexp_parser (2.0.3) rexml (3.2.4) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.3) - rspec-support (~> 3.9.3) - rspec-expectations (3.9.2) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.1) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.3) + rspec-support (~> 3.10.0) + rspec-support (3.10.1) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (0.93.1) + rubocop (1.8.1) parallel (~> 1.10) - parser (>= 2.7.1.5) + parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8) + regexp_parser (>= 1.8, < 3.0) rexml - rubocop-ast (>= 0.6.0) + rubocop-ast (>= 1.2.0, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 2.0) - rubocop-ast (0.8.0) + unicode-display_width (>= 1.4.0, < 3.0) + rubocop-ast (1.4.0) parser (>= 2.7.1.5) ruby-enum (0.8.0) i18n - ruby-progressbar (1.10.1) + ruby-progressbar (1.11.0) + ruby2_keywords (0.0.4) sawyer (0.8.2) addressable (>= 2.3.5) faraday (> 0.8, < 2.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - tzinfo (1.2.7) - thread_safe (~> 0.1) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) unicode-display_width (1.7.0) - zeitwerk (2.4.0) + zeitwerk (2.4.2) PLATFORMS ruby diff --git a/scripts/package.json b/scripts/package.json index 0057077..75fdb56 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -39,13 +39,13 @@ "popper.js": "^1.16.1", "prop-types": "^15.7.2", "rc-slider": "Galvanize-IT/slider#c569ca3b11979aced8306e6c02f37466cb7cd365", - "react": "^16.14.0", + "react": "^17.0.1", "react-addons-css-transition-group": "^15.6.2", "react-addons-test-utils": "^15.6.2", "react-beautiful-dnd": "^13.0.0", "react-copy-to-clipboard": "^5.0.3", "react-datepicker": "^3.4.1", - "react-dom": "^16.14.0", + "react-dom": "^17.0.1", "react-json-pretty": "^2.2.0", "react-paginate": "^7.0.0", "react-pdf": "^4.2.0", @@ -63,12 +63,12 @@ "@types/lodash-es": "^4.17.3", "@types/node-fetch": "^2.5.8", "@types/rc-slider": "^8.6.6", - "@types/react": "^16.9.56", + "@types/react": "^17.0.0", "@types/react-addons-css-transition-group": "^15.0.5", "@types/react-beautiful-dnd": "^13.0.0", "@types/react-copy-to-clipboard": "^5.0.0", "@types/react-datepicker": "^3.1.3", - "@types/react-dom": "^16.9.9", + "@types/react-dom": "^17.0.0", "@types/react-textarea-autosize": "^4.3.5", "@types/vimeo__player": "^2.10.0", "eslint": "^7.18.0", -- GitLab From e666c25a064546935cf7ce8827319f949f6aec13 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 22 Jan 2021 19:38:03 -1000 Subject: [PATCH 231/287] upgrading lodash Former-commit-id: 64cdeba5fa1575f4b8df26c575ebeef86790aac6 --- scripts/bundles/bundle.tar.gz.REMOVED.git-id | 2 +- scripts/bundles/node_modules.tar.gz.REMOVED.git-id | 2 +- scripts/package.json | 3 +++ scripts/yarn.lock | 6 +----- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/scripts/bundles/bundle.tar.gz.REMOVED.git-id b/scripts/bundles/bundle.tar.gz.REMOVED.git-id index 4d2c500..89036ce 100644 --- a/scripts/bundles/bundle.tar.gz.REMOVED.git-id +++ b/scripts/bundles/bundle.tar.gz.REMOVED.git-id @@ -1 +1 @@ -200756010d7b37199ed54c0795a6c377cb33f8aa \ No newline at end of file +10ac483f32e0fca9d2c80d9d452ebe0909cc8565 \ No newline at end of file diff --git a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id index f47e9e9..c8dd64b 100644 --- a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id +++ b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id @@ -1 +1 @@ -9d9fcd20003a816854393a6e7ac24b19b3a0b904 \ No newline at end of file +496063f7686db1ceb7714693ad3608674591c583 \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index 75fdb56..14e26cb 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -82,5 +82,8 @@ "postcss-import": "^12.0.1", "postcss-preset-env": "^6.7.0", "webpack-dev-server": "^3.11.2" + }, + "resolutions": { + "lodash": "^4.17.20" } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index c271a6d..cfb8198 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -5333,11 +5333,7 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.4, lodash@~4.17.10: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - -lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.5: +lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== -- GitLab From 09af63f84055c0b9a0f94d9cf0cf9407c2182d5d Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 22 Jan 2021 22:58:26 -1000 Subject: [PATCH 232/287] fixing docker file. updating modules Former-commit-id: d84029603e55753a29056d94132159677628783b --- Dockerfile | 8 +- scripts/bundles/bundle.tar.gz.REMOVED.git-id | 2 +- .../node_modules.tar.gz.REMOVED.git-id | 2 +- scripts/package.json | 10 ++- scripts/yarn.lock | 83 +++++++------------ 5 files changed, 46 insertions(+), 59 deletions(-) diff --git a/Dockerfile b/Dockerfile index 817917c..4b7920f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ RUN dnf update -y && dnf install -y \ # Setup our environment WORKDIR /app -ADD ./scripts . +COPY ./scripts . # Switch to the binary directory. WORKDIR /app/bundles @@ -49,6 +49,9 @@ RUN mv yarn-v1.22.5 /app/yarn # Switch back to app directory. WORKDIR /app +# Remove the bundles directory. +RUN rm -rf bundles + # Add write permissions. RUN chown -R 1001 . @@ -69,5 +72,8 @@ RUN bundle exec rake assets:precompile # Asset Clean RUN bundle exec rake assets:clean +# Health check. +HEALTHCHECK none + # Set the entry point. ENTRYPOINT ["/app/entrypoint-server.sh"] diff --git a/scripts/bundles/bundle.tar.gz.REMOVED.git-id b/scripts/bundles/bundle.tar.gz.REMOVED.git-id index 89036ce..33f3ce2 100644 --- a/scripts/bundles/bundle.tar.gz.REMOVED.git-id +++ b/scripts/bundles/bundle.tar.gz.REMOVED.git-id @@ -1 +1 @@ -10ac483f32e0fca9d2c80d9d452ebe0909cc8565 \ No newline at end of file +1e00d7dfa42b51862181fa5607c250fc41de3d22 \ No newline at end of file diff --git a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id index c8dd64b..aee6e2e 100644 --- a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id +++ b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id @@ -1 +1 @@ -496063f7686db1ceb7714693ad3608674591c583 \ No newline at end of file +2c1e25ca234f1529cfb072af94c49b6ff0fb3f69 \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index 14e26cb..0d87798 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -84,6 +84,14 @@ "webpack-dev-server": "^3.11.2" }, "resolutions": { - "lodash": "^4.17.20" + "acorn": "^7.1.1", + "dot-prop": "^4.2.1", + "elliptic": "^6.5.3", + "ini": "^1.3.6", + "kind-of": "^6.0.3", + "lodash": "^4.17.20", + "mathjax": "^2.7.4", + "minimist": "^1.2.3", + "websocket-extensions": "^0.1.4" } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index cfb8198..669aa41 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1297,20 +1297,7 @@ acorn-walk@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.0.0.tgz#c8ba6f0f1aac4b0a9e32d1f0af12be769528f36b" -acorn@^5.2.1: - version "5.7.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" - -acorn@^6.4.1: - version "6.4.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" - integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== - -acorn@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.0.0.tgz#26b8d1cd9a9b700350b71c0905546f64d1284e7a" - -acorn@^7.4.0: +acorn@^5.2.1, acorn@^6.4.1, acorn@^7.0.0, acorn@^7.1.1, acorn@^7.4.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== @@ -3184,9 +3171,10 @@ domutils@^1.7.0: dom-serializer "0" domelementtype "1" -dot-prop@^4.1.1: - version "4.2.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" +dot-prop@^4.1.1, dot-prop@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4" + integrity sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ== dependencies: is-obj "^1.0.0" @@ -3237,9 +3225,10 @@ element-resize-detector@^1.2.1: dependencies: batch-processor "1.0.0" -elliptic@^6.0.0: - version "6.5.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.0.tgz#2b8ed4c891b7de3200e14412a5b8248c7af505ca" +elliptic@^6.0.0, elliptic@^6.5.3: + version "6.5.3" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" + integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -4619,9 +4608,10 @@ inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" +ini@^1.3.4, ini@^1.3.5, ini@^1.3.6, ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== inline-source-map@~0.6.0: version "0.6.2" @@ -4737,7 +4727,7 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-buffer@^1.1.0, is-buffer@^1.1.5: +is-buffer@^1.1.0: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -5124,25 +5114,10 @@ killable@^1.0.1: resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" integrity sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg== -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - -kind-of@^6.0.0, kind-of@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0, kind-of@^4.0.0, kind-of@^5.0.0, kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== labeled-stream-splicer@^2.0.0: version "2.0.2" @@ -5420,6 +5395,11 @@ marked@^1.2.7: resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.7.tgz#6e14b595581d2319cdcf033a24caaf41455a01fb" integrity sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA== +mathjax@^2.7.4: + version "2.7.9" + resolved "https://registry.yarnpkg.com/mathjax/-/mathjax-2.7.9.tgz#d6b67955c173e7d719fcb2fc0288662884eb7d3d" + integrity sha512-NOGEDTIM9+MrsqnjPEjVGNx4q0GQxqm61yQwSK+/5S59i26wId5IC5gNu9/bu8+CCVl5p9G2IHcAl/wJa+5+BQ== + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -5569,15 +5549,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - -minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -minimist@^1.1.1, minimist@^1.2.5: +minimist@0.0.8, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -9376,9 +9348,10 @@ websocket-driver@^0.7.4: safe-buffer ">=5.1.0" websocket-extensions ">=0.1.1" -websocket-extensions@>=0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" +websocket-extensions@>=0.1.1, websocket-extensions@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== which-module@^2.0.0: version "2.0.0" -- GitLab From fde06f263ef88c70922f0b239b46389f978f83a6 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Sat, 23 Jan 2021 11:40:21 -1000 Subject: [PATCH 233/287] testing remove of node and yarn after build Former-commit-id: ffc64689a435283aad60303d44b9f13cf04cad3b --- Dockerfile | 5 +++++ scripts/bundles/node_modules.tar.gz.REMOVED.git-id | 2 +- scripts/package.json | 4 ++-- scripts/yarn.lock | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4b7920f..12e46f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,6 +72,11 @@ RUN bundle exec rake assets:precompile # Asset Clean RUN bundle exec rake assets:clean +# Remove node and yarn. +RUN rm -rf nodejs +RUN rm -rf yarn +RUN rm -rf node_modules + # Health check. HEALTHCHECK none diff --git a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id index aee6e2e..a170676 100644 --- a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id +++ b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id @@ -1 +1 @@ -2c1e25ca234f1529cfb072af94c49b6ff0fb3f69 \ No newline at end of file +ef4893c7feaf37f2c0a10e210a0875477edb57ef \ No newline at end of file diff --git a/scripts/package.json b/scripts/package.json index 0d87798..d730cd5 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -87,10 +87,10 @@ "acorn": "^7.1.1", "dot-prop": "^4.2.1", "elliptic": "^6.5.3", - "ini": "^1.3.6", + "ini": "^1.3.8", "kind-of": "^6.0.3", "lodash": "^4.17.20", - "mathjax": "^2.7.4", + "mathjax": "^2.7.9", "minimist": "^1.2.3", "websocket-extensions": "^0.1.4" } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index 669aa41..df7585c 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -4608,7 +4608,7 @@ inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" -ini@^1.3.4, ini@^1.3.5, ini@^1.3.6, ini@~1.3.0: +ini@^1.3.4, ini@^1.3.5, ini@^1.3.8, ini@~1.3.0: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== @@ -5395,7 +5395,7 @@ marked@^1.2.7: resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.7.tgz#6e14b595581d2319cdcf033a24caaf41455a01fb" integrity sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA== -mathjax@^2.7.4: +mathjax@^2.7.9: version "2.7.9" resolved "https://registry.yarnpkg.com/mathjax/-/mathjax-2.7.9.tgz#d6b67955c173e7d719fcb2fc0288662884eb7d3d" integrity sha512-NOGEDTIM9+MrsqnjPEjVGNx4q0GQxqm61yQwSK+/5S59i26wId5IC5gNu9/bu8+CCVl5p9G2IHcAl/wJa+5+BQ== -- GitLab From ee720cdff06283d8b9df033ab1fd027cf567468b Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Sat, 23 Jan 2021 12:04:26 -1000 Subject: [PATCH 234/287] adding node bank in Former-commit-id: 81142bccd1363e875274b78657e421d52d0c3415 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 12e46f3..c19aaa7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,7 +73,7 @@ RUN bundle exec rake assets:precompile RUN bundle exec rake assets:clean # Remove node and yarn. -RUN rm -rf nodejs +#RUN rm -rf nodejs RUN rm -rf yarn RUN rm -rf node_modules -- GitLab From 224a4583ee8ff52e0a5122b6ee1f683dfcd4d507 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Sat, 23 Jan 2021 12:42:03 -1000 Subject: [PATCH 235/287] updating npm version Former-commit-id: 0783c5d7559a1febb49a124ae64be1d2a759ea4f --- Dockerfile | 3 +-- scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id | 2 +- scripts/bundles/node_modules.tar.gz.REMOVED.git-id | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index c19aaa7..be21cc7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,8 +72,7 @@ RUN bundle exec rake assets:precompile # Asset Clean RUN bundle exec rake assets:clean -# Remove node and yarn. -#RUN rm -rf nodejs +# Remove yarn and node modules after compile. RUN rm -rf yarn RUN rm -rf node_modules diff --git a/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id b/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id index e1ab22e..5b59680 100644 --- a/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id +++ b/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id @@ -1 +1 @@ -95016bc120595a9214116f158624cf9ea07e7cb8 \ No newline at end of file +47adb6c4a299fbd9ecc10f90ca0dcd5dd1e6d4d1 \ No newline at end of file diff --git a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id index a170676..e925b2c 100644 --- a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id +++ b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id @@ -1 +1 @@ -ef4893c7feaf37f2c0a10e210a0875477edb57ef \ No newline at end of file +20baab03056513e3e89c5d262b91b38004897dc8 \ No newline at end of file -- GitLab From dd92e957754f5343255944e3c7584b107afaa0ec Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Sat, 23 Jan 2021 14:09:10 -1000 Subject: [PATCH 236/287] fixing bug in dockerfile Former-commit-id: 4e3ae887bad8533ab69ea492e2cd14713ca32a95 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index be21cc7..a895894 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,7 +39,7 @@ RUN mv node_modules /app/node_modules # Node.js RUN tar xzf node-v14.15.4-linux-x64.tar.gz RUN rm node-v14.15.4-linux-x64.tar.gz -RUN mv node-v14.15.4-linux-x64 /app/nodejs +RUN mv nodejs /app/nodejs # Yarn RUN tar xzf yarn-v1.22.5.tar.gz -- GitLab From 408b22b962e72860350546f81d18ad3faa1842b7 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Sat, 23 Jan 2021 14:19:04 -1000 Subject: [PATCH 237/287] removing symbolic link Former-commit-id: 72f243bcf0526d27c2a1fb1c6922aa7f2240c8a2 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a895894..e368355 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,7 +64,7 @@ ENV NODE_HOME /app/nodejs ENV PATH /app/node_modules/.bin:$PATH ENV PATH /app/yarn/bin:$PATH ENV PATH /app/nodejs/bin:$PATH -RUN ln -s /app/nodejs/bin/node /app/nodejs/bin/nodejs +#RUN ln -s /app/nodejs/bin/node /app/nodejs/bin/nodejs # Precompile assets RUN bundle exec rake assets:precompile -- GitLab From 12516b5faad11e7c090f78abe9073b2008963ba0 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Sun, 24 Jan 2021 02:21:26 +0000 Subject: [PATCH 238/287] Changing the way yarn and nodejs are loaded. Former-commit-id: 242003b11948c8232935d540037bba17c4ab21ed --- Dockerfile | 12 +++--------- .../node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id | 1 - scripts/bundles/nodejs.tar.gz.REMOVED.git-id | 1 + scripts/bundles/yarn-v1.22.5.tar.gz.REMOVED.git-id | 1 - scripts/bundles/yarn.tar.gz.REMOVED.git-id | 1 + 5 files changed, 5 insertions(+), 11 deletions(-) delete mode 100644 scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id create mode 100644 scripts/bundles/nodejs.tar.gz.REMOVED.git-id delete mode 100644 scripts/bundles/yarn-v1.22.5.tar.gz.REMOVED.git-id create mode 100644 scripts/bundles/yarn.tar.gz.REMOVED.git-id diff --git a/Dockerfile b/Dockerfile index e368355..3a3ea20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,28 +23,23 @@ WORKDIR /app/bundles # Redis. RUN tar xzf redis-cli.tar.gz -RUN rm redis-cli.tar.gz RUN mv redis-cli /usr/local/bin/redis-cli # Unzip the bundle directory. RUN tar xzf bundle.tar.gz -RUN rm bundle.tar.gz RUN mv bundle /usr/local/ # Unzip the node_modules. RUN tar xzf node_modules.tar.gz -RUN rm node_modules.tar.gz RUN mv node_modules /app/node_modules # Node.js -RUN tar xzf node-v14.15.4-linux-x64.tar.gz -RUN rm node-v14.15.4-linux-x64.tar.gz +RUN tar xzf nodejs.tar.gz RUN mv nodejs /app/nodejs # Yarn -RUN tar xzf yarn-v1.22.5.tar.gz -RUN rm yarn-v1.22.5.tar.gz -RUN mv yarn-v1.22.5 /app/yarn +RUN tar xzf yarn.tar.gz +RUN mv yarn /app/yarn # Switch back to app directory. WORKDIR /app @@ -64,7 +59,6 @@ ENV NODE_HOME /app/nodejs ENV PATH /app/node_modules/.bin:$PATH ENV PATH /app/yarn/bin:$PATH ENV PATH /app/nodejs/bin:$PATH -#RUN ln -s /app/nodejs/bin/node /app/nodejs/bin/nodejs # Precompile assets RUN bundle exec rake assets:precompile diff --git a/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id b/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id deleted file mode 100644 index 5b59680..0000000 --- a/scripts/bundles/node-v14.15.4-linux-x64.tar.gz.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -47adb6c4a299fbd9ecc10f90ca0dcd5dd1e6d4d1 \ No newline at end of file diff --git a/scripts/bundles/nodejs.tar.gz.REMOVED.git-id b/scripts/bundles/nodejs.tar.gz.REMOVED.git-id new file mode 100644 index 0000000..eceaef7 --- /dev/null +++ b/scripts/bundles/nodejs.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +3e9b0070e0d29a9b3cba05c6b53c8c608529e5d9 \ No newline at end of file diff --git a/scripts/bundles/yarn-v1.22.5.tar.gz.REMOVED.git-id b/scripts/bundles/yarn-v1.22.5.tar.gz.REMOVED.git-id deleted file mode 100644 index 89957a7..0000000 --- a/scripts/bundles/yarn-v1.22.5.tar.gz.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -2ffdf471020c0266a0c89ca2c125833d251e5181 \ No newline at end of file diff --git a/scripts/bundles/yarn.tar.gz.REMOVED.git-id b/scripts/bundles/yarn.tar.gz.REMOVED.git-id new file mode 100644 index 0000000..98061eb --- /dev/null +++ b/scripts/bundles/yarn.tar.gz.REMOVED.git-id @@ -0,0 +1 @@ +18cf0892e6d57bc9876394d4d7f952dca3e6ca09 \ No newline at end of file -- GitLab From acff9942a4bfa967225801af547cceaeed778f0f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Sat, 23 Jan 2021 16:27:36 -1000 Subject: [PATCH 239/287] adding scripts to rebuild the bundles. Former-commit-id: 0c15fddf37fe2e531d0bd50c57576440a2716e00 --- .gitignore | 4 +- scripts/Dockerfile.packages | 84 +++++++++++++++++++++++++++++++++++++ scripts/repackage.sh | 38 +++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 scripts/Dockerfile.packages create mode 100755 scripts/repackage.sh diff --git a/.gitignore b/.gitignore index d313f3c..651d0b2 100644 --- a/.gitignore +++ b/.gitignore @@ -34,5 +34,5 @@ tags !/scripts/public/assets/images log.txt sidekiq.log -Dockerfile.packages -repackage.sh +#Dockerfile.packages +#repackage.sh diff --git a/scripts/Dockerfile.packages b/scripts/Dockerfile.packages new file mode 100644 index 0000000..68f7093 --- /dev/null +++ b/scripts/Dockerfile.packages @@ -0,0 +1,84 @@ +ARG BASE_REGISTRY +ARG BASE_IMAGE +ARG BASE_TAG + +FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} +USER 0 + +ARG NODE_VERSION +ARG YARN_VERSION + +# Install required libs. +RUN dnf update -y && dnf install -y \ + curl \ + make \ + patch \ + git \ + zlib-devel \ + libpq-devel \ + libxml2-devel \ + libxslt-devel \ + gcc \ + gcc-c++ + +# Setup our environment +WORKDIR /app +ADD . . + +# Switch to the binary directory. +WORKDIR /app/bundles + +# Node.js +RUN curl -L https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz -o node-v$NODE_VERSION-linux-x64.tar.gz +RUN tar xzf node-v$NODE_VERSION-linux-x64.tar.gz +RUN mv node-v$NODE_VERSION-linux-x64 /app/nodejs + +# Yarn +RUN curl -L https://yarnpkg.com/downloads/$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz -o yarn-v$YARN_VERSION.tar.gz +RUN ls -la +RUN tar xzf yarn-v$YARN_VERSION.tar.gz +RUN mv yarn-v$YARN_VERSION /app/yarn + +# Switch back to app dir. +WORKDIR /app + +# Add write permissions. +RUN chown -R 1001 . + +# Become the ruby user +USER 1001 + +# Set Environment Variables +ENV NODE_HOME /app/nodejs +ENV PATH /app/node_modules/.bin:$PATH +ENV PATH /app/yarn/bin:$PATH +ENV PATH /app/nodejs/bin:$PATH +RUN ln -s /app/nodejs/bin/node /app/nodejs/bin/nodejs + +# Upgrade npm and repackage node. +RUN npm i -g npm +RUN tar czf nodejs.tar.gz nodejs + +# Repackage yarn. +RUN tar czf yarn.tar.gz yarn + +# Precompile assets +RUN yarn install +RUN tar czf node_modules.tar.gz node_modules + +# Run bundle install. +RUN gem install bundler +RUN bundle install + +# Go to bundle dir. +WORKDIR /usr/local +USER 0 +RUN tar czf bundle.tar.gz bundle +RUN cp bundle.tar.gz /app/ +USER 1001 + +# Change back to app dir. +WORKDIR /app + +# Set the entry point. +CMD tail -f /dev/null diff --git a/scripts/repackage.sh b/scripts/repackage.sh new file mode 100755 index 0000000..2fff3c7 --- /dev/null +++ b/scripts/repackage.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +BASE_REGISTRY=registry.il2.dso.mil +BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 +BASE_TAG=2.6.6.212 +NODE_VERSION=14.15.4 +YARN_VERSION=1.22.5 + +echo "Clearing node_modules." +rm -rf node_modules + +#echo "Switch to correct node version." +#source ~/.zprofile +#nvm use +# +#echo "Update yarn lock file." +#yarn install +# +#echo "Remove node_modules directory." +#rm -rf node_modules + +echo "Building docker image." +IMAGE_ID=$(docker build --file Dockerfile.packages . -q --build-arg BASE_REGISTRY=$BASE_REGISTRY --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg BASE_TAG=$BASE_TAG --build-arg NODE_VERSION=$NODE_VERSION --build-arg YARN_VERSION=$YARN_VERSION) +echo "Image ID: ${IMAGE_ID}" + +echo "Starting docker container." +CONTAINER_ID=$(docker run -d $IMAGE_ID) +echo "Container ID: ${CONTAINER_ID}" + +echo "Copying node modules and gems to the bundles directory." +docker cp $CONTAINER_ID:/app/node_modules.tar.gz bundles +docker cp $CONTAINER_ID:/app/nodejs.tar.gz bundles +docker cp $CONTAINER_ID:/app/bundle.tar.gz bundles +docker cp $CONTAINER_ID:/app/yarn.tar.gz bundles + +echo "Stopping the docker container." +docker stop $CONTAINER_ID +echo "Success" \ No newline at end of file -- GitLab From d32f05d13db80d3d5219090e40c8e25b3f38078f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 25 Jan 2021 16:14:26 -1000 Subject: [PATCH 240/287] Updating repackage script Former-commit-id: 3a768baa3ba9f2c46f559b4273f8655e49b89f90 --- scripts/Dockerfile.packages | 10 ++++++++++ scripts/repackage.sh | 15 +++++++++++---- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/scripts/Dockerfile.packages b/scripts/Dockerfile.packages index 68f7093..d65779c 100644 --- a/scripts/Dockerfile.packages +++ b/scripts/Dockerfile.packages @@ -28,6 +28,16 @@ ADD . . # Switch to the binary directory. WORKDIR /app/bundles +# Redis +RUN curl -L http://download.redis.io/redis-stable.tar.gz -o redis-stable.tar.gz +RUN tar xzf redis-stable.tar.gz +WORKDIR /app/bundles/redis-stable +RUN make redis-cli +WORKDIR /app/bundles/redis-stable/src +RUN tar czf redis-cli.tar.gz redis-cli +RUN mv redis-cli.tar.gz /app/ +WORKDIR /app/bundles + # Node.js RUN curl -L https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz -o node-v$NODE_VERSION-linux-x64.tar.gz RUN tar xzf node-v$NODE_VERSION-linux-x64.tar.gz diff --git a/scripts/repackage.sh b/scripts/repackage.sh index 2fff3c7..06991ca 100755 --- a/scripts/repackage.sh +++ b/scripts/repackage.sh @@ -6,6 +6,12 @@ BASE_TAG=2.6.6.212 NODE_VERSION=14.15.4 YARN_VERSION=1.22.5 +COPY_NODE_MODULES=false +COPY_NODEJS=false +COPY_YARN=false +COPY_REDIS_CLI=false +COPY_BUNDLE=false + echo "Clearing node_modules." rm -rf node_modules @@ -28,10 +34,11 @@ CONTAINER_ID=$(docker run -d $IMAGE_ID) echo "Container ID: ${CONTAINER_ID}" echo "Copying node modules and gems to the bundles directory." -docker cp $CONTAINER_ID:/app/node_modules.tar.gz bundles -docker cp $CONTAINER_ID:/app/nodejs.tar.gz bundles -docker cp $CONTAINER_ID:/app/bundle.tar.gz bundles -docker cp $CONTAINER_ID:/app/yarn.tar.gz bundles +if [ $COPY_NODE_MODULES == "true" ] ; then docker cp $CONTAINER_ID:/app/node_modules.tar.gz bundles ; fi +if [ $COPY_NODEJS == "true" ] ; then docker cp $CONTAINER_ID:/app/nodejs.tar.gz bundles ; fi +if [ $COPY_YARN == "true" ] ; then docker cp $CONTAINER_ID:/app/yarn.tar.gz bundles ; fi +if [ $COPY_REDIS_CLI == "true" ] ; then docker cp $CONTAINER_ID:/app/redis-cli.tar.gz bundles ; fi +if [ $COPY_BUNDLE == "true" ] ; then docker cp $CONTAINER_ID:/app/bundle.tar.gz bundles ; fi echo "Stopping the docker container." docker stop $CONTAINER_ID -- GitLab From 8411ba6b0b206c6cf4b6c1d96c9827e51a22c519 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 25 Jan 2021 16:23:00 -1000 Subject: [PATCH 241/287] testing which packages are required Former-commit-id: e92cdb7ed3760079873490096d5c1479ed3125f9 --- Dockerfile | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3a3ea20..405e31a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,17 +2,19 @@ FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 # Install required libs. -RUN dnf update -y && dnf install -y \ - curl \ - make \ - patch \ - git \ - zlib-devel \ - libpq-devel \ - libxml2-devel \ - libxslt-devel \ - gcc \ - gcc-c++ +#RUN dnf update -y && dnf install -y \ +# curl \ +# make \ +# patch \ +# git \ +# zlib-devel \ +# libpq-devel \ +# libxml2-devel \ +# libxslt-devel \ +# gcc \ +# gcc-c++ + +RUN dnf update && dnf clean # Setup our environment WORKDIR /app -- GitLab From 95bcb9beac180bacd3d4b4c649a5d1da6efe6699 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 25 Jan 2021 16:35:42 -1000 Subject: [PATCH 242/287] fixing prompt Former-commit-id: ac82c7d20f802af28abb6ab2286542fa21064c79 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 405e31a..bb3b764 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ USER 0 # gcc \ # gcc-c++ -RUN dnf update && dnf clean +RUN dnf update -y && dnf clean -y # Setup our environment WORKDIR /app -- GitLab From f07041cac992bda300be6ed097fdd7cf7813eef3 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 25 Jan 2021 16:41:47 -1000 Subject: [PATCH 243/287] fixing prompt Former-commit-id: 413da45d0fb6fb16e7431abf6b322de170c74a4f --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index bb3b764..eeefa5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ USER 0 # gcc \ # gcc-c++ -RUN dnf update -y && dnf clean -y +RUN dnf update -y && dnf clean all # Setup our environment WORKDIR /app -- GitLab From 68d9538dca7fd4fe4f749d5b0c0424de84544301 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 25 Jan 2021 17:05:51 -1000 Subject: [PATCH 244/287] changing back Former-commit-id: c4ecd0b180d24f403f4e17cdcbd197d84bb9dd2d --- Dockerfile | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index eeefa5e..b56b557 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,19 +2,17 @@ FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 # Install required libs. -#RUN dnf update -y && dnf install -y \ -# curl \ -# make \ -# patch \ -# git \ -# zlib-devel \ -# libpq-devel \ -# libxml2-devel \ -# libxslt-devel \ -# gcc \ -# gcc-c++ - -RUN dnf update -y && dnf clean all +RUN dnf update -y && dnf install -y \ + curl \ + make \ + patch \ + git \ + zlib-devel \ + libpq-devel \ + libxml2-devel \ + libxslt-devel \ + gcc \ + gcc-c++ # Setup our environment WORKDIR /app -- GitLab From 33e04c56da208d4bbe6607369949b99f6b9c291f Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 25 Jan 2021 17:20:10 -1000 Subject: [PATCH 245/287] adding libqp back in Former-commit-id: fca206a95e5d97a25bd857b3adbdf6f7de37f656 --- Dockerfile | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index b56b557..28ba5e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,16 +3,16 @@ USER 0 # Install required libs. RUN dnf update -y && dnf install -y \ - curl \ - make \ - patch \ - git \ - zlib-devel \ - libpq-devel \ - libxml2-devel \ - libxslt-devel \ - gcc \ - gcc-c++ +# curl \ +# make \ +# patch \ +# git \ +# zlib-devel \ + libpq-devel +# libxml2-devel \ +# libxslt-devel \ +# gcc \ +# gcc-c++ # Setup our environment WORKDIR /app -- GitLab From 21c4b07326b2bf4adc4dccdf6edf5ef90b750d5e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 25 Jan 2021 18:43:48 -1000 Subject: [PATCH 246/287] testing Former-commit-id: 1be329efca37ef0a26c88c8fdc0e06f332f92404 --- Dockerfile | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 28ba5e3..bc245a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,16 +3,17 @@ USER 0 # Install required libs. RUN dnf update -y && dnf install -y \ -# curl \ -# make \ -# patch \ -# git \ -# zlib-devel \ - libpq-devel -# libxml2-devel \ -# libxslt-devel \ -# gcc \ -# gcc-c++ + curl \ + make \ + patch \ + git \ + zlib-devel \ + libpq-devel \ + libxml2-devel \ + libxslt-devel \ + gcc \ + gcc-c++ \ + && dnf clean all # Setup our environment WORKDIR /app -- GitLab From 464e12176ccab8527b5ce74187794d8674ded242 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 26 Jan 2021 09:23:06 -1000 Subject: [PATCH 247/287] logging Former-commit-id: e3de9641bb064446451317fb247400ef2edd0855 --- scripts/config/application.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/config/application.rb b/scripts/config/application.rb index ef9d3f5..c3d5db7 100644 --- a/scripts/config/application.rb +++ b/scripts/config/application.rb @@ -36,6 +36,8 @@ module Forge port: Rails.application.secrets.actionmailer_port } + puts config.action_mailer.default_url_options + config.react.jsx_transform_options = { blacklist: ["spec.functionName", "validation.react", "strict"], # default options optional: ["es7.classProperties"] # pass extra babel options -- GitLab From a486a085d1973b3db137ff84ccadd94f0d3810e4 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 26 Jan 2021 12:58:31 -1000 Subject: [PATCH 248/287] testing Former-commit-id: 342fc23bcf789549c22e313b9a0fb9955cbc13b8 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index bc245a0..606150a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -68,8 +68,8 @@ RUN bundle exec rake assets:precompile RUN bundle exec rake assets:clean # Remove yarn and node modules after compile. -RUN rm -rf yarn -RUN rm -rf node_modules +#RUN rm -rf yarn +#RUN rm -rf node_modules # Health check. HEALTHCHECK none -- GitLab From f0f9f72b189399716d50d86ffac8c83323b2b718 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Tue, 26 Jan 2021 14:51:09 -1000 Subject: [PATCH 249/287] testing Former-commit-id: 4cc7359560eff08eeb4986f1c74344c130d1719e --- Dockerfile | 22 +++++++++++----------- scripts/config/application.rb | 2 -- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 606150a..f52494b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,16 +3,16 @@ USER 0 # Install required libs. RUN dnf update -y && dnf install -y \ - curl \ - make \ - patch \ - git \ - zlib-devel \ +# curl \ +# make \ +# patch \ +# git \ +# zlib-devel \ libpq-devel \ - libxml2-devel \ - libxslt-devel \ - gcc \ - gcc-c++ \ +# libxml2-devel \ +# libxslt-devel \ +# gcc \ +# gcc-c++ \ && dnf clean all # Setup our environment @@ -68,8 +68,8 @@ RUN bundle exec rake assets:precompile RUN bundle exec rake assets:clean # Remove yarn and node modules after compile. -#RUN rm -rf yarn -#RUN rm -rf node_modules +RUN rm -rf yarn +RUN rm -rf node_modules # Health check. HEALTHCHECK none diff --git a/scripts/config/application.rb b/scripts/config/application.rb index c3d5db7..ef9d3f5 100644 --- a/scripts/config/application.rb +++ b/scripts/config/application.rb @@ -36,8 +36,6 @@ module Forge port: Rails.application.secrets.actionmailer_port } - puts config.action_mailer.default_url_options - config.react.jsx_transform_options = { blacklist: ["spec.functionName", "validation.react", "strict"], # default options optional: ["es7.classProperties"] # pass extra babel options -- GitLab From 5c0777af7e945a8aa068425f32eeca303a4be79a Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 27 Jan 2021 15:40:01 -1000 Subject: [PATCH 250/287] removing npm, removining jquery 1 and 2. switching to jquery 3 Former-commit-id: 8723cc416172e8cae344b1f3b12c0b31eb96a9a7 --- scripts/Dockerfile.packages | 7 +++++-- scripts/app/assets/javascripts/application.js | 2 +- scripts/bundles/bundle.tar.gz.REMOVED.git-id | 2 +- scripts/bundles/nodejs.tar.gz.REMOVED.git-id | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/Dockerfile.packages b/scripts/Dockerfile.packages index d65779c..ae6d933 100644 --- a/scripts/Dockerfile.packages +++ b/scripts/Dockerfile.packages @@ -65,8 +65,8 @@ ENV PATH /app/yarn/bin:$PATH ENV PATH /app/nodejs/bin:$PATH RUN ln -s /app/nodejs/bin/node /app/nodejs/bin/nodejs -# Upgrade npm and repackage node. -RUN npm i -g npm +# Remove NPM and repackage node. +RUN rm -rf /app/nodejs/bin/nodejs/lib/node_modules/npm RUN tar czf nodejs.tar.gz nodejs # Repackage yarn. @@ -80,6 +80,9 @@ RUN tar czf node_modules.tar.gz node_modules RUN gem install bundler RUN bundle install +# Remove extra JS files from jquery gem. + + # Go to bundle dir. WORKDIR /usr/local USER 0 diff --git a/scripts/app/assets/javascripts/application.js b/scripts/app/assets/javascripts/application.js index fb0297b..0878c57 100644 --- a/scripts/app/assets/javascripts/application.js +++ b/scripts/app/assets/javascripts/application.js @@ -1,4 +1,4 @@ -//= require jquery +//= require jquery3 //= require rails-ujs //= require popper //= require bootstrap-sprockets diff --git a/scripts/bundles/bundle.tar.gz.REMOVED.git-id b/scripts/bundles/bundle.tar.gz.REMOVED.git-id index 33f3ce2..04f748c 100644 --- a/scripts/bundles/bundle.tar.gz.REMOVED.git-id +++ b/scripts/bundles/bundle.tar.gz.REMOVED.git-id @@ -1 +1 @@ -1e00d7dfa42b51862181fa5607c250fc41de3d22 \ No newline at end of file +e642701e6e136d20901a5e81ff06b259fbb6691d \ No newline at end of file diff --git a/scripts/bundles/nodejs.tar.gz.REMOVED.git-id b/scripts/bundles/nodejs.tar.gz.REMOVED.git-id index eceaef7..60c726e 100644 --- a/scripts/bundles/nodejs.tar.gz.REMOVED.git-id +++ b/scripts/bundles/nodejs.tar.gz.REMOVED.git-id @@ -1 +1 @@ -3e9b0070e0d29a9b3cba05c6b53c8c608529e5d9 \ No newline at end of file +a10b8c2e56c335960e156f7c4ed6b8f433d0a2bd \ No newline at end of file -- GitLab From d18c846d7c2ac039406e5f61572b546aef937cf1 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Wed, 27 Jan 2021 16:29:32 -1000 Subject: [PATCH 251/287] added setting action mailer variables on sidekiq Former-commit-id: 0c7f5228779abb390414e0a3f7458fa15ca9a7c1 --- scripts/config/initializers/sidekiq.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/config/initializers/sidekiq.rb b/scripts/config/initializers/sidekiq.rb index 1668b85..6a70575 100644 --- a/scripts/config/initializers/sidekiq.rb +++ b/scripts/config/initializers/sidekiq.rb @@ -2,3 +2,12 @@ if ENV["INLINE_SIDEKIQ"] == "true" require "sidekiq/testing/inline" Sidekiq::Testing.inline! end + +Rails.application.configure do + config.action_mailer.default_url_options = { + host: Rails.application.secrets.actionmailer_host, + port: Rails.application.secrets.actionmailer_port + } + + puts config.action_mailer.default_url_options +end \ No newline at end of file -- GitLab From 880d7eac4a82391c28d27ce851f2565b1eff601e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 28 Jan 2021 09:36:44 -1000 Subject: [PATCH 252/287] Fixing node package Former-commit-id: eb5278a85782336bb3a69b19c1a81d1611a62770 --- scripts/Dockerfile.packages | 2 +- scripts/bundles/nodejs.tar.gz.REMOVED.git-id | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/Dockerfile.packages b/scripts/Dockerfile.packages index ae6d933..51c6cca 100644 --- a/scripts/Dockerfile.packages +++ b/scripts/Dockerfile.packages @@ -66,7 +66,7 @@ ENV PATH /app/nodejs/bin:$PATH RUN ln -s /app/nodejs/bin/node /app/nodejs/bin/nodejs # Remove NPM and repackage node. -RUN rm -rf /app/nodejs/bin/nodejs/lib/node_modules/npm +RUN rm -rf /app/nodejs/lib/node_modules/npm RUN tar czf nodejs.tar.gz nodejs # Repackage yarn. diff --git a/scripts/bundles/nodejs.tar.gz.REMOVED.git-id b/scripts/bundles/nodejs.tar.gz.REMOVED.git-id index 60c726e..6370c52 100644 --- a/scripts/bundles/nodejs.tar.gz.REMOVED.git-id +++ b/scripts/bundles/nodejs.tar.gz.REMOVED.git-id @@ -1 +1 @@ -a10b8c2e56c335960e156f7c4ed6b8f433d0a2bd \ No newline at end of file +57e2f57eab29bd0c17dd1fbf1fee4bfed7d14d8c \ No newline at end of file -- GitLab From 009fea675e59df96650588832d2a280bbf695e97 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 28 Jan 2021 21:11:54 -1000 Subject: [PATCH 253/287] updating package scripts Former-commit-id: 8beb675aed0b4b9d8c3000989f7c0197c0c06506 --- scripts/Dockerfile.packages | 13 +++++++++---- scripts/repackage.sh | 3 ++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/Dockerfile.packages b/scripts/Dockerfile.packages index 51c6cca..922aec7 100644 --- a/scripts/Dockerfile.packages +++ b/scripts/Dockerfile.packages @@ -7,6 +7,7 @@ USER 0 ARG NODE_VERSION ARG YARN_VERSION +ARG JQUERY_RAILS_VERSION # Install required libs. RUN dnf update -y && dnf install -y \ @@ -78,17 +79,21 @@ RUN tar czf node_modules.tar.gz node_modules # Run bundle install. RUN gem install bundler +RUN bundle config set --local without 'development:test' RUN bundle install -# Remove extra JS files from jquery gem. +# Switch back to root user. +USER 0 +# Go to jquery-rails directory +WORKDIR /usr/local/bundle/gems/jquery-rails-$JQUERY_RAILS_VERSION/vendor/assets/javascripts +RUN ls -la +RUN rm jquery_ujs.js jquery.js jquery.min.js jquery.min.map jquery2.js jquery2.min.js jquery2.min.map -# Go to bundle dir. +# Package Rails Gems. WORKDIR /usr/local -USER 0 RUN tar czf bundle.tar.gz bundle RUN cp bundle.tar.gz /app/ -USER 1001 # Change back to app dir. WORKDIR /app diff --git a/scripts/repackage.sh b/scripts/repackage.sh index 06991ca..700cef8 100755 --- a/scripts/repackage.sh +++ b/scripts/repackage.sh @@ -5,6 +5,7 @@ BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 BASE_TAG=2.6.6.212 NODE_VERSION=14.15.4 YARN_VERSION=1.22.5 +JQUERY_RAILS_VERSION=4.4.0 COPY_NODE_MODULES=false COPY_NODEJS=false @@ -26,7 +27,7 @@ rm -rf node_modules #rm -rf node_modules echo "Building docker image." -IMAGE_ID=$(docker build --file Dockerfile.packages . -q --build-arg BASE_REGISTRY=$BASE_REGISTRY --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg BASE_TAG=$BASE_TAG --build-arg NODE_VERSION=$NODE_VERSION --build-arg YARN_VERSION=$YARN_VERSION) +IMAGE_ID=$(docker build --file Dockerfile.packages . -q --build-arg BASE_REGISTRY=$BASE_REGISTRY --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg BASE_TAG=$BASE_TAG --build-arg NODE_VERSION=$NODE_VERSION --build-arg YARN_VERSION=$YARN_VERSION --build-arg JQUERY_RAILS_VERSION=$JQUERY_RAILS_VERSION) echo "Image ID: ${IMAGE_ID}" echo "Starting docker container." -- GitLab From ce2f1181ff05367bc329f377a1c4eab3d11d3ad5 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 29 Jan 2021 17:18:39 -1000 Subject: [PATCH 254/287] updating package shell script to upload dependencies to s3 Former-commit-id: d4b386c5a727be408a2660ddea2bda23b5d140f9 --- .gitignore | 3 +-- scripts/repackage.sh | 54 ++++++++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 651d0b2..510b35a 100644 --- a/.gitignore +++ b/.gitignore @@ -34,5 +34,4 @@ tags !/scripts/public/assets/images log.txt sidekiq.log -#Dockerfile.packages -#repackage.sh +build_dependencies diff --git a/scripts/repackage.sh b/scripts/repackage.sh index 700cef8..60f7f0c 100755 --- a/scripts/repackage.sh +++ b/scripts/repackage.sh @@ -1,33 +1,36 @@ #!/bin/bash +# Image Params. BASE_REGISTRY=registry.il2.dso.mil BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 BASE_TAG=2.6.6.212 + +# Package Versions. NODE_VERSION=14.15.4 YARN_VERSION=1.22.5 JQUERY_RAILS_VERSION=4.4.0 -COPY_NODE_MODULES=false -COPY_NODEJS=false -COPY_YARN=false -COPY_REDIS_CLI=false -COPY_BUNDLE=false +# This is the profile name in your aws credentials file. +DEPENDENCY_FOLDER=build_dependencies +AWS_PROFILE_NAME=revacomm +AWS_BUCKET_NAME=learn-dependencies/forge +AWS_REGION=us-gov-west-1 + +echo "Clearing dependency folder." +rm -rf $DEPENDENCY_FOLDER +mkdir $DEPENDENCY_FOLDER echo "Clearing node_modules." rm -rf node_modules -#echo "Switch to correct node version." -#source ~/.zprofile -#nvm use -# -#echo "Update yarn lock file." -#yarn install -# -#echo "Remove node_modules directory." -#rm -rf node_modules - echo "Building docker image." -IMAGE_ID=$(docker build --file Dockerfile.packages . -q --build-arg BASE_REGISTRY=$BASE_REGISTRY --build-arg BASE_IMAGE=$BASE_IMAGE --build-arg BASE_TAG=$BASE_TAG --build-arg NODE_VERSION=$NODE_VERSION --build-arg YARN_VERSION=$YARN_VERSION --build-arg JQUERY_RAILS_VERSION=$JQUERY_RAILS_VERSION) +IMAGE_ID=$(docker build --file Dockerfile.packages . -q \ + --build-arg BASE_REGISTRY=$BASE_REGISTRY \ + --build-arg BASE_IMAGE=$BASE_IMAGE \ + --build-arg BASE_TAG=$BASE_TAG \ + --build-arg NODE_VERSION=$NODE_VERSION \ + --build-arg YARN_VERSION=$YARN_VERSION \ + --build-arg JQUERY_RAILS_VERSION=$JQUERY_RAILS_VERSION) echo "Image ID: ${IMAGE_ID}" echo "Starting docker container." @@ -35,12 +38,19 @@ CONTAINER_ID=$(docker run -d $IMAGE_ID) echo "Container ID: ${CONTAINER_ID}" echo "Copying node modules and gems to the bundles directory." -if [ $COPY_NODE_MODULES == "true" ] ; then docker cp $CONTAINER_ID:/app/node_modules.tar.gz bundles ; fi -if [ $COPY_NODEJS == "true" ] ; then docker cp $CONTAINER_ID:/app/nodejs.tar.gz bundles ; fi -if [ $COPY_YARN == "true" ] ; then docker cp $CONTAINER_ID:/app/yarn.tar.gz bundles ; fi -if [ $COPY_REDIS_CLI == "true" ] ; then docker cp $CONTAINER_ID:/app/redis-cli.tar.gz bundles ; fi -if [ $COPY_BUNDLE == "true" ] ; then docker cp $CONTAINER_ID:/app/bundle.tar.gz bundles ; fi +docker cp $CONTAINER_ID:/app/node_modules.tar.gz $DEPENDENCY_FOLDER +docker cp $CONTAINER_ID:/app/nodejs.tar.gz $DEPENDENCY_FOLDER +docker cp $CONTAINER_ID:/app/yarn.tar.gz $DEPENDENCY_FOLDER +docker cp $CONTAINER_ID:/app/redis-cli.tar.gz $DEPENDENCY_FOLDER +docker cp $CONTAINER_ID:/app/bundle.tar.gz $DEPENDENCY_FOLDER echo "Stopping the docker container." docker stop $CONTAINER_ID -echo "Success" \ No newline at end of file + +echo "Uploading all the build dependencies to AWS." +aws s3 sync $DEPENDENCY_FOLDER s3://$AWS_BUCKET_NAME --delete --profile $AWS_PROFILE_NAME --region $AWS_REGION + +echo "Removing dependency folder." +rm -rf $DEPENDENCY_FOLDER + +echo "Done!" \ No newline at end of file -- GitLab From de25100063dd2640a34a2dff02ffee9a791c14de Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 29 Jan 2021 20:24:01 -1000 Subject: [PATCH 255/287] removing binaries and updating docker file Former-commit-id: a2877e3b84cbb919a596c5839702de6ec923a649 --- Dockerfile | 36 +++++++------------ scripts/bundles/bundle.tar.gz.REMOVED.git-id | 1 - .../node_modules.tar.gz.REMOVED.git-id | 1 - scripts/bundles/nodejs.tar.gz.REMOVED.git-id | 1 - .../bundles/redis-cli.tar.gz.REMOVED.git-id | 1 - scripts/bundles/yarn.tar.gz.REMOVED.git-id | 1 - 6 files changed, 13 insertions(+), 28 deletions(-) delete mode 100644 scripts/bundles/bundle.tar.gz.REMOVED.git-id delete mode 100644 scripts/bundles/node_modules.tar.gz.REMOVED.git-id delete mode 100644 scripts/bundles/nodejs.tar.gz.REMOVED.git-id delete mode 100644 scripts/bundles/redis-cli.tar.gz.REMOVED.git-id delete mode 100644 scripts/bundles/yarn.tar.gz.REMOVED.git-id diff --git a/Dockerfile b/Dockerfile index f52494b..1191233 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,47 +3,34 @@ USER 0 # Install required libs. RUN dnf update -y && dnf install -y \ -# curl \ -# make \ -# patch \ -# git \ -# zlib-devel \ libpq-devel \ -# libxml2-devel \ -# libxslt-devel \ -# gcc \ -# gcc-c++ \ && dnf clean all # Setup our environment WORKDIR /app COPY ./scripts . - -# Switch to the binary directory. -WORKDIR /app/bundles +COPY bundle.tar.gz . +COPY node_modules.tar.gz . +COPY nodejs.tar.gz . +COPY redis-cli.tar.gz . +COPY yarn.tar.gz . # Redis. -RUN tar xzf redis-cli.tar.gz +RUN tar xzf redis-cli.tar.gz && rm redis-cli.tar.gz RUN mv redis-cli /usr/local/bin/redis-cli # Unzip the bundle directory. -RUN tar xzf bundle.tar.gz +RUN tar xzf bundle.tar.gz && rm bundle.tar.gz RUN mv bundle /usr/local/ # Unzip the node_modules. -RUN tar xzf node_modules.tar.gz -RUN mv node_modules /app/node_modules +RUN tar xzf node_modules.tar.gz && rm node_modules.tar.gz # Node.js -RUN tar xzf nodejs.tar.gz -RUN mv nodejs /app/nodejs +RUN tar xzf nodejs.tar.gz && rm nodejs.tar.gz # Yarn -RUN tar xzf yarn.tar.gz -RUN mv yarn /app/yarn - -# Switch back to app directory. -WORKDIR /app +RUN tar xzf yarn.tar.gz && rm yarn.tar.gz # Remove the bundles directory. RUN rm -rf bundles @@ -51,6 +38,9 @@ RUN rm -rf bundles # Add write permissions. RUN chown -R 1001 . +# Test +RUN ls -la + # Become the ruby user USER 1001 diff --git a/scripts/bundles/bundle.tar.gz.REMOVED.git-id b/scripts/bundles/bundle.tar.gz.REMOVED.git-id deleted file mode 100644 index 04f748c..0000000 --- a/scripts/bundles/bundle.tar.gz.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -e642701e6e136d20901a5e81ff06b259fbb6691d \ No newline at end of file diff --git a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id b/scripts/bundles/node_modules.tar.gz.REMOVED.git-id deleted file mode 100644 index e925b2c..0000000 --- a/scripts/bundles/node_modules.tar.gz.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -20baab03056513e3e89c5d262b91b38004897dc8 \ No newline at end of file diff --git a/scripts/bundles/nodejs.tar.gz.REMOVED.git-id b/scripts/bundles/nodejs.tar.gz.REMOVED.git-id deleted file mode 100644 index 6370c52..0000000 --- a/scripts/bundles/nodejs.tar.gz.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -57e2f57eab29bd0c17dd1fbf1fee4bfed7d14d8c \ No newline at end of file diff --git a/scripts/bundles/redis-cli.tar.gz.REMOVED.git-id b/scripts/bundles/redis-cli.tar.gz.REMOVED.git-id deleted file mode 100644 index 07b1028..0000000 --- a/scripts/bundles/redis-cli.tar.gz.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -e694dfedcbaeaf8927abdba4868d90eb0857e619 \ No newline at end of file diff --git a/scripts/bundles/yarn.tar.gz.REMOVED.git-id b/scripts/bundles/yarn.tar.gz.REMOVED.git-id deleted file mode 100644 index 98061eb..0000000 --- a/scripts/bundles/yarn.tar.gz.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -18cf0892e6d57bc9876394d4d7f952dca3e6ca09 \ No newline at end of file -- GitLab From 3943b3b83371901eef86d35bb22b896e02241c11 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 4 Feb 2021 11:07:53 -1000 Subject: [PATCH 256/287] removing gitlab ci file --- scripts/.gitlab-ci.yml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 scripts/.gitlab-ci.yml diff --git a/scripts/.gitlab-ci.yml b/scripts/.gitlab-ci.yml deleted file mode 100644 index dcfae11..0000000 --- a/scripts/.gitlab-ci.yml +++ /dev/null @@ -1,4 +0,0 @@ -include: - - project: 'platform-one/devops/pipeline-products' - ref: 'learn-ci' - file: 'products/tron/galvanize/learn/forge-ci.yml' -- GitLab From 56f016fdacfe30fe231b5745586bcbdf0ca1486e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 12 Feb 2021 11:55:09 -1000 Subject: [PATCH 257/287] Updating readme and adding args back into docker file --- Dockerfile | 4 ++++ README.md | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/Dockerfile b/Dockerfile index 1191233..f4b61a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,7 @@ +ARG BASE_REGISTRY=registry1.dso.mil +ARG BASE_IMAGE=opensource/ruby/ruby26 +ARG BASE_TAG=2.6.6 + FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 diff --git a/README.md b/README.md index 9098182..6b2b5c2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,29 @@ Forge ===== +## Container Usage: + +### Container info: + +- workdir: /app +- user id: 1001 +- container port: 3000 + +### Recommended Resources: + +- Min/Max CPU: 0.25/0.5 +- Min/Max Memory: 256Mi/1Gi + +### Required Environment Variables: + +- ```REDIS_URL``` - The redis server host address. +- ```S3_REGION``` - TheS3 bucket region. +- ```LEARN_BASE_URL``` - The applications base url. +- ```S3_BUCKET``` - The S3 bucket name. +- ```ACTIONMAILER_HOST``` - The containers host name. +- ```ACTIONMAILER_PORT``` - The the containers external port. +- ```FORGE_PROTOCOL``` - The services host protocol. Ex: ```http://``` + ## Standards See the [software-team-standards](https://www.github.com/Galvanize-IT/software-team-standards) for git aliases. -- GitLab From 3e64548f0be1d09b3c8d6bc6f13d7672fedee2ca Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 12 Feb 2021 16:35:29 -1000 Subject: [PATCH 258/287] adding resources section --- hardening_manifest.yaml | 48 +++++++++++++++++++++++++++++++++++++++++ scripts/repackage.sh | 9 ++++++++ 2 files changed, 57 insertions(+) diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index 87f83e3..6911341 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -28,6 +28,54 @@ labels: mil.dso.ironbank.image.type: "commercial" mil.dso.ironbank.product.name: "Learn" +# List of resources to make available to the offline build context +resources: + - auth: + type: s3 + id: galvanize + region: us-gov-west-1 + url: s3://learn-dependencies/forge/redis-cli.tar.gz + filename: redis-cli.tar.gz + validation: + type: sha256 + value: a35f87996aaf065f059141881e82a6f203276219bed2fa0231ab7c0cc93ec51e + - auth: + type: s3 + id: galvanize + region: us-gov-west-1 + url: s3://learn-dependencies/forge/node_modules.tar.gz + filename: node_modules.tar.gz + validation: + type: sha256 + value: 0a2e412b98e4861732c1486a9c8a1aa1f9589fe3658be4636ea91c8153f1a03e + - auth: + type: s3 + id: galvanize + region: us-gov-west-1 + url: s3://learn-dependencies/forge/nodejs.tar.gz + filename: nodejs.tar.gz + validation: + type: sha256 + value: 585d55c930d94984d3615486fd4ed1c7bcb84c2180703d1c13a580086c959d6b + - auth: + type: s3 + id: galvanize + region: us-gov-west-1 + url: s3://learn-dependencies/forge/yarn.tar.gz + filename: yarn.tar.gz + validation: + type: sha256 + value: 37ebe0a0b4f7265d1c385e5e31746b76865d45fb36d6bc9a4ed80ad04c81bf54 + - auth: + type: s3 + id: galvanize + region: us-gov-west-1 + url: s3://learn-dependencies/forge/bundle.tar.gz + filename: bundle.tar.gz + validation: + type: sha256 + value: 8e047082e85e8ba9bfac282c5ed57d5a5f5d6023c1a11a61b54626d8895af2c9 + # List of project maintainers maintainers: - email: "dchong@revacomm.com" diff --git a/scripts/repackage.sh b/scripts/repackage.sh index 60f7f0c..abc0f23 100755 --- a/scripts/repackage.sh +++ b/scripts/repackage.sh @@ -50,6 +50,15 @@ docker stop $CONTAINER_ID echo "Uploading all the build dependencies to AWS." aws s3 sync $DEPENDENCY_FOLDER s3://$AWS_BUCKET_NAME --delete --profile $AWS_PROFILE_NAME --region $AWS_REGION +echo "Checksums:" +cd $DEPENDENCY_FOLDER +sha256sum node_modules.tar.gz +sha256sum nodejs.tar.gz +sha256sum yarn.tar.gz +sha256sum redis-cli.tar.gz +sha256sum bundle.tar.gz +cd .. + echo "Removing dependency folder." rm -rf $DEPENDENCY_FOLDER -- GitLab From 4748374e0ba59d4bc51fe087be500d414f205541 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Thu, 18 Feb 2021 17:43:14 -1000 Subject: [PATCH 259/287] upgrading to node 14.15.5 and ruby 2.7.2 --- Dockerfile | 4 ++-- README.md | 4 ++-- hardening_manifest.yaml | 14 +++++++------- scripts/.nvmrc | 2 +- scripts/.ruby-version | 2 +- scripts/Gemfile | 2 +- scripts/Gemfile.lock | 2 +- scripts/README.md | 4 ++-- scripts/package.json | 2 +- scripts/repackage.sh | 6 +++--- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index f4b61a2..3d9cfe2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ ARG BASE_REGISTRY=registry1.dso.mil -ARG BASE_IMAGE=opensource/ruby/ruby26 -ARG BASE_TAG=2.6.6 +ARG BASE_IMAGE=opensource/ruby/ruby27 +ARG BASE_TAG=2.7.2 FROM ${BASE_REGISTRY}/${BASE_IMAGE}:${BASE_TAG} USER 0 diff --git a/README.md b/README.md index 6b2b5c2..8957ab9 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Install rbenv and get the right version of ruby (the version here may change, so version. ) - `brew install rbenv` -- `rbenv install 2.6.6` +- `rbenv install 2.7.2` Install our data stores: @@ -55,7 +55,7 @@ You will need a [Github access token](https://github.com/settings/tokens). Repla The token will need **Full control of private repositories** - `git clone git@github.com:Galvanize-IT/forge.git && cd forge` -- `rbenv local 2.6.6` +- `rbenv local 2.7.2` - `gem install bundler` - `bundle config --local GITHUB__COM YOUR_GENERATED_PERSONAL_ACCESS_TOKEN:x-oauth-basic` - `bundle` diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index 6911341..f606c32 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -13,8 +13,8 @@ tags: # Build args passed to Dockerfile ARGs args: - BASE_IMAGE: "opensource/ruby/ruby26" - BASE_TAG: "2.6.6" + BASE_IMAGE: "opensource/ruby/ruby27" + BASE_TAG: "2.7.2" # Docker image labels labels: @@ -38,7 +38,7 @@ resources: filename: redis-cli.tar.gz validation: type: sha256 - value: a35f87996aaf065f059141881e82a6f203276219bed2fa0231ab7c0cc93ec51e + value: 003cdc4277d0d021f6c54249ce277293a06fb63bf7d8fd1444ef42f595218f86 - auth: type: s3 id: galvanize @@ -47,7 +47,7 @@ resources: filename: node_modules.tar.gz validation: type: sha256 - value: 0a2e412b98e4861732c1486a9c8a1aa1f9589fe3658be4636ea91c8153f1a03e + value: 830b0824cc9d7b2c037dd5e3de5176e7908fbbe104be9f12c67fedd5e75aac37 - auth: type: s3 id: galvanize @@ -56,7 +56,7 @@ resources: filename: nodejs.tar.gz validation: type: sha256 - value: 585d55c930d94984d3615486fd4ed1c7bcb84c2180703d1c13a580086c959d6b + value: 2194da489ad24a6261607433e79d6bc4c8e8d28d8265c53249282f960339b6e0 - auth: type: s3 id: galvanize @@ -65,7 +65,7 @@ resources: filename: yarn.tar.gz validation: type: sha256 - value: 37ebe0a0b4f7265d1c385e5e31746b76865d45fb36d6bc9a4ed80ad04c81bf54 + value: 9cdc059965f8c46a49fbf8105addec82ace7a2a2486554c060e4be408a73837e - auth: type: s3 id: galvanize @@ -74,7 +74,7 @@ resources: filename: bundle.tar.gz validation: type: sha256 - value: 8e047082e85e8ba9bfac282c5ed57d5a5f5d6023c1a11a61b54626d8895af2c9 + value: b439f3b9c3becb568a061fbf3615914a6b9546cf71e266c6caa7a316ef8a751d # List of project maintainers maintainers: diff --git a/scripts/.nvmrc b/scripts/.nvmrc index 47266f8..d4cf210 100644 --- a/scripts/.nvmrc +++ b/scripts/.nvmrc @@ -1 +1 @@ -14.15.4 \ No newline at end of file +14.15.5 \ No newline at end of file diff --git a/scripts/.ruby-version b/scripts/.ruby-version index 338a5b5..37c2961 100644 --- a/scripts/.ruby-version +++ b/scripts/.ruby-version @@ -1 +1 @@ -2.6.6 +2.7.2 diff --git a/scripts/Gemfile b/scripts/Gemfile index 1a86e24..cd6da3c 100644 --- a/scripts/Gemfile +++ b/scripts/Gemfile @@ -1,5 +1,5 @@ source "https://rubygems.org" -ruby "2.6.6" +ruby "2.7.2" git_source(:github) do |repo_name| repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") diff --git a/scripts/Gemfile.lock b/scripts/Gemfile.lock index 1fc26f0..e5d5378 100644 --- a/scripts/Gemfile.lock +++ b/scripts/Gemfile.lock @@ -1521,7 +1521,7 @@ DEPENDENCIES zip-zip RUBY VERSION - ruby 2.6.6p146 + ruby 2.7.2p137 BUNDLED WITH 2.1.4 diff --git a/scripts/README.md b/scripts/README.md index b7fc7d5..82d0ef2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -12,7 +12,7 @@ Install rbenv and get the right version of ruby (the version here may change, so version. ) - `brew install rbenv` -- `rbenv install 2.6.6` +- `rbenv install 2.7.2` Install our data stores: @@ -32,7 +32,7 @@ You will need a [Github access token](https://github.com/settings/tokens). Repla The token will need **Full control of private repositories** - `git clone git@github.com:Galvanize-IT/forge.git && cd forge` -- `rbenv local 2.6.6` +- `rbenv local 2.7.2` - `gem install bundler` - `bundle config --local GITHUB__COM YOUR_GENERATED_PERSONAL_ACCESS_TOKEN:x-oauth-basic` - `bundle` diff --git a/scripts/package.json b/scripts/package.json index d730cd5..21a89b3 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -17,7 +17,7 @@ }, "homepage": "https://github.com/Galvanize-IT/forge#readme", "engines": { - "node": "14.15.4" + "node": "14" }, "dependencies": { "@rails/webpacker": "5.2.1", diff --git a/scripts/repackage.sh b/scripts/repackage.sh index abc0f23..b4e3efc 100755 --- a/scripts/repackage.sh +++ b/scripts/repackage.sh @@ -2,11 +2,11 @@ # Image Params. BASE_REGISTRY=registry.il2.dso.mil -BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby26 -BASE_TAG=2.6.6.212 +BASE_IMAGE=platform-one/devops/pipeline-templates/ironbank/ruby27 +BASE_TAG=2.7.2 # Package Versions. -NODE_VERSION=14.15.4 +NODE_VERSION=14.15.5 YARN_VERSION=1.22.5 JQUERY_RAILS_VERSION=4.4.0 -- GitLab From b9cc01ea735c83201bc1bc25c9d2fdb2a4cefcaf Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 19 Feb 2021 14:15:23 -1000 Subject: [PATCH 260/287] upgrading ruby gems and node modules --- Dockerfile | 3 - hardening_manifest.yaml | 10 +- scripts/Gemfile.lock | 1203 ++++++++++++------------ scripts/gems/block-parser/Gemfile.lock | 29 +- scripts/package.json | 42 +- scripts/yarn.lock | 409 +++++--- 6 files changed, 908 insertions(+), 788 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3d9cfe2..7cc1112 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,9 +36,6 @@ RUN tar xzf nodejs.tar.gz && rm nodejs.tar.gz # Yarn RUN tar xzf yarn.tar.gz && rm yarn.tar.gz -# Remove the bundles directory. -RUN rm -rf bundles - # Add write permissions. RUN chown -R 1001 . diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index f606c32..83b3252 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -38,7 +38,7 @@ resources: filename: redis-cli.tar.gz validation: type: sha256 - value: 003cdc4277d0d021f6c54249ce277293a06fb63bf7d8fd1444ef42f595218f86 + value: bb5f74ccdfcf66746c926dfb0e804627de3f61cc722c3cd367ab6c2c9960b950 - auth: type: s3 id: galvanize @@ -47,7 +47,7 @@ resources: filename: node_modules.tar.gz validation: type: sha256 - value: 830b0824cc9d7b2c037dd5e3de5176e7908fbbe104be9f12c67fedd5e75aac37 + value: b3ea918d9628aba96a83c9bfd3c1cd98f66a4b30daa2368b6b0dd0be9234476e - auth: type: s3 id: galvanize @@ -56,7 +56,7 @@ resources: filename: nodejs.tar.gz validation: type: sha256 - value: 2194da489ad24a6261607433e79d6bc4c8e8d28d8265c53249282f960339b6e0 + value: 480ebc0f3dca30c31b976d0f5088ce4bfe3d4aba9dca0202d2390801b80c8709 - auth: type: s3 id: galvanize @@ -65,7 +65,7 @@ resources: filename: yarn.tar.gz validation: type: sha256 - value: 9cdc059965f8c46a49fbf8105addec82ace7a2a2486554c060e4be408a73837e + value: cdd48447584ab01e81288994699bcf67d99c32a7bc3b068a2218016bb3d9063e - auth: type: s3 id: galvanize @@ -74,7 +74,7 @@ resources: filename: bundle.tar.gz validation: type: sha256 - value: b439f3b9c3becb568a061fbf3615914a6b9546cf71e266c6caa7a316ef8a751d + value: c8a38e0da532865e445280c5657653c070c3af3686380d30a3bb9de483022a5a # List of project maintainers maintainers: diff --git a/scripts/Gemfile.lock b/scripts/Gemfile.lock index e5d5378..62fa2b7 100644 --- a/scripts/Gemfile.lock +++ b/scripts/Gemfile.lock @@ -16,60 +16,60 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (6.1.1) - actionpack (= 6.1.1) - activesupport (= 6.1.1) + actioncable (6.1.3) + actionpack (= 6.1.3) + activesupport (= 6.1.3) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.1) - actionpack (= 6.1.1) - activejob (= 6.1.1) - activerecord (= 6.1.1) - activestorage (= 6.1.1) - activesupport (= 6.1.1) + actionmailbox (6.1.3) + actionpack (= 6.1.3) + activejob (= 6.1.3) + activerecord (= 6.1.3) + activestorage (= 6.1.3) + activesupport (= 6.1.3) mail (>= 2.7.1) - actionmailer (6.1.1) - actionpack (= 6.1.1) - actionview (= 6.1.1) - activejob (= 6.1.1) - activesupport (= 6.1.1) + actionmailer (6.1.3) + actionpack (= 6.1.3) + actionview (= 6.1.3) + activejob (= 6.1.3) + activesupport (= 6.1.3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.1) - actionview (= 6.1.1) - activesupport (= 6.1.1) + actionpack (6.1.3) + actionview (= 6.1.3) + activesupport (= 6.1.3) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.1) - actionpack (= 6.1.1) - activerecord (= 6.1.1) - activestorage (= 6.1.1) - activesupport (= 6.1.1) + actiontext (6.1.3) + actionpack (= 6.1.3) + activerecord (= 6.1.3) + activestorage (= 6.1.3) + activesupport (= 6.1.3) nokogiri (>= 1.8.5) - actionview (6.1.1) - activesupport (= 6.1.1) + actionview (6.1.3) + activesupport (= 6.1.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.1.1) - activesupport (= 6.1.1) + activejob (6.1.3) + activesupport (= 6.1.3) globalid (>= 0.3.6) - activemodel (6.1.1) - activesupport (= 6.1.1) - activerecord (6.1.1) - activemodel (= 6.1.1) - activesupport (= 6.1.1) - activestorage (6.1.1) - actionpack (= 6.1.1) - activejob (= 6.1.1) - activerecord (= 6.1.1) - activesupport (= 6.1.1) + activemodel (6.1.3) + activesupport (= 6.1.3) + activerecord (6.1.3) + activemodel (= 6.1.3) + activesupport (= 6.1.3) + activestorage (6.1.3) + actionpack (= 6.1.3) + activejob (= 6.1.3) + activerecord (= 6.1.3) + activesupport (= 6.1.3) marcel (~> 0.3.1) mimemagic (~> 0.3.2) - activesupport (6.1.1) + activesupport (6.1.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -81,632 +81,632 @@ GEM apitome (0.3.0) kramdown railties - ast (2.4.1) - autoprefixer-rails (10.2.0.0) + ast (2.4.2) + autoprefixer-rails (10.2.4.0) execjs aws-eventstream (1.1.0) - aws-partitions (1.418.0) + aws-partitions (1.428.0) aws-sdk (3.0.1) aws-sdk-resources (~> 3) - aws-sdk-accessanalyzer (1.14.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-accessanalyzer (1.16.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-acm (1.38.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-acm (1.39.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-acmpca (1.32.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-acmpca (1.33.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-alexaforbusiness (1.43.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-alexaforbusiness (1.44.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-amplify (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-amplify (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-amplifybackend (1.1.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-amplifybackend (1.2.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-apigateway (1.58.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-apigateway (1.59.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-apigatewaymanagementapi (1.19.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-apigatewaymanagementapi (1.20.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-apigatewayv2 (1.30.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-apigatewayv2 (1.31.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-appconfig (1.12.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-appconfig (1.13.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-appflow (1.4.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-appflow (1.6.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-appintegrationsservice (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-appintegrationsservice (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-applicationautoscaling (1.49.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-applicationautoscaling (1.50.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-applicationdiscoveryservice (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-applicationdiscoveryservice (1.34.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-applicationinsights (1.16.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-applicationinsights (1.17.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-appmesh (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-appmesh (1.34.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-appregistry (1.3.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-appregistry (1.4.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-appstream (1.49.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-appstream (1.50.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-appsync (1.37.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-appsync (1.39.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-athena (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-athena (1.35.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-auditmanager (1.1.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-auditmanager (1.3.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-augmentedairuntime (1.10.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-augmentedairuntime (1.11.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-autoscaling (1.53.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-autoscaling (1.54.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-autoscalingplans (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-autoscalingplans (1.30.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-backup (1.25.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-backup (1.27.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-batch (1.43.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-batch (1.44.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-braket (1.5.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-braket (1.6.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-budgets (1.36.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-budgets (1.37.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-chime (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-chime (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloud9 (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloud9 (1.30.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-clouddirectory (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-clouddirectory (1.30.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudformation (1.46.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudformation (1.48.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudfront (1.47.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudfront (1.48.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudhsm (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudhsm (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudhsmv2 (1.31.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudhsmv2 (1.32.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudsearch (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudsearch (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudsearchdomain (1.22.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudsearchdomain (1.23.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudtrail (1.31.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudtrail (1.33.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudwatch (1.47.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudwatch (1.49.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudwatchevents (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudwatchevents (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudwatchlogs (1.38.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cloudwatchlogs (1.39.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codeartifact (1.6.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codeartifact (1.7.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codebuild (1.65.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codebuild (1.69.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codecommit (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codecommit (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codedeploy (1.37.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codedeploy (1.38.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codeguruprofiler (1.12.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codeguruprofiler (1.13.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codegurureviewer (1.14.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codegurureviewer (1.15.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codepipeline (1.39.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codepipeline (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codestar (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codestar (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codestarconnections (1.12.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codestarconnections (1.13.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-codestarnotifications (1.8.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-codestarnotifications (1.9.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cognitoidentity (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cognitoidentity (1.30.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cognitoidentityprovider (1.48.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cognitoidentityprovider (1.49.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-cognitosync (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-cognitosync (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-comprehend (1.42.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-comprehend (1.43.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-comprehendmedical (1.23.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-comprehendmedical (1.24.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-computeoptimizer (1.11.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-computeoptimizer (1.13.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-configservice (1.55.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-configservice (1.58.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-connect (1.38.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-connect (1.40.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-connectcontactlens (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-connectcontactlens (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-connectparticipant (1.9.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-connectparticipant (1.10.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-core (3.111.2) + aws-sdk-core (3.112.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-costandusagereportservice (1.28.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-costandusagereportservice (1.29.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-costexplorer (1.56.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-costexplorer (1.58.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-customerprofiles (1.1.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-customerprofiles (1.3.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-databasemigrationservice (1.50.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-databasemigrationservice (1.51.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-dataexchange (1.10.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-dataexchange (1.12.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-datapipeline (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-datapipeline (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-datasync (1.28.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-datasync (1.29.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-dax (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-dax (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-detective (1.11.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-detective (1.13.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-devicefarm (1.39.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-devicefarm (1.40.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-devopsguru (1.2.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-devopsguru (1.4.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-directconnect (1.37.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-directconnect (1.38.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-directoryservice (1.37.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-directoryservice (1.38.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-dlm (1.37.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-dlm (1.39.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-docdb (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-docdb (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-dynamodb (1.58.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-dynamodb (1.59.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-dynamodbstreams (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-dynamodbstreams (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ebs (1.11.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ebs (1.12.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ec2 (1.221.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ec2 (1.225.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ec2instanceconnect (1.11.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ec2instanceconnect (1.12.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ecr (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ecr (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ecrpublic (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ecrpublic (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ecs (1.73.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ecs (1.74.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-efs (1.36.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-efs (1.37.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-eks (1.46.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-eks (1.48.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticache (1.50.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-elasticache (1.53.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticbeanstalk (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-elasticbeanstalk (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticinference (1.10.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-elasticinference (1.11.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticloadbalancing (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-elasticloadbalancing (1.30.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticloadbalancingv2 (1.56.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-elasticloadbalancingv2 (1.60.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticsearchservice (1.46.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-elasticsearchservice (1.48.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-elastictranscoder (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-elastictranscoder (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-emr (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-emr (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-emrcontainers (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-emrcontainers (1.2.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-eventbridge (1.18.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-eventbridge (1.19.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-firehose (1.35.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-firehose (1.36.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-fms (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-fms (1.34.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-forecastqueryservice (1.10.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-forecastqueryservice (1.11.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-forecastservice (1.14.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-forecastservice (1.15.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-frauddetector (1.15.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-frauddetector (1.16.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-fsx (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-fsx (1.34.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-gamelift (1.39.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-gamelift (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-glacier (1.35.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-glacier (1.36.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-globalaccelerator (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-globalaccelerator (1.29.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-glue (1.82.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-glue (1.83.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-gluedatabrew (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-gluedatabrew (1.4.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-greengrass (1.37.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-greengrass (1.38.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-greengrassv2 (1.1.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-greengrassv2 (1.2.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-groundstation (1.15.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-groundstation (1.16.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-guardduty (1.43.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-guardduty (1.44.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-health (1.31.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-health (1.33.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-healthlake (1.1.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-healthlake (1.2.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-honeycode (1.4.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-honeycode (1.5.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iam (1.46.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iam (1.48.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-identitystore (1.3.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-identitystore (1.4.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-imagebuilder (1.17.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-imagebuilder (1.18.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-importexport (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-importexport (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv2 (~> 1.0) - aws-sdk-inspector (1.32.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-inspector (1.33.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iot (1.64.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iot (1.66.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iot1clickdevicesservice (1.26.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iot1clickdevicesservice (1.27.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iot1clickprojects (1.26.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iot1clickprojects (1.27.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotanalytics (1.36.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotanalytics (1.37.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotdataplane (1.26.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotdataplane (1.27.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotdeviceadvisor (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotdeviceadvisor (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotevents (1.20.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotevents (1.21.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ioteventsdata (1.13.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ioteventsdata (1.14.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotfleethub (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotfleethub (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotjobsdataplane (1.25.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotjobsdataplane (1.26.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotsecuretunneling (1.9.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotsecuretunneling (1.10.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotsitewise (1.16.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotsitewise (1.19.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotthingsgraph (1.12.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotthingsgraph (1.13.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-iotwireless (1.1.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-iotwireless (1.2.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ivs (1.5.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ivs (1.7.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kafka (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kafka (1.34.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kendra (1.20.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kendra (1.21.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesis (1.30.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kinesis (1.31.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisanalytics (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kinesisanalytics (1.30.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisanalyticsv2 (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kinesisanalyticsv2 (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisvideo (1.30.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kinesisvideo (1.31.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisvideoarchivedmedia (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kinesisvideoarchivedmedia (1.31.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisvideomedia (1.26.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kinesisvideomedia (1.27.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisvideosignalingchannels (1.8.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kinesisvideosignalingchannels (1.9.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-kms (1.41.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-kms (1.42.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-lakeformation (1.11.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-lakeformation (1.12.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-lambda (1.57.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-lambda (1.59.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-lambdapreview (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-lambdapreview (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-lex (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-lex (1.34.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-lexmodelbuildingservice (1.42.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-lexmodelbuildingservice (1.43.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-lexmodelsv2 (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-lexmodelsv2 (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-lexruntimev2 (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-lexruntimev2 (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-licensemanager (1.23.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-licensemanager (1.24.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-lightsail (1.41.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-lightsail (1.44.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-locationservice (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-locationservice (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-lookoutforvision (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-lookoutforvision (1.2.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-machinelearning (1.25.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-machinelearning (1.26.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-macie (1.25.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-macie (1.27.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-macie2 (1.19.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-macie2 (1.23.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-managedblockchain (1.18.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-managedblockchain (1.20.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplacecatalog (1.9.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-marketplacecatalog (1.10.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplacecommerceanalytics (1.30.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-marketplacecommerceanalytics (1.31.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplaceentitlementservice (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-marketplaceentitlementservice (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplacemetering (1.32.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-marketplacemetering (1.33.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mediaconnect (1.28.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mediaconnect (1.29.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mediaconvert (1.61.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mediaconvert (1.63.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-medialive (1.61.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-medialive (1.64.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mediapackage (1.36.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mediapackage (1.37.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mediapackagevod (1.19.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mediapackagevod (1.20.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mediastore (1.30.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mediastore (1.31.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mediastoredata (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mediastoredata (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mediatailor (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mediatailor (1.35.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-migrationhub (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-migrationhub (1.30.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-migrationhubconfig (1.9.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-migrationhubconfig (1.10.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mobile (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mobile (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mq (1.34.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mq (1.35.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mturk (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mturk (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-mwaa (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-mwaa (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-neptune (1.32.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-neptune (1.33.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-networkfirewall (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-networkfirewall (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-networkmanager (1.9.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-networkmanager (1.10.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-opsworks (1.30.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-opsworks (1.31.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-opsworkscm (1.40.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-opsworkscm (1.41.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-organizations (1.55.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-organizations (1.57.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-outposts (1.13.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-outposts (1.14.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-personalize (1.20.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-personalize (1.21.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-personalizeevents (1.14.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-personalizeevents (1.16.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-personalizeruntime (1.20.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-personalizeruntime (1.21.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-pi (1.25.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-pi (1.26.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-pinpoint (1.48.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-pinpoint (1.50.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-pinpointemail (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-pinpointemail (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-pinpointsmsvoice (1.21.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-pinpointsmsvoice (1.22.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-polly (1.38.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-polly (1.39.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-pricing (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-pricing (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-prometheusservice (1.1.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-prometheusservice (1.2.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-qldb (1.11.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-qldb (1.12.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-qldbsession (1.10.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-qldbsession (1.12.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-quicksight (1.39.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-quicksight (1.42.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ram (1.22.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ram (1.23.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-rds (1.111.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-rds (1.115.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-rdsdataservice (1.23.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-rdsdataservice (1.24.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-redshift (1.53.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-redshift (1.54.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-redshiftdataapiservice (1.2.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-redshiftdataapiservice (1.4.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-rekognition (1.47.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-rekognition (1.48.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-resourcegroups (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-resourcegroups (1.34.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-resourcegroupstaggingapi (1.35.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-resourcegroupstaggingapi (1.36.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) aws-sdk-resources (3.94.0) aws-sdk-accessanalyzer (~> 1) @@ -967,165 +967,165 @@ GEM aws-sdk-workmailmessageflow (~> 1) aws-sdk-workspaces (~> 1) aws-sdk-xray (~> 1) - aws-sdk-robomaker (1.31.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-robomaker (1.33.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-route53 (1.45.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-route53 (1.46.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-route53domains (1.28.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-route53domains (1.29.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-route53resolver (1.22.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-route53resolver (1.23.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.87.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-s3 (1.88.1) + aws-sdk-core (~> 3, >= 3.112.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.1) - aws-sdk-s3control (1.26.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-s3control (1.27.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-s3outposts (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-s3outposts (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemaker (1.75.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-sagemaker (1.78.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemakeredgemanager (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-sagemakeredgemanager (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemakerfeaturestoreruntime (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-sagemakerfeaturestoreruntime (1.1.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemakerruntime (1.28.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-sagemakerruntime (1.29.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-savingsplans (1.12.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-savingsplans (1.13.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-schemas (1.10.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-schemas (1.11.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-secretsmanager (1.43.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-secretsmanager (1.44.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-securityhub (1.38.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-securityhub (1.40.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-serverlessapplicationrepository (1.32.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-serverlessapplicationrepository (1.33.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-servicecatalog (1.57.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-servicecatalog (1.58.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-servicediscovery (1.31.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-servicediscovery (1.32.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-servicequotas (1.12.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-servicequotas (1.13.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ses (1.36.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ses (1.37.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-sesv2 (1.14.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-sesv2 (1.16.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-shield (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-shield (1.34.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-signer (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-signer (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-simpledb (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-simpledb (1.25.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv2 (~> 1.0) - aws-sdk-sms (1.27.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-sms (1.28.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-snowball (1.35.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-snowball (1.36.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-sns (1.37.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-sns (1.38.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-sqs (1.35.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-sqs (1.36.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ssm (1.103.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ssm (1.104.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ssoadmin (1.4.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ssoadmin (1.5.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-ssooidc (1.8.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-ssooidc (1.9.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-states (1.37.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-states (1.38.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-storagegateway (1.52.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-storagegateway (1.53.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-support (1.28.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-support (1.29.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-swf (1.25.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-swf (1.26.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-synthetics (1.10.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-synthetics (1.11.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-textract (1.22.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-textract (1.23.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-timestreamquery (1.2.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-timestreamquery (1.3.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-timestreamwrite (1.2.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-timestreamwrite (1.3.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-transcribeservice (1.50.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-transcribeservice (1.51.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-transcribestreamingservice (1.24.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-transcribestreamingservice (1.26.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-transfer (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-transfer (1.30.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-translate (1.29.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-translate (1.30.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-waf (1.36.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-waf (1.37.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-wafregional (1.37.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-wafregional (1.38.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-wafv2 (1.14.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-wafv2 (1.16.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-wellarchitected (1.0.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-wellarchitected (1.2.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-workdocs (1.28.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-workdocs (1.29.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-worklink (1.21.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-worklink (1.22.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-workmail (1.33.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-workmail (1.35.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-workmailmessageflow (1.9.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-workmailmessageflow (1.11.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-workspaces (1.49.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-workspaces (1.50.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) - aws-sdk-xray (1.35.0) - aws-sdk-core (~> 3, >= 3.109.0) + aws-sdk-xray (1.36.0) + aws-sdk-core (~> 3, >= 3.112.0) aws-sigv4 (~> 1.1) aws-sigv2 (1.0.1) aws-sigv4 (1.2.2) @@ -1143,33 +1143,38 @@ GEM rack (>= 0.9.0) binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) - bootsnap (1.5.1) + bootsnap (1.7.2) msgpack (~> 1.0) - bootstrap (4.5.3) + bootstrap (4.6.0) autoprefixer-rails (>= 9.1.0) popper_js (>= 1.14.3, < 2) sassc-rails (>= 2.0.0) browser (5.3.0) builder (3.2.4) byebug (11.1.3) - capybara (3.34.0) + capybara (3.35.3) addressable mini_mime (>= 0.1.3) nokogiri (~> 1.8) rack (>= 1.6.0) rack-test (>= 0.6.3) - regexp_parser (~> 1.5) + regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) childprocess (3.0.0) coderay (1.1.3) - commonmarker (0.21.1) + commonmarker (0.21.2) ruby-enum (~> 0.5) concurrent-ruby (1.1.8) connection_pool (2.2.3) crack (0.4.5) rexml crass (1.0.6) - database_cleaner (1.8.5) + database_cleaner (2.0.1) + database_cleaner-active_record (~> 2.0.0) + database_cleaner-active_record (2.0.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) debug_inspector (1.0.0) deterministic (0.6.0) diff-lcs (1.4.4) @@ -1192,8 +1197,8 @@ GEM faraday-net_http (1.0.1) ffi (1.14.2) flamegraph (0.9.5) - font-awesome-rails (4.7.0.6) - railties (>= 3.2, < 6.2) + font-awesome-rails (4.7.0.7) + railties (>= 3.2, < 7) foreman (0.87.2) github-markup (3.0.5) github_url (0.2.1) @@ -1206,11 +1211,11 @@ GEM temple (>= 0.8.0) tilt hashdiff (1.0.1) - honeybadger (4.7.2) + honeybadger (4.7.3) httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.8.7) + i18n (1.8.9) concurrent-ruby (~> 1.0) iniparse (1.5.0) jmespath (1.4.0) @@ -1246,17 +1251,17 @@ GEM method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2020.1104) + mime-types-data (3.2021.0212) mimemagic (0.3.5) mini_mime (1.0.2) mini_portile2 (2.5.0) minitest (5.14.3) - msgpack (1.3.3) + msgpack (1.4.2) multi_json (1.15.0) multi_xml (0.6.0) multipart-post (2.1.1) mustache (1.1.1) - nio4r (2.5.4) + nio4r (2.5.5) nokogiri (1.11.1) mini_portile2 (~> 2.5.0) racc (~> 1.4) @@ -1266,7 +1271,7 @@ GEM overcommit (0.57.0) childprocess (>= 0.6.3, < 5) iniparse (~> 1.4) - pagy (3.10.0) + pagy (3.11.0) parallel (1.20.1) parser (3.0.0.0) ast (~> 2.4.1) @@ -1274,34 +1279,34 @@ GEM popper_js (1.16.0) psych (3.3.0) public_suffix (4.0.6) - puma (5.1.1) + puma (5.2.1) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) racc (1.5.2) rack (2.2.3) - rack-attack (6.3.1) + rack-attack (6.5.0) rack (>= 1.0, < 3) - rack-mini-profiler (2.3.0) + rack-mini-profiler (2.3.1) rack (>= 1.2.0) rack-proxy (0.6.5) rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.1.1) - actioncable (= 6.1.1) - actionmailbox (= 6.1.1) - actionmailer (= 6.1.1) - actionpack (= 6.1.1) - actiontext (= 6.1.1) - actionview (= 6.1.1) - activejob (= 6.1.1) - activemodel (= 6.1.1) - activerecord (= 6.1.1) - activestorage (= 6.1.1) - activesupport (= 6.1.1) + rails (6.1.3) + actioncable (= 6.1.3) + actionmailbox (= 6.1.3) + actionmailer (= 6.1.3) + actionpack (= 6.1.3) + actiontext (= 6.1.3) + actionview (= 6.1.3) + activejob (= 6.1.3) + activemodel (= 6.1.3) + activerecord (= 6.1.3) + activestorage (= 6.1.3) + activesupport (= 6.1.3) bundler (>= 1.15.0) - railties (= 6.1.1) + railties (= 6.1.3) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.5) actionpack (>= 5.0.1.rc1) @@ -1312,9 +1317,9 @@ GEM nokogiri (>= 1.6) rails-html-sanitizer (1.3.0) loofah (~> 2.3) - railties (6.1.1) - actionpack (= 6.1.1) - activesupport (= 6.1.1) + railties (6.1.3) + actionpack (= 6.1.3) + activesupport (= 6.1.3) method_source rake (>= 0.8.7) thor (~> 1.0) @@ -1331,7 +1336,7 @@ GEM tilt redcarpet (3.5.1) redis (4.2.5) - regexp_parser (1.8.2) + regexp_parser (2.0.3) rexml (3.2.4) rspec (3.10.0) rspec-core (~> 3.10.0) @@ -1342,7 +1347,7 @@ GEM rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) - rspec-mocks (3.10.1) + rspec-mocks (3.10.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) rspec-rails (4.0.2) @@ -1353,14 +1358,14 @@ GEM rspec-expectations (~> 3.10) rspec-mocks (~> 3.10) rspec-support (~> 3.10) - rspec-support (3.10.1) + rspec-support (3.10.2) rspec_api_documentation (6.1.0) activesupport (>= 3.0.0) mustache (~> 1.0, >= 0.99.4) rspec (~> 3.0) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.8.1) + rubocop (1.10.0) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) @@ -1369,9 +1374,9 @@ GEM rubocop-ast (>= 1.2.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.4.0) + rubocop-ast (1.4.1) parser (>= 2.7.1.5) - ruby-enum (0.8.0) + ruby-enum (0.9.0) i18n ruby-progressbar (1.11.0) ruby2_keywords (0.0.4) @@ -1389,7 +1394,7 @@ GEM sawyer (0.8.2) addressable (>= 2.3.5) faraday (> 0.8, < 2.0) - scout_apm (4.0.3) + scout_apm (4.0.4) parser selenium-webdriver (3.142.7) childprocess (>= 0.5, < 4.0) @@ -1422,7 +1427,7 @@ GEM terminal-table (1.6.0) thor (1.1.0) tilt (2.0.10) - timecop (0.9.2) + timecop (0.9.4) ts_routes (1.0.3) railties (>= 4.0) tzinfo (2.0.4) @@ -1432,7 +1437,7 @@ GEM underscore-rails (1.8.3) unicode-display_width (2.0.0) vcr (6.0.0) - webmock (3.11.1) + webmock (3.11.2) addressable (>= 2.3.6) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) diff --git a/scripts/gems/block-parser/Gemfile.lock b/scripts/gems/block-parser/Gemfile.lock index aaece8f..3ca1255 100644 --- a/scripts/gems/block-parser/Gemfile.lock +++ b/scripts/gems/block-parser/Gemfile.lock @@ -16,9 +16,9 @@ PATH GEM remote: https://rubygems.org/ specs: - activemodel (6.1.1) - activesupport (= 6.1.1) - activesupport (6.1.1) + activemodel (6.1.3) + activesupport (= 6.1.3) + activesupport (6.1.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -26,9 +26,9 @@ GEM zeitwerk (~> 2.3) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) - ast (2.4.1) + ast (2.4.2) byebug (11.1.3) - commonmarker (0.21.1) + commonmarker (0.21.2) ruby-enum (~> 0.5) concurrent-ruby (1.1.8) diff-lcs (1.4.4) @@ -45,11 +45,11 @@ GEM httparty (0.18.1) mime-types (~> 3.0) multi_xml (>= 0.5.2) - i18n (1.8.7) + i18n (1.8.9) concurrent-ruby (~> 1.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2020.1104) + mime-types-data (3.2021.0212) mini_portile2 (2.5.0) minitest (5.14.3) multi_xml (0.6.0) @@ -80,13 +80,13 @@ GEM rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) - rspec-mocks (3.10.1) + rspec-mocks (3.10.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) - rspec-support (3.10.1) + rspec-support (3.10.2) rspec_junit_formatter (0.4.1) rspec-core (>= 2, < 4, != 2.12.0) - rubocop (1.8.1) + rubocop (1.10.0) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) @@ -95,20 +95,19 @@ GEM rubocop-ast (>= 1.2.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.4.0) + rubocop-ast (1.4.1) parser (>= 2.7.1.5) - ruby-enum (0.8.0) + ruby-enum (0.9.0) i18n ruby-progressbar (1.11.0) ruby2_keywords (0.0.4) sawyer (0.8.2) addressable (>= 2.3.5) faraday (> 0.8, < 2.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) + terminal-table (1.6.0) tzinfo (2.0.4) concurrent-ruby (~> 1.0) - unicode-display_width (1.7.0) + unicode-display_width (2.0.0) zeitwerk (2.4.2) PLATFORMS diff --git a/scripts/package.json b/scripts/package.json index 21a89b3..3930fb2 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -26,15 +26,15 @@ "@vimeo/player": "^2.15.0", "babel-preset-react": "^6.24.1", "brace": "^0.11.1", - "browserify": "^16.5.2", + "browserify": "^17.0.0", "browserify-incremental": "^3.1.1", - "dropzone": "^5.7.2", + "dropzone": "^5.7.6", "highlightjs": "^9.16.2", "immutability-helper": "^3.1.1", "lodash-es": "^4.17.20", - "marked": "^1.2.7", + "marked": "^2.0.0", "moment": "^2.29.1", - "moment-timezone": "^0.5.32", + "moment-timezone": "^0.5.33", "objectify-array": "^2.1.0", "popper.js": "^1.16.1", "prop-types": "^15.7.2", @@ -44,38 +44,38 @@ "react-addons-test-utils": "^15.6.2", "react-beautiful-dnd": "^13.0.0", "react-copy-to-clipboard": "^5.0.3", - "react-datepicker": "^3.4.1", + "react-datepicker": "^3.5.0", "react-dom": "^17.0.1", "react-json-pretty": "^2.2.0", - "react-paginate": "^7.0.0", + "react-paginate": "^7.1.0", "react-pdf": "^4.2.0", "react-scroll-sync": "^0.8.0", "react-sizeme": "^2.6.12", - "react-textarea-autosize": "^8.3.0", - "react-tooltip": "^4.2.13", + "react-textarea-autosize": "^8.3.1", + "react-tooltip": "^4.2.14", "react_ujs": "^2.6.1", "reactify": "^1.1.1", - "ts-loader": "^8.0.14", - "typescript": "^4.1.3", + "ts-loader": "^8.0.17", + "typescript": "^4.1.5", "unfetch": "^4.2.0" }, "devDependencies": { - "@types/lodash-es": "^4.17.3", + "@types/lodash-es": "^4.17.4", "@types/node-fetch": "^2.5.8", "@types/rc-slider": "^8.6.6", - "@types/react": "^17.0.0", + "@types/react": "^17.0.2", "@types/react-addons-css-transition-group": "^15.0.5", "@types/react-beautiful-dnd": "^13.0.0", "@types/react-copy-to-clipboard": "^5.0.0", "@types/react-datepicker": "^3.1.3", - "@types/react-dom": "^17.0.0", + "@types/react-dom": "^17.0.1", "@types/react-textarea-autosize": "^4.3.5", "@types/vimeo__player": "^2.10.0", - "eslint": "^7.18.0", + "eslint": "^7.20.0", "eslint-config-airbnb": "^18.2.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jsx-a11y": "^6.4.1", - "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-promise": "^4.3.1", "eslint-plugin-react": "^7.22.0", "eslint-plugin-standard": "^5.0.0", "postcss-flexbugs-fixes": "^4.2.1", @@ -84,14 +84,14 @@ "webpack-dev-server": "^3.11.2" }, "resolutions": { - "acorn": "^7.1.1", - "dot-prop": "^4.2.1", - "elliptic": "^6.5.3", - "ini": "^1.3.8", + "acorn": "^8.0.5", + "dot-prop": "^6.0.1", + "elliptic": "^6.5.4", + "ini": "^2.0.0", "kind-of": "^6.0.3", "lodash": "^4.17.20", - "mathjax": "^2.7.9", - "minimist": "^1.2.3", + "mathjax": "^3.1.2", + "minimist": "^1.2.5", "websocket-extensions": "^0.1.4" } } diff --git a/scripts/yarn.lock b/scripts/yarn.lock index df7585c..ecbe464 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@babel/code-frame@7.12.11": + version "7.12.11" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f" + integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw== + dependencies: + "@babel/highlight" "^7.10.4" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" @@ -948,10 +955,10 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/lodash-es@^4.17.3": - version "4.17.3" - resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.3.tgz#87eb0b3673b076b8ee655f1890260a136af09a2d" - integrity sha512-iHI0i7ZAL1qepz1Y7f3EKg/zUMDwDfTzitx+AlHhJJvXwenP682ZyGbgPSc5Ej3eEAKVbNWKFuwOadCj5vBbYQ== +"@types/lodash-es@^4.17.4": + version "4.17.4" + resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.4.tgz#b2e440d2bf8a93584a9fd798452ec497986c9b97" + integrity sha512-BBz79DCJbD2CVYZH67MBeHZRX++HF+5p8Mo5MzjZi64Wac39S3diedJYHZtScbRVf4DjZyN6LzA0SB0zy+HSSQ== dependencies: "@types/lodash" "*" @@ -1043,10 +1050,10 @@ date-fns "^2.0.1" popper.js "^1.14.1" -"@types/react-dom@^17.0.0": - version "17.0.0" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.0.tgz#b3b691eb956c4b3401777ee67b900cb28415d95a" - integrity sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g== +"@types/react-dom@^17.0.1": + version "17.0.1" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.1.tgz#d92d77d020bfb083e07cc8e0ac9f933599a4d56a" + integrity sha512-yIVyopxQb8IDZ7SOHeTovurFq+fXiPICa+GV3gp0Xedsl+MwQlMLKmvrnEjFbQxjliH5YVAEWFh975eVNmKj7Q== dependencies: "@types/react" "*" @@ -1079,10 +1086,10 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/react@^17.0.0": - version "17.0.0" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.0.tgz#5af3eb7fad2807092f0046a1302b7823e27919b8" - integrity sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw== +"@types/react@^17.0.2": + version "17.0.2" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8" + integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA== dependencies: "@types/prop-types" "*" csstype "^3.0.2" @@ -1297,10 +1304,10 @@ acorn-walk@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.0.0.tgz#c8ba6f0f1aac4b0a9e32d1f0af12be769528f36b" -acorn@^5.2.1, acorn@^6.4.1, acorn@^7.0.0, acorn@^7.1.1, acorn@^7.4.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^5.2.1, acorn@^6.4.1, acorn@^7.0.0, acorn@^7.4.0, acorn@^8.0.5: + version "8.0.5" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.5.tgz#a3bfb872a74a6a7f661bc81b9849d9cac12601b7" + integrity sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg== add-dom-event-listener@^1.1.0: version "1.1.0" @@ -1468,6 +1475,11 @@ arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" +array-filter@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-1.0.0.tgz#baf79e62e6ef4c2a4c0b831232daffec251f9d83" + integrity sha1-uveeYubvTCpMC4MSMtr/7CUfnYM= + array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -1618,6 +1630,13 @@ autoprefixer@^9.6.1: postcss "^7.0.32" postcss-value-parser "^4.1.0" +available-typed-arrays@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz#6b098ca9d8039079ee3f77f7b783c4480ba513f5" + integrity sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ== + dependencies: + array-filter "^1.0.0" + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" @@ -1810,10 +1829,15 @@ bluebird@^3.5.5: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" +bn.js@^4.11.9: + version "4.11.9" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" + integrity sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw== + body-parser@1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -1878,9 +1902,10 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.0.1: +brorand@^1.0.1, brorand@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= browser-pack@^6.0.1: version "6.1.0" @@ -1970,10 +1995,10 @@ browserify-zlib@^0.2.0, browserify-zlib@~0.2.0: dependencies: pako "~1.0.5" -browserify@^16.5.2: - version "16.5.2" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.5.2.tgz#d926835e9280fa5fd57f5bc301f2ef24a972ddfe" - integrity sha512-TkOR1cQGdmXU9zW4YukWzWVSJwrxmNdADFbqbE3HFgQWe5wqZmOawqZ7J/8MPCwk/W8yY7Y0h+7mOtcZxLP23g== +browserify@^17.0.0: + version "17.0.0" + resolved "https://registry.yarnpkg.com/browserify/-/browserify-17.0.0.tgz#4c48fed6c02bfa2b51fd3b670fddb805723cdc22" + integrity sha512-SaHqzhku9v/j6XsQMRxPyBrSP3gnwmE27gLJYZgMT2GeK3J0+0toN+MnuNYDfHwVGQfLiMZ7KSNSIXHemy905w== dependencies: JSONStream "^1.0.3" assert "^1.4.0" @@ -1987,31 +2012,31 @@ browserify@^16.5.2: constants-browserify "~1.0.0" crypto-browserify "^3.0.0" defined "^1.0.0" - deps-sort "^2.0.0" + deps-sort "^2.0.1" domain-browser "^1.2.0" duplexer2 "~0.1.2" - events "^2.0.0" + events "^3.0.0" glob "^7.1.0" has "^1.0.0" htmlescape "^1.1.0" https-browserify "^1.0.0" inherits "~2.0.1" - insert-module-globals "^7.0.0" + insert-module-globals "^7.2.1" labeled-stream-splicer "^2.0.0" mkdirp-classic "^0.5.2" module-deps "^6.2.3" os-browserify "~0.3.0" parents "^1.0.1" - path-browserify "~0.0.0" + path-browserify "^1.0.0" process "~0.11.0" punycode "^1.3.2" querystring-es3 "~0.2.0" read-only-stream "^2.0.0" readable-stream "^2.0.2" resolve "^1.1.4" - shasum "^1.0.0" + shasum-object "^1.0.0" shell-quote "^1.6.1" - stream-browserify "^2.0.0" + stream-browserify "^3.0.0" stream-http "^3.0.0" string_decoder "^1.1.1" subarg "^1.0.0" @@ -2020,7 +2045,7 @@ browserify@^16.5.2: timers-browserify "^1.0.1" tty-browserify "0.0.1" url "~0.11.0" - util "~0.10.1" + util "~0.12.0" vm-browserify "^1.0.0" xtend "^4.0.0" @@ -2144,7 +2169,7 @@ cached-path-relative@^1.0.0, cached-path-relative@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" -call-bind@^1.0.0: +call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== @@ -3039,12 +3064,13 @@ depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" -deps-sort@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.0.tgz#091724902e84658260eb910748cccd1af6e21fb5" +deps-sort@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.1.tgz#9dfdc876d2bcec3386b6829ac52162cda9fa208d" + integrity sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw== dependencies: JSONStream "^1.0.3" - shasum "^1.0.0" + shasum-object "^1.0.0" subarg "^1.0.0" through2 "^2.0.0" @@ -3171,17 +3197,17 @@ domutils@^1.7.0: dom-serializer "0" domelementtype "1" -dot-prop@^4.1.1, dot-prop@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4" - integrity sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ== +dot-prop@^4.1.1, dot-prop@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-6.0.1.tgz#fc26b3cf142b9e59b74dbd39ed66ce620c681083" + integrity sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA== dependencies: - is-obj "^1.0.0" + is-obj "^2.0.0" -dropzone@^5.7.2: - version "5.7.2" - resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.7.2.tgz#91bee1572dda515d40901da304bc79dddf309b4c" - integrity sha512-m217bJHtf0J1IiKn4Tv6mnu1h5QvQNBnKZ39gma7hzGQhIZMxYq1vYEHs4AVd4ThFwmALys+52NAOD4zdLTG4w== +dropzone@^5.7.6: + version "5.7.6" + resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.7.6.tgz#a5ebb603ea2aaf630118c37a7be59bb3444ad6e1" + integrity sha512-z38j+PZsH38rFGOK2rQ877t6c0cPos053fPp3RKaDvCDjAw4KflQQGn7BhCWeFq9Zl1hinB8khPrQdJ6cIBryQ== duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: version "0.1.4" @@ -3225,18 +3251,18 @@ element-resize-detector@^1.2.1: dependencies: batch-processor "1.0.0" -elliptic@^6.0.0, elliptic@^6.5.3: - version "6.5.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.3.tgz#cb59eb2efdaf73a0bd78ccd7015a62ad6e0f93d6" - integrity sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw== +elliptic@^6.0.0, elliptic@^6.5.4: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" + bn.js "^4.11.9" + brorand "^1.1.0" hash.js "^1.0.0" - hmac-drbg "^1.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" email-addresses@^3.0.1: version "3.0.3" @@ -3354,6 +3380,26 @@ es-abstract@^1.18.0-next.0, es-abstract@^1.18.0-next.1: string.prototype.trimend "^1.0.1" string.prototype.trimstart "^1.0.1" +es-abstract@^1.18.0-next.2: + version "1.18.0-next.2" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0-next.2.tgz#088101a55f0541f595e7e057199e27ddc8f3a5c2" + integrity sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.2" + is-negative-zero "^2.0.1" + is-regex "^1.1.1" + object-inspect "^1.9.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + string.prototype.trimend "^1.0.3" + string.prototype.trimstart "^1.0.3" + es-to-primitive@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" @@ -3454,10 +3500,10 @@ eslint-plugin-jsx-a11y@^6.4.1: jsx-ast-utils "^3.1.0" language-tags "^1.0.5" -eslint-plugin-promise@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz#845fd8b2260ad8f82564c1222fce44ad71d9418a" - integrity sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw== +eslint-plugin-promise@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz#61485df2a359e03149fdafc0a68b0e030ad2ac45" + integrity sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ== eslint-plugin-react@^7.22.0: version "7.22.0" @@ -3514,12 +3560,12 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8" integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ== -eslint@^7.18.0: - version "7.18.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.18.0.tgz#7fdcd2f3715a41fe6295a16234bd69aed2c75e67" - integrity sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ== +eslint@^7.20.0: + version "7.20.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.20.0.tgz#db07c4ca4eda2e2316e7aa57ac7fc91ec550bdc7" + integrity sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw== dependencies: - "@babel/code-frame" "^7.0.0" + "@babel/code-frame" "7.12.11" "@eslint/eslintrc" "^0.3.0" ajv "^6.10.0" chalk "^4.0.0" @@ -3531,7 +3577,7 @@ eslint@^7.18.0: eslint-utils "^2.1.0" eslint-visitor-keys "^2.0.0" espree "^7.3.1" - esquery "^1.2.0" + esquery "^1.4.0" esutils "^2.0.2" file-entry-cache "^6.0.0" functional-red-black-tree "^1.0.1" @@ -3587,10 +3633,10 @@ esprima@~3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" -esquery@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" - integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" @@ -3629,11 +3675,6 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5" - integrity sha512-3Zmiobend8P9DjmKAty0Era4jV8oJ0yGYe2nJJAxgymF9+N8F2m0hhZiMoWtcfepExzNKZumFU3ksdQbInGWCg== - events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -3776,6 +3817,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-safe-stringify@^2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" + integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== + faye-websocket@^0.11.3: version "0.11.3" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.3.tgz#5c0e9a8968e8912c286639fde977a8b209f2508e" @@ -3945,6 +3991,11 @@ for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -4353,9 +4404,10 @@ highlightjs@^9.16.2: resolved "https://registry.yarnpkg.com/highlightjs/-/highlightjs-9.16.2.tgz#07ea6cc7c93340fc440734fb7abf28558f1f0fe1" integrity sha512-FK1vmMj8BbEipEy8DLIvp71t5UsC7n2D6En/UfM/91PCwmOpj6f2iu0Y0coRC62KSRHHC+dquM2xMULV/X7NFg== -hmac-drbg@^1.0.0: +hmac-drbg@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" @@ -4596,7 +4648,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" @@ -4608,10 +4660,10 @@ inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" -ini@^1.3.4, ini@^1.3.5, ini@^1.3.8, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== +ini@^1.3.4, ini@^1.3.5, ini@^2.0.0, ini@~1.3.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== inline-source-map@~0.6.0: version "0.6.2" @@ -4619,9 +4671,10 @@ inline-source-map@~0.6.0: dependencies: source-map "~0.5.3" -insert-module-globals@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.2.0.tgz#ec87e5b42728479e327bd5c5c71611ddfb4752ba" +insert-module-globals@^7.2.1: + version "7.2.1" + resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.2.1.tgz#d5e33185181a4e1f33b15f7bf100ee91890d5cb3" + integrity sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg== dependencies: JSONStream "^1.0.3" acorn-node "^1.5.2" @@ -4730,6 +4783,7 @@ is-binary-path@~2.1.0: is-buffer@^1.1.0: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== is-callable@^1.1.4: version "1.1.4" @@ -4829,6 +4883,11 @@ is-fullwidth-code-point@^3.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== +is-generator-function@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.8.tgz#dfb5c2b120e02b0a8d9d2c6806cd5621aa922f7b" + integrity sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ== + is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" @@ -4846,6 +4905,11 @@ is-negative-zero@^2.0.0: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.0.tgz#9553b121b0fac28869da9ed459e20c7543788461" integrity sha1-lVOxIbD6wohp2p7UWeIMdUN4hGE= +is-negative-zero@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24" + integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== + is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" @@ -4857,9 +4921,10 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" +is-obj@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" + integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== is-path-cwd@^2.0.0: version "2.2.0" @@ -4928,6 +4993,17 @@ is-symbol@^1.0.2: dependencies: has-symbols "^1.0.0" +is-typed-array@^1.1.3: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e" + integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug== + dependencies: + available-typed-arrays "^1.0.2" + call-bind "^1.0.2" + es-abstract "^1.18.0-next.2" + foreach "^2.0.5" + has-symbols "^1.0.1" + is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -5038,12 +5114,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= -json-stable-stringify@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" - dependencies: - jsonify "~0.0.0" - json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -5072,10 +5142,6 @@ jsonfile@^4.0.0: optionalDependencies: graceful-fs "^4.1.6" -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - jsonparse@0.0.5: version "0.0.5" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-0.0.5.tgz#330542ad3f0a654665b778f3eb2d9a9fa507ac64" @@ -5390,15 +5456,15 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.7.tgz#6e14b595581d2319cdcf033a24caaf41455a01fb" - integrity sha512-No11hFYcXr/zkBvL6qFmAp1z6BKY3zqLMHny/JN/ey+al7qwCM2+CMBL9BOgqMxZU36fz4cCWfn2poWIf7QRXA== +marked@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.0.tgz#9662bbcb77ebbded0662a7be66ff929a8611cee5" + integrity sha512-NqRSh2+LlN2NInpqTQnS614Y/3NkVMFFU6sJlRFEpxJ/LHuK/qJECH7/fXZjk4VZstPW/Pevjil/VtSONsLc7Q== -mathjax@^2.7.9: - version "2.7.9" - resolved "https://registry.yarnpkg.com/mathjax/-/mathjax-2.7.9.tgz#d6b67955c173e7d719fcb2fc0288662884eb7d3d" - integrity sha512-NOGEDTIM9+MrsqnjPEjVGNx4q0GQxqm61yQwSK+/5S59i26wId5IC5gNu9/bu8+CCVl5p9G2IHcAl/wJa+5+BQ== +mathjax@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/mathjax/-/mathjax-3.1.2.tgz#95c0d45ce2330ef7b6a815cebe7d61ecc26bbabd" + integrity sha512-BojKspBv4nNWzO1wC6VEI+g9gHDOhkaGHGgLxXkasdU4pwjdO5AXD5M/wcLPkXYPjZ/N+6sU8rjQTlyvN2cWiQ== md5.js@^1.3.4: version "1.3.5" @@ -5539,7 +5605,7 @@ minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" -minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: +minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" @@ -5549,7 +5615,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: dependencies: brace-expansion "^1.1.7" -minimist@0.0.8, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5: +minimist@0.0.8, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -5670,10 +5736,10 @@ module-deps@^6.2.3: through2 "^2.0.0" xtend "^4.0.0" -moment-timezone@^0.5.32: - version "0.5.32" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.32.tgz#db7677cc3cc680fd30303ebd90b0da1ca0dfecc2" - integrity sha512-Z8QNyuQHQAmWucp8Knmgei8YNo28aLjJq6Ma+jy1ZSpSk5nyfRT8xgUbSQvD2+2UajISfenndwvFuH3NGS+nvA== +moment-timezone@^0.5.33: + version "0.5.33" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.33.tgz#b252fd6bb57f341c9b59a5ab61a8e51a73bbd22c" + integrity sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w== dependencies: moment ">= 2.9.0" @@ -5991,6 +6057,11 @@ object-inspect@^1.8.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0" integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA== +object-inspect@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" + integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw== + object-is@^1.0.1: version "1.1.3" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.3.tgz#2e3b9e65560137455ee3bd62aec4d90a2ea1cc81" @@ -6301,10 +6372,15 @@ pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" -path-browserify@0.0.1, path-browserify@~0.0.0: +path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" +path-browserify@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-complete-extname@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/path-complete-extname/-/path-complete-extname-1.0.0.tgz#f889985dc91000c815515c0bfed06c5acda0752b" @@ -7372,10 +7448,10 @@ react-copy-to-clipboard@^5.0.3: copy-to-clipboard "^3" prop-types "^15.5.8" -react-datepicker@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-3.4.1.tgz#3c8e8989f1ab31a767c17170a2d1318aa3c3b9ef" - integrity sha512-ASyVb7UmVx1vzeITidD7Cr/EXRXhKyjjbSkBndPc1MipYq4rqQ3eMFgvRQzpsXc3JmIMFgICm7nmN6Otc1GE/Q== +react-datepicker@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-3.5.0.tgz#fbac7d6d4b29001420d4411132c3d5f5d591d15c" + integrity sha512-iTRz15aG9lEF7tJVjPcGWvfALb2/RjqHVTvQO7J0DEvGSGZnTJpHlkTrBGBjJ9+i5zM4zcRGoStpjhaezS9p1g== dependencies: classnames "^2.2.6" date-fns "^2.0.1" @@ -7417,10 +7493,10 @@ react-onclickoutside@^6.9.0: resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.9.0.tgz#a54bc317ae8cf6131a5d78acea55a11067f37a1f" integrity sha512-8ltIY3bC7oGhj2nPAvWOGi+xGFybPNhJM0V1H8hY/whNcXgmDeaeoCMPPd8VatrpTsUWjb/vGzrmu6SrXVty3A== -react-paginate@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/react-paginate/-/react-paginate-7.0.0.tgz#af206ef92a2b6ef87646855eb1612157254ad0c0" - integrity sha512-mzPwHGJfSs79JBGX2V0v/FfQp3yWdz0XRrB9JvsUbJdsxqCt4osk1O669+K8VPQ0Lh9v0lJsnLLoJwnsgdJFng== +react-paginate@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/react-paginate/-/react-paginate-7.1.0.tgz#7dc244ad7ca2db59b6eb83472655de3c126be1bc" + integrity sha512-OZm87+Qsixw3UlpR57Va6I32wC3SJ9eRsMDi2PjWSdoMVszi3B35A6AcuXB0If/AwxJeEqspYVBJLNf8UScB7g== dependencies: prop-types "^15.6.1" @@ -7475,10 +7551,10 @@ react-sizeme@^2.6.12: shallowequal "^1.1.0" throttle-debounce "^2.1.0" -react-textarea-autosize@^8.3.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.0.tgz#e6e2fd186d9f61bb80ac6e2dcb4c55504f93c2fa" - integrity sha512-3GLWFAan2pbwBeoeNDoqGmSbrShORtgWfaWX0RJDivsUrpShh01saRM5RU/i4Zmf+whpBVEY5cA90Eq8Ub1N3w== +react-textarea-autosize@^8.3.1: + version "8.3.1" + resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-8.3.1.tgz#b942a934cc660ecfc645717d1fb84344b69dcb15" + integrity sha512-Vk02C3RWKLjx1wSwcVuPwfTuyGIemBB2MjDi01OnBYxKWSJFA/O7IOzr9FrO8AuRlkupk4X6Kjew2mYyEDXi0A== dependencies: "@babel/runtime" "^7.10.2" use-composed-ref "^1.0.0" @@ -7491,10 +7567,10 @@ react-tools@~0.13.0: commoner "^0.10.0" jstransform "^10.1.0" -react-tooltip@^4.2.13: - version "4.2.13" - resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.13.tgz#908db8a41dc10ae2ae9cc1864746cde939aaab0f" - integrity sha512-iAZ02wSxChLWb7Vnu0zeQMyAo/jiGHrwFNILWaR3pCKaFVRjKcv/B6TBI4+Xd66xLXVzLngwJ91Tf5D+mqAqVA== +react-tooltip@^4.2.14: + version "4.2.14" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-4.2.14.tgz#8e06b5926fdf6672e78d8ccadaa16bef40d131d7" + integrity sha512-hS2kAlpjyH5MXL9DaGKsdmEFCIEuMD2RZXkEJeNjmDe05dHpqj93o5JgpmczAgQFk099+JSsnHUDo7pIOuyMDQ== dependencies: prop-types "^15.7.2" uuid "^7.0.3" @@ -7591,7 +7667,7 @@ read-pkg@^2.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.6.0: +readable-stream@^3.0.6, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== @@ -8071,7 +8147,7 @@ setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" -sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: +sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" dependencies: @@ -8095,12 +8171,12 @@ shallowequal@^1.0.1, shallowequal@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8" -shasum@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" +shasum-object@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shasum-object/-/shasum-object-1.0.0.tgz#0b7b74ff5b66ecf9035475522fa05090ac47e29e" + integrity sha512-Iqo5rp/3xVi6M4YheapzZhhGPVs0yZwHj7wvwQ1B9z8H6zk+FEnI7y3Teq7qwnekfEhu8WmG2z0z4iWZaxLWVg== dependencies: - json-stable-stringify "~0.0.0" - sha.js "~2.4.4" + fast-safe-stringify "^2.0.7" shebang-command@^1.2.0: version "1.2.0" @@ -8368,13 +8444,21 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" -stream-browserify@^2.0.0, stream-browserify@^2.0.1: +stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" dependencies: inherits "~2.0.1" readable-stream "^2.0.2" +stream-browserify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" + integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== + dependencies: + inherits "~2.0.4" + readable-stream "^3.5.0" + stream-combiner2@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" @@ -8477,6 +8561,14 @@ string.prototype.trimend@^1.0.1: define-properties "^1.1.3" es-abstract "^1.17.5" +string.prototype.trimend@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz#a22bd53cca5c7cf44d7c9d5c732118873d6cd18b" + integrity sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + string.prototype.trimstart@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" @@ -8485,6 +8577,14 @@ string.prototype.trimstart@^1.0.1: define-properties "^1.1.3" es-abstract "^1.17.5" +string.prototype.trimstart@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz#9b4cb590e123bb36564401d59824298de50fd5aa" + integrity sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -8853,10 +8953,10 @@ ts-essentials@^2.0.3: resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== -ts-loader@^8.0.14: - version "8.0.14" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.14.tgz#e46ac1f8dcb88808d0b1335d2eae65b74bd78fe8" - integrity sha512-Jt/hHlUnApOZjnSjTmZ+AbD5BGlQFx3f1D0nYuNKwz0JJnuDGHJas6az+FlWKwwRTu+26GXpv249A8UAnYUpqA== +ts-loader@^8.0.17: + version "8.0.17" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.17.tgz#98f2ccff9130074f4079fd89b946b4c637b1f2fc" + integrity sha512-OeVfSshx6ot/TCxRwpBHQ/4lRzfgyTkvi7ghDVrLXOHzTbSK413ROgu/xNqM72i3AFeAIJgQy78FwSMKmOW68w== dependencies: chalk "^4.1.0" enhanced-resolve "^4.0.0" @@ -8930,10 +9030,10 @@ typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" -typescript@^4.1.3: - version "4.1.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" - integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== +typescript@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72" + integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA== umd@^3.0.0: version "3.0.3" @@ -9105,11 +9205,17 @@ util@^0.11.0: dependencies: inherits "2.0.3" -util@~0.10.1: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" +util@~0.12.0: + version "0.12.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.3.tgz#971bb0292d2cc0c892dab7c6a5d37c2bec707888" + integrity sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog== dependencies: - inherits "2.0.3" + inherits "^2.0.3" + is-arguments "^1.0.4" + is-generator-function "^1.0.7" + is-typed-array "^1.1.3" + safe-buffer "^5.1.2" + which-typed-array "^1.1.2" utils-merge@1.0.1: version "1.0.1" @@ -9357,6 +9463,19 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" +which-typed-array@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff" + integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA== + dependencies: + available-typed-arrays "^1.0.2" + call-bind "^1.0.0" + es-abstract "^1.18.0-next.1" + foreach "^2.0.5" + function-bind "^1.1.1" + has-symbols "^1.0.1" + is-typed-array "^1.1.3" + which@1, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" -- GitLab From 0d04a99edcc3f86418a5ec1b9fafb1ba2edb9122 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 19 Feb 2021 16:21:19 -1000 Subject: [PATCH 261/287] removing yarn install from assets:precompile setp --- scripts/Rakefile | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/Rakefile b/scripts/Rakefile index 9a5ea73..ecab9bc 100644 --- a/scripts/Rakefile +++ b/scripts/Rakefile @@ -4,3 +4,12 @@ require_relative "config/application" Rails.application.load_tasks + +# This removes the yarn install task from assets:precompile task to prevent +# Rails from trying to rerun yarn install. +Rake::Task["yarn:install"].clear +namespace :yarn do + task :install do + # DO NOTHING. + end +end \ No newline at end of file -- GitLab From 62c9b759674259868f3d7a6fa47a701c3437755e Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 19 Feb 2021 16:29:28 -1000 Subject: [PATCH 262/287] removing extra logging --- scripts/config/environment.rb | 8 +------- scripts/config/initializers/sidekiq.rb | 2 -- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/scripts/config/environment.rb b/scripts/config/environment.rb index 45506d9..856fba2 100644 --- a/scripts/config/environment.rb +++ b/scripts/config/environment.rb @@ -2,10 +2,4 @@ require_relative 'application' # Initialize the Rails application. -Rails.application.initialize! - -# puts "==========================================" -# puts "Host: #{Rails.configuration.database_configuration[Rails.env]["host"]}" -# puts "Database: #{Rails.configuration.database_configuration[Rails.env]["database"]}" -# puts "User: #{Rails.configuration.database_configuration[Rails.env]["username"]}" -# puts "==========================================" \ No newline at end of file +Rails.application.initialize! \ No newline at end of file diff --git a/scripts/config/initializers/sidekiq.rb b/scripts/config/initializers/sidekiq.rb index 6a70575..387dfe4 100644 --- a/scripts/config/initializers/sidekiq.rb +++ b/scripts/config/initializers/sidekiq.rb @@ -8,6 +8,4 @@ Rails.application.configure do host: Rails.application.secrets.actionmailer_host, port: Rails.application.secrets.actionmailer_port } - - puts config.action_mailer.default_url_options end \ No newline at end of file -- GitLab From 16880e888cd3f14089bd9685a9c834716a490ad0 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Fri, 19 Feb 2021 17:04:03 -1000 Subject: [PATCH 263/287] Changing env variable names --- scripts/.env.example | 5 ++++- scripts/config/database.yml | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/.env.example b/scripts/.env.example index 7759360..843dfc2 100644 --- a/scripts/.env.example +++ b/scripts/.env.example @@ -14,4 +14,7 @@ appS3BucketUUID= S3_BUCKET= host= port= -protocol= \ No newline at end of file +protocol= +PG_USER= +APP_DB_ADMIN_PASSWORD= +PG_DATABASE= \ No newline at end of file diff --git a/scripts/config/database.yml b/scripts/config/database.yml index b1c7d3f..6c0ebd6 100644 --- a/scripts/config/database.yml +++ b/scripts/config/database.yml @@ -3,9 +3,9 @@ default: &default encoding: unicode pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> host: <%= ENV["PGHOST"] || "localhost" %> - username: <%= ENV["POSTGRES_USER"] || "learn_admin_user" %> + username: <%= ENV["PG_USER"] || "learn_admin_user" %> password: <%= ENV["APP_DB_ADMIN_PASSWORD"] || "postgres" %> - database: <%= ENV["POSTGRES_DB"] || "learn_db" %> + database: <%= ENV["PG_DATABASE"] || "learn_db" %> min_messages: warning pool: 5 timeout: 5000 -- GitLab From d9c9939c9b26e8805188a0244d18bd2a97be4604 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 22 Feb 2021 11:17:18 -1000 Subject: [PATCH 264/287] updating dependencies --- Dockerfile | 2 +- hardening_manifest.yaml | 10 +++++----- scripts/package.json | 6 +++--- scripts/yarn.lock | 24 ++++++++++++------------ 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7cc1112..7700811 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,4 +66,4 @@ RUN rm -rf node_modules HEALTHCHECK none # Set the entry point. -ENTRYPOINT ["/app/entrypoint-server.sh"] +ENTRYPOINT ["/app/entrypoint-server.sh"] \ No newline at end of file diff --git a/hardening_manifest.yaml b/hardening_manifest.yaml index 83b3252..979c006 100644 --- a/hardening_manifest.yaml +++ b/hardening_manifest.yaml @@ -38,7 +38,7 @@ resources: filename: redis-cli.tar.gz validation: type: sha256 - value: bb5f74ccdfcf66746c926dfb0e804627de3f61cc722c3cd367ab6c2c9960b950 + value: 7fb9930394f5fd5ccadb911a3b5bcebe165c41233808a64c8c61a9a0a4c260cd - auth: type: s3 id: galvanize @@ -47,7 +47,7 @@ resources: filename: node_modules.tar.gz validation: type: sha256 - value: b3ea918d9628aba96a83c9bfd3c1cd98f66a4b30daa2368b6b0dd0be9234476e + value: 27a4e6cf0ec7893a18dfd8c44c7c6df97d3aeb27875e5944b20090a1b5423f18 - auth: type: s3 id: galvanize @@ -56,7 +56,7 @@ resources: filename: nodejs.tar.gz validation: type: sha256 - value: 480ebc0f3dca30c31b976d0f5088ce4bfe3d4aba9dca0202d2390801b80c8709 + value: 57b77995875b9dfb072b696a37f15d42f133088f3201afdd5293e8e2fc58e3ff - auth: type: s3 id: galvanize @@ -65,7 +65,7 @@ resources: filename: yarn.tar.gz validation: type: sha256 - value: cdd48447584ab01e81288994699bcf67d99c32a7bc3b068a2218016bb3d9063e + value: 9a0158dd443697c7fa37693a1efc5500061772fb7a4d7cd0e6c91ee00c7e8707 - auth: type: s3 id: galvanize @@ -74,7 +74,7 @@ resources: filename: bundle.tar.gz validation: type: sha256 - value: c8a38e0da532865e445280c5657653c070c3af3686380d30a3bb9de483022a5a + value: 040f618566d8033e0fba7b38e2b19fa1d8885d17bd2effa4dbf9014fcbf5c3f4 # List of project maintainers maintainers: diff --git a/scripts/package.json b/scripts/package.json index 3930fb2..f8845a5 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -31,7 +31,7 @@ "dropzone": "^5.7.6", "highlightjs": "^9.16.2", "immutability-helper": "^3.1.1", - "lodash-es": "^4.17.20", + "lodash-es": "^4.17.21", "marked": "^2.0.0", "moment": "^2.29.1", "moment-timezone": "^0.5.33", @@ -67,7 +67,7 @@ "@types/react-addons-css-transition-group": "^15.0.5", "@types/react-beautiful-dnd": "^13.0.0", "@types/react-copy-to-clipboard": "^5.0.0", - "@types/react-datepicker": "^3.1.3", + "@types/react-datepicker": "^3.1.5", "@types/react-dom": "^17.0.1", "@types/react-textarea-autosize": "^4.3.5", "@types/vimeo__player": "^2.10.0", @@ -89,7 +89,7 @@ "elliptic": "^6.5.4", "ini": "^2.0.0", "kind-of": "^6.0.3", - "lodash": "^4.17.20", + "lodash": "^4.17.21", "mathjax": "^3.1.2", "minimist": "^1.2.5", "websocket-extensions": "^0.1.4" diff --git a/scripts/yarn.lock b/scripts/yarn.lock index ecbe464..8c8be98 100644 --- a/scripts/yarn.lock +++ b/scripts/yarn.lock @@ -1041,10 +1041,10 @@ dependencies: "@types/react" "*" -"@types/react-datepicker@^3.1.3": - version "3.1.3" - resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-3.1.3.tgz#4525475c451091477de27ed52c029a9043ec6b11" - integrity sha512-Gca56Pa8hpy3eO2naRLdC63iflER7JXxgdHTGJ6yF5DjvhQoirRPqCJerWLq6TOWd+sEsZWwMm17Q17YRVLFtw== +"@types/react-datepicker@^3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-3.1.5.tgz#653a8a80eb083ab2cf7d724d90d590018e030d1c" + integrity sha512-aPl3ay63H4ugUeGSWQqQCVqpo9nS2SSnnX3QNclr9Ze82PWBTaXRFCLXOcUP+JgnjCabb3ZF4XwTom2Pob3hPQ== dependencies: "@types/react" "*" date-fns "^2.0.1" @@ -5309,10 +5309,10 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" -lodash-es@^4.17.20: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.20.tgz#29f6332eefc60e849f869c264bc71126ad61e8f7" - integrity sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA== +lodash-es@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" + integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== lodash._getnative@^3.0.0: version "3.9.1" @@ -5374,10 +5374,10 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: - version "4.17.20" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" - integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== loglevel@^1.6.8: version "1.7.0" -- GitLab From e7a271e145f2842f3f5624ef24b3e0ddad650cbf Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 22 Feb 2021 11:28:40 -1000 Subject: [PATCH 265/287] adding health check --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7700811..4644a61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -63,7 +63,7 @@ RUN rm -rf yarn RUN rm -rf node_modules # Health check. -HEALTHCHECK none +HEALTHCHECK --timeout=60s CMD curl -fs http://localhost:3000/hello || exit 1 # Set the entry point. ENTRYPOINT ["/app/entrypoint-server.sh"] \ No newline at end of file -- GitLab From 92a1b63a387ef4ebdeabca8a5886abf7696baf8b Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 22 Feb 2021 12:03:34 -1000 Subject: [PATCH 266/287] updating health check --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 4644a61..99ba906 100644 --- a/Dockerfile +++ b/Dockerfile @@ -63,7 +63,8 @@ RUN rm -rf yarn RUN rm -rf node_modules # Health check. -HEALTHCHECK --timeout=60s CMD curl -fs http://localhost:3000/hello || exit 1 +HEALTHCHECK --start-period=60s --interval=60s --timeout=30s --retries=3 \ + CMD curl -fs http://localhost:3000/hello || exit 1 # Set the entry point. ENTRYPOINT ["/app/entrypoint-server.sh"] \ No newline at end of file -- GitLab From f8abf995e8394ff1164f0915d24b276820c22eb4 Mon Sep 17 00:00:00 2001 From: Michael Uranaka Date: Mon, 22 Feb 2021 16:25:25 -1000 Subject: [PATCH 267/287] upgrading to bootstrap 3.4.1 --- scripts/public/javascripts/apitome/application.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/public/javascripts/apitome/application.js b/scripts/public/javascripts/apitome/application.js index e23a0f5..b82cd0d 100644 --- a/scripts/public/javascripts/apitome/application.js +++ b/scripts/public/javascripts/apitome/application.js @@ -5,13 +5,13 @@ (function(e,undefined){var t,n,r=typeof undefined,i=e.location,o=e.document,s=o.documentElement,a=e.jQuery,u=e.$,l={},c=[],p="2.0.3",f=c.concat,h=c.push,d=c.slice,g=c.indexOf,m=l.toString,y=l.hasOwnProperty,v=p.trim,x=function(e,n){return new x.fn.init(e,n,t)},b=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,w=/\S+/g,T=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,k=/^-ms-/,N=/-([\da-z])/gi,E=function(e,t){return t.toUpperCase()},S=function(){o.removeEventListener("DOMContentLoaded",S,!1),e.removeEventListener("load",S,!1),x.ready()};x.fn=x.prototype={jquery:p,constructor:x,init:function(e,t,n){var r,i;if(!e)return this;if("string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:T.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof x?t[0]:t,x.merge(this,x.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:o,!0)),C.test(r[1])&&x.isPlainObject(t))for(r in t)x.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return i=o.getElementById(r[2]),i&&i.parentNode&&(this.length=1,this[0]=i),this.context=o,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?n.ready(e):(e.selector!==undefined&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return d.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,t,n,r,i,o,s=arguments[0]||{},a=1,u=arguments.length,l=!1;for("boolean"==typeof s&&(l=s,s=arguments[1]||{},a=2),"object"==typeof s||x.isFunction(s)||(s={}),u===a&&(s=this,--a);u>a;a++)if(null!=(e=arguments[a]))for(t in e)n=s[t],r=e[t],s!==r&&(l&&r&&(x.isPlainObject(r)||(i=x.isArray(r)))?(i?(i=!1,o=n&&x.isArray(n)?n:[]):o=n&&x.isPlainObject(n)?n:{},s[t]=x.extend(l,o,r)):r!==undefined&&(s[t]=r));return s},x.extend({expando:"jQuery"+(p+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=a),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){(e===!0?--x.readyWait:x.isReady)||(x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(o,[x]),x.fn.trigger&&x(o).trigger("ready").off("ready")))},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray,isWindow:function(e){return null!=e&&e===e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?l[m.call(e)]||"object":typeof e},isPlainObject:function(e){if("object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!y.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(t){return!1}return!0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||o;var r=C.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:JSON.parse,parseXML:function(e){var t,n;if(!e||"string"!=typeof e)return null;try{n=new DOMParser,t=n.parseFromString(e,"text/xml")}catch(r){t=undefined}return(!t||t.getElementsByTagName("parsererror").length)&&x.error("Invalid XML: "+e),t},noop:function(){},globalEval:function(e){var t,n=eval;e=x.trim(e),e&&(1===e.indexOf("use strict")?(t=o.createElement("script"),t.text=e,o.head.appendChild(t).parentNode.removeChild(t)):n(e))},camelCase:function(e){return e.replace(k,"ms-").replace(N,E)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,s=j(e);if(n){if(s){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(s){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:function(e){return null==e?"":v.call(e)},makeArray:function(e,t){var n=t||[];return null!=e&&(j(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:g.call(t,e,n)},merge:function(e,t){var n=t.length,r=e.length,i=0;if("number"==typeof n)for(;n>i;i++)e[r++]=t[i];else while(t[i]!==undefined)e[r++]=t[i++];return e.length=r,e},grep:function(e,t,n){var r,i=[],o=0,s=e.length;for(n=!!n;s>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,s=j(e),a=[];if(s)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(a[a.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(a[a.length]=r);return f.apply([],a)},guid:1,proxy:function(e,t){var n,r,i;return"string"==typeof t&&(n=e[t],t=e,e=n),x.isFunction(e)?(r=d.call(arguments,2),i=function(){return e.apply(t||this,r.concat(d.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):undefined},access:function(e,t,n,r,i,o,s){var a=0,u=e.length,l=null==n;if("object"===x.type(n)){i=!0;for(a in n)x.access(e,t,a,n[a],!0,o,s)}else if(r!==undefined&&(i=!0,x.isFunction(r)||(s=!0),l&&(s?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(x(e),n)})),t))for(;u>a;a++)t(e[a],n,s?r:r.call(e[a],a,t(e[a],n)));return i?e:l?t.call(e):u?t(e[0],n):o},now:Date.now,swap:function(e,t,n,r){var i,o,s={};for(o in t)s[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=s[o];return i}}),x.ready.promise=function(t){return n||(n=x.Deferred(),"complete"===o.readyState?setTimeout(x.ready):(o.addEventListener("DOMContentLoaded",S,!1),e.addEventListener("load",S,!1))),n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){l["[object "+t+"]"]=t.toLowerCase()});function j(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}t=x(o),function(e,undefined){var t,n,r,i,o,s,a,u,l,c,p,f,h,d,g,m,y,v="sizzle"+-new Date,b=e.document,w=0,T=0,C=st(),k=st(),N=st(),E=!1,S=function(e,t){return e===t?(E=!0,0):0},j=typeof undefined,D=1<<31,A={}.hasOwnProperty,L=[],q=L.pop,H=L.push,O=L.push,F=L.slice,P=L.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},R="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",M="[\\x20\\t\\r\\n\\f]",W="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",$=W.replace("w","w#"),B="\\["+M+"*("+W+")"+M+"*(?:([*^$|!~]?=)"+M+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+$+")|)|)"+M+"*\\]",I=":("+W+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+B.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+M+"+|((?:^|[^\\\\])(?:\\\\.)*)"+M+"+$","g"),_=RegExp("^"+M+"*,"+M+"*"),X=RegExp("^"+M+"*([>+~]|"+M+")"+M+"*"),U=RegExp(M+"*[+~]"),Y=RegExp("="+M+"*([^\\]'\"]*)"+M+"*\\]","g"),V=RegExp(I),G=RegExp("^"+$+"$"),J={ID:RegExp("^#("+W+")"),CLASS:RegExp("^\\.("+W+")"),TAG:RegExp("^("+W.replace("w","w*")+")"),ATTR:RegExp("^"+B),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:RegExp("^(?:"+R+")$","i"),needsContext:RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Q=/^[^{]+\{\s*\[native \w/,K=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Z=/^(?:input|select|textarea|button)$/i,et=/^h\d$/i,tt=/'|\\/g,nt=RegExp("\\\\([\\da-f]{1,6}"+M+"?|("+M+")|.)","ig"),rt=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{O.apply(L=F.call(b.childNodes),b.childNodes),L[b.childNodes.length].nodeType}catch(it){O={apply:L.length?function(e,t){H.apply(e,F.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function ot(e,t,r,i){var o,s,a,u,l,f,g,m,x,w;if((t?t.ownerDocument||t:b)!==p&&c(t),t=t||p,r=r||[],!e||"string"!=typeof e)return r;if(1!==(u=t.nodeType)&&9!==u)return[];if(h&&!i){if(o=K.exec(e))if(a=o[1]){if(9===u){if(s=t.getElementById(a),!s||!s.parentNode)return r;if(s.id===a)return r.push(s),r}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(a))&&y(t,s)&&s.id===a)return r.push(s),r}else{if(o[2])return O.apply(r,t.getElementsByTagName(e)),r;if((a=o[3])&&n.getElementsByClassName&&t.getElementsByClassName)return O.apply(r,t.getElementsByClassName(a)),r}if(n.qsa&&(!d||!d.test(e))){if(m=g=v,x=t,w=9===u&&e,1===u&&"object"!==t.nodeName.toLowerCase()){f=gt(e),(g=t.getAttribute("id"))?m=g.replace(tt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",l=f.length;while(l--)f[l]=m+mt(f[l]);x=U.test(e)&&t.parentNode||t,w=f.join(",")}if(w)try{return O.apply(r,x.querySelectorAll(w)),r}catch(T){}finally{g||t.removeAttribute("id")}}}return kt(e.replace(z,"$1"),t,r,i)}function st(){var e=[];function t(n,r){return e.push(n+=" ")>i.cacheLength&&delete t[e.shift()],t[n]=r}return t}function at(e){return e[v]=!0,e}function ut(e){var t=p.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function lt(e,t){var n=e.split("|"),r=e.length;while(r--)i.attrHandle[n[r]]=t}function ct(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function ft(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function ht(e){return at(function(t){return t=+t,at(function(n,r){var i,o=e([],n.length,t),s=o.length;while(s--)n[i=o[s]]&&(n[i]=!(r[i]=n[i]))})})}s=ot.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},n=ot.support={},c=ot.setDocument=function(e){var t=e?e.ownerDocument||e:b,r=t.defaultView;return t!==p&&9===t.nodeType&&t.documentElement?(p=t,f=t.documentElement,h=!s(t),r&&r.attachEvent&&r!==r.top&&r.attachEvent("onbeforeunload",function(){c()}),n.attributes=ut(function(e){return e.className="i",!e.getAttribute("className")}),n.getElementsByTagName=ut(function(e){return e.appendChild(t.createComment("")),!e.getElementsByTagName("*").length}),n.getElementsByClassName=ut(function(e){return e.innerHTML="
    ",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),n.getById=ut(function(e){return f.appendChild(e).id=v,!t.getElementsByName||!t.getElementsByName(v).length}),n.getById?(i.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){return e.getAttribute("id")===t}}):(delete i.find.ID,i.filter.ID=function(e){var t=e.replace(nt,rt);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),i.find.TAG=n.getElementsByTagName?function(e,t){return typeof t.getElementsByTagName!==j?t.getElementsByTagName(e):undefined}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},i.find.CLASS=n.getElementsByClassName&&function(e,t){return typeof t.getElementsByClassName!==j&&h?t.getElementsByClassName(e):undefined},g=[],d=[],(n.qsa=Q.test(t.querySelectorAll))&&(ut(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll(":checked").length||d.push(":checked")}),ut(function(e){var n=t.createElement("input");n.setAttribute("type","hidden"),e.appendChild(n).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&d.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||d.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),d.push(",.*:")})),(n.matchesSelector=Q.test(m=f.webkitMatchesSelector||f.mozMatchesSelector||f.oMatchesSelector||f.msMatchesSelector))&&ut(function(e){n.disconnectedMatch=m.call(e,"div"),m.call(e,"[s!='']:x"),g.push("!=",I)}),d=d.length&&RegExp(d.join("|")),g=g.length&&RegExp(g.join("|")),y=Q.test(f.contains)||f.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},S=f.compareDocumentPosition?function(e,r){if(e===r)return E=!0,0;var i=r.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(r);return i?1&i||!n.sortDetached&&r.compareDocumentPosition(e)===i?e===t||y(b,e)?-1:r===t||y(b,r)?1:l?P.call(l,e)-P.call(l,r):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,n){var r,i=0,o=e.parentNode,s=n.parentNode,a=[e],u=[n];if(e===n)return E=!0,0;if(!o||!s)return e===t?-1:n===t?1:o?-1:s?1:l?P.call(l,e)-P.call(l,n):0;if(o===s)return ct(e,n);r=e;while(r=r.parentNode)a.unshift(r);r=n;while(r=r.parentNode)u.unshift(r);while(a[i]===u[i])i++;return i?ct(a[i],u[i]):a[i]===b?-1:u[i]===b?1:0},t):p},ot.matches=function(e,t){return ot(e,null,null,t)},ot.matchesSelector=function(e,t){if((e.ownerDocument||e)!==p&&c(e),t=t.replace(Y,"='$1']"),!(!n.matchesSelector||!h||g&&g.test(t)||d&&d.test(t)))try{var r=m.call(e,t);if(r||n.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return ot(t,p,null,[e]).length>0},ot.contains=function(e,t){return(e.ownerDocument||e)!==p&&c(e),y(e,t)},ot.attr=function(e,t){(e.ownerDocument||e)!==p&&c(e);var r=i.attrHandle[t.toLowerCase()],o=r&&A.call(i.attrHandle,t.toLowerCase())?r(e,t,!h):undefined;return o===undefined?n.attributes||!h?e.getAttribute(t):(o=e.getAttributeNode(t))&&o.specified?o.value:null:o},ot.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},ot.uniqueSort=function(e){var t,r=[],i=0,o=0;if(E=!n.detectDuplicates,l=!n.sortStable&&e.slice(0),e.sort(S),E){while(t=e[o++])t===e[o]&&(i=r.push(o));while(i--)e.splice(r[i],1)}return e},o=ot.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=o(t);return n},i=ot.selectors={cacheLength:50,createPseudo:at,match:J,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(nt,rt),e[3]=(e[4]||e[5]||"").replace(nt,rt),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||ot.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&ot.error(e[0]),e},PSEUDO:function(e){var t,n=!e[5]&&e[2];return J.CHILD.test(e[0])?null:(e[3]&&e[4]!==undefined?e[2]=e[4]:n&&V.test(n)&&(t=gt(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(nt,rt).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=C[e+" "];return t||(t=RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&C(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=ot.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),s="last"!==e.slice(-4),a="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,p,f,h,d,g=o!==s?"nextSibling":"previousSibling",m=t.parentNode,y=a&&t.nodeName.toLowerCase(),x=!u&&!a;if(m){if(o){while(g){p=t;while(p=p[g])if(a?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;d=g="only"===e&&!d&&"nextSibling"}return!0}if(d=[s?m.firstChild:m.lastChild],s&&x){c=m[v]||(m[v]={}),l=c[e]||[],h=l[0]===w&&l[1],f=l[0]===w&&l[2],p=h&&m.childNodes[h];while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[w,h,f];break}}else if(x&&(l=(t[v]||(t[v]={}))[e])&&l[0]===w)f=l[1];else while(p=++h&&p&&p[g]||(f=h=0)||d.pop())if((a?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(x&&((p[v]||(p[v]={}))[e]=[w,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||ot.error("unsupported pseudo: "+e);return r[v]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?at(function(e,n){var i,o=r(e,t),s=o.length;while(s--)i=P.call(e,o[s]),e[i]=!(n[i]=o[s])}):function(e){return r(e,0,n)}):r}},pseudos:{not:at(function(e){var t=[],n=[],r=a(e.replace(z,"$1"));return r[v]?at(function(e,t,n,i){var o,s=r(e,null,i,[]),a=e.length;while(a--)(o=s[a])&&(e[a]=!(t[a]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:at(function(e){return function(t){return ot(e,t).length>0}}),contains:at(function(e){return function(t){return(t.textContent||t.innerText||o(t)).indexOf(e)>-1}}),lang:at(function(e){return G.test(e||"")||ot.error("unsupported lang: "+e),e=e.replace(nt,rt).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===f},focus:function(e){return e===p.activeElement&&(!p.hasFocus||p.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!i.pseudos.empty(e)},header:function(e){return et.test(e.nodeName)},input:function(e){return Z.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:ht(function(){return[0]}),last:ht(function(e,t){return[t-1]}),eq:ht(function(e,t,n){return[0>n?n+t:n]}),even:ht(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:ht(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:ht(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:ht(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}},i.pseudos.nth=i.pseudos.eq;for(t in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})i.pseudos[t]=pt(t);for(t in{submit:!0,reset:!0})i.pseudos[t]=ft(t);function dt(){}dt.prototype=i.filters=i.pseudos,i.setFilters=new dt;function gt(e,t){var n,r,o,s,a,u,l,c=k[e+" "];if(c)return t?0:c.slice(0);a=e,u=[],l=i.preFilter;while(a){(!n||(r=_.exec(a)))&&(r&&(a=a.slice(r[0].length)||a),u.push(o=[])),n=!1,(r=X.exec(a))&&(n=r.shift(),o.push({value:n,type:r[0].replace(z," ")}),a=a.slice(n.length));for(s in i.filter)!(r=J[s].exec(a))||l[s]&&!(r=l[s](r))||(n=r.shift(),o.push({value:n,type:s,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?ot.error(e):k(e,u).slice(0)}function mt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function yt(e,t,n){var i=t.dir,o=n&&"parentNode"===i,s=T++;return t.first?function(t,n,r){while(t=t[i])if(1===t.nodeType||o)return e(t,n,r)}:function(t,n,a){var u,l,c,p=w+" "+s;if(a){while(t=t[i])if((1===t.nodeType||o)&&e(t,n,a))return!0}else while(t=t[i])if(1===t.nodeType||o)if(c=t[v]||(t[v]={}),(l=c[i])&&l[0]===p){if((u=l[1])===!0||u===r)return u===!0}else if(l=c[i]=[p],l[1]=e(t,n,a)||r,l[1]===!0)return!0}}function vt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function xt(e,t,n,r,i){var o,s=[],a=0,u=e.length,l=null!=t;for(;u>a;a++)(o=e[a])&&(!n||n(o,r,i))&&(s.push(o),l&&t.push(a));return s}function bt(e,t,n,r,i,o){return r&&!r[v]&&(r=bt(r)),i&&!i[v]&&(i=bt(i,o)),at(function(o,s,a,u){var l,c,p,f=[],h=[],d=s.length,g=o||Ct(t||"*",a.nodeType?[a]:a,[]),m=!e||!o&&t?g:xt(g,f,e,a,u),y=n?i||(o?e:d||r)?[]:s:m;if(n&&n(m,y,a,u),r){l=xt(y,h),r(l,[],a,u),c=l.length;while(c--)(p=l[c])&&(y[h[c]]=!(m[h[c]]=p))}if(o){if(i||e){if(i){l=[],c=y.length;while(c--)(p=y[c])&&l.push(m[c]=p);i(null,y=[],l,u)}c=y.length;while(c--)(p=y[c])&&(l=i?P.call(o,p):f[c])>-1&&(o[l]=!(s[l]=p))}}else y=xt(y===s?y.splice(d,y.length):y),i?i(null,s,y,u):O.apply(s,y)})}function wt(e){var t,n,r,o=e.length,s=i.relative[e[0].type],a=s||i.relative[" "],l=s?1:0,c=yt(function(e){return e===t},a,!0),p=yt(function(e){return P.call(t,e)>-1},a,!0),f=[function(e,n,r){return!s&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;o>l;l++)if(n=i.relative[e[l].type])f=[yt(vt(f),n)];else{if(n=i.filter[e[l].type].apply(null,e[l].matches),n[v]){for(r=++l;o>r;r++)if(i.relative[e[r].type])break;return bt(l>1&&vt(f),l>1&&mt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&wt(e.slice(l,r)),o>r&&wt(e=e.slice(r)),o>r&&mt(e))}f.push(n)}return vt(f)}function Tt(e,t){var n=0,o=t.length>0,s=e.length>0,a=function(a,l,c,f,h){var d,g,m,y=[],v=0,x="0",b=a&&[],T=null!=h,C=u,k=a||s&&i.find.TAG("*",h&&l.parentNode||l),N=w+=null==C?1:Math.random()||.1;for(T&&(u=l!==p&&l,r=n);null!=(d=k[x]);x++){if(s&&d){g=0;while(m=e[g++])if(m(d,l,c)){f.push(d);break}T&&(w=N,r=++n)}o&&((d=!m&&d)&&v--,a&&b.push(d))}if(v+=x,o&&x!==v){g=0;while(m=t[g++])m(b,y,l,c);if(a){if(v>0)while(x--)b[x]||y[x]||(y[x]=q.call(f));y=xt(y)}O.apply(f,y),T&&!a&&y.length>0&&v+t.length>1&&ot.uniqueSort(f)}return T&&(w=N,u=C),b};return o?at(a):a}a=ot.compile=function(e,t){var n,r=[],i=[],o=N[e+" "];if(!o){t||(t=gt(e)),n=t.length;while(n--)o=wt(t[n]),o[v]?r.push(o):i.push(o);o=N(e,Tt(i,r))}return o};function Ct(e,t,n){var r=0,i=t.length;for(;i>r;r++)ot(e,t[r],n);return n}function kt(e,t,r,o){var s,u,l,c,p,f=gt(e);if(!o&&1===f.length){if(u=f[0]=f[0].slice(0),u.length>2&&"ID"===(l=u[0]).type&&n.getById&&9===t.nodeType&&h&&i.relative[u[1].type]){if(t=(i.find.ID(l.matches[0].replace(nt,rt),t)||[])[0],!t)return r;e=e.slice(u.shift().value.length)}s=J.needsContext.test(e)?0:u.length;while(s--){if(l=u[s],i.relative[c=l.type])break;if((p=i.find[c])&&(o=p(l.matches[0].replace(nt,rt),U.test(u[0].type)&&t.parentNode||t))){if(u.splice(s,1),e=o.length&&mt(u),!e)return O.apply(r,o),r;break}}}return a(e,f)(o,t,!h,r,U.test(e)),r}n.sortStable=v.split("").sort(S).join("")===v,n.detectDuplicates=E,c(),n.sortDetached=ut(function(e){return 1&e.compareDocumentPosition(p.createElement("div"))}),ut(function(e){return e.innerHTML="
    ","#"===e.firstChild.getAttribute("href")})||lt("type|href|height|width",function(e,t,n){return n?undefined:e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),n.attributes&&ut(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||lt("value",function(e,t,n){return n||"input"!==e.nodeName.toLowerCase()?undefined:e.defaultValue}),ut(function(e){return null==e.getAttribute("disabled")})||lt(R,function(e,t,n){var r;return n?undefined:(r=e.getAttributeNode(t))&&r.specified?r.value:e[t]===!0?t.toLowerCase():null}),x.find=ot,x.expr=ot.selectors,x.expr[":"]=x.expr.pseudos,x.unique=ot.uniqueSort,x.text=ot.getText,x.isXMLDoc=ot.isXML,x.contains=ot.contains}(e);var D={};function A(e){var t=D[e]={};return x.each(e.match(w)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?D[e]||A(e):x.extend({},e);var t,n,r,i,o,s,a=[],u=!e.once&&[],l=function(p){for(t=e.memory&&p,n=!0,s=i||0,i=0,o=a.length,r=!0;a&&o>s;s++)if(a[s].apply(p[0],p[1])===!1&&e.stopOnFalse){t=!1;break}r=!1,a&&(u?u.length&&l(u.shift()):t?a=[]:c.disable())},c={add:function(){if(a){var n=a.length;(function s(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&c.has(n)||a.push(n):n&&n.length&&"string"!==r&&s(n)})})(arguments),r?o=a.length:t&&(i=n,l(t))}return this},remove:function(){return a&&x.each(arguments,function(e,t){var n;while((n=x.inArray(t,a,n))>-1)a.splice(n,1),r&&(o>=n&&o--,s>=n&&s--)}),this},has:function(e){return e?x.inArray(e,a)>-1:!(!a||!a.length)},empty:function(){return a=[],o=0,this},disable:function(){return a=u=t=undefined,this},disabled:function(){return!a},lock:function(){return u=undefined,t||c.disable(),this},locked:function(){return!u},fireWith:function(e,t){return!a||n&&!u||(t=t||[],t=[e,t.slice?t.slice():t],r?u.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var s=o[0],a=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var s=o[2],a=o[3];r[o[1]]=s.add,a&&s.add(function(){n=a},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=s.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=d.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),s=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?d.call(arguments):r,n===a?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},a,u,l;if(r>1)for(a=Array(r),u=Array(r),l=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(s(t,l,n)).fail(o.reject).progress(s(t,u,a)):--i;return i||o.resolveWith(l,n),o.promise()}}),x.support=function(t){var n=o.createElement("input"),r=o.createDocumentFragment(),i=o.createElement("div"),s=o.createElement("select"),a=s.appendChild(o.createElement("option"));return n.type?(n.type="checkbox",t.checkOn=""!==n.value,t.optSelected=a.selected,t.reliableMarginRight=!0,t.boxSizingReliable=!0,t.pixelPosition=!1,n.checked=!0,t.noCloneChecked=n.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!a.disabled,n=o.createElement("input"),n.value="t",n.type="radio",t.radioValue="t"===n.value,n.setAttribute("checked","t"),n.setAttribute("name","t"),r.appendChild(n),t.checkClone=r.cloneNode(!0).cloneNode(!0).lastChild.checked,t.focusinBubbles="onfocusin"in e,i.style.backgroundClip="content-box",i.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===i.style.backgroundClip,x(function(){var n,r,s="padding:0;margin:0;border:0;display:block;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box",a=o.getElementsByTagName("body")[0];a&&(n=o.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",a.appendChild(n).appendChild(i),i.innerHTML="",i.style.cssText="-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%",x.swap(a,null!=a.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===i.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(i,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(i,null)||{width:"4px"}).width,r=i.appendChild(o.createElement("div")),r.style.cssText=i.style.cssText=s,r.style.marginRight=r.style.width="0",i.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),a.removeChild(n))}),t):t}({});var L,q,H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,O=/([A-Z])/g;function F(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=x.expando+Math.random()}F.uid=1,F.accepts=function(e){return e.nodeType?1===e.nodeType||9===e.nodeType:!0},F.prototype={key:function(e){if(!F.accepts(e))return 0;var t={},n=e[this.expando];if(!n){n=F.uid++;try{t[this.expando]={value:n},Object.defineProperties(e,t)}catch(r){t[this.expando]=n,x.extend(e,t)}}return this.cache[n]||(this.cache[n]={}),n},set:function(e,t,n){var r,i=this.key(e),o=this.cache[i];if("string"==typeof t)o[t]=n;else if(x.isEmptyObject(o))x.extend(this.cache[i],t);else for(r in t)o[r]=t[r];return o},get:function(e,t){var n=this.cache[this.key(e)];return t===undefined?n:n[t]},access:function(e,t,n){var r;return t===undefined||t&&"string"==typeof t&&n===undefined?(r=this.get(e,t),r!==undefined?r:this.get(e,x.camelCase(t))):(this.set(e,t,n),n!==undefined?n:t)},remove:function(e,t){var n,r,i,o=this.key(e),s=this.cache[o];if(t===undefined)this.cache[o]={};else{x.isArray(t)?r=t.concat(t.map(x.camelCase)):(i=x.camelCase(t),t in s?r=[t,i]:(r=i,r=r in s?[r]:r.match(w)||[])),n=r.length;while(n--)delete s[r[n]]}},hasData:function(e){return!x.isEmptyObject(this.cache[e[this.expando]]||{})},discard:function(e){e[this.expando]&&delete this.cache[e[this.expando]]}},L=new F,q=new F,x.extend({acceptData:F.accepts,hasData:function(e){return L.hasData(e)||q.hasData(e)},data:function(e,t,n){return L.access(e,t,n)},removeData:function(e,t){L.remove(e,t)},_data:function(e,t,n){return q.access(e,t,n)},_removeData:function(e,t){q.remove(e,t)}}),x.fn.extend({data:function(e,t){var n,r,i=this[0],o=0,s=null;if(e===undefined){if(this.length&&(s=L.get(i),1===i.nodeType&&!q.get(i,"hasDataAttrs"))){for(n=i.attributes;n.length>o;o++)r=n[o].name,0===r.indexOf("data-")&&(r=x.camelCase(r.slice(5)),P(i,r,s[r]));q.set(i,"hasDataAttrs",!0)}return s}return"object"==typeof e?this.each(function(){L.set(this,e)}):x.access(this,function(t){var n,r=x.camelCase(e);if(i&&t===undefined){if(n=L.get(i,e),n!==undefined)return n;if(n=L.get(i,r),n!==undefined)return n;if(n=P(i,r,undefined),n!==undefined)return n}else this.each(function(){var n=L.get(this,r);L.set(this,r,t),-1!==e.indexOf("-")&&n!==undefined&&L.set(this,e,t)})},null,t,arguments.length>1,null,!0)},removeData:function(e){return this.each(function(){L.remove(this,e)})}});function P(e,t,n){var r;if(n===undefined&&1===e.nodeType)if(r="data-"+t.replace(O,"-$1").toLowerCase(),n=e.getAttribute(r),"string"==typeof n){try{n="true"===n?!0:"false"===n?!1:"null"===n?null:+n+""===n?+n:H.test(n)?JSON.parse(n):n}catch(i){}L.set(e,t,n)}else n=undefined;return n}x.extend({queue:function(e,t,n){var r;return e?(t=(t||"fx")+"queue",r=q.get(e,t),n&&(!r||x.isArray(n)?r=q.access(e,t,x.makeArray(n)):r.push(n)),r||[]):undefined},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),s=function(){x.dequeue(e,t) };"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,s,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return q.get(e,n)||q.access(e,n,{empty:x.Callbacks("once memory").add(function(){q.remove(e,[t+"queue",n])})})}}),x.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),n>arguments.length?x.queue(this[0],e):t===undefined?this:this.each(function(){var n=x.queue(this,e,t);x._queueHooks(this,e),"fx"===e&&"inprogress"!==n[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=x.Deferred(),o=this,s=this.length,a=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=undefined),e=e||"fx";while(s--)n=q.get(o[s],e+"queueHooks"),n&&n.empty&&(r++,n.empty.add(a));return a(),i.promise(t)}});var R,M,W=/[\t\r\n\f]/g,$=/\r/g,B=/^(?:input|select|textarea|button)$/i;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return this.each(function(){delete this[x.propFix[e]||e]})},addClass:function(e){var t,n,r,i,o,s=0,a=this.length,u="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,s=0,a=this.length,u=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(u)for(t=(e||"").match(w)||[];a>s;s++)if(n=this[s],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(W," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var t,i=0,o=x(this),s=e.match(w)||[];while(t=s[i++])o.hasClass(t)?o.removeClass(t):o.addClass(t)}else(n===r||"boolean"===n)&&(this.className&&q.set(this,"__className__",this.className),this.className=this.className||e===!1?"":q.get(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(W," ").indexOf(t)>=0)return!0;return!1},val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=x.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,x(this).val()):e,null==i?i="":"number"==typeof i?i+="":x.isArray(i)&&(i=x.map(i,function(e){return null==e?"":e+""})),t=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&t.set(this,i,"value")!==undefined||(this.value=i))});if(i)return t=x.valHooks[i.type]||x.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&(n=t.get(i,"value"))!==undefined?n:(n=i.value,"string"==typeof n?n.replace($,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,s=o?null:[],a=o?i+1:r.length,u=0>i?a:o?i:0;for(;a>u;u++)if(n=r[u],!(!n.selected&&u!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),s=i.length;while(s--)r=i[s],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,t,n){var i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===r?x.prop(e,t,n):(1===s&&x.isXMLDoc(e)||(t=t.toLowerCase(),i=x.attrHooks[t]||(x.expr.match.bool.test(t)?M:R)),n===undefined?i&&"get"in i&&null!==(o=i.get(e,t))?o:(o=x.find.attr(e,t),null==o?undefined:o):null!==n?i&&"set"in i&&(o=i.set(e,n,t))!==undefined?o:(e.setAttribute(t,n+""),n):(x.removeAttr(e,t),undefined))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(w);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)&&(e[r]=!1),e.removeAttribute(n)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,t,n){var r,i,o,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return o=1!==s||!x.isXMLDoc(e),o&&(t=x.propFix[t]||t,i=x.propHooks[t]),n!==undefined?i&&"set"in i&&(r=i.set(e,n,t))!==undefined?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){return e.hasAttribute("tabindex")||B.test(e.nodeName)||e.href?e.tabIndex:-1}}}}),M={set:function(e,t,n){return t===!1?x.removeAttr(e,n):e.setAttribute(n,n),n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,t){var n=x.expr.attrHandle[t]||x.find.attr;x.expr.attrHandle[t]=function(e,t,r){var i=x.expr.attrHandle[t],o=r?undefined:(x.expr.attrHandle[t]=undefined)!=n(e,t,r)?t.toLowerCase():null;return x.expr.attrHandle[t]=i,o}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,t){return x.isArray(t)?e.checked=x.inArray(x(e).val(),t)>=0:undefined}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var I=/^key/,z=/^(?:mouse|contextmenu)|click/,_=/^(?:focusinfocus|focusoutblur)$/,X=/^([^.]*)(?:\.(.+)|)$/;function U(){return!0}function Y(){return!1}function V(){try{return o.activeElement}catch(e){}}x.event={global:{},add:function(e,t,n,i,o){var s,a,u,l,c,p,f,h,d,g,m,y=q.get(e);if(y){n.handler&&(s=n,n=s.handler,o=s.selector),n.guid||(n.guid=x.guid++),(l=y.events)||(l=y.events={}),(a=y.handle)||(a=y.handle=function(e){return typeof x===r||e&&x.event.triggered===e.type?undefined:x.event.dispatch.apply(a.elem,arguments)},a.elem=e),t=(t||"").match(w)||[""],c=t.length;while(c--)u=X.exec(t[c])||[],d=m=u[1],g=(u[2]||"").split(".").sort(),d&&(f=x.event.special[d]||{},d=(o?f.delegateType:f.bindType)||d,f=x.event.special[d]||{},p=x.extend({type:d,origType:m,data:i,handler:n,guid:n.guid,selector:o,needsContext:o&&x.expr.match.needsContext.test(o),namespace:g.join(".")},s),(h=l[d])||(h=l[d]=[],h.delegateCount=0,f.setup&&f.setup.call(e,i,g,a)!==!1||e.addEventListener&&e.addEventListener(d,a,!1)),f.add&&(f.add.call(e,p),p.handler.guid||(p.handler.guid=n.guid)),o?h.splice(h.delegateCount++,0,p):h.push(p),x.event.global[d]=!0);e=null}},remove:function(e,t,n,r,i){var o,s,a,u,l,c,p,f,h,d,g,m=q.hasData(e)&&q.get(e);if(m&&(u=m.events)){t=(t||"").match(w)||[""],l=t.length;while(l--)if(a=X.exec(t[l])||[],h=g=a[1],d=(a[2]||"").split(".").sort(),h){p=x.event.special[h]||{},h=(r?p.delegateType:p.bindType)||h,f=u[h]||[],a=a[2]&&RegExp("(^|\\.)"+d.join("\\.(?:.*\\.|)")+"(\\.|$)"),s=o=f.length;while(o--)c=f[o],!i&&g!==c.origType||n&&n.guid!==c.guid||a&&!a.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(f.splice(o,1),c.selector&&f.delegateCount--,p.remove&&p.remove.call(e,c));s&&!f.length&&(p.teardown&&p.teardown.call(e,d,m.handle)!==!1||x.removeEvent(e,h,m.handle),delete u[h])}else for(h in u)x.event.remove(e,h+t[l],n,r,!0);x.isEmptyObject(u)&&(delete m.handle,q.remove(e,"events"))}},trigger:function(t,n,r,i){var s,a,u,l,c,p,f,h=[r||o],d=y.call(t,"type")?t.type:t,g=y.call(t,"namespace")?t.namespace.split("."):[];if(a=u=r=r||o,3!==r.nodeType&&8!==r.nodeType&&!_.test(d+x.event.triggered)&&(d.indexOf(".")>=0&&(g=d.split("."),d=g.shift(),g.sort()),c=0>d.indexOf(":")&&"on"+d,t=t[x.expando]?t:new x.Event(d,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=g.join("."),t.namespace_re=t.namespace?RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=undefined,t.target||(t.target=r),n=null==n?[t]:x.makeArray(n,[t]),f=x.event.special[d]||{},i||!f.trigger||f.trigger.apply(r,n)!==!1)){if(!i&&!f.noBubble&&!x.isWindow(r)){for(l=f.delegateType||d,_.test(l+d)||(a=a.parentNode);a;a=a.parentNode)h.push(a),u=a;u===(r.ownerDocument||o)&&h.push(u.defaultView||u.parentWindow||e)}s=0;while((a=h[s++])&&!t.isPropagationStopped())t.type=s>1?l:f.bindType||d,p=(q.get(a,"events")||{})[t.type]&&q.get(a,"handle"),p&&p.apply(a,n),p=c&&a[c],p&&x.acceptData(a)&&p.apply&&p.apply(a,n)===!1&&t.preventDefault();return t.type=d,i||t.isDefaultPrevented()||f._default&&f._default.apply(h.pop(),n)!==!1||!x.acceptData(r)||c&&x.isFunction(r[d])&&!x.isWindow(r)&&(u=r[c],u&&(r[c]=null),x.event.triggered=d,r[d](),x.event.triggered=undefined,u&&(r[c]=u)),t.result}},dispatch:function(e){e=x.event.fix(e);var t,n,r,i,o,s=[],a=d.call(arguments),u=(q.get(this,"events")||{})[e.type]||[],l=x.event.special[e.type]||{};if(a[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),t=0;while((i=s[t++])&&!e.isPropagationStopped()){e.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(o.namespace))&&(e.handleObj=o,e.data=o.data,r=((x.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,a),r!==undefined&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,s=[],a=t.delegateCount,u=e.target;if(a&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!==this;u=u.parentNode||this)if(u.disabled!==!0||"click"!==e.type){for(r=[],n=0;a>n;n++)o=t[n],i=o.selector+" ",r[i]===undefined&&(r[i]=o.needsContext?x(i,this).index(u)>=0:x.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&s.push({elem:u,handlers:r})}return t.length>a&&s.push({elem:this,handlers:t.slice(a)}),s},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,t){var n,r,i,s=t.button;return null==e.pageX&&null!=t.clientX&&(n=e.target.ownerDocument||o,r=n.documentElement,i=n.body,e.pageX=t.clientX+(r&&r.scrollLeft||i&&i.scrollLeft||0)-(r&&r.clientLeft||i&&i.clientLeft||0),e.pageY=t.clientY+(r&&r.scrollTop||i&&i.scrollTop||0)-(r&&r.clientTop||i&&i.clientTop||0)),e.which||s===undefined||(e.which=1&s?1:2&s?3:4&s?2:0),e}},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,s=e,a=this.fixHooks[i];a||(this.fixHooks[i]=a=z.test(i)?this.mouseHooks:I.test(i)?this.keyHooks:{}),r=a.props?this.props.concat(a.props):this.props,e=new x.Event(s),t=r.length;while(t--)n=r[t],e[n]=s[n];return e.target||(e.target=o),3===e.target.nodeType&&(e.target=e.target.parentNode),a.filter?a.filter(e,s):e},special:{load:{noBubble:!0},focus:{trigger:function(){return this!==V()&&this.focus?(this.focus(),!1):undefined},delegateType:"focusin"},blur:{trigger:function(){return this===V()&&this.blur?(this.blur(),!1):undefined},delegateType:"focusout"},click:{trigger:function(){return"checkbox"===this.type&&this.click&&x.nodeName(this,"input")?(this.click(),!1):undefined},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==undefined&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)},x.Event=function(e,t){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.getPreventDefault&&e.getPreventDefault()?U:Y):this.type=e,t&&x.extend(this,t),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,undefined):new x.Event(e,t)},x.Event.prototype={isDefaultPrevented:Y,isPropagationStopped:Y,isImmediatePropagationStopped:Y,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=U,e&&e.preventDefault&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=U,e&&e.stopPropagation&&e.stopPropagation()},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=U,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&o.addEventListener(e,r,!0)},teardown:function(){0===--n&&o.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,t,n,r,i){var o,s;if("object"==typeof e){"string"!=typeof t&&(n=n||t,t=undefined);for(s in e)this.on(s,t,n,e[s],i);return this}if(null==n&&null==r?(r=t,n=t=undefined):null==r&&("string"==typeof t?(r=n,n=undefined):(r=n,n=t,t=undefined)),r===!1)r=Y;else if(!r)return this;return 1===i&&(o=r,r=function(e){return x().off(e),o.apply(this,arguments)},r.guid=o.guid||(o.guid=x.guid++)),this.each(function(){x.event.add(this,e,r,n,t)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,x(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return(t===!1||"function"==typeof t)&&(n=t,t=undefined),n===!1&&(n=Y),this.each(function(){x.event.remove(this,e,n,t)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?x.event.trigger(e,t,n,!0):undefined}});var G=/^.[^:#\[\.,]*$/,J=/^(?:parents|prev(?:Until|All))/,Q=x.expr.match.needsContext,K={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t=x(e,this),n=t.length;return this.filter(function(){var e=0;for(;n>e;e++)if(x.contains(this,t[e]))return!0})},not:function(e){return this.pushStack(et(this,e||[],!0))},filter:function(e){return this.pushStack(et(this,e||[],!1))},is:function(e){return!!et(this,"string"==typeof e&&Q.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],s=Q.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(s?s.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?g.call(x(e),this[0]):g.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function Z(e,t){while((e=e[t])&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return Z(e,"nextSibling")},prev:function(e){return Z(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return e.contentDocument||x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(K[e]||x.unique(i),J.test(e)&&i.reverse()),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,t,n){var r=[],i=n!==undefined;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&x(e).is(n))break;r.push(e)}return r},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function et(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(G.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return g.call(t,e)>=0!==n})}var tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,nt=/<([\w:]+)/,rt=/<|&#?\w+;/,it=/<(?:script|style|link)/i,ot=/^(?:checkbox|radio)$/i,st=/checked\s*(?:[^=]|=\s*.checked.)/i,at=/^$|\/(?:java|ecma)script/i,ut=/^true\/(.*)/,lt=/^\s*\s*$/g,ct={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ct.optgroup=ct.option,ct.tbody=ct.tfoot=ct.colgroup=ct.caption=ct.thead,ct.th=ct.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===undefined?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||o).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=pt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(mt(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&dt(mt(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++)1===e.nodeType&&(x.cleanData(mt(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var t=this[0]||{},n=0,r=this.length;if(e===undefined&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!it.test(e)&&!ct[(nt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(tt,"<$1>");try{for(;r>n;n++)t=this[n]||{},1===t.nodeType&&(x.cleanData(mt(t,!1)),t.innerHTML=e);t=0}catch(i){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=f.apply([],e);var r,i,o,s,a,u,l=0,c=this.length,p=this,h=c-1,d=e[0],g=x.isFunction(d);if(g||!(1>=c||"string"!=typeof d||x.support.checkClone)&&st.test(d))return this.each(function(r){var i=p.eq(r);g&&(e[0]=d.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(r=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),i=r.firstChild,1===r.childNodes.length&&(r=i),i)){for(o=x.map(mt(r,"script"),ft),s=o.length;c>l;l++)a=r,l!==h&&(a=x.clone(a,!0,!0),s&&x.merge(o,mt(a,"script"))),t.call(this[l],a,l);if(s)for(u=o[o.length-1].ownerDocument,x.map(o,ht),l=0;s>l;l++)a=o[l],at.test(a.type||"")&&!q.access(a,"globalEval")&&x.contains(u,a)&&(a.src?x._evalUrl(a.src):x.globalEval(a.textContent.replace(lt,"")))}return this}}),x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=[],i=x(e),o=i.length-1,s=0;for(;o>=s;s++)n=s===o?this:this.clone(!0),x(i[s])[t](n),h.apply(r,n.get());return this.pushStack(r)}}),x.extend({clone:function(e,t,n){var r,i,o,s,a=e.cloneNode(!0),u=x.contains(e.ownerDocument,e);if(!(x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(s=mt(a),o=mt(e),r=0,i=o.length;i>r;r++)yt(o[r],s[r]);if(t)if(n)for(o=o||mt(e),s=s||mt(a),r=0,i=o.length;i>r;r++)gt(o[r],s[r]);else gt(e,a);return s=mt(a,"script"),s.length>0&&dt(s,!u&&mt(e,"script")),a},buildFragment:function(e,t,n,r){var i,o,s,a,u,l,c=0,p=e.length,f=t.createDocumentFragment(),h=[];for(;p>c;c++)if(i=e[c],i||0===i)if("object"===x.type(i))x.merge(h,i.nodeType?[i]:i);else if(rt.test(i)){o=o||f.appendChild(t.createElement("div")),s=(nt.exec(i)||["",""])[1].toLowerCase(),a=ct[s]||ct._default,o.innerHTML=a[1]+i.replace(tt,"<$1>")+a[2],l=a[0];while(l--)o=o.lastChild;x.merge(h,o.childNodes),o=f.firstChild,o.textContent=""}else h.push(t.createTextNode(i));f.textContent="",c=0;while(i=h[c++])if((!r||-1===x.inArray(i,r))&&(u=x.contains(i.ownerDocument,i),o=mt(f.appendChild(i),"script"),u&&dt(o),n)){l=0;while(i=o[l++])at.test(i.type||"")&&n.push(i)}return f},cleanData:function(e){var t,n,r,i,o,s,a=x.event.special,u=0;for(;(n=e[u])!==undefined;u++){if(F.accepts(n)&&(o=n[q.expando],o&&(t=q.cache[o]))){if(r=Object.keys(t.events||{}),r.length)for(s=0;(i=r[s])!==undefined;s++)a[i]?x.event.remove(n,i):x.removeEvent(n,i,t.handle);q.cache[o]&&delete q.cache[o]}delete L.cache[n[L.expando]]}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}});function pt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function ft(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function ht(e){var t=ut.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function dt(e,t){var n=e.length,r=0;for(;n>r;r++)q.set(e[r],"globalEval",!t||q.get(t[r],"globalEval"))}function gt(e,t){var n,r,i,o,s,a,u,l;if(1===t.nodeType){if(q.hasData(e)&&(o=q.access(e),s=q.set(t,o),l=o.events)){delete s.handle,s.events={};for(i in l)for(n=0,r=l[i].length;r>n;n++)x.event.add(t,i,l[i][n])}L.hasData(e)&&(a=L.access(e),u=x.extend({},a),L.set(t,u))}}function mt(e,t){var n=e.getElementsByTagName?e.getElementsByTagName(t||"*"):e.querySelectorAll?e.querySelectorAll(t||"*"):[];return t===undefined||t&&x.nodeName(e,t)?x.merge([e],n):n}function yt(e,t){var n=t.nodeName.toLowerCase();"input"===n&&ot.test(e.type)?t.checked=e.checked:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}x.fn.extend({wrapAll:function(e){var t;return x.isFunction(e)?this.each(function(t){x(this).wrapAll(e.call(this,t))}):(this[0]&&(t=x(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this)},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var vt,xt,bt=/^(none|table(?!-c[ea]).+)/,wt=/^margin/,Tt=RegExp("^("+b+")(.*)$","i"),Ct=RegExp("^("+b+")(?!px)[a-z%]+$","i"),kt=RegExp("^([+-])=("+b+")","i"),Nt={BODY:"block"},Et={position:"absolute",visibility:"hidden",display:"block"},St={letterSpacing:0,fontWeight:400},jt=["Top","Right","Bottom","Left"],Dt=["Webkit","O","Moz","ms"];function At(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=Dt.length;while(i--)if(t=Dt[i]+n,t in e)return t;return r}function Lt(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function qt(t){return e.getComputedStyle(t,null)}function Ht(e,t){var n,r,i,o=[],s=0,a=e.length;for(;a>s;s++)r=e[s],r.style&&(o[s]=q.get(r,"olddisplay"),n=r.style.display,t?(o[s]||"none"!==n||(r.style.display=""),""===r.style.display&&Lt(r)&&(o[s]=q.access(r,"olddisplay",Rt(r.nodeName)))):o[s]||(i=Lt(r),(n&&"none"!==n||!i)&&q.set(r,"olddisplay",i?n:x.css(r,"display"))));for(s=0;a>s;s++)r=e[s],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[s]||"":"none"));return e}x.fn.extend({css:function(e,t){return x.access(this,function(e,t,n){var r,i,o={},s=0;if(x.isArray(t)){for(r=qt(e),i=t.length;i>s;s++)o[t[s]]=x.css(e,t[s],!1,r);return o}return n!==undefined?x.style(e,t,n):x.css(e,t)},e,t,arguments.length>1)},show:function(){return Ht(this,!0)},hide:function(){return Ht(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Lt(this)?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=vt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":"cssFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,s,a=x.camelCase(t),u=e.style;return t=x.cssProps[a]||(x.cssProps[a]=At(u,a)),s=x.cssHooks[t]||x.cssHooks[a],n===undefined?s&&"get"in s&&(i=s.get(e,!1,r))!==undefined?i:u[t]:(o=typeof n,"string"===o&&(i=kt.exec(n))&&(n=(i[1]+1)*i[2]+parseFloat(x.css(e,t)),o="number"),null==n||"number"===o&&isNaN(n)||("number"!==o||x.cssNumber[a]||(n+="px"),x.support.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),s&&"set"in s&&(n=s.set(e,n,r))===undefined||(u[t]=n)),undefined)}},css:function(e,t,n,r){var i,o,s,a=x.camelCase(t);return t=x.cssProps[a]||(x.cssProps[a]=At(e.style,a)),s=x.cssHooks[t]||x.cssHooks[a],s&&"get"in s&&(i=s.get(e,!0,n)),i===undefined&&(i=vt(e,t,r)),"normal"===i&&t in St&&(i=St[t]),""===n||n?(o=parseFloat(i),n===!0||x.isNumeric(o)?o||0:i):i}}),vt=function(e,t,n){var r,i,o,s=n||qt(e),a=s?s.getPropertyValue(t)||s[t]:undefined,u=e.style;return s&&(""!==a||x.contains(e.ownerDocument,e)||(a=x.style(e,t)),Ct.test(a)&&wt.test(t)&&(r=u.width,i=u.minWidth,o=u.maxWidth,u.minWidth=u.maxWidth=u.width=a,a=s.width,u.width=r,u.minWidth=i,u.maxWidth=o)),a};function Ot(e,t,n){var r=Tt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function Ft(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,s=0;for(;4>o;o+=2)"margin"===n&&(s+=x.css(e,n+jt[o],!0,i)),r?("content"===n&&(s-=x.css(e,"padding"+jt[o],!0,i)),"margin"!==n&&(s-=x.css(e,"border"+jt[o]+"Width",!0,i))):(s+=x.css(e,"padding"+jt[o],!0,i),"padding"!==n&&(s+=x.css(e,"border"+jt[o]+"Width",!0,i)));return s}function Pt(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=qt(e),s=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=vt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Ct.test(i))return i;r=s&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+Ft(e,t,n||(s?"border":"content"),r,o)+"px"}function Rt(e){var t=o,n=Nt[e];return n||(n=Mt(e,t),"none"!==n&&n||(xt=(xt||x("