summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-01-20 09:16:11 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-20 09:16:11 +0000
commitedaa33dee2ff2f7ea3fac488d41558eb5f86d68c (patch)
tree11f143effbfeba52329fb7afbd05e6e2a3790241 /app
parentd8a5691316400a0f7ec4f83832698f1988eb27c1 (diff)
downloadgitlab-ce-edaa33dee2ff2f7ea3fac488d41558eb5f86d68c.tar.gz
Add latest changes from gitlab-org/gitlab@14-7-stable-eev14.7.0-rc42
Diffstat (limited to 'app')
-rw-r--r--app/assets/images/file_icons.svg2
-rw-r--r--app/assets/javascripts/admin/users/components/user_actions.vue3
-rw-r--r--app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue13
-rw-r--r--app/assets/javascripts/api/packages_api.js8
-rw-r--r--app/assets/javascripts/behaviors/copy_code.js3
-rw-r--r--app/assets/javascripts/behaviors/copy_to_clipboard.js46
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js7
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js234
-rw-r--r--app/assets/javascripts/behaviors/preview_markdown.js7
-rw-r--r--app/assets/javascripts/blob/blob_line_permalink_updater.js2
-rw-r--r--app/assets/javascripts/blob/components/blob_header.vue2
-rw-r--r--app/assets/javascripts/blob/components/blob_header_filepath.vue11
-rw-r--r--app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue4
-rw-r--r--app/assets/javascripts/blob/line_highlighter.js (renamed from app/assets/javascripts/line_highlighter.js)0
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue29
-rw-r--r--app/assets/javascripts/boards/components/board_filtered_search.vue17
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue2
-rw-r--r--app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue2
-rw-r--r--app/assets/javascripts/boards/constants.js4
-rw-r--r--app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql4
-rw-r--r--app/assets/javascripts/boards/graphql/issue.fragment.graphql35
-rw-r--r--app/assets/javascripts/boards/graphql/issue_create.mutation.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql2
-rw-r--r--app/assets/javascripts/boards/graphql/lists_issues.query.graphql4
-rw-r--r--app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql4
-rw-r--r--app/assets/javascripts/boards/stores/actions.js2
-rw-r--r--app/assets/javascripts/branches/branches_delete_modal.js53
-rw-r--r--app/assets/javascripts/clusters/agents/components/show.vue13
-rw-r--r--app/assets/javascripts/clusters/agents/graphql/provider.js26
-rw-r--r--app/assets/javascripts/clusters/agents/index.js24
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_options.vue200
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_table.vue65
-rw-r--r--app/assets/javascripts/clusters_list/components/agents.vue6
-rw-r--r--app/assets/javascripts/clusters_list/constants.js4
-rw-r--r--app/assets/javascripts/clusters_list/graphql/cache_update.js22
-rw-r--r--app/assets/javascripts/clusters_list/graphql/mutations/delete_agent.mutation.graphql5
-rw-r--r--app/assets/javascripts/clusters_list/index.js2
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js64
-rw-r--r--app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue2
-rw-r--r--app/assets/javascripts/content_editor/constants.js7
-rw-r--r--app/assets/javascripts/content_editor/extensions/code.js13
-rw-r--r--app/assets/javascripts/content_editor/extensions/code_block_highlight.js9
-rw-r--r--app/assets/javascripts/content_editor/extensions/frontmatter.js11
-rw-r--r--app/assets/javascripts/content_editor/extensions/image.js11
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_serializer.js2
-rw-r--r--app/assets/javascripts/content_editor/services/serialization_helpers.js9
-rw-r--r--app/assets/javascripts/content_editor/services/track_input_rules_and_shortcuts.js48
-rw-r--r--app/assets/javascripts/crm/components/form.vue232
-rw-r--r--app/assets/javascripts/cycle_analytics/components/base.vue2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/path_navigation.vue2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_table.vue33
-rw-r--r--app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue4
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_discussion.vue85
-rw-r--r--app/assets/javascripts/design_management/components/design_notes/design_note_signed_out.vue50
-rw-r--r--app/assets/javascripts/design_management/components/design_presentation.vue4
-rw-r--r--app/assets/javascripts/design_management/components/design_sidebar.vue24
-rw-r--r--app/assets/javascripts/design_management/index.js4
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue2
-rw-r--r--app/assets/javascripts/diffs/components/image_diff_overlay.vue16
-rw-r--r--app/assets/javascripts/dropzone_input.js1
-rw-r--r--app/assets/javascripts/editor/source_editor.js2
-rw-r--r--app/assets/javascripts/emoji/components/picker.vue16
-rw-r--r--app/assets/javascripts/environments/components/confirm_rollback_modal.vue3
-rw-r--r--app/assets/javascripts/environments/components/deployment.vue25
-rw-r--r--app/assets/javascripts/environments/components/deployment_status_badge.vue60
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.vue25
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue11
-rw-r--r--app/assets/javascripts/environments/components/environment_stop.vue27
-rw-r--r--app/assets/javascripts/environments/components/new_environment_folder.vue26
-rw-r--r--app/assets/javascripts/environments/components/new_environment_item.vue265
-rw-r--r--app/assets/javascripts/environments/components/new_environments_app.vue39
-rw-r--r--app/assets/javascripts/environments/components/stop_environment_modal.vue15
-rw-r--r--app/assets/javascripts/environments/graphql/mutations/action.mutation.graphql5
-rw-r--r--app/assets/javascripts/environments/graphql/mutations/set_environment_to_stop.mutation.graphql3
-rw-r--r--app/assets/javascripts/environments/graphql/queries/environment_to_stop.query.graphql3
-rw-r--r--app/assets/javascripts/environments/graphql/queries/is_environment_stopping.query.graphql3
-rw-r--r--app/assets/javascripts/environments/graphql/queries/is_last_deployment.query.graphql3
-rw-r--r--app/assets/javascripts/environments/graphql/resolvers.js18
-rw-r--r--app/assets/javascripts/environments/graphql/typedefs.graphql6
-rw-r--r--app/assets/javascripts/experimental_flags.js15
-rw-r--r--app/assets/javascripts/flash.js139
-rw-r--r--app/assets/javascripts/gitlab_version_check.js20
-rw-r--r--app/assets/javascripts/google_cloud/components/deployments_service_table.vue61
-rw-r--r--app/assets/javascripts/google_cloud/components/home.vue17
-rw-r--r--app/assets/javascripts/google_tag_manager/index.js122
-rw-r--r--app/assets/javascripts/graphql_shared/fragment_types/vulnerability_location_types.js17
-rw-r--r--app/assets/javascripts/graphql_shared/fragments/issue.fragment.graphql37
-rw-r--r--app/assets/javascripts/group.js7
-rw-r--r--app/assets/javascripts/groups/components/item_stats.vue2
-rw-r--r--app/assets/javascripts/groups/groups_list.js (renamed from app/assets/javascripts/groups_list.js)2
-rw-r--r--app/assets/javascripts/groups/landing.js (renamed from app/assets/javascripts/landing.js)0
-rw-r--r--app/assets/javascripts/groups/store/groups_store.js3
-rw-r--r--app/assets/javascripts/groups/transfer_edit.js (renamed from app/assets/javascripts/transfer_edit.js)0
-rw-r--r--app/assets/javascripts/header_search/components/app.vue7
-rw-r--r--app/assets/javascripts/helpers/event_hub_factory.js7
-rw-r--r--app/assets/javascripts/ide/components/jobs/stage.vue5
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/actions.js8
-rw-r--r--app/assets/javascripts/init_confirm_danger.js18
-rw-r--r--app/assets/javascripts/integrations/constants.js5
-rw-r--r--app/assets/javascripts/integrations/edit/components/dynamic_field.vue5
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue143
-rw-r--r--app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue2
-rw-r--r--app/assets/javascripts/integrations/edit/index.js7
-rw-r--r--app/assets/javascripts/integrations/edit/store/actions.js23
-rw-r--r--app/assets/javascripts/integrations/edit/store/mutations.js9
-rw-r--r--app/assets/javascripts/integrations/edit/store/state.js2
-rw-r--r--app/assets/javascripts/integrations/overrides/components/integration_overrides.vue7
-rw-r--r--app/assets/javascripts/integrations/overrides/components/integration_tabs.vue52
-rw-r--r--app/assets/javascripts/integrations/overrides/index.js5
-rw-r--r--app/assets/javascripts/issuable/bulk_update_sidebar/index.js28
-rw-r--r--app/assets/javascripts/issuable/bulk_update_sidebar/init_issue_status_select.js17
-rw-r--r--app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js10
-rw-r--r--app/assets/javascripts/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar.js19
-rw-r--r--app/assets/javascripts/issuable/components/csv_import_modal.vue6
-rw-r--r--app/assets/javascripts/issuable/index.js37
-rw-r--r--app/assets/javascripts/issues/constants.js6
-rw-r--r--app/assets/javascripts/issues/create_merge_request_dropdown.js (renamed from app/assets/javascripts/create_merge_request_dropdown.js)14
-rw-r--r--app/assets/javascripts/issues/form.js24
-rw-r--r--app/assets/javascripts/issues/index.js88
-rw-r--r--app/assets/javascripts/issues/init_filtered_search_service_desk.js11
-rw-r--r--app/assets/javascripts/issues/issue.js2
-rw-r--r--app/assets/javascripts/issues/list/components/issue_card_time_info.vue (renamed from app/assets/javascripts/issues_list/components/issue_card_time_info.vue)0
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue (renamed from app/assets/javascripts/issues_list/components/issues_list_app.vue)17
-rw-r--r--app/assets/javascripts/issues/list/components/jira_issues_import_status_app.vue (renamed from app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue)0
-rw-r--r--app/assets/javascripts/issues/list/components/new_issue_dropdown.vue (renamed from app/assets/javascripts/issues_list/components/new_issue_dropdown.vue)2
-rw-r--r--app/assets/javascripts/issues/list/constants.js (renamed from app/assets/javascripts/issues_list/constants.js)64
-rw-r--r--app/assets/javascripts/issues/list/eventhub.js (renamed from app/assets/javascripts/issues_list/eventhub.js)0
-rw-r--r--app/assets/javascripts/issues/list/index.js (renamed from app/assets/javascripts/issues_list/index.js)36
-rw-r--r--app/assets/javascripts/issues/list/queries/get_issues.query.graphql (renamed from app/assets/javascripts/issues_list/queries/get_issues.query.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql (renamed from app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/get_issues_list_details.query.graphql (renamed from app/assets/javascripts/issues_list/queries/get_issues_list_details.query.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/issue.fragment.graphql (renamed from app/assets/javascripts/issues_list/queries/issue.fragment.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/label.fragment.graphql (renamed from app/assets/javascripts/issues_list/queries/label.fragment.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/milestone.fragment.graphql (renamed from app/assets/javascripts/issues_list/queries/milestone.fragment.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/reorder_issues.mutation.graphql (renamed from app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/search_labels.query.graphql (renamed from app/assets/javascripts/issues_list/queries/search_labels.query.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/search_milestones.query.graphql (renamed from app/assets/javascripts/issues_list/queries/search_milestones.query.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/search_projects.query.graphql (renamed from app/assets/javascripts/issues_list/queries/search_projects.query.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/search_users.query.graphql (renamed from app/assets/javascripts/issues_list/queries/search_users.query.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/queries/user.fragment.graphql (renamed from app/assets/javascripts/issues_list/queries/user.fragment.graphql)0
-rw-r--r--app/assets/javascripts/issues/list/utils.js (renamed from app/assets/javascripts/issues_list/utils.js)4
-rw-r--r--app/assets/javascripts/issues/manual_ordering.js6
-rw-r--r--app/assets/javascripts/issues/new/index.js4
-rw-r--r--app/assets/javascripts/issues/related_merge_requests/index.js32
-rw-r--r--app/assets/javascripts/issues/sentry_error_stack_trace/index.js22
-rw-r--r--app/assets/javascripts/issues/show.js59
-rw-r--r--app/assets/javascripts/issues/show/components/app.vue8
-rw-r--r--app/assets/javascripts/issues/show/components/fields/type.vue10
-rw-r--r--app/assets/javascripts/issues/show/components/header_actions.vue13
-rw-r--r--app/assets/javascripts/issues/show/components/sentry_error_stack_trace.vue (renamed from app/assets/javascripts/issues/sentry_error_stack_trace/components/sentry_error_stack_trace.vue)0
-rw-r--r--app/assets/javascripts/issues/show/constants.js26
-rw-r--r--app/assets/javascripts/issues/show/index.js (renamed from app/assets/javascripts/issues/show/incident.js)90
-rw-r--r--app/assets/javascripts/issues/show/issue.js86
-rw-r--r--app/assets/javascripts/issues_list/components/issuable.vue441
-rw-r--r--app/assets/javascripts/issues_list/components/issuables_list_app.vue426
-rw-r--r--app/assets/javascripts/issues_list/service_desk_helper.js111
-rw-r--r--app/assets/javascripts/jira_import/utils/constants.js2
-rw-r--r--app/assets/javascripts/jira_import/utils/jira_import_utils.js2
-rw-r--r--app/assets/javascripts/jobs/bridge/app.vue106
-rw-r--r--app/assets/javascripts/jobs/bridge/components/sidebar.vue65
-rw-r--r--app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql70
-rw-r--r--app/assets/javascripts/jobs/components/job_log_controllers.vue1
-rw-r--r--app/assets/javascripts/jobs/components/sidebar_job_details_container.vue9
-rw-r--r--app/assets/javascripts/jobs/index.js13
-rw-r--r--app/assets/javascripts/labels/components/delete_label_modal.vue13
-rw-r--r--app/assets/javascripts/labels/create_label_dropdown.js4
-rw-r--r--app/assets/javascripts/labels/index.js2
-rw-r--r--app/assets/javascripts/lib/mermaid.js61
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js10
-rw-r--r--app/assets/javascripts/lib/utils/constants.js1
-rw-r--r--app/assets/javascripts/lib/utils/resize_observer.js58
-rw-r--r--app/assets/javascripts/main.js15
-rw-r--r--app/assets/javascripts/members/components/table/members_table.vue43
-rw-r--r--app/assets/javascripts/members/constants.js30
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue7
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue44
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue67
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue7
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/common.js2
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js7
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js3
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql4
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js39
-rw-r--r--app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue19
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue10
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue8
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue19
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue10
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue7
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue6
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue9
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue20
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/constants.js7
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql10
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/index.js58
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/details.js27
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue (renamed from app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue)35
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/router.js20
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/persisted_search.vue80
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/components/registry_breadcrumb.vue (renamed from app/assets/javascripts/packages_and_registries/container_registry/explorer/components/registry_breadcrumb.vue)11
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/utils.js35
-rw-r--r--app/assets/javascripts/pages/admin/integrations/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/admin/labels/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/admin/runners/edit/index.js3
-rw-r--r--app/assets/javascripts/pages/admin/runners/show/index.js3
-rw-r--r--app/assets/javascripts/pages/dashboard/todos/index/todos.js8
-rw-r--r--app/assets/javascripts/pages/explore/groups/index.js6
-rw-r--r--app/assets/javascripts/pages/groups/edit/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js10
-rw-r--r--app/assets/javascripts/pages/groups/labels/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js4
-rw-r--r--app/assets/javascripts/pages/groups/new/index.js7
-rw-r--r--app/assets/javascripts/pages/groups/packages/index.js8
-rw-r--r--app/assets/javascripts/pages/groups/packages/index/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/settings/access_tokens/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/settings/integrations/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/help/index/index.js5
-rw-r--r--app/assets/javascripts/pages/profiles/keys/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/blob/show/index.js6
-rw-r--r--app/assets/javascripts/pages/projects/branches/index/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/find_file/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/imports/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/incidents/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js11
-rw-r--r--app/assets/javascripts/pages/projects/issues/new/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/service_desk/index.js7
-rw-r--r--app/assets/javascripts/pages/projects/issues/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/labels/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/new/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/packages/packages/index.js8
-rw-r--r--app/assets/javascripts/pages/projects/packages/packages/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/packages/packages/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js4
-rw-r--r--app/assets/javascripts/pages/projects/services/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue40
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/index.js31
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js4
-rw-r--r--app/assets/javascripts/pages/registrations/new/index.js4
-rw-r--r--app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue4
-rw-r--r--app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue1
-rw-r--r--app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue10
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue4
-rw-r--r--app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue22
-rw-r--r--app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue8
-rw-r--r--app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue17
-rw-r--r--app/assets/javascripts/pipeline_editor/constants.js2
-rw-r--r--app/assets/javascripts/pipeline_editor/index.js2
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue43
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/jobs/jobs_app.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/parsing_utils.js22
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/unwrapping_utils.js9
-rw-r--r--app/assets/javascripts/pipelines/constants.js2
-rw-r--r--app/assets/javascripts/pipelines/graphql/fragmentTypes.json1
-rw-r--r--app/assets/javascripts/pipelines/pipeline_shared_client.js9
-rw-r--r--app/assets/javascripts/pipelines/utils.js6
-rw-r--r--app/assets/javascripts/profile/add_ssh_key_validation.js17
-rw-r--r--app/assets/javascripts/project_select_combo_button.js19
-rw-r--r--app/assets/javascripts/projects/project_find_file.js (renamed from app/assets/javascripts/project_find_file.js)0
-rw-r--r--app/assets/javascripts/projects/project_import.js (renamed from app/assets/javascripts/project_import.js)2
-rw-r--r--app/assets/javascripts/projects/project_visibility.js (renamed from app/assets/javascripts/project_visibility.js)21
-rw-r--r--app/assets/javascripts/projects/star.js (renamed from app/assets/javascripts/star.js)8
-rw-r--r--app/assets/javascripts/related_issues/components/related_issues_list.vue2
-rw-r--r--app/assets/javascripts/repository/components/blob_button_group.vue25
-rw-r--r--app/assets/javascripts/repository/components/blob_content_viewer.vue10
-rw-r--r--app/assets/javascripts/repository/components/blob_controls.vue119
-rw-r--r--app/assets/javascripts/repository/components/blob_edit.vue1
-rw-r--r--app/assets/javascripts/repository/components/delete_blob_modal.vue4
-rw-r--r--app/assets/javascripts/repository/components/fork_suggestion.vue1
-rw-r--r--app/assets/javascripts/repository/components/preview/index.vue9
-rw-r--r--app/assets/javascripts/repository/components/upload_blob_modal.vue4
-rw-r--r--app/assets/javascripts/repository/index.js21
-rw-r--r--app/assets/javascripts/repository/queries/blob_controls.query.graphql18
-rw-r--r--app/assets/javascripts/repository/queries/blob_info.query.graphql16
-rw-r--r--app/assets/javascripts/repository/queries/path_locks.fragment.graphql3
-rw-r--r--app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue (renamed from app/assets/javascripts/runner/runner_details/runner_details_app.vue)27
-rw-r--r--app/assets/javascripts/runner/admin_runner_edit/index.js (renamed from app/assets/javascripts/runner/runner_details/index.js)6
-rw-r--r--app/assets/javascripts/runner/admin_runners/admin_runners_app.vue131
-rw-r--r--app/assets/javascripts/runner/admin_runners/index.js30
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_actions_cell.vue17
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_status_cell.vue12
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue34
-rw-r--r--app/assets/javascripts/runner/components/runner_header.vue52
-rw-r--r--app/assets/javascripts/runner/components/runner_status_badge.vue8
-rw-r--r--app/assets/javascripts/runner/components/runner_type_alert.vue54
-rw-r--r--app/assets/javascripts/runner/components/runner_update_form.vue10
-rw-r--r--app/assets/javascripts/runner/components/search_tokens/status_token_config.js4
-rw-r--r--app/assets/javascripts/runner/components/search_tokens/tag_token.vue4
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_online_stat.vue17
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_stats.vue49
-rw-r--r--app/assets/javascripts/runner/components/stat/runner_status_stat.vue65
-rw-r--r--app/assets/javascripts/runner/constants.js5
-rw-r--r--app/assets/javascripts/runner/graphql/get_group_runners.query.graphql2
-rw-r--r--app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql20
-rw-r--r--app/assets/javascripts/runner/graphql/get_runners.query.graphql1
-rw-r--r--app/assets/javascripts/runner/graphql/get_runners_count.query.graphql10
-rw-r--r--app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql2
-rw-r--r--app/assets/javascripts/runner/graphql/runner_node.fragment.graphql4
-rw-r--r--app/assets/javascripts/runner/group_runners/group_runners_app.vue56
-rw-r--r--app/assets/javascripts/runner/runner_search_utils.js28
-rw-r--r--app/assets/javascripts/runner/runner_update_form_utils.js (renamed from app/assets/javascripts/runner/runner_details/runner_update_form_utils.js)0
-rw-r--r--app/assets/javascripts/security_configuration/components/app.vue32
-rw-r--r--app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue1
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js2
-rw-r--r--app/assets/javascripts/security_configuration/components/training_provider_list.vue130
-rw-r--r--app/assets/javascripts/security_configuration/graphql/configure_security_training_providers.mutation.graphql9
-rw-r--r--app/assets/javascripts/security_configuration/index.js30
-rw-r--r--app/assets/javascripts/security_configuration/resolver.js56
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue15
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js7
-rw-r--r--app/assets/javascripts/sidebar/sidebar_mediator.js52
-rw-r--r--app/assets/javascripts/sidebar/stores/sidebar_store.js4
-rw-r--r--app/assets/javascripts/tracking/index.js5
-rw-r--r--app/assets/javascripts/tree.js64
-rw-r--r--app/assets/javascripts/version_check_image.js6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue50
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue45
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue15
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue62
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue8
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue22
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/constants.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js173
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue20
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js12
-rw-r--r--app/assets/javascripts/vue_shared/components/actions_button.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/clipboard_button.vue60
-rw-r--r--app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js17
-rw-r--r--app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue25
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/gitlab_version_check.vue67
-rw-r--r--app/assets/javascripts/vue_shared/components/line_numbers.vue30
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue75
-rw-r--r--app/assets/javascripts/vue_shared/components/modal_copy_button.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue5
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue19
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue12
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/source_editor.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/source_viewer.vue45
-rw-r--r--app/assets/javascripts/vue_shared/components/web_ide_link.vue96
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar.vue1
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue8
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/components/issuable_tabs.vue6
-rw-r--r--app/assets/javascripts/vue_shared/issuable/list/constants.js2
-rw-r--r--app/assets/javascripts/vue_shared/issuable/show/constants.js5
-rw-r--r--app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue18
-rw-r--r--app/assets/javascripts/work_items/components/item_title.vue4
-rw-r--r--app/assets/javascripts/work_items/constants.js2
-rw-r--r--app/assets/javascripts/work_items/pages/work_item_root.vue16
-rw-r--r--app/assets/stylesheets/_page_specific_files.scss1
-rw-r--r--app/assets/stylesheets/components/whats_new.scss6
-rw-r--r--app/assets/stylesheets/framework/contextual_sidebar.scss8
-rw-r--r--app/assets/stylesheets/framework/diffs.scss28
-rw-r--r--app/assets/stylesheets/framework/emojis.scss4
-rw-r--r--app/assets/stylesheets/framework/flash.scss4
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/framework/layout.scss8
-rw-r--r--app/assets/stylesheets/framework/mixins.scss26
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss10
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss2
-rw-r--r--app/assets/stylesheets/framework/system_messages.scss10
-rw-r--r--app/assets/stylesheets/framework/typography.scss15
-rw-r--r--app/assets/stylesheets/framework/variables.scss31
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss2
-rw-r--r--app/assets/stylesheets/framework/wells.scss27
-rw-r--r--app/assets/stylesheets/highlight/white_base.scss49
-rw-r--r--app/assets/stylesheets/notify.scss12
-rw-r--r--app/assets/stylesheets/page_bundles/boards.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/build.scss6
-rw-r--r--app/assets/stylesheets/page_bundles/cycle_analytics.scss6
-rw-r--r--app/assets/stylesheets/page_bundles/import.scss2
-rw-r--r--app/assets/stylesheets/page_bundles/issues_list.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/members.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss2
-rw-r--r--app/assets/stylesheets/pages/issues.scss2
-rw-r--r--app/assets/stylesheets/pages/labels.scss12
-rw-r--r--app/assets/stylesheets/pages/login.scss4
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss18
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/profile.scss25
-rw-r--r--app/assets/stylesheets/pages/settings.scss12
-rw-r--r--app/assets/stylesheets/pages/sherlock.scss31
-rw-r--r--app/assets/stylesheets/pages/users.scss4
-rw-r--r--app/assets/stylesheets/performance_bar.scss2
-rw-r--r--app/assets/stylesheets/startup/startup-dark.scss64
-rw-r--r--app/assets/stylesheets/startup/startup-general.scss59
-rw-r--r--app/assets/stylesheets/startup/startup-signin.scss11
-rw-r--r--app/assets/stylesheets/themes/theme_helper.scss24
-rw-r--r--app/assets/stylesheets/utilities.scss22
-rw-r--r--app/controllers/admin/runner_projects_controller.rb6
-rw-r--r--app/controllers/admin/runners_controller.rb10
-rw-r--r--app/controllers/admin/users_controller.rb2
-rw-r--r--app/controllers/autocomplete_controller.rb7
-rw-r--r--app/controllers/concerns/access_tokens_actions.rb83
-rw-r--r--app/controllers/concerns/check_rate_limit.rb5
-rw-r--r--app/controllers/concerns/integrations/actions.rb3
-rw-r--r--app/controllers/concerns/integrations/params.rb1
-rw-r--r--app/controllers/concerns/sessionless_authentication.rb2
-rw-r--r--app/controllers/graphql_controller.rb7
-rw-r--r--app/controllers/groups/application_controller.rb12
-rw-r--r--app/controllers/groups/boards_controller.rb1
-rw-r--r--app/controllers/groups/dependency_proxy_for_containers_controller.rb30
-rw-r--r--app/controllers/groups/packages_controller.rb5
-rw-r--r--app/controllers/groups/runners_controller.rb7
-rw-r--r--app/controllers/groups/settings/access_tokens_controller.rb18
-rw-r--r--app/controllers/groups_controller.rb4
-rw-r--r--app/controllers/import/gitlab_controller.rb2
-rw-r--r--app/controllers/oauth/token_info_controller.rb2
-rw-r--r--app/controllers/profiles/emails_controller.rb2
-rw-r--r--app/controllers/profiles_controller.rb3
-rw-r--r--app/controllers/projects/analytics/cycle_analytics/stages_controller.rb2
-rw-r--r--app/controllers/projects/analytics/cycle_analytics/summary_controller.rb2
-rw-r--r--app/controllers/projects/boards_controller.rb3
-rw-r--r--app/controllers/projects/google_cloud/base_controller.rb35
-rw-r--r--app/controllers/projects/google_cloud/deployments_controller.rb13
-rw-r--r--app/controllers/projects/google_cloud/service_accounts_controller.rb64
-rw-r--r--app/controllers/projects/issues_controller.rb13
-rw-r--r--app/controllers/projects/jobs_controller.rb1
-rw-r--r--app/controllers/projects/mattermosts_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests/diffs_controller.rb5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb9
-rw-r--r--app/controllers/projects/packages/infrastructure_registry_controller.rb6
-rw-r--r--app/controllers/projects/packages/packages_controller.rb3
-rw-r--r--app/controllers/projects/prometheus/metrics_controller.rb6
-rw-r--r--app/controllers/projects/security/configuration_controller.rb31
-rw-r--r--app/controllers/projects/service_hook_logs_controller.rb4
-rw-r--r--app/controllers/projects/services_controller.rb7
-rw-r--r--app/controllers/projects/settings/access_tokens_controller.rb70
-rw-r--r--app/controllers/projects/settings/ci_cd_controller.rb8
-rw-r--r--app/controllers/projects/settings/repository_controller.rb3
-rw-r--r--app/controllers/projects/tree_controller.rb4
-rw-r--r--app/controllers/projects_controller.rb2
-rw-r--r--app/controllers/registrations_controller.rb7
-rw-r--r--app/controllers/repositories/lfs_api_controller.rb2
-rw-r--r--app/controllers/sandbox_controller.rb11
-rw-r--r--app/controllers/search_controller.rb8
-rw-r--r--app/controllers/sherlock/application_controller.rb14
-rw-r--r--app/controllers/sherlock/file_samples_controller.rb9
-rw-r--r--app/controllers/sherlock/queries_controller.rb9
-rw-r--r--app/controllers/sherlock/transactions_controller.rb21
-rw-r--r--app/controllers/users_controller.rb3
-rw-r--r--app/events/ci/pipeline_created_event.rb14
-rw-r--r--app/experiments/change_continuous_onboarding_link_urls_experiment.rb9
-rw-r--r--app/experiments/new_project_sast_enabled_experiment.rb3
-rw-r--r--app/experiments/require_verification_for_namespace_creation_experiment.rb27
-rw-r--r--app/experiments/templates/new_project_readme_content/readme_advanced.md.tt2
-rw-r--r--app/finders/ci/runners_finder.rb12
-rw-r--r--app/finders/environments/environments_by_deployments_finder.rb3
-rw-r--r--app/finders/fork_targets_finder.rb4
-rw-r--r--app/finders/group_descendants_finder.rb16
-rw-r--r--app/finders/group_members_finder.rb33
-rw-r--r--app/finders/groups/user_groups_finder.rb3
-rw-r--r--app/finders/groups_finder.rb4
-rw-r--r--app/finders/issues_finder.rb6
-rw-r--r--app/finders/merge_requests_finder.rb9
-rw-r--r--app/finders/packages/package_file_finder.rb6
-rw-r--r--app/finders/projects/members/effective_access_level_finder.rb8
-rw-r--r--app/finders/user_group_notification_settings_finder.rb10
-rw-r--r--app/finders/user_recent_events_finder.rb24
-rw-r--r--app/graphql/mutations/clusters/agent_tokens/revoke.rb35
-rw-r--r--app/graphql/mutations/issues/set_crm_contacts.rb9
-rw-r--r--app/graphql/mutations/issues/set_escalation_status.rb46
-rw-r--r--app/graphql/mutations/work_items/create.rb57
-rw-r--r--app/graphql/queries/pipelines/get_pipeline_details.query.graphql21
-rw-r--r--app/graphql/resolvers/base_issues_resolver.rb3
-rw-r--r--app/graphql/resolvers/ci/config_resolver.rb2
-rw-r--r--app/graphql/resolvers/clusters/agent_tokens_resolver.rb9
-rw-r--r--app/graphql/resolvers/concerns/resolves_pipelines.rb4
-rw-r--r--app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb6
-rw-r--r--app/graphql/resolvers/design_management/version_in_collection_resolver.rb6
-rw-r--r--app/graphql/resolvers/group_milestones_resolver.rb6
-rw-r--r--app/graphql/resolvers/merge_requests_resolver.rb4
-rw-r--r--app/graphql/resolvers/users/groups_resolver.rb4
-rw-r--r--app/graphql/resolvers/work_items/types_resolver.rb15
-rw-r--r--app/graphql/types/ci/config/config_type.rb2
-rw-r--r--app/graphql/types/ci/job_type.rb6
-rw-r--r--app/graphql/types/ci/pipeline_message_type.rb16
-rw-r--r--app/graphql/types/ci/pipeline_type.rb23
-rw-r--r--app/graphql/types/ci/runner_type.rb15
-rw-r--r--app/graphql/types/clusters/agent_token_status_enum.rb14
-rw-r--r--app/graphql/types/clusters/agent_token_type.rb5
-rw-r--r--app/graphql/types/commit_type.rb2
-rw-r--r--app/graphql/types/deprecated_mutations.rb3
-rw-r--r--app/graphql/types/group_type.rb5
-rw-r--r--app/graphql/types/incident_management/escalation_status_enum.rb14
-rw-r--r--app/graphql/types/issue_type.rb9
-rw-r--r--app/graphql/types/issue_type_enum.rb2
-rw-r--r--app/graphql/types/merge_request_type.rb22
-rw-r--r--app/graphql/types/mutation_type.rb4
-rw-r--r--app/graphql/types/packages/package_details_type.rb51
-rw-r--r--app/graphql/types/project_type.rb48
-rw-r--r--app/graphql/types/projects/topic_type.rb3
-rw-r--r--app/graphql/types/release_type.rb11
-rw-r--r--app/graphql/types/repository/blob_type.rb19
-rw-r--r--app/graphql/types/sha_format_enum.rb19
-rw-r--r--app/graphql/types/user_interface.rb3
-rw-r--r--app/graphql/types/work_item_type.rb23
-rw-r--r--app/graphql/types/work_items/type_type.rb18
-rw-r--r--app/helpers/admin/background_migrations_helper.rb14
-rw-r--r--app/helpers/application_helper.rb13
-rw-r--r--app/helpers/application_settings_helper.rb44
-rw-r--r--app/helpers/auth_helper.rb19
-rw-r--r--app/helpers/button_helper.rb2
-rw-r--r--app/helpers/ci/jobs_helper.rb8
-rw-r--r--app/helpers/ci/pipeline_editor_helper.rb1
-rw-r--r--app/helpers/ci/runners_helper.rb11
-rw-r--r--app/helpers/commits_helper.rb18
-rw-r--r--app/helpers/custom_metrics_helper.rb2
-rw-r--r--app/helpers/environment_helper.rb2
-rw-r--r--app/helpers/environments_helper.rb2
-rw-r--r--app/helpers/groups/crm_settings_helper.rb9
-rw-r--r--app/helpers/groups_helper.rb10
-rw-r--r--app/helpers/hooks_helper.rb2
-rw-r--r--app/helpers/integrations_helper.rb17
-rw-r--r--app/helpers/issues_helper.rb19
-rw-r--r--app/helpers/learn_gitlab_helper.rb8
-rw-r--r--app/helpers/namespaces_helper.rb7
-rw-r--r--app/helpers/nav/top_nav_helper.rb15
-rw-r--r--app/helpers/nav_helper.rb4
-rw-r--r--app/helpers/notes_helper.rb2
-rw-r--r--app/helpers/packages_helper.rb28
-rw-r--r--app/helpers/page_layout_helper.rb2
-rw-r--r--app/helpers/preferences_helper.rb10
-rw-r--r--app/helpers/projects/cluster_agents_helper.rb5
-rw-r--r--app/helpers/projects/issues/service_desk_helper.rb35
-rw-r--r--app/helpers/projects_helper.rb25
-rw-r--r--app/helpers/search_helper.rb4
-rw-r--r--app/helpers/snippets_helper.rb7
-rw-r--r--app/helpers/sorting_titles_values_helper.rb4
-rw-r--r--app/helpers/ssh_keys_helper.rb10
-rw-r--r--app/helpers/tracking_helper.rb4
-rw-r--r--app/helpers/tree_helper.rb5
-rw-r--r--app/helpers/version_check_helper.rb9
-rw-r--r--app/helpers/webpack_helper.rb4
-rw-r--r--app/mailers/previews/notify_preview.rb2
-rw-r--r--app/models/active_session.rb15
-rw-r--r--app/models/alert_management/alert.rb13
-rw-r--r--app/models/application_setting.rb26
-rw-r--r--app/models/application_setting_implementation.rb11
-rw-r--r--app/models/audit_event.rb1
-rw-r--r--app/models/bulk_imports/file_transfer/project_config.rb6
-rw-r--r--app/models/ci/build.rb64
-rw-r--r--app/models/ci/job_artifact.rb16
-rw-r--r--app/models/ci/namespace_mirror.rb6
-rw-r--r--app/models/ci/pipeline.rb22
-rw-r--r--app/models/ci/project_mirror.rb3
-rw-r--r--app/models/ci/runner.rb66
-rw-r--r--app/models/ci/secure_file.rb33
-rw-r--r--app/models/clusters/agent.rb12
-rw-r--r--app/models/clusters/agent_token.rb42
-rw-r--r--app/models/clusters/agents/activity_event.rb2
-rw-r--r--app/models/clusters/applications/runner.rb4
-rw-r--r--app/models/commit_status.rb17
-rw-r--r--app/models/concerns/ci/contextable.rb36
-rw-r--r--app/models/concerns/ci/metadatable.rb10
-rw-r--r--app/models/concerns/forced_email_confirmation.rb26
-rw-r--r--app/models/concerns/has_wiki.rb2
-rw-r--r--app/models/concerns/import_state/sidekiq_job_tracker.rb2
-rw-r--r--app/models/concerns/incident_management/escalatable.rb11
-rw-r--r--app/models/concerns/packages/debian/distribution.rb9
-rw-r--r--app/models/concerns/packages/destructible.rb15
-rw-r--r--app/models/concerns/packages/installable.rb16
-rw-r--r--app/models/concerns/participable.rb2
-rw-r--r--app/models/concerns/routable.rb5
-rw-r--r--app/models/concerns/runner_token_expiration_interval.rb22
-rw-r--r--app/models/concerns/ttl_expirable.rb8
-rw-r--r--app/models/container_repository.rb6
-rw-r--r--app/models/customer_relations/contact.rb15
-rw-r--r--app/models/customer_relations/issue_contact.rb8
-rw-r--r--app/models/dependency_proxy/blob.rb1
-rw-r--r--app/models/dependency_proxy/manifest.rb1
-rw-r--r--app/models/deployment.rb10
-rw-r--r--app/models/email.rb9
-rw-r--r--app/models/experiment.rb24
-rw-r--r--app/models/external_pull_request.rb5
-rw-r--r--app/models/group.rb46
-rw-r--r--app/models/group/crm_settings.rb10
-rw-r--r--app/models/group_group_link.rb2
-rw-r--r--app/models/hooks/project_hook.rb5
-rw-r--r--app/models/hooks/service_hook.rb4
-rw-r--r--app/models/hooks/web_hook.rb5
-rw-r--r--app/models/instance_configuration.rb3
-rw-r--r--app/models/integration.rb1
-rw-r--r--app/models/integrations/base_chat_notification.rb8
-rw-r--r--app/models/integrations/datadog.rb47
-rw-r--r--app/models/integrations/jira.rb6
-rw-r--r--app/models/internal_id.rb2
-rw-r--r--app/models/issue.rb14
-rw-r--r--app/models/key.rb2
-rw-r--r--app/models/label.rb2
-rw-r--r--app/models/loose_foreign_keys/deleted_record.rb2
-rw-r--r--app/models/loose_foreign_keys/modification_tracker.rb2
-rw-r--r--app/models/member.rb13
-rw-r--r--app/models/members/group_member.rb2
-rw-r--r--app/models/members/project_namespace_member.rb7
-rw-r--r--app/models/merge_request.rb22
-rw-r--r--app/models/namespace.rb6
-rw-r--r--app/models/namespace_setting.rb23
-rw-r--r--app/models/namespaces/traversal/linear.rb13
-rw-r--r--app/models/namespaces/traversal/linear_scopes.rb37
-rw-r--r--app/models/namespaces/traversal/recursive_scopes.rb4
-rw-r--r--app/models/onboarding_progress.rb26
-rw-r--r--app/models/packages/debian/group_distribution.rb4
-rw-r--r--app/models/packages/debian/project_distribution.rb1
-rw-r--r--app/models/packages/package.rb10
-rw-r--r--app/models/packages/package_file.rb26
-rw-r--r--app/models/pages_domain.rb29
-rw-r--r--app/models/preloaders/environments/deployment_preloader.rb43
-rw-r--r--app/models/project.rb40
-rw-r--r--app/models/project_ci_cd_setting.rb6
-rw-r--r--app/models/project_setting.rb10
-rw-r--r--app/models/protectable_dropdown.rb12
-rw-r--r--app/models/ref_matcher.rb4
-rw-r--r--app/models/repository.rb2
-rw-r--r--app/models/route.rb1
-rw-r--r--app/models/user.rb149
-rw-r--r--app/models/users/callout.rb3
-rw-r--r--app/models/work_item.rb6
-rw-r--r--app/models/work_item/type.rb55
-rw-r--r--app/models/work_items/type.rb64
-rw-r--r--app/policies/global_policy.rb2
-rw-r--r--app/policies/group_member_policy.rb5
-rw-r--r--app/policies/group_policy.rb24
-rw-r--r--app/policies/project_policy.rb6
-rw-r--r--app/policies/work_items/type_policy.rb9
-rw-r--r--app/presenters/blob_presenter.rb20
-rw-r--r--app/presenters/ci/runner_presenter.rb4
-rw-r--r--app/presenters/label_presenter.rb6
-rw-r--r--app/presenters/packages/conan/package_presenter.rb8
-rw-r--r--app/presenters/packages/detail/package_presenter.rb12
-rw-r--r--app/presenters/packages/npm/package_presenter.rb6
-rw-r--r--app/presenters/packages/nuget/presenter_helpers.rb9
-rw-r--r--app/presenters/packages/pypi/package_presenter.rb8
-rw-r--r--app/presenters/project_presenter.rb2
-rw-r--r--app/presenters/service_hook_presenter.rb4
-rw-r--r--app/serializers/analytics_build_entity.rb7
-rw-r--r--app/serializers/analytics_issue_entity.rb7
-rw-r--r--app/serializers/environment_serializer.rb12
-rw-r--r--app/serializers/group_child_entity.rb2
-rw-r--r--app/serializers/merge_request_poll_cached_widget_entity.rb5
-rw-r--r--app/serializers/merge_request_poll_widget_entity.rb4
-rw-r--r--app/serializers/merge_request_sidebar_basic_entity.rb4
-rw-r--r--app/services/alert_management/alerts/update_service.rb31
-rw-r--r--app/services/audit_event_service.rb2
-rw-r--r--app/services/auth/container_registry_authentication_service.rb13
-rw-r--r--app/services/auto_merge/base_service.rb6
-rw-r--r--app/services/bulk_imports/archive_extraction_service.rb4
-rw-r--r--app/services/bulk_imports/file_decompression_service.rb27
-rw-r--r--app/services/bulk_imports/file_download_service.rb28
-rw-r--r--app/services/bulk_imports/file_export_service.rb4
-rw-r--r--app/services/bulk_imports/lfs_objects_export_service.rb64
-rw-r--r--app/services/ci/archive_trace_service.rb4
-rw-r--r--app/services/ci/create_pipeline_service.rb11
-rw-r--r--app/services/ci/destroy_pipeline_service.rb12
-rw-r--r--app/services/ci/job_artifacts/delete_project_artifacts_service.rb11
-rw-r--r--app/services/ci/job_artifacts/destroy_all_expired_service.rb31
-rw-r--r--app/services/ci/job_artifacts/expire_project_build_artifacts_service.rb35
-rw-r--r--app/services/ci/pipeline_processing/atomic_processing_service.rb4
-rw-r--r--app/services/ci/pipelines/add_job_service.rb6
-rw-r--r--app/services/ci/play_build_service.rb5
-rw-r--r--app/services/ci/process_build_service.rb19
-rw-r--r--app/services/ci/process_sync_events_service.rb8
-rw-r--r--app/services/ci/register_runner_service.rb36
-rw-r--r--app/services/ci/retry_build_service.rb35
-rw-r--r--app/services/ci/stuck_builds/drop_helpers.rb12
-rw-r--r--app/services/ci/update_build_queue_service.rb14
-rw-r--r--app/services/clusters/agent_tokens/create_service.rb5
-rw-r--r--app/services/clusters/agent_tokens/track_usage_service.rb54
-rw-r--r--app/services/clusters/agents/create_activity_event_service.rb28
-rw-r--r--app/services/clusters/agents/delete_expired_events_service.rb25
-rw-r--r--app/services/concerns/issues/issue_type_helpers.rb2
-rw-r--r--app/services/dependency_proxy/download_blob_service.rb38
-rw-r--r--app/services/dependency_proxy/find_or_create_blob_service.rb48
-rw-r--r--app/services/deployments/archive_in_project_service.rb4
-rw-r--r--app/services/deployments/create_for_build_service.rb25
-rw-r--r--app/services/deployments/update_environment_service.rb2
-rw-r--r--app/services/design_management/copy_design_collection/copy_service.rb2
-rw-r--r--app/services/emails/confirm_service.rb2
-rw-r--r--app/services/error_tracking/collect_error_service.rb21
-rw-r--r--app/services/events/destroy_service.rb15
-rw-r--r--app/services/google_cloud/create_service_accounts_service.rb60
-rw-r--r--app/services/google_cloud/service_accounts_service.rb19
-rw-r--r--app/services/groups/update_service.rb10
-rw-r--r--app/services/import/gitlab_projects/create_project_from_remote_file_service.rb21
-rw-r--r--app/services/import/validate_remote_git_endpoint_service.rb2
-rw-r--r--app/services/incident_management/issuable_escalation_statuses/after_update_service.rb42
-rw-r--r--app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb99
-rw-r--r--app/services/issuable_base_service.rb14
-rw-r--r--app/services/issues/base_service.rb36
-rw-r--r--app/services/issues/build_service.rb24
-rw-r--r--app/services/issues/create_service.rb5
-rw-r--r--app/services/issues/set_crm_contacts_service.rb2
-rw-r--r--app/services/issues/update_service.rb21
-rw-r--r--app/services/labels/transfer_service.rb31
-rw-r--r--app/services/loose_foreign_keys/process_deleted_records_service.rb1
-rw-r--r--app/services/merge_requests/add_todo_when_build_fails_service.rb4
-rw-r--r--app/services/merge_requests/base_service.rb4
-rw-r--r--app/services/merge_requests/handle_assignees_change_service.rb4
-rw-r--r--app/services/merge_requests/merge_base_service.rb2
-rw-r--r--app/services/merge_requests/remove_approval_service.rb1
-rw-r--r--app/services/merge_requests/squash_service.rb2
-rw-r--r--app/services/packages/maven/metadata/sync_service.rb13
-rw-r--r--app/services/packages/terraform_module/create_package_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb8
-rw-r--r--app/services/projects/fork_service.rb6
-rw-r--r--app/services/projects/overwrite_project_service.rb4
-rw-r--r--app/services/projects/prometheus/alerts/notify_service.rb31
-rw-r--r--app/services/projects/update_pages_configuration_service.rb109
-rw-r--r--app/services/projects/update_remote_mirror_service.rb41
-rw-r--r--app/services/projects/update_service.rb19
-rw-r--r--app/services/resource_access_tokens/create_service.rb5
-rw-r--r--app/services/resource_access_tokens/revoke_service.rb10
-rw-r--r--app/services/search_service.rb4
-rw-r--r--app/services/users/upsert_credit_card_validation_service.rb5
-rw-r--r--app/services/web_hook_service.rb41
-rw-r--r--app/services/work_items/build_service.rb11
-rw-r--r--app/services/work_items/create_service.rb19
-rw-r--r--app/uploaders/ci/secure_file_uploader.rb46
-rw-r--r--app/validators/json_schemas/error_tracking_event_payload.json107
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml5
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml6
-rw-r--r--app/views/admin/application_settings/_email.html.haml2
-rw-r--r--app/views/admin/application_settings/_external_authorization_service_form.html.haml23
-rw-r--r--app/views/admin/application_settings/_floc.html.haml2
-rw-r--r--app/views/admin/application_settings/_localization.html.haml4
-rw-r--r--app/views/admin/application_settings/_outbound.html.haml15
-rw-r--r--app/views/admin/application_settings/_prometheus.html.haml2
-rw-r--r--app/views/admin/application_settings/_registry.html.haml4
-rw-r--r--app/views/admin/application_settings/_runner_registrars_form.html.haml2
-rw-r--r--app/views/admin/application_settings/_sourcegraph.html.haml2
-rw-r--r--app/views/admin/application_settings/_spam.html.haml4
-rw-r--r--app/views/admin/application_settings/_third_party_offers.html.haml6
-rw-r--r--app/views/admin/application_settings/appearances/_form.html.haml12
-rw-r--r--app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml2
-rw-r--r--app/views/admin/application_settings/general.html.haml1
-rw-r--r--app/views/admin/application_settings/network.html.haml6
-rw-r--r--app/views/admin/background_migrations/_migration.html.haml2
-rw-r--r--app/views/admin/background_migrations/index.html.haml9
-rw-r--r--app/views/admin/cohorts/_cohorts_table.html.haml2
-rw-r--r--app/views/admin/dashboard/index.html.haml4
-rw-r--r--app/views/admin/deploy_keys/index.html.haml43
-rw-r--r--app/views/admin/groups/_group.html.haml3
-rw-r--r--app/views/admin/labels/_form.html.haml31
-rw-r--r--app/views/admin/labels/edit.html.haml2
-rw-r--r--app/views/admin/labels/new.html.haml2
-rw-r--r--app/views/admin/runners/edit.html.haml99
-rw-r--r--app/views/admin/runners/show.html.haml93
-rw-r--r--app/views/admin/users/_head.html.haml15
-rw-r--r--app/views/admin/users/_users.html.haml10
-rw-r--r--app/views/admin/users/projects.html.haml2
-rw-r--r--app/views/ci/runner/_how_to_setup_runner.html.haml2
-rw-r--r--app/views/clusters/clusters/_advanced_settings.html.haml2
-rw-r--r--app/views/clusters/clusters/_integrations.html.haml2
-rw-r--r--app/views/clusters/clusters/_namespace.html.haml2
-rw-r--r--app/views/clusters/clusters/_provider_details_form.html.haml4
-rw-r--r--app/views/clusters/clusters/gcp/_form.html.haml6
-rw-r--r--app/views/clusters/clusters/show.html.haml2
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml8
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml61
-rw-r--r--app/views/devise/confirmations/almost_there.haml2
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/devise/shared/_signup_omniauth_provider_list.haml2
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml56
-rw-r--r--app/views/graphiql/rails/editors/show.html.erb99
-rw-r--r--app/views/groups/_home_panel.html.haml13
-rw-r--r--app/views/groups/_new_group_fields.html.haml5
-rw-r--r--app/views/groups/edit.html.haml5
-rw-r--r--app/views/groups/issues.html.haml14
-rw-r--r--app/views/groups/merge_requests.html.haml2
-rw-r--r--app/views/groups/new.html.haml2
-rw-r--r--app/views/groups/packages/index.html.haml5
-rw-r--r--app/views/groups/runners/_group_runners.html.haml2
-rw-r--r--app/views/groups/runners/_runner.html.haml17
-rw-r--r--app/views/groups/settings/_ip_restriction_registration_features_cta.html.haml8
-rw-r--r--app/views/groups/settings/_permissions.html.haml12
-rw-r--r--app/views/groups/settings/_project_access_token_creation.html.haml9
-rw-r--r--app/views/groups/settings/_resource_access_token_creation.html.haml11
-rw-r--r--app/views/groups/settings/access_tokens/index.html.haml50
-rw-r--r--app/views/groups/settings/ci_cd/_auto_devops_form.html.haml3
-rw-r--r--app/views/help/index.html.haml15
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml46
-rw-r--r--app/views/layouts/header/_gitlab_version.html.haml11
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml1
-rw-r--r--app/views/layouts/header/_marketing_links.html.haml34
-rw-r--r--app/views/layouts/header/_sign_in_register_button.html.haml6
-rw-r--r--app/views/notify/_note_email.html.haml6
-rw-r--r--app/views/notify/repository_push_email.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml2
-rw-r--r--app/views/profiles/chat_names/_chat_name.html.haml2
-rw-r--r--app/views/profiles/chat_names/index.html.haml3
-rw-r--r--app/views/profiles/emails/index.html.haml14
-rw-r--r--app/views/profiles/gpg_keys/index.html.haml2
-rw-r--r--app/views/profiles/keys/_form.html.haml4
-rw-r--r--app/views/profiles/keys/index.html.haml7
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml61
-rw-r--r--app/views/profiles/preferences/show.html.haml13
-rw-r--r--app/views/profiles/show.html.haml12
-rw-r--r--app/views/projects/_home_panel.html.haml13
-rw-r--r--app/views/projects/_import_project_pane.html.haml23
-rw-r--r--app/views/projects/_merge_request_merge_checks_settings.html.haml2
-rw-r--r--app/views/projects/_merge_request_merge_commit_template.html.haml11
-rw-r--r--app/views/projects/_merge_request_merge_method_settings.html.haml3
-rw-r--r--app/views/projects/_merge_request_merge_suggestions_settings.html.haml11
-rw-r--r--app/views/projects/_merge_request_squash_commit_template.html.haml11
-rw-r--r--app/views/projects/_merge_request_squash_options_settings.html.haml4
-rw-r--r--app/views/projects/_new_project_fields.html.haml27
-rw-r--r--app/views/projects/branches/_branch.html.haml45
-rw-r--r--app/views/projects/branches/_delete_protected_modal.html.haml42
-rw-r--r--app/views/projects/branches/index.html.haml4
-rw-r--r--app/views/projects/buttons/_fork.html.haml2
-rw-r--r--app/views/projects/ci/pipeline_editor/show.html.haml1
-rw-r--r--app/views/projects/commit/_limit_exceeded_message.html.haml13
-rw-r--r--app/views/projects/commit/branches.html.haml5
-rw-r--r--app/views/projects/default_branch/_show.html.haml2
-rw-r--r--app/views/projects/deployments/_deployment.html.haml3
-rw-r--r--app/views/projects/diffs/_email_line.html.haml21
-rw-r--r--app/views/projects/diffs/_file_header.html.haml2
-rw-r--r--app/views/projects/diffs/_line.html.haml2
-rw-r--r--app/views/projects/diffs/_text_file.html.haml36
-rw-r--r--app/views/projects/edit.html.haml10
-rw-r--r--app/views/projects/forks/index.html.haml2
-rw-r--r--app/views/projects/import/jira/show.html.haml2
-rw-r--r--app/views/projects/issues/_by_email_description.html.haml4
-rw-r--r--app/views/projects/issues/_design_management.html.haml6
-rw-r--r--app/views/projects/issues/_issue.html.haml1
-rw-r--r--app/views/projects/issues/_issues.html.haml29
-rw-r--r--app/views/projects/issues/_service_desk_empty_state.html.haml12
-rw-r--r--app/views/projects/issues/service_desk.html.haml13
-rw-r--r--app/views/projects/jobs/show.html.haml2
-rw-r--r--app/views/projects/mattermosts/_no_teams.html.haml2
-rw-r--r--app/views/projects/mattermosts/_team_selection.html.haml2
-rw-r--r--app/views/projects/merge_requests/_widget.html.haml43
-rw-r--r--app/views/projects/merge_requests/invalid.html.haml11
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/projects/mirrors/_authentication_method.html.haml2
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml2
-rw-r--r--app/views/projects/mirrors/_mirror_repos_form.html.haml2
-rw-r--r--app/views/projects/packages/packages/index.html.haml5
-rw-r--r--app/views/projects/packages/packages/show.html.haml9
-rw-r--r--app/views/projects/pages/_list.html.haml6
-rw-r--r--app/views/projects/pipelines/_info.html.haml34
-rw-r--r--app/views/projects/pipelines/show.html.haml1
-rw-r--r--app/views/projects/prometheus/metrics/edit.html.haml2
-rw-r--r--app/views/projects/prometheus/metrics/new.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_branches_list.html.haml2
-rw-r--r--app/views/projects/protected_branches/shared/_protected_branch.html.haml6
-rw-r--r--app/views/projects/protected_tags/shared/_protected_tag.html.haml2
-rw-r--r--app/views/projects/protected_tags/shared/_tags_list.html.haml2
-rw-r--r--app/views/projects/registry/repositories/index.html.haml2
-rw-r--r--app/views/projects/runners/_group_runners.html.haml4
-rw-r--r--app/views/projects/runners/_specific_runners.html.haml2
-rw-r--r--app/views/projects/services/_form.html.haml9
-rw-r--r--app/views/projects/settings/access_tokens/index.html.haml16
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml8
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml18
-rw-r--r--app/views/projects/settings/packages_and_registries/show.html.haml4
-rw-r--r--app/views/projects/starrers/index.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml3
-rw-r--r--app/views/projects/triggers/_index.html.haml4
-rw-r--r--app/views/registrations/welcome/show.html.haml6
-rw-r--r--app/views/sandbox/mermaid.html.erb9
-rw-r--r--app/views/shared/_confirm_modal.html.haml21
-rw-r--r--app/views/shared/_integration_settings.html.haml (renamed from app/views/shared/_service_settings.html.haml)2
-rw-r--r--app/views/shared/_label.html.haml32
-rw-r--r--app/views/shared/_new_project_item_select.html.haml2
-rw-r--r--app/views/shared/_old_visibility_level.html.haml2
-rw-r--r--app/views/shared/_registration_features_discovery_message.html.haml8
-rw-r--r--app/views/shared/_web_ide_button.html.haml2
-rw-r--r--app/views/shared/access_tokens/_form.html.haml8
-rw-r--r--app/views/shared/access_tokens/_table.html.haml12
-rw-r--r--app/views/shared/empty_states/_deploy_keys.html.haml9
-rw-r--r--app/views/shared/empty_states/_issues.html.haml2
-rw-r--r--app/views/shared/empty_states/_merge_requests.html.haml2
-rw-r--r--app/views/shared/empty_states/_wikis.html.haml2
-rw-r--r--app/views/shared/gitpod/_enable_gitpod_modal.html.haml12
-rw-r--r--app/views/shared/hook_logs/_content.html.haml3
-rw-r--r--app/views/shared/hook_logs/_recent_deliveries_table.html.haml3
-rw-r--r--app/views/shared/hook_logs/_status_label.html.haml7
-rw-r--r--app/views/shared/integrations/_form.html.haml4
-rw-r--r--app/views/shared/integrations/edit.html.haml5
-rw-r--r--app/views/shared/integrations/overrides.html.haml3
-rw-r--r--app/views/shared/issuable/_form.html.haml4
-rw-r--r--app/views/shared/issuable/_label_page_create.html.haml5
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml7
-rw-r--r--app/views/shared/issuable/form/_merge_params.html.haml2
-rw-r--r--app/views/shared/labels/_form.html.haml27
-rw-r--r--app/views/shared/members/_badge.html.haml4
-rw-r--r--app/views/shared/members/_blocked_badge.html.haml2
-rw-r--r--app/views/shared/members/_its_you_badge.html.haml2
-rw-r--r--app/views/shared/members/_two_factor_auth_badge.html.haml2
-rw-r--r--app/views/shared/nav/_sidebar_menu.html.haml2
-rw-r--r--app/views/shared/notes/_hints.html.haml4
-rw-r--r--app/views/shared/runners/_shared_runners_description.html.haml2
-rw-r--r--app/views/shared/web_hooks/_form.html.haml32
-rw-r--r--app/views/shared/web_hooks/_hook.html.haml15
-rw-r--r--app/views/shared/web_hooks/_index.html.haml2
-rw-r--r--app/views/sherlock/file_samples/show.html.haml55
-rw-r--r--app/views/sherlock/queries/_backtrace.html.haml31
-rw-r--r--app/views/sherlock/queries/_general.html.haml54
-rw-r--r--app/views/sherlock/queries/show.html.haml26
-rw-r--r--app/views/sherlock/transactions/_file_samples.html.haml24
-rw-r--r--app/views/sherlock/transactions/_general.html.haml38
-rw-r--r--app/views/sherlock/transactions/_queries.html.haml24
-rw-r--r--app/views/sherlock/transactions/index.html.haml41
-rw-r--r--app/views/sherlock/transactions/show.html.haml36
-rw-r--r--app/views/users/show.html.haml6
-rw-r--r--app/workers/all_queues.yml49
-rw-r--r--app/workers/ci/build_finished_worker.rb2
-rw-r--r--app/workers/ci/external_pull_requests/create_pipeline_worker.rb2
-rw-r--r--app/workers/ci/job_artifacts/expire_project_build_artifacts_worker.rb20
-rw-r--r--app/workers/clusters/agents/delete_expired_events_worker.rb21
-rw-r--r--app/workers/concerns/application_worker.rb16
-rw-r--r--app/workers/concerns/cluster_agent_queue.rb10
-rw-r--r--app/workers/concerns/dependency_proxy/expireable.rb2
-rw-r--r--app/workers/concerns/packages/cleanup_artifact_worker.rb (renamed from app/workers/concerns/dependency_proxy/cleanup_worker.rb)17
-rw-r--r--app/workers/dependency_proxy/cleanup_blob_worker.rb7
-rw-r--r--app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb4
-rw-r--r--app/workers/dependency_proxy/cleanup_manifest_worker.rb7
-rw-r--r--app/workers/dependency_proxy/image_ttl_group_policy_worker.rb4
-rw-r--r--app/workers/email_receiver_worker.rb33
-rw-r--r--app/workers/expire_build_artifacts_worker.rb4
-rw-r--r--app/workers/loose_foreign_keys/cleanup_worker.rb28
-rw-r--r--app/workers/merge_requests/update_head_pipeline_worker.rb23
-rw-r--r--app/workers/metrics/dashboard/sync_dashboards_worker.rb2
-rw-r--r--app/workers/packages/cleanup_package_file_worker.rb44
-rw-r--r--app/workers/packages/cleanup_package_registry_worker.rb41
-rw-r--r--app/workers/pages_update_configuration_worker.rb16
-rw-r--r--app/workers/pages_worker.rb9
-rw-r--r--app/workers/update_external_pull_requests_worker.rb2
-rw-r--r--app/workers/update_head_pipeline_for_merge_request_worker.rb2
-rw-r--r--app/workers/web_hook_worker.rb12
964 files changed, 10250 insertions, 6178 deletions
diff --git a/app/assets/images/file_icons.svg b/app/assets/images/file_icons.svg
index ec38020f978..def87dd9163 100644
--- a/app/assets/images/file_icons.svg
+++ b/app/assets/images/file_icons.svg
@@ -1 +1 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 24 24" id="actionscript" xmlns="http://www.w3.org/2000/svg"><text style="line-height:113.99999857%" x="5.605" y="15.892" transform="scale(.91325 1.095)" font-weight="400" font-size="42.822" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/><path style="line-height:125%" d="M4.744 2.031c-1.157 0-1.994.31-2.51.93-.515.612-.771 1.678-.771 3.197v2.467c0 1.408-.402 2.111-1.201 2.111v2.035c.8 0 1.2.679 1.2 2.036v2.654c0 1.512.26 2.562.78 3.152.52.59 1.355.885 2.502.885V19.43c-.447 0-.77-.151-.97-.453-.195-.303-.292-.815-.292-1.538v-2.267c0-1.807-.404-2.937-1.214-3.395v-.045c.81-.464 1.214-1.581 1.214-3.351V6.025c0-1.283.42-1.925 1.262-1.925V2.03zm14.66 0V4.1c.842 0 1.262.642 1.262 1.925v2.268c0 1.843.402 2.996 1.207 3.46v.046c-.805.442-1.207 1.544-1.207 3.306v2.356c0 .715-.099 1.22-.299 1.516-.2.302-.52.453-.963.453v2.068c1.152 0 1.984-.295 2.494-.885.516-.59.772-1.663.772-3.218V14.84c0-1.379.404-2.069 1.209-2.069v-2.035c-.805 0-1.21-.696-1.21-2.09V6.113c0-1.49-.255-2.54-.77-3.152-.516-.62-1.348-.93-2.495-.93zm-3.054 4.46c-.455 0-.886.057-1.293.173a3.056 3.056 0 0 0-1.078.527c-.308.241-.551.549-.731.924-.18.37-.27.817-.27 1.336 0 .663.165 1.227.493 1.695.33.468.831.864 1.502 1.188.263.125.509.249.736.37.227.12.422.244.586.374.168.13.299.271.394.424a.963.963 0 0 1 .145.521c0 .144-.03.28-.09.405a.9.9 0 0 1-.275.318c-.12.088-.272.158-.455.21a2.34 2.34 0 0 1-.635.075c-.415 0-.825-.083-1.233-.25a3.644 3.644 0 0 1-1.13-.763v2.222a3.68 3.68 0 0 0 1.101.418c.427.093.875.139 1.346.139.459 0 .894-.05 1.305-.152a3.002 3.002 0 0 0 1.09-.5c.31-.237.556-.543.736-.918.183-.38.275-.849.275-1.405 0-.403-.052-.755-.156-1.056a2.542 2.542 0 0 0-.45-.813 3.295 3.295 0 0 0-.704-.633 6.754 6.754 0 0 0-.922-.535 12.4 12.4 0 0 1-.676-.348c-.2-.115-.37-.231-.51-.347a1.502 1.502 0 0 1-.322-.375.91.91 0 0 1-.115-.453c0-.153.033-.288.101-.408a.948.948 0 0 1 .29-.32c.123-.089.275-.156.454-.202a2.18 2.18 0 0 1 .598-.078c.16 0 .326.015.502.043.18.028.36.07.539.13.18.056.354.13.522.218.171.088.329.188.472.304V6.871a4.039 4.039 0 0 0-.957-.285 6.448 6.448 0 0 0-1.185-.096zm-8.774.165l-3.123 9.967h2.094l.605-2.217h3.053l.61 2.217h2.107L9.869 6.656H7.576zm1.072 1.78h.047c.028.347.077.646.145.896l.922 3.35H7.564l.934-3.377c.08-.288.13-.578.15-.87z" font-weight="400" font-size="51.019" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="android" xmlns="http://www.w3.org/2000/svg"><path d="M15 5h-1V4h1m-5 1H9V4h1m5.53-1.84L16.84.85c.19-.19.19-.51 0-.71a.513.513 0 0 0-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.14a.501.501 0 0 0-.7 0c-.2.2-.2.52 0 .71l1.31 1.31C6.97 3.26 6 5 6 7h12c0-2-1-3.75-2.47-4.84M20.5 8A1.5 1.5 0 0 0 19 9.5v7a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 20.5 8m-17 0A1.5 1.5 0 0 0 2 9.5v7A1.5 1.5 0 0 0 3.5 18 1.5 1.5 0 0 0 5 16.5v-7A1.5 1.5 0 0 0 3.5 8M6 18a1 1 0 0 0 1 1h1v3.5A1.5 1.5 0 0 0 9.5 24a1.5 1.5 0 0 0 1.5-1.5V19h2v3.5a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5V19h1a1 1 0 0 0 1-1V8H6v10z" fill="#c0ca33"/></symbol><symbol viewBox="0 0 24 24" id="angular" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="angular-component" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#0288d1"/></symbol><symbol viewBox="0 0 24 24" id="angular-directive" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ab47bc"/></symbol><symbol viewBox="0 0 24 24" id="angular-guard" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-pipe" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#00897b"/></symbol><symbol viewBox="0 0 24 24" id="angular-resolver" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-routing" xmlns="http://www.w3.org/2000/svg"><path d="M11 10H5L3 8l2-2h6V3l1-1 1 1v1h6l2 2-2 2h-6v2h6l2 2-2 2h-6v6a2 2 0 0 1 2 2H9a2 2 0 0 1 2-2V10z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-service" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ffca28"/></symbol><symbol viewBox="0 0 100 100" id="apiblueprint" xmlns="http://www.w3.org/2000/svg"><title>api-blueprint</title><path d="M50.133 7.521A16.998 16.998 0 0 0 33.135 24.52a16.998 16.998 0 0 0 4.945 11.974L24.861 57.398a16.998 16.998 0 0 0-3.175-.308A16.998 16.998 0 0 0 4.688 74.088a16.998 16.998 0 0 0 16.998 16.998 16.998 16.998 0 0 0 16.998-16.998 16.998 16.998 0 0 0-7.063-13.773l12.576-19.89a16.998 16.998 0 0 0 5.936 1.093 16.998 16.998 0 0 0 6.154-1.155l12.537 19.83a16.998 16.998 0 0 0-7.244 13.895 16.998 16.998 0 0 0 16.998 17 16.998 16.998 0 0 0 16.998-17A16.998 16.998 0 0 0 78.578 57.09a16.998 16.998 0 0 0-2.95.262L62.337 36.327A16.998 16.998 0 0 0 67.13 24.52 16.998 16.998 0 0 0 50.132 7.522z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="applescript" xmlns="http://www.w3.org/2000/svg"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" fill="#78909c"/></symbol><symbol viewBox="0 0 24 24" id="appveyor" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c-.084 0-.165.008-.248.01a10 10 0 0 0-.266.01 9.952 9.952 0 0 0-.754.066 10 10 0 0 0-.148.018 9.855 9.855 0 0 0-.93.177 10 10 0 0 0-.07.02c-.196.049-.392.1-.584.16v.012a10 10 0 0 0-2 .875V3.34c-.02.012-.038.027-.059.039a10 10 0 0 0-.953.635c-.09.067-.172.142-.26.213a10 10 0 0 0-.628.546c-.109.104-.211.211-.315.319a10 10 0 0 0-.476.539c-.1.12-.201.237-.295.361a10 10 0 0 0-.52.766c-.088.143-.17.288-.252.435a10 10 0 0 0-.363.723c-.072.161-.136.327-.2.492a10 10 0 0 0-.269.778c-.02.067-.044.131-.062.199a10 10 0 0 0-.008.027c-.098.364-.166.728-.22 1.09-.012.077-.024.153-.034.23a9.85 9.85 0 0 0-.08 1.182c0 .03-.006.057-.006.086a10 10 0 0 0 .008.148c.001.094-.002.188.002.282l.011.004a10 10 0 0 0 .333 2.158l-.012-.004c.012.047.033.091.047.139a10 10 0 0 0 .322.955c.02.052.037.106.059.158a10 10 0 0 0 .503 1.035c.065.116.14.226.21.34a10 10 0 0 0 .423.64c.092.128.187.252.285.375a10 10 0 0 0 .448.52c.112.123.222.248.341.365a10 10 0 0 0 .803.719 10 10 0 0 0 .01.006c.099.078.207.146.309.22a10 10 0 0 0 .648.442c.138.085.28.163.424.242a10 10 0 0 0 .715.358c.114.051.226.106.343.154a10 10 0 0 0 1.133.389c.016.004.031.01.047.015a10 10 0 0 0 .461.098 10 10 0 0 0 .482.103 10 10 0 0 0 .418.051 10 10 0 0 0 .575.065 10 10 0 0 0 .144.005A10 10 0 0 0 12 22a10 10 0 0 0 .197-.01 10 10 0 0 0 .496-.025 10 10 0 0 0 .49-.043 10 10 0 0 0 .489-.074 10 10 0 0 0 .51-.098 10 10 0 0 0 .47-.12 10 10 0 0 0 .477-.14 10 10 0 0 0 .47-.172 10 10 0 0 0 .481-.197 10 10 0 0 0 .414-.201 10 10 0 0 0 .475-.252 10 10 0 0 0 .39-.238 10 10 0 0 0 .452-.301 10 10 0 0 0 .38-.291 10 10 0 0 0 .385-.315 10 10 0 0 0 .375-.347 10 10 0 0 0 .36-.363 10 10 0 0 0 .293-.334 10 10 0 0 0 .353-.434 10 10 0 0 0 .28-.393 10 10 0 0 0 .263-.4 10 10 0 0 0 .264-.461 10 10 0 0 0 .228-.436 10 10 0 0 0 .195-.437 10 10 0 0 0 .196-.48 10 10 0 0 0 .228-.69 10 10 0 0 0 .028-.094 10 10 0 0 0 .021-.066 10 10 0 0 0 .098-.461 10 10 0 0 0 .103-.482 10 10 0 0 0 .051-.418 10 10 0 0 0 .065-.575 10 10 0 0 0 .005-.144A10 10 0 0 0 22 12a10 10 0 0 0-.01-.197 10 10 0 0 0-.025-.496 10 10 0 0 0-.043-.49 10 10 0 0 0-.074-.489 10 10 0 0 0-.098-.51 10 10 0 0 0-.12-.47 10 10 0 0 0-.14-.477 10 10 0 0 0-.172-.47 10 10 0 0 0-.197-.481 10 10 0 0 0-.201-.414 10 10 0 0 0-.252-.475 10 10 0 0 0-.238-.39 10 10 0 0 0-.301-.452 10 10 0 0 0-.291-.38 10 10 0 0 0-.315-.385 10 10 0 0 0-.347-.375 10 10 0 0 0-.363-.36 10 10 0 0 0-.334-.293 10 10 0 0 0-.434-.353 10 10 0 0 0-.393-.28 10 10 0 0 0-.4-.263 10 10 0 0 0-.461-.264 10 10 0 0 0-.436-.228 10 10 0 0 0-.437-.196 10 10 0 0 0-.48-.195 10 10 0 0 0-.69-.228 10 10 0 0 0-.094-.028 10 10 0 0 0-.066-.021 10 10 0 0 0-.461-.098 10 10 0 0 0-.482-.103 10 10 0 0 0-.418-.051 10 10 0 0 0-.575-.065 10 10 0 0 0-.144-.005A10 10 0 0 0 12 2zm-.016 5.002a5 5 0 0 1 .262.01 5 5 0 0 1 .227.011 5 5 0 0 1 .341.05 5 5 0 0 1 .135.019 5 5 0 0 1 .014.004 5 5 0 0 1 .115.025 5 5 0 0 1 .303.076 5 5 0 0 1 .265.086 5 5 0 0 1 .2.074 5 5 0 0 1 .242.106 5 5 0 0 1 .228.11 5 5 0 0 1 .196.109 5 5 0 0 1 .244.15 5 5 0 0 1 .17.12 5 5 0 0 1 .224.171 5 5 0 0 1 .186.16 5 5 0 0 1 .176.164 5 5 0 0 1 .172.18 5 5 0 0 1 .177.203 5 5 0 0 1 .133.172 5 5 0 0 1 .16.223 5 5 0 0 1 .133.214 5 5 0 0 1 .12.21 5 5 0 0 1 .107.216 5 5 0 0 1 .109.24 5 5 0 0 1 .084.223 5 5 0 0 1 .08.242 5 5 0 0 1 .07.264 5 5 0 0 1 .047.207 5 5 0 0 1 .045.277 5 5 0 0 1 .028.227 5 5 0 0 1 .02.351 5 5 0 0 1 .003.079 5 5 0 0 1-.012.271 5 5 0 0 1-.011.227 5 5 0 0 1-.05.341 5 5 0 0 1-.019.135 5 5 0 0 1-.004.014 5 5 0 0 1-.025.115 5 5 0 0 1-.076.303 5 5 0 0 1-.086.265 5 5 0 0 1-.074.2 5 5 0 0 1-.106.242 5 5 0 0 1-.11.228 5 5 0 0 1-.109.196 5 5 0 0 1-.15.244 5 5 0 0 1-.12.17 5 5 0 0 1-.171.224 5 5 0 0 1-.16.186 5 5 0 0 1-.164.176 5 5 0 0 1-.18.172 5 5 0 0 1-.203.177l-.002.002c-.018.019-.028.035-.047.053l-3.959 5.09-3.05-.979a141.684 141.684 0 0 0 3.177-3.084 5 5 0 0 1-.103-.015 5 5 0 0 1-.149-.024 5 5 0 0 1-.115-.025 5 5 0 0 1-3.57-3.04 5.072 5.072 0 0 1-.206-.661 5 5 0 0 1-.033-.147c-.025-.118-.036-.24-.054-.36-.987.993-1.964 1.993-2.954 3.05l-.98-3.053 5.092-3.957c.043-.044.082-.07.125-.11a5 5 0 0 1 .71-.634c.18-.13.367-.25.561-.356a5 5 0 0 1 .16-.08 4.94 4.94 0 0 1 .516-.222 5 5 0 0 1 .147-.057c.211-.07.43-.123.654-.164a5 5 0 0 1 .172-.027c.236-.035.476-.058.722-.059zM12 9a3 3 0 0 0-.053.002 3 3 0 0 0-.166.01 3 3 0 0 0-.133.011 3 3 0 0 0-.17.026 3 3 0 0 0-.113.021 3 3 0 0 0-.19.05 3 3 0 0 0-.103.03 3 3 0 0 0-.16.057 3 3 0 0 0-.129.053 3 3 0 0 0-.146.072 3 3 0 0 0-.12.063 3 3 0 0 0-.132.082 3 3 0 0 0-.123.08 3 3 0 0 0-.116.088 3 3 0 0 0-.126.105 3 3 0 0 0-.1.094 3 3 0 0 0-.111.111 3 3 0 0 0-.096.107 3 3 0 0 0-.094.116 3 3 0 0 0-.098.136 3 3 0 0 0-.072.11 3 3 0 0 0-.076.133 3 3 0 0 0-.07.132 3 3 0 0 0-.063.14 3 3 0 0 0-.054.14 3 3 0 0 0-.077.228 3 3 0 0 0-.007.026 3 3 0 0 0-.03.138 3 3 0 0 0-.031.149 3 3 0 0 0-.014.11 3 3 0 0 0-.02.183 3 3 0 0 0-.001.052A3 3 0 0 0 9 12a3 3 0 0 0 .002.053 3 3 0 0 0 .01.166 3 3 0 0 0 .011.133 3 3 0 0 0 .026.17 3 3 0 0 0 .021.113 3 3 0 0 0 .05.19 3 3 0 0 0 .03.103 3 3 0 0 0 .057.16 3 3 0 0 0 .053.129 3 3 0 0 0 .072.146 3 3 0 0 0 .063.12 3 3 0 0 0 .082.132 3 3 0 0 0 .08.123 3 3 0 0 0 .088.116 3 3 0 0 0 .105.126 3 3 0 0 0 .094.1 3 3 0 0 0 .111.111 3 3 0 0 0 .107.096 3 3 0 0 0 .116.094 3 3 0 0 0 .136.098 3 3 0 0 0 .11.072 3 3 0 0 0 .133.076 3 3 0 0 0 .132.07 3 3 0 0 0 .135.06 3 3 0 0 0 .153.061 3 3 0 0 0 .216.07 3 3 0 0 0 .004.003 3 3 0 0 0 .026.007 3 3 0 0 0 .138.03 3 3 0 0 0 .149.031 3 3 0 0 0 .11.014 3 3 0 0 0 .183.02 3 3 0 0 0 .011.001 3 3 0 0 0 .041 0A3 3 0 0 0 12 15a3 3 0 0 0 .053-.002 3 3 0 0 0 .166-.01 3 3 0 0 0 .133-.011 3 3 0 0 0 .17-.026 3 3 0 0 0 .113-.021 3 3 0 0 0 .19-.05 3 3 0 0 0 .103-.03 3 3 0 0 0 .16-.057 3 3 0 0 0 .129-.053 3 3 0 0 0 .146-.072 3 3 0 0 0 .12-.063 3 3 0 0 0 .132-.082 3 3 0 0 0 .123-.08 3 3 0 0 0 .116-.088 3 3 0 0 0 .126-.105 3 3 0 0 0 .1-.094 3 3 0 0 0 .111-.111 3 3 0 0 0 .096-.107 3 3 0 0 0 .094-.116 3 3 0 0 0 .098-.136 3 3 0 0 0 .072-.11 3 3 0 0 0 .076-.133 3 3 0 0 0 .07-.132 3 3 0 0 0 .06-.135 3 3 0 0 0 .061-.153 3 3 0 0 0 .07-.216 3 3 0 0 0 .003-.004 3 3 0 0 0 .007-.026 3 3 0 0 0 .03-.138 3 3 0 0 0 .031-.149 3 3 0 0 0 .002-.008 3 3 0 0 0 .012-.101 3 3 0 0 0 .02-.184 3 3 0 0 0 .001-.011 3 3 0 0 0 0-.041A3 3 0 0 0 15 12a3 3 0 0 0-.002-.053 3 3 0 0 0-.01-.166 3 3 0 0 0-.011-.133 3 3 0 0 0-.026-.17 3 3 0 0 0-.021-.113 3 3 0 0 0-.05-.19 3 3 0 0 0-.03-.103 3 3 0 0 0-.057-.16 3 3 0 0 0-.053-.129 3 3 0 0 0-.072-.146 3 3 0 0 0-.063-.12 3 3 0 0 0-.082-.132 3 3 0 0 0-.08-.123 3 3 0 0 0-.088-.116 3 3 0 0 0-.105-.126 3 3 0 0 0-.094-.1 3 3 0 0 0-.111-.111 3 3 0 0 0-.107-.096 3 3 0 0 0-.116-.094 3 3 0 0 0-.136-.098 3 3 0 0 0-.11-.072 3 3 0 0 0-.133-.076 3 3 0 0 0-.132-.07 3 3 0 0 0-.14-.063 3 3 0 0 0-.14-.054 3 3 0 0 0-.228-.077 3 3 0 0 0-.026-.007 3 3 0 0 0-.138-.03 3 3 0 0 0-.149-.031 3 3 0 0 0-.008-.002 3 3 0 0 0-.101-.012 3 3 0 0 0-.184-.02 3 3 0 0 0-.011-.001 3 3 0 0 0-.041 0A3 3 0 0 0 12 9z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 720 720" id="arduino" xmlns="http://www.w3.org/2000/svg"><defs><symbol id="ana" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-opacity="100%" stroke-width="60" stroke="#00979c" d="M174 30a10.5 10.1 0 0 0 0 280C364 320 344 30 544 30a10.5 10.1 0 0 1 0 280C354 320 374 30 174 30"/><path d="M528 205v-32.8h-32.5v-13.7H528V126h13.9v32.5h32.5v13.7h-32.5V205H528z" text-anchor="middle" fill="#00979c" stroke-width="20" stroke="#00979c" font-family="sans-serif" font-size="167"/><path fill="#00979c" stroke="#00979c" stroke-width="23.6" transform="matrix(1.56 0 0 .64 -366 .528)" d="M321 266v-17.4h53.3V266H321z"/></symbol></defs><title>Layer 1</title><use x="20.063" y="360.85" transform="matrix(.997 0 0 .997 -18.596 -159.19)" xlink:href="#ana"/></symbol><symbol viewBox="0 0 24 24" id="assembly" xmlns="http://www.w3.org/2000/svg"><path d="M1.746 1.566v20.905H5.13v-2.088H3.438V3.656h1.69v-2.09H1.747zm17.219 0v2.09h1.693v16.727h-1.693v2.09h3.383V1.566h-3.383zM15.196 3.988c-.5 0-.93.076-1.29.225-.359.15-.652.372-.877.671-.226.302-.39.673-.494 1.108a6.715 6.715 0 0 0-.155 1.54c0 .573.049 1.083.15 1.528.1.442.264.811.49 1.11.222.298.512.524.872.676.36.153.795.23 1.304.23.518 0 .954-.075 1.308-.224.353-.153.643-.376.869-.671.219-.29.38-.661.484-1.112.104-.454.156-.967.156-1.54 0-.573-.052-1.079-.152-1.515a2.92 2.92 0 0 0-.485-1.106 2.09 2.09 0 0 0-.868-.686c-.354-.155-.79-.234-1.312-.234zm-6.814.12a.941.941 0 0 1-.138.458.849.849 0 0 1-.356.296A1.71 1.71 0 0 1 7.385 5a5.244 5.244 0 0 1-.631.037v1.11H8.19v3.6H6.754v1.188h4.545V9.745H9.894V4.11H8.382zm6.814 1.138c.375 0 .643.176.805.527.161.348.241.933.241 1.756 0 .814-.082 1.399-.247 1.756-.164.356-.43.534-.799.534-.369 0-.636-.178-.8-.534-.165-.357-.248-.941-.248-1.749 0-.829.082-1.415.243-1.763.162-.35.43-.527.805-.527zm-6.33 7.64c-.5 0-.93.073-1.29.223-.359.15-.651.374-.877.673-.225.302-.39.67-.494 1.106a6.715 6.715 0 0 0-.155 1.54c0 .573.05 1.082.15 1.527.1.442.264.814.49 1.112.222.3.514.525.874.677.36.152.793.229 1.302.229.519 0 .954-.076 1.308-.225.354-.153.643-.376.869-.672.22-.29.38-.66.484-1.111.104-.455.156-.967.156-1.54 0-.573-.05-1.079-.15-1.515a2.923 2.923 0 0 0-.487-1.106 2.084 2.084 0 0 0-.867-.686c-.353-.156-.791-.232-1.313-.232zm5.846.119a.941.941 0 0 1-.138.457.85.85 0 0 1-.356.296 1.71 1.71 0 0 1-.503.137 5.245 5.245 0 0 1-.631.037v1.112h1.435v3.597h-1.435v1.189h4.545v-1.189h-1.405v-5.636h-1.512zm-5.846 1.137c.375 0 .643.176.805.527.162.347.241.933.241 1.756 0 .813-.08 1.399-.245 1.755-.164.357-.432.534-.8.534-.37 0-.637-.177-.802-.534-.164-.356-.245-.939-.245-1.746 0-.83.08-1.418.242-1.765.161-.35.43-.527.804-.527z" fill="#ff6e40"/></symbol><symbol viewBox="0 0 24 24" id="aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="api" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apa"/><linearGradient id="apa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#apb"/><linearGradient id="apb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#apc"/><linearGradient id="apc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#apd"/><linearGradient id="apd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#ape"/><linearGradient id="ape" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#apf"/><linearGradient id="apf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apg"/><linearGradient id="apg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="app" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#aph"/><linearGradient id="aph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><g transform="rotate(11.282 -1.694 21.569) scale(.47102)" clip-rule="evenodd" fill="none" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#api)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#apj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#apk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#apl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#apm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#apn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#apo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#app)"/></g></symbol><symbol viewBox="0 0 24 24" id="autohotkey" xmlns="http://www.w3.org/2000/svg"><path d="M5 3c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5zm3.668 3.447a.9.9 0 0 1 .652.256.84.84 0 0 1 .262.625c0 .34-.014.852-.041 1.537-.022.68-.033 1.19-.033 1.53 0 .111-.016.326-.047.644a6.149 6.149 0 0 0-.033.68l2.578-.485c1.007-.179 1.874-.281 2.603-.308.018-.3.048-1.105.088-2.416.01-.345.115-.742.317-1.19.25-.55.533-.826.851-.826.237 0 .448.08.631.236.197.17.295.382.295.637a.775.775 0 0 1-.025.201c-.09.327-.135.612-.135.854 0 .125-.014.32-.041.584-.023.26-.033.453-.033.578 0 .425-.022 1.056-.067 1.893a38.963 38.963 0 0 0-.068 1.892c0 .327.025.816.074 1.465.05.649.074 1.136.074 1.463a.84.84 0 0 1-.261.625.893.893 0 0 1-.65.254 1 1 0 0 1-.686-.254.777.777 0 0 1-.29-.611c0-.327-.015-.818-.046-1.471a39.552 39.552 0 0 1-.041-1.47c0-.256.004-.482.013-.679-.702.032-1.57.142-2.603.33-.86.157-1.719.316-2.578.477-.01.304-.042.812-.096 1.523a22.354 22.354 0 0 0-.066 1.538.84.84 0 0 1-.262.625.893.893 0 0 1-.65.253.898.898 0 0 1-.653-.253.84.84 0 0 1-.262-.625c0-.452.038-1.128.114-2.028.08-.9.12-1.575.12-2.027 0-.573.015-1.436.042-2.586.027-1.155.04-2.017.04-2.59a.84.84 0 0 1 .263-.625.895.895 0 0 1 .65-.256z" fill="#4caf50"/></symbol><symbol viewBox="0 0 24 24" id="autoit" xmlns="http://www.w3.org/2000/svg"><defs id="ardefs8"><style id="arstyle4482">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style><style id="arstyle4510">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style></defs><g id="arg4522" transform="translate(-59.538 -26.404) scale(.0555)"><path d="M12.8 2.133A10.666 10.666 0 0 0 2.136 12.799 10.666 10.666 0 0 0 12.8 23.465a10.666 10.666 0 0 0 10.668-10.666A10.666 10.666 0 0 0 12.8 2.133zm.15 4.713c.456 0 .836.105 1.142.314.306.21.565.469.78.78l6.089 8.812H9.627l1.82-2.506h3.36c.315 0 .589.01.822.03a11.93 11.93 0 0 1-.473-.663 39.13 39.13 0 0 0-.517-.75l-1.748-2.578-4.577 6.467H4.746l6.25-8.813c.204-.281.46-.534.772-.757.31-.224.705-.336 1.181-.336z" transform="matrix(16.89188 0 0 16.89188 1072.761 475.745)" id="arcircle4514" fill="#1976d2" stroke-width=".026"/></g></symbol><symbol viewBox="0 0 213.33333 213.33333" id="babel" xmlns="http://www.w3.org/2000/svg"><path d="M50.22 199.659c-.875-.406-1.261-1.6-.857-2.652.404-1.053.12-1.914-.63-1.914s-1.615.748-1.92 1.663c-.328.983-1.27.302-2.304-1.667-.962-1.831-3.718-5.533-6.126-8.226-9.418-10.535-7.71-27.444 5.432-53.77 12.459-24.96 23.117-39.033 45.966-60.696 30.229-28.66 52.679-46.223 70.587-55.22 10.98-5.518 13.025-5.059 2.778.624-11.004 6.102-11.378 6.359-10.512 7.226.33.33 7.306-2.67 15.504-6.667 15.87-7.737 16.34-7.912 16.34-6.082 0 .652-4.95 3.738-11 6.858-13.062 6.736-12.722 6.48-10.472 7.872 1.117.69 5.428-.582 11.54-3.406 5.367-2.48 10.397-4.508 11.179-4.508 2.755 0-3.928 5.302-11.541 9.157-20.437 10.35-68.937 46.043-68.07 50.097.166.777-5.792 7.639-13.241 15.248-15.257 15.587-26.14 30.002-33.748 44.706-6.379 12.326-7.457 17.734-5.385 26.996 3.482 15.56 11.592 18.366 31.482 10.895 28.228-10.603 45.758-28.704 47.022-48.556.602-9.442-1.317-13.479-8.52-17.93-4.01-2.48-5.268-2.621-12.065-1.365-4.173.771-10.153 2.906-13.289 4.744s-6.455 3.34-7.377 3.34c-.922 0-3.216 1.336-5.096 2.968-1.88 1.633.48-1.13 5.247-6.14 6.82-7.167 7.956-8.9 5.333-8.132-5.208 1.525-10.194 4.33-15.649 8.803-2.76 2.264-.923.175 4.08-4.641 11.565-11.131 21.183-15.97 33.088-16.641 17.097-.966 27.254 5.805 31.964 21.31 2.435 8.017 2.609 10.24 1.353 17.37-1.65 9.361-7.034 21.553-15.593 35.307-4.398 7.067-8.434 11.427-15.588 16.844-9.166 6.94-15.654 11.02-15.654 9.845 0-.295 2.455-2.161 5.455-4.147 8.818-5.835 5.075-5.377-8.326 1.02-6.854 3.27-15.199 6.593-18.542 7.38-7.106 1.675-30.527 3.164-32.846 2.089zm-8.408-19.899c0-1.1-.6-2-1.333-2-.734 0-1.334.9-1.334 2s.6 2 1.333 2c.734 0 1.334-.9 1.334-2zm89.255-8.204c1.53-1.945 2.473-3.845 2.097-4.222-.377-.377-.836-.435-1.02-.13-.182.306-1.787 2.206-3.565 4.223-1.778 2.016-2.571 3.666-1.763 3.666s2.72-1.591 4.25-3.536zm-77.644-1.745c-.82-2.172-1.74-3.7-2.045-3.396-.951.952 1.088 7.345 2.343 7.345.656 0 .522-1.777-.298-3.95zm82.303-27.915c-.837-.837-3.217 2.55-3.184 4.53.012.734.896.178 1.965-1.235 1.07-1.413 1.618-2.896 1.219-3.295zm-66.238-36.904c-1.312-1.312-3.676.702-3.676 3.133 0 2.035.175 2.031 2.254-.047 1.24-1.24 1.88-2.628 1.422-3.086zm39.657.768c4.403-2.196 6.8-3.986 5.333-3.982-2.838.01-16.667 6.028-16.667 7.254 0 1.6 3.717.527 11.333-3.272zm16.667-5.333c0-.733-.9-1.333-2-1.333s-2 .6-2 1.333.9 1.333 2 1.333 2-.6 2-1.333zm-3.334-3.923l5.334-1.104-7.334-.133c-4.033-.073-8.233.45-9.333 1.16-2.539 1.64 3.572 1.682 11.333.077zm35.738-63.976c2.788-1.69 4.765-3.376 4.393-3.748-.947-.947-11.942 5.654-14.237 8.548-1.792 2.258-1.714 2.276 1.44.329a1452.76 1452.76 0 0 1 8.403-5.13z" fill="#ffca28" stroke-width="1.333"/></symbol><symbol viewBox="0 0 400 400" fill-opacity=".05" id="bithound" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.88 0 0 .88 24.121 2.895)" fill="#e53935" fill-opacity="1"><path d="M370.5 207c-1.5-14.8-4.8-29.9-9.5-44-13.5-40.3-38.6-81.6-70.3-110.1-1.4-1.2-6.7-4.4-8.7-3.3-5.2 2.9 4.6 22.8 5.8 26.4 7.4 22 12.1 45.3 6.8 68.3-7.1 30.4-30.4 51.7-61.5 54.3-17.1 1.4-34.3-.5-51.4 1.5-25.6 3-51.7 11.8-68 32.8-1.9 2.4-3.6 5.1-5.2 7.9h-.4c-6.3.7-12.6-2-15.7-3.7-.8-.5-1.6-.9-2.2-1.2-19-10.5-33-34-41.6-53.4-3.9-9-7.2-18.4-9.3-27.9-1-4.3-1.1-8.8-1.3-13.2-.1-2.7.3-6.5-1.2-8.9-3.3-5.2-7.5-.2-8.2 4-1.1 6.9-2.1 13.7-1.8 20.7.5 11.8 3.8 23.5 8 34.5 6.2 16.2 14.9 31.1 26.2 44.4 4.7 5.5 9.7 10.6 15.1 15.3 4.8 4.3 10.9 7.7 14.5 13.2 4.2 6.3 4.9 14.1 4.5 21.4-1 19.3-1.6 37.4 3.9 56.2 4.8 16.7 10.8 33.8 20.8 48.1 5 7.1 11.2 14.6 18 19.9 4.6 3.6 13.3 4 8.3-9.2-11.1-29.3-12.1-59.7 5.2-87.1 14.5-22.8 40.1-43.1 69-39.5 42.5 5.3 72.1 44.3 70 86-.6 11.7-1 21.7-4.7 32.7-1.5 4.4-2.6 10-1.5 14.6 1.8 7.8 10.5 4.9 14.3-.2 10.3-14 21.1-27.6 30.8-42 31.6-47.2 47-101.8 41.3-158.5z"/><path d="M132.4 92.1c.7 2.3 1.4 4.8 1.9 7.5.1 1.1.4 2.3 1 3.4 2.6 6.8 8.9 10.5 14.8 14 3.6 2.2 10.1 4.3 14.1 5.9 5.2 2.1 16.4-.6 21.7-1 12.2-1 23.5-5.3 34.7 1.2-57.4 67.3-3.2 82.3 38.8 49.9 48-37 2.8-124.3 2.8-124.3s-1-6.8-19.2-10.8c-1.7-.9-3.4-1.7-5.1-2.4-18-8.3-34.2 5.3-47.2 16.4-3.8 3.2-7.5 6.4-11.5 9.4-5.4 4-11.2 7.3-17.3 10.2-6.4 3-14 6.4-21.1 6.7-1 0-2.9.2-4.9.6-3.1.3-4.7 1.1-5.4 2.5-1.2 1-2 2.4-1.8 4.2.2 2.5 1.4 4.6 2.7 6.2.4.1.7.3 1 .4z"/></g></symbol><symbol viewBox="0 0 400.00001 399.99999" id="bower" xmlns="http://www.w3.org/2000/svg"><g transform="translate(12.061 33.203) scale(.81733)"><path d="M447.61 200.08c-23.139-22.234-138.85-36.114-175.36-40.154a107.137 107.137 0 0 0 4.517-12.944 146.107 146.107 0 0 1 15.905-5.901c.677 1.997 3.865 9.648 5.682 13.279 73.415 2.025 77.184-54.557 80.17-70.058 2.92-15.157 2.771-29.802 27.953-56.575-37.516-10.933-91.467 16.945-109.54 58.437-6.79-2.545-13.597-4.424-20.328-5.586-4.824-19.46-29.944-73.672-95.863-73.672-83.46 0-174.43 68.853-174.43 185.41 0 97.976 66.891 183.84 104.68 183.84 16.505 0 30.703-12.36 34.036-23.44 2.795 7.597 11.368 31.213 14.184 37.225 4.162 8.89 23.41 16.583 31.833 7.357 10.83 6.017 30.703 9.641 41.534-6.405 20.86 4.412 39.3-8.026 39.702-22.868 10.235-.546 15.256-14.918 13.021-26.363-1.647-8.426-19.248-38.66-26.113-49.098 13.59 11.054 48.013 14.183 52.194.007 21.911 17.198 56.057 8.171 58.765-5.815 26.624 6.917 57.16-8.276 52.146-26.676 42.771-2.958 37.296-48.464 25.296-59.996z" fill="#543729" stroke-width=".973"/><path d="M328.514 103.025c9.212-18.277 20.788-38.234 35.409-50.58-16.093 6.485-31.981 25.873-41.375 46.595a144.914 144.914 0 0 0-14.552-8.132c13.105-27.972 43.555-51.332 77.112-53.157-22.477 20.385-14.498 62.754-32.979 85.183-5.288-5.311-17.43-15.562-23.615-19.909zm-14.53 29.762c.01-.7.272-6.094.763-8.557-1.288-.304-9.3-1.87-13.476-1.772-.304 5.245 2.204 14.17 4.684 19.541 17.075-.358 29.408-5.471 36.667-10.172-6.18-2.88-16.726-5.442-24.745-6.974-.894 1.851-3.097 6.568-3.892 7.934z" fill="#00acee"/><g stroke-width=".973"><path d="M250.54 277.39c.004.024.015.057.018.082-2.165-4.657-4.463-10.314-7.208-17.708 10.688 15.557 44.184 7.533 42.427-6.407 16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455 28 5.4 54.832 10.783 63.256 12.938-5.595 9.123-18.339 15.566-37.549 11.089 10.38 14.14-9.773 31.105-37.844 21.76 6.18 13.883-18.814 26.38-47.22 11.91.361 13.889-35.24 15.488-49.315.143zm55.543-70.194c32.497 2.495 86.238 7.34 119.51 11.997-2.102-10.828-7.844-13.921-25.905-18.772-19.425 2.072-68.706 6.913-93.604 6.776z" fill="#2baf2b"/><path d="M285.78 253.36c16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455-33.103-6.383-67.84-12.788-75.719-13.908 4.78.254 12.702.797 22.59 1.556 24.899.137 74.18-4.704 93.604-6.775-31.452-7.975-95.666-19.613-140.01-22.48-2.055 3.003-5.833 8.097-12.413 13.51-19.403 41.053-54.557 68.34-93.454 68.34-11.335 0-24.018-1.912-38.233-6.456-8.865 9.497-46.661 16.694-77.329 1.641 24.326 56.961 80.74 94.984 143.19 94.984 52.591 0 75.912-53.704 70.808-67.914-1.238-3.45-6.145-14.889-8.891-22.283 10.689 15.556 44.185 7.532 42.429-6.408z" fill="#ffcc2f"/><path d="M253.91 145.27c4.644-2.526 20.69-12.253 35.981-15.908a67.843 67.843 0 0 1-.536-5.12c-10.032 2.403-28.945 10.51-39.784-.661 22.866 6.9 34.283-6.149 51.09-6.149 10.014 0 24.305 2.798 35.57 7.22-9.061-8.37-38.772-33.63-75.558-33.717-8.213 9.957-17.09 31.526-6.764 54.334z" fill="#cecece"/><path d="M115.58 253.33c14.215 4.544 26.898 6.457 38.233 6.457 38.896 0 74.05-27.29 93.454-68.341-14.351 11.978-39.291 22.228-78.241 22.228 34.694-7.866 64.56-25.156 79.753-50.427-10.68-16.998-22.263-54.603 7.07-84.33-4.512-14.497-26.475-52.766-75.095-52.766-84.85 0-155.17 71.001-155.17 166.15 0 22.525 4.547 43.65 12.67 62.664 30.666 15.054 68.462 7.858 77.327-1.64z" fill="#ef5734"/><path d="M141.03 108.45c0 21.644 17.546 39.191 39.19 39.191s39.192-17.548 39.192-39.191c0-21.644-17.548-39.191-39.192-39.191-21.644 0-39.19 17.547-39.19 39.191z" fill="#ffcc2f"/><path d="M156.76 108.45c0 12.958 10.507 23.463 23.463 23.463 12.96 0 23.464-10.506 23.464-23.463 0-12.959-10.504-23.464-23.464-23.464-12.957 0-23.463 10.506-23.463 23.464z" fill="#543729"/><ellipse cx="180.22" cy="98.044" rx="13.673" ry="8.501" fill="#fff"/></g></g></symbol><symbol viewBox="0 0 140 140" id="browserlist" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><path d="M70.314 10.066a59.828 59.828 0 0 0-59.828 59.828 59.828 59.828 0 0 0 59.828 59.828 59.828 59.828 0 0 0 59.828-59.828 59.828 59.828 0 0 0-59.828-59.828zm-4.836 8.785c.496 4.043 1.352 7.322 2.572 10.223 4.779-4.287 10.265-7.546 16.041-9.02-.981 3.938-1.357 7.295-1.261 10.43 6.026-2.314 12.349-3.404 18.3-2.706-3.182 2.413-5.482 4.717-7.128 7.015-2.201 12.074 6.858 20.43 14.779 24.551a5.128 5.128 0 0 1 5.183-3.888 5.128 5.128 0 0 1 3.7 8.435v.002c-.487 1.055-2.002 2.343-3.497 3.219-4.075 2.39-11.172 5.736-20.914 7.39.045 1.214.077 2.453.077 3.747 0 4.817-.485 8.291-1.385 10.699-3.3 13.313-12.648 26.76-24.695 31.95.357-4.083.197-7.485-.402-10.591-5.582 3.218-11.646 5.278-17.623 5.52h-.002c1.785-3.662 2.855-6.878 3.412-9.976-6.347.996-12.727.742-18.377-1.17 2.93-2.732 5.054-5.314 6.673-7.96-6.292-1.344-12.169-3.87-16.766-7.686 3.822-1.544 6.795-3.239 9.3-5.197-5.426-3.517-10.034-7.998-12.972-13.23 4.012-.07 7.321-.568 10.3-1.453-3.786-5.215-6.468-11.032-7.333-16.951 3.861 1.405 7.196 2.133 10.36 2.355-1.662-6.22-2.081-12.605-.768-18.436 3.03 2.634 5.824 4.48 8.63 5.815.678-6.406 2.576-12.52 5.893-17.496 1.926 3.622 3.914 6.391 6.111 8.672 2.93-5.754 6.9-10.798 11.791-14.262zm26.465 19.557c-2.395 5.514-1.665 11.297-.555 18.732a2.138 2.138 0 0 0 .28-4.178 3.419 3.419 0 1 1 .092 6.704c.574 3.882 1.157 8.18 1.421 13.125a67.143 67.143 0 0 0 3.25-.649c6.616-1.487 12.258-3.801 16.871-6.506.45-.264.884-.563 1.276-.867.366-.557.333-.957.035-1.285-4.831-1.245-10.891-4.53-15.258-8.795-4.764-4.653-7.428-10.164-7.412-16.281z" fill="#ffca28" stroke-width=".855"/></symbol><symbol viewBox="0 0 140 140" id="browserlist_light" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><g transform="translate(10.823 10.1)" stroke-width=".855"><circle cx="59.492" cy="59.795" r="59.828" fill="#ffca28"/><path d="M54.656 8.752c-4.89 3.464-8.862 8.508-11.791 14.262-2.198-2.28-4.185-5.05-6.111-8.672-3.318 4.976-5.216 11.09-5.893 17.496-2.807-1.335-5.6-3.18-8.63-5.814-1.314 5.83-.895 12.216.767 18.436-3.164-.223-6.498-.95-10.36-2.356.865 5.92 3.548 11.737 7.333 16.951-2.978.885-6.287 1.383-10.3 1.453 2.939 5.233 7.547 9.714 12.972 13.23-2.505 1.959-5.478 3.654-9.299 5.198 4.596 3.815 10.474 6.341 16.766 7.685-1.62 2.647-3.743 5.228-6.674 7.96 5.65 1.912 12.03 2.166 18.377 1.17-.556 3.098-1.626 6.314-3.412 9.975h.002c5.977-.24 12.042-2.3 17.623-5.52.6 3.108.76 6.51.402 10.593 12.047-5.19 21.395-18.638 24.695-31.951.9-2.408 1.385-5.881 1.385-10.7 0-1.293-.031-2.531-.076-3.745 9.742-1.655 16.839-5.001 20.914-7.39 1.494-.877 3.01-2.165 3.496-3.22v-.002a5.128 5.128 0 0 0-3.7-8.435 5.128 5.128 0 0 0-5.183 3.889c-7.92-4.122-16.98-12.477-14.779-24.551 1.646-2.299 3.947-4.603 7.13-7.016-5.952-.698-12.276.392-18.302 2.707-.095-3.135.28-6.492 1.262-10.43-5.776 1.473-11.262 4.733-16.041 9.02-1.22-2.902-2.076-6.18-2.572-10.223zm26.465 19.557c-.015 6.117 2.648 11.628 7.412 16.281 4.366 4.265 10.426 7.55 15.258 8.795.298.328.331.728-.035 1.285-.392.304-.825.603-1.275.867-4.613 2.704-10.256 5.019-16.871 6.506-1.071.24-2.154.458-3.25.649-.265-4.945-.848-9.243-1.422-13.125a3.419 3.419 0 1 0-.092-6.703 2.138 2.138 0 0 1-.28 4.177c-1.11-7.435-1.84-13.218.555-18.732z" fill="#37474f"/></g></symbol><symbol viewBox="0 0 24 24" id="bucklescript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm14.1 8.858a5.5 5.5 0 0 1 1.26.145c.417.093.778.213 1.082.357v1.723h-.18a3.281 3.281 0 0 0-.959-.603 2.867 2.867 0 0 0-1.155-.247c-.14 0-.277.011-.416.035a1.4 1.4 0 0 0-.395.12.756.756 0 0 0-.291.231.54.54 0 0 0-.123.348c0 .198.065.35.196.456.13.104.376.2.738.288.237.057.466.11.683.164.22.054.455.128.706.222.496.188.86.444 1.095.77.238.32.357.738.357 1.253 0 .737-.271 1.336-.813 1.798-.538.46-1.27.689-2.197.689a5.447 5.447 0 0 1-1.402-.161 6.725 6.725 0 0 1-1.117-.416v-1.794h.183c.344.318.73.563 1.155.734.429.17.839.256 1.233.256.1 0 .235-.01.4-.03.166-.02.3-.055.403-.102a.97.97 0 0 0 .313-.225c.084-.09.127-.223.127-.4a.568.568 0 0 0-.183-.424c-.119-.12-.294-.213-.526-.276-.243-.067-.5-.128-.773-.185a5.523 5.523 0 0 1-.76-.227c-.544-.204-.936-.48-1.177-.828-.237-.351-.357-.786-.357-1.305 0-.697.27-1.265.81-1.703.54-.442 1.235-.663 2.083-.663zm-8.981.135h2.51c.521 0 .903.02 1.143.06.243.041.484.13.721.266.246.144.43.338.548.583.121.24.181.518.181.83 0 .36-.082.68-.247.959a1.697 1.697 0 0 1-.7.642v.04c.423.098.758.298 1.004.603.249.305.373.706.373 1.205 0 .361-.063.686-.19.97-.125.285-.296.52-.516.707a2.31 2.31 0 0 1-.845.472c-.304.094-.69.141-1.159.141H8.12v-7.478zm1.659 1.372v1.582h.262c.263 0 .486-.007.672-.017.185-.01.332-.043.44-.1.15-.077.248-.175.294-.295.046-.124.07-.266.07-.427a.91.91 0 0 0-.083-.371.518.518 0 0 0-.282-.277 1.187 1.187 0 0 0-.456-.086c-.18-.007-.433-.01-.76-.01h-.157zm0 2.873V18.1H9.9c.469 0 .804-.002 1.007-.006.202-.003.39-.046.56-.13a.712.712 0 0 0 .357-.33c.067-.142.099-.302.099-.483 0-.237-.04-.42-.121-.547-.078-.13-.214-.228-.405-.291a1.842 1.842 0 0 0-.538-.072 49.47 49.47 0 0 0-.716-.003h-.366z" fill="#26a69a" stroke-width="1.067"/></symbol><symbol viewBox="0 0 24 24" id="c" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 15.97l.42 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96-1.14-1.27-1.68-2.88-1.68-4.83C6 9.9 6.68 8.13 8 6.89 9.28 5.64 10.92 5 12.9 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.6 2.49-1.04-.34c-.4-.1-.87-.15-1.4-.15-1.15-.01-2.11.36-2.86 1.1-.76.73-1.14 1.85-1.18 3.34.01 1.36.37 2.42 1.08 3.2.71.77 1.7 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.09-.32z" fill="#0277bd"/></symbol><symbol viewBox="0 0 300 300" id="cabal" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -822.52)" fill-rule="evenodd" color="#000"><rect transform="matrix(-.98339 .18149 .60192 .79856 0 0)" x="405.55" y="967.22" width="107.25" height="156.59" rx="12.306" ry="12.31" fill="#2d9bbd"/><rect transform="matrix(-.98528 .17093 -.59175 .80612 0 0)" x="-1156.5" y="1461.9" width="108.34" height="123.15" rx="10.69" ry="12.31" fill="#4a4bcd"/><path d="M52.112 965.158c-1.343 3.515-26.292 23.248-25.744 27.277.548 4.03 29.812 16.023 32.04 19.027s10.545 41.668 13.603 42.5 18.828-31.274 21.548-32.932c2.72-1.658 32.808 2.503 34.15-1.01 1.343-3.515-18.174-35.352-18.721-39.381-.548-4.03 9.732-40.12 7.502-43.125-2.229-3.005-30.06 9.427-33.118 8.594-3.059-.833-26.793-27.3-29.514-25.643-2.72 1.657-.405 41.177-1.747 44.693z" fill="#2e5bc1"/></g></symbol><symbol viewBox="0 0 24 24" id="cake" xmlns="http://www.w3.org/2000/svg"><path d="M12.254 6.621a1.807 1.807 0 0 0 1.808-1.807c0-.344-.09-.66-.262-.932l-1.546-2.684-1.546 2.684a1.72 1.72 0 0 0-.262.932 1.808 1.808 0 0 0 1.808 1.807m4.158 9.04l-.967-.976-.976.976c-1.175 1.166-3.236 1.175-4.42 0l-.959-.976-.994.976a3.134 3.134 0 0 1-3.977.353v4.167a.904.904 0 0 0 .904.904h14.463a.904.904 0 0 0 .904-.904v-4.167a3.134 3.134 0 0 1-3.977-.353m1.265-6.328h-4.52V7.525H11.35v1.808H6.83a2.712 2.712 0 0 0-2.711 2.712v1.392c0 .977.795 1.772 1.771 1.772.489 0 .94-.18 1.248-.515l1.952-1.926 1.908 1.926c.669.669 1.835.669 2.504 0l1.916-1.926 1.944 1.926c.316.334.768.515 1.247.515.976 0 1.78-.795 1.78-1.772v-1.392a2.712 2.712 0 0 0-2.711-2.712z" fill="#ff7043" stroke-width=".904"/></symbol><symbol viewBox="0 0 24 24" id="certificate" xmlns="http://www.w3.org/2000/svg"><path d="M4 3c-1.11 0-2 .89-2 2v10a2 2 0 0 0 2 2h8v5l3-3 3 3v-5h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H4m8 2l3 2 3-2v3.5l3 1.5-3 1.5V15l-3-2-3 2v-3.5L9 10l3-1.5V5M4 5h5v2H4V5m0 4h3v2H4V9m0 4h5v2H4v-2z" fill="#ff5722"/></symbol><symbol viewBox="0 0 24 24" id="changelog" xmlns="http://www.w3.org/2000/svg"><path d="M11 7v5.11l4.71 2.79.79-1.28-4-2.37V7m0-5C8.97 2 5.91 3.92 4.27 6.77L2 4.5V11h6.5L5.75 8.25C6.96 5.73 9.5 4 12.5 4a7.5 7.5 0 0 1 7.5 7.5 7.5 7.5 0 0 1-7.5 7.5c-3.27 0-6.03-2.09-7.06-5h-2.1c1.1 4.03 4.77 7 9.16 7 5.24 0 9.5-4.25 9.5-9.5A9.5 9.5 0 0 0 12.5 2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="clojure" xmlns="http://www.w3.org/2000/svg"><path d="M3.355 1.78c-.845 0-1.525.68-1.525 1.525v17.441c0 .845.68 1.525 1.525 1.525h17.442c.845 0 1.525-.68 1.525-1.525V3.305c0-.845-.68-1.526-1.525-1.526H3.355zm6.168 2.572h1.963l6.368 14.931H15.93l-3.38-8.086-3.349 8.086H7.21l4.346-10.38-2.032-4.551z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="cmake" xmlns="http://www.w3.org/2000/svg"><path d="M11.99 2.965L2.977 20.999l9.874-8.47-.863-9.564z" fill="#1e88e5"/><path d="M12.007 2.963l.002.29 1.312 14.498-.001.006.023.26 7.362 2.979h.416l-.158-.311-.114-.228h-.002l-8.84-17.494z" fill="#e53935"/><path d="M8.607 16.11L2.98 20.995h17.743v-.016L8.607 16.11z" fill="#7cb342"/></symbol><symbol class="bfmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate" xmlns="http://www.w3.org/2000/svg"><path class="bfsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#eee" stroke-width="2.849"/></symbol><symbol class="bgmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate_light" xmlns="http://www.w3.org/2000/svg"><path class="bgsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#455a64" stroke-width="2.849"/></symbol><symbol viewBox="0 0 24 24" id="coffee" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="coldfusion" xmlns="http://www.w3.org/2000/svg"><rect transform="rotate(90)" x="2.283" y="-21.86" width="19.487" height="19.487" ry="0" fill="#0d3858" stroke="#4dd0e1" stroke-width=".7"/><text x="6.653" y="16.426" fill="#4dd0e1" font-family="Calibri" font-size="29.001" font-weight="bold" letter-spacing="0" stroke-width=".725" word-spacing="0" style="line-height:1.25"><tspan x="6.653" y="16.426" font-family="'Segoe UI'" font-size="10.634" font-weight="normal">C<tspan font-size="11.844">f</tspan></tspan></text></symbol><symbol viewBox="0 0 24 24" id="conduct" xmlns="http://www.w3.org/2000/svg"><path d="M10 17l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9m-6-6a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#cddc39"/></symbol><symbol viewBox="0 0 24 24" id="console" xmlns="http://www.w3.org/2000/svg"><path d="M20 19V7H4v12h16m0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16m-7 14v-2h5v2h-5m-3.42-4L5.57 9H8.4l3.3 3.3c.39.39.39 1.03 0 1.42L8.42 17H5.59z" fill="#ff7043"/></symbol><symbol viewBox="0 0 24 24" id="contributing" xmlns="http://www.w3.org/2000/svg"><path d="M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="cpp" xmlns="http://www.w3.org/2000/svg"><path d="M10.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C1.56 15.77 1 14.16 1 12.21c.05-2.31.72-4.08 2-5.32C4.32 5.64 5.96 5 7.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M11 11h2V9h2v2h2v2h-2v2h-2v-2h-2v-2m7 0h2V9h2v2h2v2h-2v2h-2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="credits" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v2H3V3m4 4h10v2H7V7m-4 4h18v2H3v-2m4 4h10v2H7v-2m-4 4h18v2H3v-2z" fill="#9ccc65"/></symbol><symbol viewBox="0 0 200 200" id="crystal" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" stroke-width="1.153" fill="#cfd8dc"/></symbol><symbol viewBox="0 0 200 200" id="crystal_light" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" fill="#37474f" stroke-width="1.153"/></symbol><symbol viewBox="0 0 24 24" id="csharp" xmlns="http://www.w3.org/2000/svg"><path d="M11.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C2.56 15.77 2 14.16 2 12.21c.05-2.31.72-4.08 2-5.32C5.32 5.64 6.96 5 8.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M13.89 19l.61-4H13l.34-2h1.5l.32-2h-1.5L14 9h1.5l.61-4h2l-.61 4h1l.61-4h2l-.61 4H22l-.34 2h-1.5l-.32 2h1.5L21 15h-1.5l-.61 4h-2l.61-4h-1l-.61 4h-2m2.95-6h1l.32-2h-1l-.32 2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="css" xmlns="http://www.w3.org/2000/svg"><path d="M5 3l-.65 3.34h13.59L17.5 8.5H3.92l-.66 3.33h13.59l-.76 3.81-5.48 1.81-4.75-1.81.33-1.64H2.85l-.79 4 7.85 3 9.05-3 1.2-6.03.24-1.21L21.94 3H5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="css-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#42a5f5"/><path d="M4.676 3l-.488 2.51h10.211l-.33 1.623H3.864l-.496 2.502H13.58l-.57 2.863-4.119 1.36-3.569-1.36.248-1.232H3.06l-.593 3.005 5.898 2.254 6.8-2.254.902-4.53.18-.91L17.406 3H4.675z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 33 33" id="cucumber" xmlns="http://www.w3.org/2000/svg"><title>cucumber-mark-transparent-pips</title><g transform="translate(0 -5)" fill="none" fill-rule="evenodd"><path d="M-4-1h40v40H-4z"/><path d="M16.641 7.092c-7.028 0-12.714 5.686-12.714 12.714 0 6.187 4.435 11.327 10.288 12.471v3.64C21.824 34.77 28.561 28.73 29.063 20.8c.303-4.772-2.076-9.644-6.09-12.01a10.575 10.575 0 0 0-1.455-.728l-.243-.097c-.223-.082-.448-.175-.68-.242a12.614 12.614 0 0 0-3.954-.632zm2.62 4.707a1.387 1.387 0 0 0-1.213.485c-.233.31-.379.611-.534.923-.466 1.087-.31 2.251.388 3.105 1.087-.233 2.01-.927 2.475-2.014a2.45 2.45 0 0 0 .243-1.02c.048-.824-.634-1.404-1.359-1.479zm-5.654.073c-.708.068-1.382.63-1.382 1.407 0 .31.087.709.243 1.02.466 1.086 1.46 1.78 2.546 2.013.621-.854.782-2.018.316-3.105-.155-.311-.3-.617-.534-.85a1.364 1.364 0 0 0-1.188-.485zm-3.809 3.735c-1.224.063-1.77 1.602-.752 2.402.31.233.612.403.922.559 1.087.466 2.344.306 3.275-.316-.233-1.009-1.023-1.936-2.11-2.402-.388-.155-.703-.243-1.092-.243-.087-.009-.161-.004-.243 0zm11.961 4.708a3.551 3.551 0 0 0-2.013.582c.233 1.01 1.023 1.936 2.11 2.401.389.156.705.244 1.093.244 1.397.077 2.08-1.65.994-2.427-.31-.233-.611-.379-.922-.534a3.354 3.354 0 0 0-1.262-.266zm-10.603.072a3.376 3.376 0 0 0-1.261.267c-.389.155-.69.325-.923.558-1.009.854-.33 2.48 1.068 2.402.388 0 .782-.087 1.092-.243 1.087-.465 1.859-1.392 2.014-2.401a3.474 3.474 0 0 0-1.99-.582zm3.931 2.378c-1.087.233-2.009.927-2.475 2.014-.155.31-.243.684-.243.995-.077 1.32 1.724 2.028 2.5 1.02.233-.312.378-.613.534-.923.466-1.01.306-2.174-.316-3.106zm2.887.073c-.621.854-.781 2.019-.315 3.106.155.31.3.615.534.848.854.932 2.65.243 2.572-.921 0-.31-.088-.71-.243-1.02-.466-1.087-1.46-1.78-2.547-2.013z" fill="#4caf50" stroke-width=".776"/></g></symbol><symbol id="cuda" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style>.bust0{fill:#76b900}</style><title>NVIDIA-Logo</title><path id="buEye_Mark" class="bust0" d="M76.362 75.199V64.116c1.095-.068 2.19-.137 3.284-.137 30.377-.958 50.286 26.135 50.286 26.135s-21.483 29.83-44.539 29.83c-3.079 0-6.089-.48-8.962-1.438v-33.66c11.836 1.436 14.23 6.636 21.277 18.471l15.804-13.273s-11.562-15.12-30.992-15.12c-2.053-.068-4.105.069-6.158.274m0-36.67v16.556l3.284-.205c42.213-1.437 69.784 34.618 69.784 34.618s-31.608 38.45-64.516 38.45c-2.873 0-5.678-.274-8.483-.753v10.262c2.326.274 4.72.48 7.046.48 30.65 0 52.817-15.668 74.3-34.14 3.558 2.874 18.13 9.784 21.14 12.794-20.388 17.104-67.937 30.856-94.893 30.856-2.6 0-5.062-.137-7.525-.41v14.436h116.44V38.532zm0 79.977v8.757C48.038 122.2 40.17 92.712 40.17 92.712s13.615-15.05 36.192-17.514v9.579h-.068c-11.836-1.437-21.14 9.646-21.14 9.646s5.268 18.678 21.209 24.082M26.077 91.481S42.839 66.714 76.43 64.115v-9.03C39.213 58.094 7.057 89.565 7.057 89.565s18.199 52.68 69.305 57.47v-9.579c-37.492-4.652-50.286-45.975-50.286-45.975z" fill="#8bc34a" stroke-width=".684"/></symbol><symbol viewBox="0 0 24 24" id="dart" xmlns="http://www.w3.org/2000/svg"><title>Dart</title><path d="M12.486 1.385a.978.978 0 0 0-.682.281l-.01.007-6.387 3.692 6.371 6.372v.004l7.659 7.659 1.46-2.63-5.265-12.64-2.456-2.457a.972.972 0 0 0-.69-.288z" fill="#00ca94"/><path d="M5.422 5.35L1.73 11.733l-.007.01a.967.967 0 0 0 .006 1.371l3.059 3.061 11.963 4.706 2.704-1.502-.073-.073-.018.002-7.5-7.512h-.01L5.423 5.35z" fill="#1565c0"/><path d="M5.405 5.353l6.518 6.525h.01l7.502 7.51 2.855-.544.005-8.449-3.016-2.955c-.66-.647-1.675-1.064-2.695-1.202l.002-.032-11.181-.853z" fill="#1565c0"/><path d="M5.414 5.361l6.521 6.522v.009l7.506 7.506-.546 2.855h-8.448l-2.954-3.017c-.647-.66-1.064-1.676-1.2-2.696l-.033.003L5.414 5.36z" fill="#00ee94"/></symbol><symbol viewBox="0 0 24 24" id="database" xmlns="http://www.w3.org/2000/svg"><path d="M12 3C7.58 3 4 4.79 4 7s3.58 4 8 4 8-1.79 8-4-3.58-4-8-4M4 9v3c0 2.21 3.58 4 8 4s8-1.79 8-4V9c0 2.21-3.58 4-8 4s-8-1.79-8-4m0 5v3c0 2.21 3.58 4 8 4s8-1.79 8-4v-3c0 2.21-3.58 4-8 4s-8-1.79-8-4z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="diff" xmlns="http://www.w3.org/2000/svg"><path d="M3 1c-1.11 0-2 .89-2 2v11c0 1.11.89 2 2 2h2v-2H3V3h11v2h2V3c0-1.11-.89-2-2-2H3m6 6c-1.11 0-2 .89-2 2v2h2V9h2V7H9m4 0v2h1v1h2V7h-3m5 0v2h2v11H9v-2H7v2c0 1.11.89 2 2 2h11c1.11 0 2-.89 2-2V9c0-1.11-.89-2-2-2h-2m-4 5v2h-2v2h2c1.11 0 2-.89 2-2v-2h-2m-7 1v3h3v-2H9v-1H7z" fill="#42a5f5"/></symbol><symbol id="docker" viewBox="0 0 41 34.5" xmlns="http://www.w3.org/2000/svg"><style id="bystyle2">.byst0{fill:#fff}.byst1{clip-path:url(#bySVGID_4_)}</style><g id="byg34" transform="translate(.292 1.9)" fill="#0087c9"><g id="byg32"><g id="byg30"><g id="byg28"><g id="byg26"><g id="byg9"><path id="bySVGID_1_" class="byst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2c1.2 0 2.1.9 2.1 2s-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g></g></g></g></g></g></symbol><symbol viewBox="0 0 24 24" id="document" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m9 16v-2H6v2h9m3-4v-2H6v2h12z" fill="#42a5f5"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone_light" xmlns="http://www.w3.org/2000/svg"><g fill="#424242" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol viewBox="0 0 3473 3473" id="editorconfig" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" xmlns="http://www.w3.org/2000/svg"><defs id="ccdefs4"><style id="ccstyle2">.ccfil2{fill:#020202}.ccfil0{fill:#e3e3f8}.ccfil5{fill:#efefef}.ccfil6{fill:#faf1f1}.ccfil3{fill:#fdf2f2}.ccfil1{fill:#fdfdfd}.ccfil4{fill:#fef3f3}</style></defs><g id="ccLayer_x0020_1" transform="matrix(.8945 0 0 .8945 138.649 275.985)"><g id="cc_631799120"><g id="ccg11"><path class="ccfil0" d="M967 1895c46-30 84-105 61-158-63 27-60 89-61 158z" id="ccpath7" fill="#e3e3f8"/><path class="ccfil0" d="M1679 2067c50-16 98-72 71-130-39 27-64 64-71 130z" id="ccpath9" fill="#e3e3f8"/></g><g id="ccg21"><path class="ccfil1" d="M280 2895c0 63 16 131 60 155 162 91 730 20 923-23 101-23 183-98 278-139 214-93 369-168 540-293 124-91 321-347 342-500l-169-38c-4 172-43 211-196 251-103 28-304 34-409 16-139-23-202-96-265-179-122-162 27-275-166-286-203 249-561 70-718 45-67 97-224 727-222 871 97-33 158 3 245 37 308 119 39 224-84 193-84-20-110-75-159-110z" id="ccpath13" fill="#fdfdfd"/><path class="ccfil1" d="M683 1458c125 24 236 76 342 129 173 86 204 74 220 194 2 22-2 34 61 54 106 33-61-26 223-25 169 1 556 69 681 148 52 33 42 75 218 70-2-207-57-516-138-706-99-230-230-265-497-351-156-50-614-105-756-17-133 83-158 182-282 356-36 51-49 90-72 148z" id="ccpath15" fill="#fdfdfd"/><path class="ccfil1" d="M1784 1883c100 41-5 306-144 242-45-127 62-199 91-256-60-9-231-36-282-17-66 25-81 166-47 232 160 314 867 247 792 3-30-99-58-115-159-149-81-27-162-55-251-55z" id="ccpath17" fill="#fdfdfd"/><path class="ccfil1" d="M527 1848c80 77 261 89 378 95 15-155 28-271 152-262 61 83 29 181-35 244 109-1 172-83 156-202-92-66-371-198-511-217-39 42-135 272-140 342z" id="ccpath19" fill="#fdfdfd"/></g><path class="ccfil2" d="M339 2838c66-6 238 44 252 100-107 13-243 3-252-100zm-59 57c49 35 75 90 159 110 123 31 392-74 84-193-87-34-148-70-245-37-2-144 155-774 222-871 157 25 515 204 718-45 193 11 44 124 166 286 63 83 126 156 265 179 105 18 306 12 409-16 153-40 192-79 196-251l169 38c-21 153-218 409-342 500-171 125-326 200-540 293-95 41-177 116-278 139-193 43-761 114-923 23-44-24-60-92-60-155zm1399-828c7-66 32-103 71-130 27 58-21 114-71 130zm105-184c89 0 170 28 251 55 101 34 129 50 159 149 75 244-632 311-792-3-34-66-19-207 47-232 51-19 222 8 282 17-29 57-136 129-91 256 139 64 244-201 144-242zm-817 12c1-69-2-131 61-158 23 53-15 128-61 158zm-440-47c5-70 101-300 140-342 140 19 419 151 511 217 16 119-47 201-156 202 64-63 96-161 35-244-124-9-137 107-152 262-117-6-298-18-378-95zm-100-80c-37-102-37-261 120-274l-80 223c-21 48-21 37-40 51zm256-310c23-58 36-97 72-148 124-174 149-273 282-356 142-88 600-33 756 17 267 86 398 121 497 351 81 190 136 499 138 706-176 5-166-37-218-70-125-79-512-147-681-148-284-1-117 58-223 25-63-20-59-32-61-54-16-120-47-108-220-194-106-53-217-105-342-129zm1770-49c-19-63 16-59 77-102 35-25 63-51 106-75 161-90 461-105 589 2 52 43 137 127 124 237-27 219-177 339-300 439-125 102-333 207-548 137-18-44-4-323-25-426-19-92-9-102 44-157 156-162 494-280 686-141 81 60 58 83 100 129 52-56-45-244-403-232-243 8-348 198-450 189zM997 840c5-139 133-427 261-527 155-120 317-233 555-98 59 33 56 50 62 132 5 79-2 108-22 172-158 510-290 217-796 338 19-166 163-314 243-391 137-133 236-219 442-191 57 95 63 155-6 266-92 148-115 139-101 240 72-18 94-88 127-158 201-420-91-471-270-394-120 51-334 287-404 429-14 28-29 64-42 95zm792 21c21-125 145-156 145-541 0-166-204-315-471-204-229 94-264 166-386 350-115 174-111 365-210 526-29 46-55 62-87 108-23 34-40 77-63 117-47 77-95 133-133 225-120 3-221 5-233 129-16 170 64 212 64 276-1 69-281 765-203 1180 22 114 97 115 217 129 289 35 664 23 923-81l470-225c119-67 319-194 408-287 63-65 96-120 150-197 74-108 76-106 92-253 98 18 281 61 342 114-7 69-41 36-41 98 39 1 104-48 120-102-41-60-84-50-143-98 47-37 132-54 197-81 140-58 379-234 438-394 47-129 12-344-64-428-80-88-266-133-418-133-181 0-368 130-514 186-56-49-60-105-101-159-47-64-353-224-499-255z" id="ccpath23" fill="#020202"/><path class="ccfil3" d="M2453 1409c102 9 207-181 450-189 358-12 455 176 403 232-42-46-19-69-100-129-192-139-530-21-686 141-53 55-63 65-44 157 21 103 7 382 25 426 215 70 423-35 548-137 123-100 273-220 300-439 13-110-72-194-124-237-128-107-428-92-589-2-43 24-71 50-106 75-61 43-96 39-77 102z" id="ccpath25" fill="#fdf2f2"/><path class="ccfil4" d="M997 840l49-87c13-31 28-67 42-95 70-142 284-378 404-429 179-77 471-26 270 394-33 70-55 140-127 158-14-101 9-92 101-240 69-111 63-171 6-266-206-28-305 58-442 191-80 77-224 225-243 391 506-121 638 172 796-338 20-64 27-93 22-172-6-82-3-99-62-132-238-135-400-22-555 98-128 100-256 388-261 527z" id="ccpath27" fill="#fef3f3"/><path class="ccfil5" d="M427 1768c19-14 19-3 40-51l80-223c-157 13-157 172-120 274z" id="ccpath29" fill="#efefef"/><path class="ccfil6" d="M591 2938c-14-56-186-106-252-100 9 103 145 113 252 100z" id="ccpath31" fill="#faf1f1"/></g></g></symbol><symbol viewBox="0 0 24 24" id="elixir" xmlns="http://www.w3.org/2000/svg"><path d="M12.431 22.383c-3.86 0-6.99-3.64-6.99-8.13 0-3.678 2.774-8.172 4.916-10.91 1.014-1.295 2.931-2.321 2.931-2.321s-.982 5.238 1.683 7.318c2.365 1.847 4.105 4.25 4.105 6.363 0 4.232-2.784 7.68-6.645 7.68z" fill="#9575cd" stroke-width="1.256"/></symbol><symbol viewBox="0 0 323.00001 322.99999" id="elm" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.8053 0 0 .8053 30.106 31.524)"><path fill="#f0ad00" d="M160.8 153.865l68.028-68.03H92.77z"/><path fill="#7fd13b" d="M160.983 5.098H12.033l68.524 68.525H229.51z"/><path fill="#7fd13b" stroke-width=".974" d="M243.906 88.021l74.136 74.137-74.474 74.475-74.137-74.137z"/><path fill="#60b5cc" d="M318.2 145.045V5.098H178.252z"/><path fill="#5a6378" d="M152.164 162.499L3.4 13.733v297.533z"/><path fill="#f0ad00" d="M252.205 245.27l65.995 65.996v-131.99z"/><path fill="#60b5cc" d="M160.8 171.134L12.034 319.899h297.53z"/></g></symbol><symbol viewBox="0 0 24 24" id="email" xmlns="http://www.w3.org/2000/svg"><path d="M20 8l-8 5-8-5V6l8 5 8-5m0-2H4c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 30 30" id="erlang" xmlns="http://www.w3.org/2000/svg"><path style="line-height:1.25;-inkscape-font-specification:'Wide Latin'" d="M5.217 4.367c-.048.052-.097.1-.144.153C2.697 7.182 1.51 10.798 1.51 15.366c0 4.418 1.156 7.862 3.46 10.34h19.414c2.553-1.152 4.127-3.43 4.127-3.43l-3.147-2.52-1.454 1.381c-.866.773-.845.931-2.314 1.78-1.496.674-3.04.966-4.634.966-2.516 0-4.423-.909-5.723-2.059-1.286-1.15-1.985-4.511-2.097-6.68l17.458.067-.182-1.472s-.847-7.129-2.542-9.372zm8.76.846c1.565 0 3.22.535 3.96 1.471.742.937.932 1.667.974 3.524H9.12c.111-1.955.436-2.81 1.372-3.697.937-.888 2.03-1.298 3.484-1.298z" font-weight="400" font-size="48" font-family="Wide Latin" letter-spacing="0" word-spacing="0" fill="#f44336" stroke-width=".97"/></symbol><symbol viewBox="0 0 299.99999 300.00001" id="eslint" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2.88 18.438) scale(1.0344)"><path d="M97.021 99.016l48.432-27.962c1.212-.7 2.706-.7 3.918 0l48.433 27.962a3.92 3.92 0 0 1 1.959 3.393v55.924a3.924 3.924 0 0 1-1.959 3.394l-48.433 27.962c-1.212.7-2.706.7-3.918 0l-48.432-27.962a3.92 3.92 0 0 1-1.959-3.394v-55.924a3.922 3.922 0 0 1 1.959-3.393" fill="#7986cb"/><path d="M273.34 124.49L215.473 23.82c-2.102-3.64-5.985-6.325-10.188-6.325H89.545c-4.204 0-8.088 2.685-10.19 6.325L21.488 124.27c-2.102 3.641-2.102 8.236 0 11.877l57.867 99.847c2.102 3.64 5.986 5.501 10.19 5.501h115.74c4.203 0 8.087-1.805 10.188-5.446l57.867-100.01c2.104-3.639 2.104-7.907.001-11.547m-47.917 48.41c0 1.48-.891 2.849-2.174 3.59l-73.71 42.527a4.194 4.194 0 0 1-4.17 0l-73.767-42.527c-1.282-.741-2.179-2.109-2.179-3.59V87.847c0-1.481.884-2.849 2.167-3.59l73.707-42.527a4.185 4.185 0 0 1 4.168 0l73.772 42.527c1.283.741 2.186 2.109 2.186 3.59z" fill="#3f51b5"/></g></symbol><symbol viewBox="0 0 24 24" id="exe" xmlns="http://www.w3.org/2000/svg"><path d="M19 4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h14m0 14V8H5v10h14z" fill="#e64a19"/></symbol><symbol viewBox="0 0 24 24" id="favicon" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2 9.19 8.62 2 9.24l5.45 4.73L5.82 21 12 17.27z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="file" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m5 2H6v16h12v-9h-7V4z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 400 400" id="firebase" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 103)"><path d="M72.55 208.77l44.456-292.29 56.209 90.445L195.49-37.57 330.6 209.28z" fill="#ffa712"/><path d="M195.7 276.73l134.9-67.45-46.5-224.83L72.55 208.77z" fill="#fcca3f"/><path d="M173.22 6.932L72.56 208.772l136.35-144.58z" fill="#f6820c"/></g></symbol><symbol viewBox="0 0 24 24" id="flash" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cma"><stop offset="0" stop-color="#d92f3c"/><stop offset="1" stop-color="#791223"/></linearGradient><linearGradient xlink:href="#cma" id="cmb" x1="2.373" y1="12.027" x2="21.86" y2="12.027" gradientUnits="userSpaceOnUse" gradientTransform="translate(-.09 -24.144)"/></defs><rect width="19.487" height="19.487" x="2.283" y="-21.86" transform="rotate(90)" ry="0" fill="url(#cmb)"/><path style="line-height:125%" d="M16.802 5.768l-.013.002a6.43 6.43 0 0 0-1.182.192 5.062 5.062 0 0 0-1.494.718c-.428.323-.817.72-1.17 1.191-.34.48-.682 1.032-1.022 1.66-.12.228-.233.424-.35.636v.002h-.004l-1.34 2.394-.005-.002c-.238.443-.461.847-.665 1.198a4.358 4.358 0 0 1-.716.94 2.79 2.79 0 0 1-.907.594c-.072.027-.161.042-.242.063h-.989v2.414h.989v-.002a6.427 6.427 0 0 0 1.185-.192 5.062 5.062 0 0 0 1.494-.718 5.94 5.94 0 0 0 1.171-1.191c.34-.48.681-1.033 1.021-1.66.12-.228.235-.425.353-.637l.006.002.003-.005.037-.066h2.53v.002h1.124v-2.408h-.33v-.001h-1.98c.22-.407.432-.789.621-1.115.214-.37.452-.682.717-.94a2.79 2.79 0 0 1 .906-.594c.07-.027.16-.041.239-.061h.992V8.18h-.002V5.77h-.977v-.002z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol class="cnflow-logo" viewBox="0 0 299.99999 300" id="flow" xmlns="http://www.w3.org/2000/svg"><title>Flow logo</title><path d="M38.75 33.427l77.461 77.47H54.436l61.145 61.16H38.437l93.462 93.478v-77.158l.01-.01v-77.47h-.01V66.982h46.691l20.394 20.393H153.57v76.531h22.05l24.474 24.473h-15.806l-.01-.01v.01h-31.665l-.01-.01v.01h-.313l.313.313v77.148h109.149l-39.2-39.2v-15.806l8.465 8.466v-77.37h-15.682l.017-38.191 30.09 30.086V56.362h-64.874l-22.94-22.934H113.71z" fill="#fbc02d" fill-opacity=".976" stroke-width=".955" class="cnflow-logo-mark"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="coa" x1="-388.15%" x2="237.68%" y1="-144.18%" y2="430.41%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cob" x1="72.945%" x2="-97.052%" y1="84.424%" y2="-147.7%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="coc" x1="-283.88%" x2="287.54%" y1="-693.6%" y2="101.71%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cod" x1="-821.19%" x2="101.99%" y1="-469.05%" y2="288.24%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coe" x1="-140.36%" x2="419.01%" y1="-230.93%" y2="261.98%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cof" x1="191.08%" x2="20.358%" y1="253.95%" y2="20.403%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cog" x1="-388.09%" x2="237.67%" y1="-173.85%" y2="518.99%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#coa"/><linearGradient id="coj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cob"/><linearGradient id="cok" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#coc"/><linearGradient id="col" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cod"/><linearGradient id="com" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#coe"/><linearGradient id="con" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cof"/><linearGradient id="coo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cog"/><linearGradient id="cop" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#coh"/><linearGradient id="coh" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#coi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#coj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cok)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#col)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#com)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#con)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#coo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cop)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia-open" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cpi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpa"/><linearGradient id="cpa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cpb"/><linearGradient id="cpb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#cpc"/><linearGradient id="cpc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cpd"/><linearGradient id="cpd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#cpe"/><linearGradient id="cpe" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cpf"/><linearGradient id="cpf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpg"/><linearGradient id="cpg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpp" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#cph"/><linearGradient id="cph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#cpi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#cpj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cpk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#cpl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#cpm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#cpn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#cpo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cpp)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#cddc39" fill-rule="nonzero"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#cddc39"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00acc1" fill-rule="nonzero"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00acc1"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#e57373" fill-rule="nonzero"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" stroke-width=".644"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#e57373"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" fill-rule="evenodd" stroke-width=".644"/></symbol><symbol id="folder-docker" clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><defs id="cydefs10"><path id="cySVGID_2_" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></defs><path id="cypath2" d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><style id="cystyle2">.cyst0{fill:#fff}.cyst1{clip-path:url(#cySVGID_4_)}</style><g id="cyg34" transform="translate(8.319 9.626) scale(.39491)" fill="#b3e5fc"><g id="cyg32"><g id="cyg30"><title id="cytitle4">Group 3</title><g id="cyg28"><g id="cyg26"><g id="cyg9"><path id="cySVGID_1_" class="cyst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g><g id="cyg24"><clipPath id="cySVGID_4_"><use id="cyuse14" width="100%" height="100%" xlink:href="#cySVGID_2_"/></clipPath><g id="cyg22" class="cyst1" clip-path="url(#cySVGID_4_)"><g id="cyg20"><g id="cyg18"><path id="cySVGID_3_" class="cyst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></g></g></g></g></g></g></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docker-open" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="cza"><use width="100%" height="100%" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#SVGID_2_"/></clipPath></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><g transform="matrix(.3949 0 0 .39489 8.319 9.626)" fill="#b3e5fc"><title>Group 3</title><path class="czst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/><g class="czst1" clip-path="url(#cza)"><path class="czst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#01579b" fill-rule="nonzero"/><style>.dcst0{fill:#1173b6}.st1{fill:#585d67}</style><path class="dcst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#01579b"/><path class="ddst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M21.132 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.217 1.217m.608-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M21.133 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.216 1.217m.609-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ffca28" fill-rule="nonzero"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.411 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill-rule="nonzero" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ffca28"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.412 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#c0ca33" fill-rule="nonzero"/><path d="M17.39 12.544a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#c0ca33"/><path d="M17.391 12.543a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.036 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.968 4.968 0 0 1-2.679 2.203m-.155-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.1-1.238h2.894c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399h-1.806a4.902 4.902 0 0 1 2.672-2.202c-.37.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.049.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.94 4.94 0 0 1 2.679 2.202m-4.281-3.712a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.037 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.967 4.967 0 0 1-2.68 2.203m-.154-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.099-1.238h2.895c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399H13.06a4.902 4.902 0 0 1 2.672-2.202c-.371.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.05.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.941 4.941 0 0 1 2.679 2.202M17.34 9.322a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M16.473 13.927c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a17.015 17.015 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359a5.558 5.558 0 0 0-.203.604c.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.569-1.053c-.21-.372-.435-.702-.639-1.032-.38-.022-.78-.022-1.2-.022-.422 0-.823 0-1.202.022-.204.33-.428.66-.639 1.032l-.569 1.053.57 1.054c.21.372.434.702.638 1.032.38.021.78.021 1.201.021.421 0 .822 0 1.201-.02.204-.331.428-.661.639-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.245-9.284c-.436-.267-1.405.14-2.522 1.194.366.414.724.864 1.06 1.334.577.057 1.146.14 1.686.253.359-1.503.225-2.535-.224-2.78m-.492 4.03l.204.358c.077-.203.154-.407.203-.604-.19-.042-.4-.077-.618-.112l.211.358m1.018-4.95c1.033.589 1.145 2.141.71 3.953 1.784.527 3.069 1.398 3.069 2.584 0 1.187-1.285 2.058-3.07 2.585.436 1.812.324 3.364-.709 3.954-1.025.59-2.423-.085-3.77-1.37-1.35 1.285-2.747 1.96-3.78 1.37-1.025-.59-1.137-2.142-.702-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.203 6.538c.239.527.45 1.054.625 1.588 1.475-.443 2.303-1.075 2.303-1.588 0-.512-.828-1.144-2.303-1.587a15.81 15.81 0 0 1-.625 1.587m-7.136 0a15.806 15.806 0 0 1-.625-1.587c-1.474.443-2.303 1.075-2.303 1.587 0 .513.829 1.145 2.303 1.588.176-.534.387-1.06.625-1.588m6.321 1.588l-.21.358c.217-.035.428-.07.617-.113-.049-.196-.126-.4-.203-.604l-.204.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.522 1.194.45-.246.583-1.278.224-2.781-.54.112-1.11.196-1.685.253-.337.47-.695.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.113.049.196.126.4.203.604l.204-.359m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M16.473 13.928c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a16.997 16.997 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359c-.077.204-.154.408-.203.604.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.57-1.053c-.21-.372-.434-.702-.638-1.032-.38-.022-.78-.022-1.201-.022-.421 0-.822 0-1.2.022-.205.33-.43.66-.64 1.032l-.569 1.053.569 1.054c.21.372.435.702.64 1.032.378.021.779.021 1.2.021.421 0 .822 0 1.2-.02.205-.33.43-.661.64-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.244-9.284c-.435-.267-1.404.14-2.52 1.194.364.414.723.864 1.06 1.334.575.057 1.144.14 1.685.253.358-1.503.225-2.535-.225-2.78m-.491 4.03l.203.358c.078-.203.155-.407.204-.604-.19-.042-.4-.077-.618-.112l.21.358m1.02-4.95c1.032.589 1.144 2.141.708 3.953 1.784.527 3.07 1.398 3.07 2.584 0 1.187-1.286 2.058-3.07 2.585.436 1.812.323 3.364-.709 3.954-1.025.59-2.423-.085-3.771-1.37-1.348 1.285-2.746 1.96-3.778 1.37-1.026-.59-1.138-2.142-.703-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.204 6.538c.24.527.45 1.054.625 1.588 1.475-.443 2.304-1.075 2.304-1.588 0-.512-.829-1.144-2.304-1.587a15.81 15.81 0 0 1-.625 1.587m-7.135 0a15.808 15.808 0 0 1-.625-1.587c-1.475.443-2.303 1.075-2.303 1.587 0 .513.828 1.145 2.303 1.588.176-.534.386-1.06.625-1.588m6.32 1.588l-.21.358c.218-.035.428-.07.618-.113a5.56 5.56 0 0 0-.204-.604l-.203.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.521 1.194.45-.246.583-1.278.225-2.781-.54.112-1.11.196-1.685.253-.338.47-.696.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.112.049.197.126.4.203.604l.204-.358m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#fbc02d" fill-rule="nonzero"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.434h-1.217v8.518a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#fbc02d"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.433h-1.217v8.519a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#4caf50" fill-rule="nonzero"/><g fill="#c8e6c9" transform="translate(2.065 -.225) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#4caf50"/><g fill="#c8e6c9" fill-rule="evenodd" transform="translate(2.064 -.224) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1e88e5" fill-rule="nonzero"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1e88e5"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.811 8.52l-5.988 5.506-3.346-2.522-1.383.805 3.298 3.03-3.298 3.032 1.383.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.622v6.396l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.81 8.52l-5.988 5.506-3.346-2.522-1.384.805 3.3 3.03-3.3 3.032 1.384.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.621v6.397l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><g transform="translate(8.459 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688"/><g transform="translate(8.458 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#03a9f4" fill-rule="nonzero"/><g transform="translate(9.192 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#03a9f4"/><g transform="translate(9.193 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol viewBox="0 0 24 24" id="font" xmlns="http://www.w3.org/2000/svg"><path d="M9.62 12L12 5.67 14.37 12M11 3L5.5 17h2.25l1.12-3h6.25l1.13 3h2.25L13 3h-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 500 500" id="fsharp" xmlns="http://www.w3.org/2000/svg"><path d="M235.906 36.66L21.963 250.601l213.943 213.943v-84.36L106.209 250.487l129.697-129.696z" fill="#378bba" stroke-width="14.706"/><path d="M235.906 156.614l-93.622 93.62 93.622 93.622z" fill="#378bba" stroke-width="15.006"/><path d="M263.417 36.64L477.36 250.583 263.417 464.526v-84.36l129.696-129.697-129.696-129.696z" fill="#30b9db" stroke-width="14.706"/></symbol><symbol viewBox="0 0 152.99 160.01" id="fusebox" xmlns="http://www.w3.org/2000/svg"><defs id="fkdefs4"><style id="fkstyle2">.fkcls-1{fill:#fff}.fkcls-2{fill:#515151}.fkcls-3{fill:#1d79bf}.fkcls-4{fill:#383838}</style></defs><title id="fktitle6">Asset 3</title><g id="fkLayer_2" data-name="Layer 2" transform="matrix(.87285 0 0 .87285 10.17 10.175)"><g id="fkFuse_Box" data-name="Fuse Box"><g id="fkLOGO"><path class="fkcls-1" id="fkpolygon8" fill="#fff" d="M76.56 2.19l74.22 24.93-7.7 87.77-65.41 42.66-69.79-43.93-5.7-86.13z"/><path class="fkcls-2" d="M77.69 160L5.87 114.81 0 26 76.55 0 153 25.67l-7.94 90.4zM9.88 112.43l67.77 42.66 63.45-41.39 7.47-85.13-72-24.18L4.36 28.95z" id="fkpath10" fill="#515151"/><path class="fkcls-3" id="fkpolygon12" fill="#1d79bf" d="M76.4 148.8V61.68l66.93-29.82-5.99 78.77z"/><path id="fkF" class="fkcls-4" fill="#383838" d="M76.4 148.8l-60.35-37.39L9.63 31.8 76.4 61.68z"/><path class="fkcls-1" d="M25.58 52.73l.54 15.93 37.35 18.18.12 14.69-37-18.21 1.64 37.1-14.56-9-5.05-80.55 67.79 30.82v15.46z" id="fkpath15" fill="#fff"/><path class="fkcls-1" d="M135.91 90.77c-.08 13.12-6.33 26.59-16.77 33.12l-42.8 27.93V61.71l42.27-18.84c5.16-2.41 9.51-1.43 12.4 3.11 1.9 3 2.89 7.23 2.86 12.21A35.69 35.69 0 0 1 129.34 76c4.29 2 6.66 6.55 6.57 14.77zM123 63.76c0-4.64-2-6.93-4.92-5.45l-29 14.48L89 90l29.44-15.59c2.5-1.32 4.56-5.91 4.56-10.65zM125.15 96c0-5.71-2.42-8.24-6.55-5.93L89 106.64v19.58l29.34-17.46c4.43-2.64 6.79-7.27 6.81-12.76z" id="fkpath17" fill="#fff"/><path id="fkTOP" class="fkcls-4" fill="#383838" d="M76.4 8.82L9.71 31.77l109.77 2.38-84.02 9.21L76.4 61.68l20.76-9.25-27.73-1.37 49.78-8.46 24.12-10.74z"/></g></g></g></symbol><symbol viewBox="0 0 24 24" id="git" xmlns="http://www.w3.org/2000/svg"><path d="M2.6 10.59L8.38 4.8l1.69 1.7c-.24.85.15 1.78.93 2.23v5.54c-.6.34-1 .99-1 1.73a2 2 0 0 0 2 2 2 2 0 0 0 2-2c0-.74-.4-1.39-1-1.73V9.41l2.07 2.09c-.07.15-.07.32-.07.5a2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2c-.18 0-.35 0-.5.07L13.93 7.5a1.98 1.98 0 0 0-1.15-2.34c-.43-.16-.88-.2-1.28-.09L9.8 3.38l.79-.78c.78-.79 2.04-.79 2.82 0l7.99 7.99c.79.78.79 2.04 0 2.82l-7.99 7.99c-.78.79-2.04.79-2.82 0L2.6 13.41c-.79-.78-.79-2.04 0-2.82z" fill="#e64a19"/></symbol><symbol viewBox="0 0 494 455" id="gitlab" xmlns="http://www.w3.org/2000/svg"><title>logo</title><defs><path id="fma" d="M0 1173.3h2000V0H0v1173.3z"/></defs><g transform="matrix(.88256 0 0 -.88256 -286.767 742.766)" fill="none" fill-rule="evenodd"><mask id="fmb" fill="#fff"><use width="100%" height="100%" xlink:href="#fma"/></mask><g><g transform="translate(358.67 358.67)"><path d="M492.532 195.445l-27.559 84.815-54.617 168.1c-2.81 8.648-15.045 8.648-17.856 0l-54.619-168.1h-181.37l-54.62 168.1c-2.81 8.648-15.045 8.648-17.856 0l-54.617-168.1-27.557-84.815a18.775 18.775 0 0 1 6.82-20.992l238.51-173.29 238.51 173.29a18.777 18.777 0 0 1 6.82 20.992" fill="#fc6d26"/><path d="M247.2 1.16l90.684 279.1h-181.37z" fill="#e24329"/><path d="M247.201 1.16l-90.684 279.09H29.427z" fill="#fc6d26"/><path d="M29.422 280.256L1.862 195.44a18.774 18.774 0 0 1 6.822-20.991L247.194 1.16z" fill="#fca326"/><path d="M29.422 280.26h127.09l-54.619 168.1c-2.81 8.65-15.047 8.65-17.856 0z" fill="#e24329"/><path d="M247.2 1.16l90.684 279.09h127.09z" fill="#fc6d26"/><path d="M464.98 280.256l27.559-84.815a18.774 18.774 0 0 0-6.821-20.991L247.208 1.16z" fill="#fca326"/><path d="M464.97 280.26H337.88l54.619 168.1c2.81 8.65 15.047 8.65 17.856 0z" fill="#e24329"/></g></g></g></symbol><symbol viewBox="0 0 24 24" id="go" xmlns="http://www.w3.org/2000/svg"><path d="M10.575 1.695c-2.634 0-4.756 2.453-4.756 5.502v4.6l-.027-.003v4.71c0 3.05 2.123 5.502 4.757 5.502h2.286c2.634 0 4.757-2.453 4.757-5.502v-4.6a5.1 5.1 0 0 0 .026.003v-4.71c0-3.049-2.122-5.502-4.756-5.502h-2.287z" fill="#73cddc"/><rect width="2.289" height="3.335" x="-1.178" y="6.092" ry="1.125" transform="matrix(.4849 -.87457 .85979 .51065 0 0)" fill="#73cddc"/><rect width="2.297" height="3.39" x="10.261" y="-15.076" ry="1.143" transform="matrix(.44646 .8948 -.89204 .45195 0 0)" fill="#73cddc"/><circle cx="9.267" cy="5.13" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><circle cx="14.214" cy="5.116" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><ellipse cx="8.039" cy="5.051" rx=".792" ry=".901" fill="#030d18"/><path d="M11.792 9.556l.763.138a.403.689 0 0 1 .008.138.403.689 0 0 1-.402.69.403.689 0 0 1-.404-.69.403.689 0 0 1 .035-.276z" fill="#fff" stroke="#fff" stroke-width=".155"/><ellipse cx="8.51" cy="5.365" rx=".138" ry=".166" fill="#fff"/><ellipse cx="12.945" cy="5.189" rx=".792" ry=".901" fill="#030d18"/><ellipse cx="13.414" cy="5.446" rx=".138" ry=".166" fill="#fff"/><ellipse cx="-12.982" cy="-3.409" rx=".708" ry="1.026" transform="rotate(-129.403)" fill="#f6d2a1" stroke-width=".4"/><path d="M11.772 9.553l-.757.135a.4.672 0 0 0-.008.135.4.672 0 0 0 .4.672.4.672 0 0 0 .4-.672.4.672 0 0 0-.035-.27z" fill="#fff" stroke="#fff" stroke-width=".153"/><ellipse cx="1.841" cy="-21.563" rx=".707" ry="1.026" transform="scale(1 -1) rotate(50.597)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="-17.281" cy="-21.784" rx=".864" ry="1.27" transform="matrix(.3054 -.95222 -.97065 -.2405 0 0)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="22.885" cy="2.587" rx=".864" ry="1.27" transform="matrix(.22652 .974 .95652 -.29167 0 0)" fill="#f6d2a1" stroke-width=".4"/><path d="M10.708 8.392a.594.594 0 0 0-.594.597v.115c0 .331.264.598.594.598h.386a.973.772 0 0 1 .697-.235.973.772 0 0 1 .698.235h.334c.33 0 .594-.267.594-.598V8.99a.595.595 0 0 0-.594-.597h-2.115z" fill="#f6d2a1" stroke="#657075" stroke-width=".1"/><ellipse cx="11.734" cy="8.203" rx="1.208" ry=".68" fill="#030d18" stroke="#fff" stroke-width=".162"/></symbol><symbol viewBox="0 0 24 24" id="gradle" xmlns="http://www.w3.org/2000/svg"><path d="M21.718 5.503c-.731-1.315-2.04-1.708-2.963-1.727-1.133-.023-2.065.605-1.888 1.017.037.088.25.55.38.741.19.275.527.064.646 0 .353-.187.73-.248 1.16-.198.409.048.954.3 1.319 1.001.859 1.652-1.794 5.05-5.114 2.697-3.32-2.353-6.548-1.574-8.01-1.1-1.462.475-2.135.952-1.556 2.055.785 1.498.524 1.038 1.285 2.28 1.21 1.97 3.856-.908 3.856-.908-1.972 2.906-3.662 2.204-4.31 1.188a15.864 15.864 0 0 1-1.038-1.97c-4.993 1.76-3.642 9.534-3.642 9.534h2.48c.632-2.862 2.892-2.757 3.28 0h1.892c1.673-5.59 5.914 0 5.914 0h2.466c-.69-3.812 1.388-5.01 2.697-7.246 1.31-2.235 2.551-4.969 1.146-7.364zm-6.362 7.362c-1.304-.426-.837-1.723-.837-1.723s1.139.368 2.68.87c-.09.403-.856 1.175-1.843.853z" fill="#0097a7" stroke-width=".47"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 300 300" id="graphcool" xmlns="http://www.w3.org/2000/svg"><path d="M246.886 107.727c-12.237-6.892-27.616 2.1-30.081 3.646l-52.834 29.965c-7.8-6.196-18.914-5.933-26.412.625-7.499 6.558-9.24 17.537-4.14 26.094 5.102 8.556 15.588 12.246 24.923 8.768 9.335-3.478 14.852-13.129 13.111-22.937l52.688-29.9.321-.196c3.464-2.188 11.5-5.462 15.256-3.34 2.706 1.524 4.252 6.629 4.376 14.148h-.066v66.092a17.313 17.313 0 0 1-8.635 14.95l-75.739 43.755a17.312 17.312 0 0 1-17.261 0l-75.74-43.756a17.312 17.312 0 0 1-8.634-14.95V113.22c.01-6.165 3.3-11.86 8.634-14.95l68.549-39.562c6.522 7.482 17.451 9.25 26 4.206s12.283-15.468 8.886-24.794c-3.397-9.327-12.962-14.904-22.751-13.27-9.79 1.636-17.022 10.02-17.204 19.944L59.397 85.632a31.932 31.932 0 0 0-15.978 27.588v87.454a31.933 31.933 0 0 0 15.927 27.602l75.74 43.755a31.934 31.934 0 0 0 31.846 0l75.74-43.755a31.933 31.933 0 0 0 15.927-27.58V137.12h.05c.373-14.913-3.616-24.794-11.762-29.389z" fill="#27ae60" stroke="#27ae60" stroke-width="7.883622079999999"/></symbol><symbol viewBox="0 0 400 400" id="graphql" xmlns="http://www.w3.org/2000/svg"><path d="M67.008 293.022l-13.143-7.588L200.282 31.839l13.143 7.588z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M50.855 265.174H343.69v15.177H50.855z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M203.122 358.269L56.649 273.7l7.589-13.143 146.472 84.568zm127.24-220.407L183.889 53.293l7.589-13.143 146.472 84.568z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M64.278 137.803l-7.588-13.142 146.472-84.568 7.588 13.143z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M327.661 293.025L181.244 39.43l13.143-7.589 146.417 253.596zM62.466 114.597h15.176v169.136H62.466zm254.528 0h15.176v169.136h-15.176z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M200.538 351.845l-6.628-11.481L321.3 266.812l6.629 11.48z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M352.284 288.67c-8.777 15.268-28.342 20.48-43.61 11.703-15.268-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.36 8.869 20.57 28.342 11.703 43.61M97.574 141.567c-8.778 15.268-28.343 20.48-43.61 11.703-15.269-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.268 8.869 20.479 28.342 11.702 43.61M42.353 288.67c-8.777-15.268-3.566-34.741 11.702-43.61 15.268-8.776 34.741-3.565 43.61 11.703 8.776 15.268 3.565 34.741-11.703 43.61-15.36 8.776-34.833 3.565-43.61-11.703m254.71-147.103c-8.776-15.268-3.565-34.741 11.703-43.61 15.268-8.776 34.742-3.565 43.61 11.703 8.777 15.268 3.566 34.741-11.702 43.61-15.268 8.776-34.833 3.565-43.61-11.703m-99.745 236.608c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907c0 17.554-14.262 31.907-31.907 31.907m0-294.206c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907-14.262 31.907-31.907 31.907" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/></symbol><symbol viewBox="0 0 24 24" id="groovy" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.982a10.119 10.119 0 0 0-10.12 10.12A10.119 10.119 0 0 0 12 22.22 10.119 10.119 0 0 0 22.12 12.1 10.119 10.119 0 0 0 12 1.983zm1.254 2.422c.91 0 1.647.261 2.213.78.571.518.857 1.188.857 2.013 0 .889-.319 1.673-.959 2.35-.64.677-1.376 1.015-2.207 1.015-.486 0-.89-.119-1.213-.357-.317-.238-.476-.532-.476-.88 0-.212.06-.4.181-.563.127-.164.274-.246.438-.246.159 0 .238.092.238.277 0 .164.06.29.182.38.121.09.261.136.42.136.423 0 .828-.29 1.215-.866.391-.582.587-1.202.587-1.863 0-.465-.151-.844-.453-1.135-.301-.296-.69-.445-1.166-.445-.714 0-1.406.318-2.078.953-.666.635-1.211 1.47-1.635 2.506-.417 1.031-.627 2.014-.627 2.945 0 .857.185 1.54.555 2.047.37.503.863.754 1.477.754 1.037 0 2.027-.734 2.974-2.2l1.493-.212c.185-.026.277.018.277.135 0 .053-.072.28-.215.681-.143.402-.337 1.074-.586 2.016.82-.476 1.455-1.003 1.904-1.58v.914c-.36.418-1.046.888-2.062 1.412-.212 1.407-.682 2.493-1.406 3.26-.725.772-1.54 1.16-2.444 1.16-.433 0-.775-.102-1.023-.303-.243-.2-.365-.477-.365-.832 0-.984.955-1.94 2.865-2.865.2-.714.395-1.356.586-1.928-.333.482-.817.907-1.451 1.278-.635.37-1.225.554-1.77.554-.889 0-1.628-.383-2.22-1.15-.588-.772-.881-1.748-.881-2.928 0-1.243.333-2.42 1-3.531a7.747 7.747 0 0 1 2.625-2.674c1.084-.672 2.134-1.008 3.15-1.008zM12.03 16.592c-1.375.687-2.062 1.365-2.062 2.031 0 .354.169.533.508.533.666 0 1.184-.856 1.554-2.564z" fill="#26c6da"/></symbol><symbol viewBox="0 0 24 24" id="gulp" xmlns="http://www.w3.org/2000/svg"><path d="M8.37 15.94a596.238 596.238 0 0 1-.482-4.982c.002-.042-.225-.077-.505-.077h-.508V8.95h3.966V5.198l1.871-1.124c1.14-.685 1.978-1.125 2.144-1.125.4 0 .866.506.866.939 0 .19-.057.422-.127.517-.07.095-.722.53-1.45.966l-1.321.792-.029 1.393-.028 1.393h3.972v1.932h-.98l-.495 4.983-.495 4.983H8.854l-.485-4.906z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="h" xmlns="http://www.w3.org/2000/svg"><path d="M16.745 19.818h-3.007v-5.882q0-2.381-1.736-2.381-.869 0-1.438.663-.56.662-.56 1.718v5.882H6.988V4.533h3.016v6.508h.037q1.186-1.802 3.193-1.802 3.511 0 3.511 4.239z" stroke-width=".478" fill="#0277bd"/></symbol><symbol viewBox="0 0 253.6 253.6" id="hack" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-29.243 -29.515) scale(1.2301)"><path fill="#607d8b" d="M69.496 159.551v52.576l51.77-52.576zM123.507 41.523l-54.01 52.755v55.084l54.01-54.009z"/><path fill="#eceff1" d="M130.023 95.663v51.501l52.128-51.5z"/><path fill="#607d8b" d="M185.465 101.867l-55.442 55.174v55.083l55.442-55.262z"/><path fill="#ffa000" d="M73.068 154.283l50.427.09v-50.248z"/></g></symbol><symbol viewBox="0 0 300 300.00001" id="haml" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 165.6)"><path d="M78.42-132.307c-12.047-.302-26.924 5.998-26.924 5.998l49.195 99.791L74.605 85.005c23.81 20.134 50.07 10.504 50.07 10.504L136.76 9.212c1.526 1.446 3.146 2.77 4.777 3.995 5.244 3.714 10.925 6.553 16.606 8.738 5.68 2.185 11.583 3.933 17.482 5.244 3.933.874 7.645 1.53 11.578 1.967-1.748 3.933-2.84 8.083-2.621 12.672 0 .437.22.873.656 1.092h.217c4.152 2.185 8.521 3.934 13.328 5.027 4.589.874 9.615 1.312 14.422.656 5.026-.655 10.051-2.623 13.984-5.9 3.933-3.278 6.774-7.648 8.522-12.237l.219-.218v-.217l.656-5.899v-.22c2.185-1.311 4.37-2.621 6.555-4.37 2.622-2.184 5.025-4.589 6.773-7.648 1.748-3.059 2.84-6.774 2.621-10.488-.218-3.496-1.53-6.99-3.06-10.049-1.53-3.059-3.495-5.901-5.68-8.523-4.37-5.026-9.614-9.176-15.295-12.454-5.462-3.496-11.581-6.338-17.7-8.304l-2.404-.656-1.962-.655c-1.311-.437-2.406-1.092-3.498-1.53-2.185-1.31-3.717-2.622-4.809-4.37-2.185-3.278-2.403-8.301-1.31-13.545.218-1.311.656-2.623 1.093-3.934a96.064 96.064 0 0 0 1.31-4.152c.314-1.412.51-2.829.598-4.402l29.203-25.553c-2.275-8.404-27.488-17.158-27.488-17.158l-74.931 63.726-43.243-81.584c-1.553-.35-3.218-.527-4.94-.57zm107.682 73.14c-.449 2.336-.647 4.795-.647 7.258.219 3.715 1.311 7.87 3.715 11.366 2.403 3.496 5.68 6.117 8.957 7.646a29.663 29.663 0 0 0 5.027 1.967l2.623.654 2.184.438c5.68 1.53 11.142 3.714 16.168 6.554 5.025 2.84 9.833 6.337 13.766 10.27s6.992 8.959 7.43 13.984c.218 3.496-.22 6.118-1.313 8.303-1.093 2.404-2.84 4.588-4.807 6.555-.874.874-1.966 1.747-2.84 2.402a27.11 27.11 0 0 0-.654-5.898c-.219-1.093-.438-1.966-.875-3.059-.437-.874-.872-1.966-1.965-2.621-.218 0-.44-.001-.44.217-1.31 3.277-3.494 6.12-5.898 8.086-2.403 1.966-5.462 2.84-8.521 3.058-3.06.219-6.338-.436-9.616-1.31-3.277-.874-6.552-1.968-9.83-3.06l-.439-.22c-.656-.218-1.526.002-1.963.44-1.748 2.185-3.06 4.149-4.59 6.334a58.435 58.435 0 0 0-2.84 5.027c-3.933-1.53-7.649-2.841-11.582-4.37-5.462-2.186-10.925-4.37-15.95-6.991-5.245-2.404-10.268-5.246-14.638-8.524-3.15-2.363-6.062-4.845-8.185-7.681l2.404-17.172z" fill="#f4511e" stroke-width="0" stroke-linejoin="round"/></g></symbol><symbol viewBox="0 0 24 24" id="handlebars" xmlns="http://www.w3.org/2000/svg"><path d="M8.55 10.32c-2.753 0-4.202 3.48-5.793 3.48-.98 0-1.126-.677-1.126-.915 0-.332.236-.706.564-.706.59 0 .414.77.414.77s.798-.555.272-1.298c-.42-.595-1.31-.623-1.92-.17-.617.458-1.057 1.146-.853 2.287.1.551.468 1.35 1.233 1.805.764.455 1.925.566 2.335.566 2.194 0 4.342-1.633 6.639-2.322a5.513 5.513 0 0 1 1.497-.222 6.19 6.19 0 0 1 1.92.226c2.296.689 4.444 2.323 6.638 2.323.41 0 1.57-.11 2.335-.566.765-.455 1.132-1.256 1.231-1.807.204-1.14-.235-1.829-.853-2.287-.61-.453-1.497-.423-1.918.172-.526.743.27 1.297.27 1.297s-.176-.77.414-.77c.329 0 .565.373.565.705 0 .238-.147.914-1.126.914-1.592 0-3.04-3.478-5.794-3.478-2.565 0-3.076 1.177-3.462 1.718-.004.005-.005.011-.008.016-.005-.006-.007-.013-.012-.02-.386-.54-.896-1.717-3.461-1.717z" fill="#ff7043" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 300.00001 300" id="haskell" xmlns="http://www.w3.org/2000/svg"><g stroke-width="2.422"><path d="M23.928 240.5l59.94-89.852-59.94-89.855h44.955l59.94 89.855-59.94 89.852z" fill="#ef5350"/><path d="M83.869 240.5l59.94-89.852-59.94-89.855h44.955l119.88 179.71h-44.95l-37.46-56.156-37.468 56.156z" fill="#ffa726"/><path d="M228.72 188.08l-19.98-29.953h69.93v29.956h-49.95zm-29.97-44.924l-19.98-29.953h99.901v29.953z" fill="#ffee58"/></g></symbol><symbol viewBox="0 0 210 210" id="haxe" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -87)"><path fill="#f68712" stroke-width=".221" d="M42.78 191.545l63.431-63.43 63.431 63.43-63.431 63.431z"/><path d="M42.8 191.592L31.193 148.28 19.59 104.97 62.9 116.575l43.311 11.605-31.706 31.706z" fill="#fab20b" stroke-width=".266"/><path d="M105.956 128.111l-43.19-11.544-43.177-11.597 22.927.185 23.228.294 20.264 11.36z" fill="#fbc707" stroke-width=".265"/><path d="M19.59 104.97l11.596 43.176 11.545 43.19-11.303-19.948-11.36-20.263-.294-23.228z" fill="#fff200" stroke-width=".265"/><path d="M106.23 128.133l43.312-11.605 43.311-11.605-11.605 43.31-11.605 43.312-31.706-31.706z" fill="#f47216" stroke-width=".266"/><path d="M169.711 191.289l11.545-43.19 11.597-43.176-.185 22.927-.294 23.228-11.36 20.263z" fill="#f1471d" stroke-width=".265"/><path d="M192.853 104.923l-43.176 11.597-43.19 11.544 19.947-11.303 20.264-11.36 23.228-.293z" fill="#fbc707" stroke-width=".265"/><path d="M169.643 191.545l11.605 43.31 11.605 43.312-43.311-11.605-43.311-11.606 31.706-31.705z" fill="#f25c19" stroke-width=".266"/><path d="M106.487 255.025l43.19 11.544 43.176 11.598-22.927-.185-23.228-.294-20.264-11.36z" fill="#f68712" stroke-width=".265"/><path d="M192.853 278.167l-11.597-43.176-11.545-43.19 11.303 19.947 11.36 20.264.294 23.228z" fill="#f1471d" stroke-width=".265"/><path d="M106.211 254.976l-43.31 11.605-43.312 11.605 11.605-43.31L42.8 191.563l31.706 31.706z" fill="#f89c0e" stroke-width=".266"/><path d="M42.731 191.82l-11.545 43.19-11.597 43.176.185-22.927.294-23.228 11.36-20.263z" fill="#fff200" stroke-width=".265"/><path d="M19.59 278.186l43.175-11.597 43.19-11.544-19.947 11.303-20.264 11.36-23.228.293z" fill="#f25c19" stroke-width=".265"/></g></symbol><symbol viewBox="0 0 144 152" id="heroku" xmlns="http://www.w3.org/2000/svg"><path d="M118.68 13.279H26.865c-6.337 0-11.476 5.139-11.476 11.476V129.32c0 6.338 5.139 11.477 11.476 11.477h91.813c6.338 0 11.477-5.14 11.477-11.477V24.755c0-6.337-5.139-11.476-11.477-11.476zM44.08 121.669V96.165l14.346 12.752zm44.632 0v-38.08c-.063-2.976-1.496-6.551-7.97-6.551-12.966 0-27.51 6.52-27.654 6.586l-9.008 4.08V32.407h12.752v36.201c6.366-2.072 15.266-4.321 23.91-4.321 7.882 0 12.6 3.099 15.17 5.698 5.484 5.547 5.56 12.613 5.551 13.43v38.255zm3.188-68.54H79.149c5.011-6.576 8.158-13.496 9.564-20.723h12.751c-.86 7.243-3.796 14.187-9.563 20.722z" fill="#6963b9"/></symbol><symbol viewBox="0 0 24 24" id="hpp" xmlns="http://www.w3.org/2000/svg"><path d="M9.757 19.818H6.751v-5.882q0-2.381-1.737-2.381-.868 0-1.438.663-.56.662-.56 1.718v5.882H0V4.533h3.016v6.508h.037Q4.24 9.239 6.247 9.239q3.51 0 3.51 4.239z" stroke-width=".478" fill="#0277bd"/><path d="M13.073 11.448v2h-2v2h2v2h2v-2h2v-2h-2v-2zm7 0v2h-2v2h2v2h2v-2h2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="html" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.56l4.07-1.13.55-6.1H9.38L9.2 8.3h7.6l.2-1.99H7l.56 6.01h6.89l-.23 2.58-2.22.6-2.22-.6-.14-1.66h-2l.29 3.19L12 17.56M4.07 3h15.86L18.5 19.2 12 21l-6.5-1.8L4.07 3z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" id="http" xmlns="http://www.w3.org/2000/svg"><path d="M16.046 13.784c.074-.613.13-1.225.13-1.856s-.056-1.244-.13-1.856h3.137c.148.594.241 1.215.241 1.856a7.65 7.65 0 0 1-.241 1.856m-4.78 5.16c.557-1.03.984-2.144 1.281-3.304h2.738a7.452 7.452 0 0 1-4.019 3.304m-.232-5.16H9.828a12.314 12.314 0 0 1-.149-1.856c0-.631.056-1.253.149-1.856h4.343c.084.603.149 1.225.149 1.856 0 .63-.065 1.243-.149 1.856M12 19.315c-.77-1.113-1.393-2.348-1.773-3.675h3.545c-.38 1.327-1.002 2.562-1.773 3.675m-3.712-11.1h-2.71a7.353 7.353 0 0 1 4.01-3.304c-.557 1.03-.975 2.144-1.3 3.304m-2.71 7.425h2.71c.325 1.16.743 2.274 1.3 3.304a7.433 7.433 0 0 1-4.01-3.304m-.761-1.856a7.65 7.65 0 0 1-.241-1.856c0-.64.093-1.262.241-1.856h3.137c-.074.612-.13 1.225-.13 1.856 0 .63.056 1.243.13 1.856m4.046-9.253c.77 1.114 1.393 2.357 1.773 3.684h-3.545c.38-1.327 1.002-2.57 1.773-3.684m6.422 3.684h-2.738a14.523 14.523 0 0 0-1.28-3.304 7.412 7.412 0 0 1 4.018 3.304m-6.423-5.568c-5.132 0-9.28 4.176-9.28 9.28a9.28 9.28 0 0 0 9.28 9.282 9.28 9.28 0 0 0 9.281-9.281A9.28 9.28 0 0 0 12 2.647z" fill="#e53935" stroke-width=".928"/></symbol><symbol viewBox="0 0 24 24" id="image" xmlns="http://www.w3.org/2000/svg"><path d="M13.009 9.202h5.368l-5.368-5.368v5.368M6.177 2.37h7.808l5.856 5.856v11.711a1.952 1.952 0 0 1-1.952 1.952H6.178a1.951 1.951 0 0 1-1.952-1.952V4.322c0-1.083.868-1.952 1.952-1.952m0 17.567h11.71V12.13l-3.903 3.903-1.952-1.951-5.856 5.855M8.13 9.202a1.952 1.952 0 0 0-1.952 1.952 1.952 1.952 0 0 0 1.952 1.952 1.952 1.952 0 0 0 1.952-1.952A1.952 1.952 0 0 0 8.13 9.202z" fill="#26a69a" stroke-width=".976"/></symbol><symbol viewBox="0 0 512 512" id="ionic" xmlns="http://www.w3.org/2000/svg"><g fill="#4f8ff7"><path d="M423.592 132.804A31.855 31.855 0 0 0 429 115c0-17.675-14.33-32-32-32a31.853 31.853 0 0 0-17.805 5.409C344.709 63.015 302.11 48 256 48 141.125 48 48 141.125 48 256c0 114.877 93.125 208 208 208 114.873 0 208-93.123 208-208 0-46.111-15.016-88.71-40.408-123.196zM391.83 391.832c-17.646 17.646-38.191 31.499-61.064 41.174-23.672 10.012-48.826 15.089-74.766 15.089-25.94 0-51.095-5.077-74.767-15.089-22.873-9.675-43.417-23.527-61.064-41.174s-31.5-38.191-41.174-61.064C68.982 307.096 63.905 281.94 63.905 256c0-25.94 5.077-51.095 15.089-74.767 9.674-22.873 23.527-43.417 41.174-61.064s38.191-31.5 61.064-41.174c23.673-10.013 48.828-15.09 74.768-15.09 25.939 0 51.094 5.077 74.766 15.089a191.221 191.221 0 0 1 37.802 21.327A31.853 31.853 0 0 0 365 115c0 17.675 14.327 32 32 32 5.293 0 10.28-1.293 14.678-3.568a191.085 191.085 0 0 1 21.327 37.801c10.013 23.672 15.09 48.827 15.09 74.767 0 25.939-5.077 51.096-15.09 74.768-9.675 22.873-23.527 43.418-41.175 61.064z"/><circle cx="256.003" cy="256" r="96"/></g></symbol><symbol viewBox="0 0 24 24" id="java" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="javascript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v18H3V3m4.73 15.04c.4.85 1.19 1.55 2.54 1.55 1.5 0 2.53-.8 2.53-2.55v-5.78h-1.7V17c0 .86-.35 1.08-.9 1.08-.58 0-.82-.4-1.09-.87l-1.38.83m5.98-.18c.5.98 1.51 1.73 3.09 1.73 1.6 0 2.8-.83 2.8-2.36 0-1.41-.81-2.04-2.25-2.66l-.42-.18c-.73-.31-1.04-.52-1.04-1.02 0-.41.31-.73.81-.73.48 0 .8.21 1.09.73l1.31-.87c-.55-.96-1.33-1.33-2.4-1.33-1.51 0-2.48.96-2.48 2.23 0 1.38.81 2.03 2.03 2.55l.42.18c.78.34 1.24.55 1.24 1.13 0 .48-.45.83-1.15.83-.83 0-1.31-.43-1.67-1.03l-1.38.8z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="javascript-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#ffca28"/><path d="M2.444 2.506h14.135v14.136H2.444V2.506m3.714 11.811c.315.668.935 1.218 1.995 1.218 1.178 0 1.987-.629 1.987-2.003V8.993H8.805v4.508c0 .675-.275.848-.707.848-.455 0-.644-.314-.856-.683l-1.084.651m4.697-.14c.392.769 1.185 1.358 2.426 1.358 1.257 0 2.199-.652 2.199-1.854 0-1.107-.636-1.602-1.767-2.089l-.33-.141c-.573-.243-.816-.408-.816-.801 0-.322.243-.573.636-.573.377 0 .628.165.856.573l1.028-.683c-.432-.754-1.044-1.045-1.884-1.045-1.186 0-1.948.754-1.948 1.752 0 1.083.636 1.594 1.594 2.002l.33.141c.613.267.974.432.974.888 0 .377-.354.652-.903.652-.652 0-1.029-.338-1.312-.81l-1.083.63z" fill="#ffca28"/></symbol><symbol viewBox="0 0 180 180" id="jenkins" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="gia"><path transform="scale(1 -1)" fill="#37474f" d="M.899-144.42h144.42V0H.899z"/></clipPath></defs><g transform="matrix(1.0691 0 0 -1.0691 9.4 166.143)" clip-path="url(#gia)"><g fill-rule="evenodd"><path d="M107.96 30.661l-12.506-1.876-16.883-1.876-10.943-.312-10.629.312-8.13 2.502-7.19 7.815-5.628 15.945-1.25 3.44-7.504 2.5-4.377 7.191-3.126 10.317 3.44 9.067 8.128 2.814 6.565-3.127 3.127-6.878 3.752.626 1.25 1.563-1.25 7.19-.313 9.068 1.876 12.505-.074 7.143 5.701 9.114 10.005 7.19 17.508 7.504 19.383-2.814 16.883-12.193 7.817-12.505 5.002-9.067 1.25-22.51-3.752-19.384-6.877-17.195-6.566-9.066" fill="#f0d6b7"/><path d="M97.334-23.425l-44.709-1.876v-7.503l3.752-26.262-1.876-2.19-31.264 10.63-2.19 3.752-3.126 35.328-7.19 21.26-1.563 5.002 25.01 17.195 7.818 3.127 6.877-8.441 5.94-5.315 6.88-2.188 3.125-.938L68.57 1.899l2.814-3.44 7.19 2.502-5.002-9.693 27.2-12.818-3.439-1.876" fill="#335061"/><path d="M23.238 85.687l8.128 2.814 6.566-3.127 3.127-6.878 3.751.626.938 3.751-1.876 7.19 1.876 17.197-1.563 9.379 5.627 6.565 12.193 9.692-3.44 4.69-17.194-8.442-7.191-5.627-4.064-8.754-6.253-8.442-1.876-10.005 1.251-10.63" fill="#6d6b6d"/><path d="M36.055 115.07s4.69 11.567 23.448 17.195c18.759 5.628.938 4.065.938 4.065l-20.321-7.817-7.817-7.816-3.438-6.253 7.19.626M26.676 87.875s-6.566 21.886 18.446 25.012l-.938 3.752-17.195-4.065-5.003-16.257 1.251-10.63 3.439 2.188" fill="#dcd9d8"/></g><g fill="#f7e4cd"><path d="M36.681 58.799l4.094 3.966s1.847-.214 2.16-2.402c.312-2.19 1.25-21.886 14.693-32.516 1.227-.97-10.004 1.564-10.004 1.564L37.62 45.042M94.209 64.739s.729 9.477 3.28 8.748c2.553-.729 2.553-3.28 2.553-3.28s-6.198-4.01-5.833-5.468" fill-rule="evenodd"/><path d="M120.16 99.442s-5.153-1.088-5.628-5.628c-.474-4.54 5.628-.938 6.566-.625M82.327 99.129s-6.879-.938-6.879-5.314c0-4.378 7.817-4.065 10.005-2.19"/><g fill-rule="evenodd"><path d="M39.807 78.808s-11.881 7.191-13.131.312c-1.25-6.877-4.065-11.88 1.876-19.07l-4.064 1.25-3.752 9.691-1.25 9.38 7.19 7.504 8.129-.626 4.69-3.751.312-4.69M45.435 98.504s5.315 27.512 32.203 32.827c22.136 4.375 33.765-.938 38.142-5.94 0 0-19.696 23.447-38.455 16.257-18.759-7.191-32.514-20.322-32.202-28.762.532-14.377.313-14.382.313-14.382M117.97 122.27s-9.066.312-9.38-7.817c0 0 0-1.25.625-2.5 0 0 7.192 8.129 11.568 3.751"/><path d="M78.268 111.1s-1.56 12.477-12.199 5.223c-6.878-4.69-6.252-11.255-5.002-12.505s.91-3.77 1.862-2.04c.952 1.728.638 7.356 4.078 8.918 3.439 1.564 9.077 3.31 11.26.404"/></g></g><g fill="#49728b" fill-rule="evenodd"><path d="M48.874 26.597L19.486 13.466s12.193-48.46 5.94-63.467l-4.377 1.563-.313 18.446-8.128 35.015-3.44 9.692 30.639 20.633 9.067-8.753M51.896-.206l4.17-5.087v-18.76h-5.003s-.625 13.132-.625 14.696c0 1.563.624 7.19.624 7.19M52-26.866l-14.069-.625 4.065-2.813L52-31.868"/></g><g fill-rule="evenodd"><path d="M100.15-23.739l11.567.313 2.814-28.764-11.881-1.563-2.5 30.014" fill="#335061"/><path d="M103.27-23.739l17.508.938s7.19 18.133 7.19 19.07c0 .939 6.253 26.263 6.253 26.263l-14.069 14.694-2.813 2.501-7.504-7.503V3.148l-6.565-26.887" fill="#335061"/><path d="M111.09-21.55l-10.942-2.188 1.563-8.755c4.064-1.876 10.943 3.127 10.943 3.127M111.4 33.162l21.885-16.257.626 7.503-16.57 15.32-5.94-6.566" fill="#49728b"/><path d="M62.85-85.332l-6.473 26.266-3.22 19.38-.531 14.385 29.296 1.56 18.226.003-1.658-32.83 2.814-25.324-.312-4.69-23.76-1.876-14.382 3.126" fill="#fff"/><path d="M96.083-23.426s-1.563-32.515 3.127-55.65c0 0-9.38-5.94-23.136-7.503l26.262.938 3.126 1.875-3.752 51.273-.938 10.944" fill="#dcd9d8"/><path d="M115.06-49.691l12.193 3.44 23.135 1.25 3.44 10.629-6.254 18.446-7.19.938-10.005-3.127-9.599-4.686-5.095.935-3.972-1.56" fill="#fff"/><path d="M114.84-43.435s8.128 3.751 9.38 3.438L120.78-22.8l4.065 1.563s2.814-16.257 2.814-18.133c0 0 17.507-.938 19.07-.938 0 0 3.752 7.191 2.814 14.694l3.44-10.005.312-5.628-5.002-7.503-5.627-1.25-9.38.312-3.126 4.064-10.943-1.563-3.44-1.25" fill="#dcd9d8"/></g><path d="M102.56-21.241L95.682-3.733l-7.19 10.317s1.562 4.377 3.75 4.377h7.192l6.878-2.501-.625-11.568-3.127-18.134" fill="#fff"/><path d="M103.9-15.297S95.145 1.585 95.145 4.086c0 0 1.563 3.752 3.752 2.814 2.19-.938 6.879-3.439 6.879-3.439v5.94l-10.63 2.19-7.19-.939 12.193-28.763 2.5-.313" fill="#dcd9d8" fill-rule="evenodd"/><path d="M65.664 25.968l-8.661.942-8.13 2.501v-2.814l3.972-4.38 12.506-5.627" fill="#fff"/><path d="M51.689 25.031s9.693-4.065 12.819-3.127l.311-3.748-8.752 1.872-5.316 3.752.938 1.251" fill="#dcd9d8" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43" fill="#d33833" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669" fill="#d33833" fill-rule="evenodd"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695" fill="#d33833" fill-rule="evenodd"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M102.87 10.649s-2.19 3.127-.626 4.065c1.564.938 3.127 0 4.065 1.563s0 2.501.313 4.377 1.877 2.189 3.44 2.501c1.562.313 5.94.938 6.565-.625l-1.876 5.627-3.752 1.25-11.88-6.877-.626-3.44v-6.877M70.041.331c-.376 4.88-.773 9.752-1.215 14.626-.662 7.279 1.748 6.009 8.057 6.009.964 0 5.933-1.15 6.289-1.876 1.705-3.483-2.851-2.709 1.964-5.335 4.065-2.216 11.246 1.346 9.603 6.273-.919 1.095-4.789.341-6.176 1.06l-7.327 3.8c-3.108 1.612-10.29 3.962-13.603 1.709-8.395-5.71.53-19.974 3.524-25.93" fill="#ef3d3a" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M78.268 111.1c-8.521 1.985-12.755-3.566-15.338-9.323-2.306.559-1.389 3.695-.806 5.294 1.525 4.194 7.672 9.778 12.694 9.02 2.161-.325 5.086-2.301 3.45-4.99M119.79 101.4l.404-.016c1.926-4 3.593-8.238 6.022-11.769-1.628-3.79-12.322-7.144-12.157-.338 2.313 1.01 6.305.206 8.356 1.497-1.186 3.254-2.897 6.024-2.625 10.626M82.63 101.29c1.827-3.35 2.422-6.868 5.019-9.4 1.17-1.14 3.444-2.529 2.316-5.698-.263-.747-2.189-2.414-3.3-2.741-4.06-1.2-13.521-.248-10.317 4.814 3.358-.157 7.871-2.18 10.38.257-1.927 3.081-5.363 9.177-4.098 12.768M118.26 67.253c-6.113-3.927-12.93-8.197-22.947-7.207-2.14 1.86-2.956 6.002-.877 8.737 1.082-1.861.402-5.284 3.419-5.799 5.684-.972 12.299 3.477 16.387 5.032 2.535 4.275-.219 5.847-2.503 8.597-4.675 5.636-10.947 12.622-10.72 21.06 1.89 1.37 2.053-2.092 2.325-2.722 2.44-5.714 8.585-13.021 13.07-17.912 1.1-1.205 2.914-2.36 3.115-3.157.582-2.315-1.513-5.09-1.27-6.63M37.668 71.387c-1.916 1.094-2.372 5.91-4.622 6.048-3.215.195-2.629-6.25-2.616-10.018-2.213 2.009-2.602 8.194-.976 11.37-1.853.91-2.68-1.003-3.708-1.677 1.32 9.595 14.036 4.45 11.922-5.723M122.15 63.257c-2.846-5.417-6.871-11.382-15.222-11.555-.17 1.75-.3 4.411.009 5.464 6.384.614 10.325 3.863 15.212 6.091M82.149 59.745c5.326-2.8 15.114-3.102 22.353-2.89.388-1.586.379-3.545.394-5.48-9.305-.463-20.307 1.84-22.747 8.37M81.136 54.523c3.683-9.247 16.341-8.182 27.016-7.927-.47-1.2-1.489-2.62-2.755-3.132-3.42-1.392-12.855-2.448-17.604.074-3.011 1.601-4.946 5.219-6.596 7.34-.797 1.024-4.765 3.64-.06 3.645"/></g><path d="M117.82 3.516c-4.322-7.402-8.457-15.005-13.585-21.534 2.15 6.32 3.07 16.9 3.394 24.965 4.498 2.105 8.349-.474 10.191-3.43" fill="#81b0c4" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M141.07-23.089c-4.839-.969-8.239-5.671-12.959-5.37 2.594 3.658 7.14 5.2 12.959 5.37M143.21-30.661c-3.944-.417-8.576-1.055-12.577-.726 1.894 2.892 9.19 1.894 12.577.726M144.58-37.19c-4.433-.096-9.942-.008-14.155.346 2.492 2.677 11.28.993 14.155-.346"/></g><g fill-rule="evenodd"><path d="M109.48-55.057c.636-5.567 2.843-11.207 2.566-17.304-2.45-.827-3.858-1.55-7.142-1.545-.232 5.181-.925 13.102-.718 18.041 1.615-.107 3.997 1.154 5.294.808" fill="#dcd9d8"/><path d="M102.33 26.985c-2.226-1.453-4.121-3.267-6.259-4.818-4.74-.235-7.327.328-10.81 3.05.057.219.407.121.42.39 5.075-2.262 11.524.92 16.648 1.378" fill="#f0d6b7"/><path d="M75.694-7.603c1.394 6.04 6.857 9.17 11.817 12.497 5.12-6.498 8.234-14.855 11.663-22.92-8.102 2.443-16.38 6.406-23.481 10.423" fill="#81b0c4"/><path d="M104.18-55.865c-.207-4.94.486-12.86.718-18.041 3.283-.004 4.691.718 7.142 1.545.276 6.096-1.93 11.737-2.566 17.304-1.298.346-3.679-.914-5.294-.808zm-51.13 28.09c2.165-19.906 5.301-36.639 11.054-54.266 12.766-3.876 28.157-4.214 39.441-.716-2.072 9.948-1.167 22.06-2.378 32.677-.912 7.98-.447 16.009-1.698 24.15-13.673 2.844-33 .665-46.418-1.845zm49.651 1.72c-.115-8.549.383-16.982 1.036-25.542 3.282.493 5.51.822 8.56 1.49-.99 8.241-.869 17.514-2.886 24.804-2.332-.023-4.385.027-6.71-.752zm16.653 1.378c-1.558.357-3.372.014-4.86-.015.7-6.969 2.397-14.659 2.995-21.974 2.342-.073 3.593 1.032 5.52 1.403.102 6.421-.562 15.268-3.655 20.586zm25.215-23.038c4.882 1.186 7.952 7.165 6.586 13.305-.916 4.127-2.548 11.898-4.295 14.538-1.29 1.953-4.79 4.51-7.584 2.72-4.545-2.91-12.552-3.755-15.867-7.278 1.662-5.534 2.178-13.135 2.864-20.146 5.678-.354 12.665 1.562 17.387-.471-3.297-1.068-7.575-1.077-10.423-2.633 2.328-1.125 7.778-.897 11.332-.035zM99.17-18.025c-3.43 8.063-6.543 16.42-11.663 22.918-4.96-3.327-10.423-6.456-11.817-12.497 7.1-4.017 15.379-7.98 23.481-10.422zm8.453 24.971c-.325-8.065-1.245-18.644-3.395-24.965 5.128 6.53 9.263 14.132 13.585 21.534-1.842 2.957-5.693 5.536-10.19 3.431zm-9.582 3.405c-1.943.21-3.592-2.233-6.117-1.177-.58-.64-1.105-1.333-1.695-1.958 5.579-6.723 8.114-16.262 12.423-24.163 2.312 7.59 2.045 15.904 2.555 24.188-3.177-.201-4.94 2.873-7.166 3.11zm-6.161 8.132c-.208-2.303.328-3.056.791-5.695 7.57-2.367 6.248 10.388-.791 5.695zm-8.394 2.755c-3.261 1.782-8.161 3.723-12.374 4.527-5.222.999-4.732-7.123-4.51-11.968.173-3.836 2.168-7.893 3.035-10.441.406-1.19.498-2.453 1.515-2.69 1.798-.418 7.73 1.954 9.42 2.875 3.575 1.95 6.348 5.045 9.384 7.123.04 1.011.078 2.021.119 3.032-1.826.91-3.935 1.555-6.615 1.673 1.818.914 4.492.901 6.148 1.989.016.405.033.81.047 1.21-3.024.234-4.176 1.58-6.17 2.67zm-31.152 5.659c-2.707-2.748 7.592-6.494 10.871-6.696-.018 1.739.991 3.378.788 4.626-3.895.684-9.013.232-11.66 2.07zm33.345-1.29c-.013-.27-.363-.172-.42-.39 3.482-2.722 6.07-3.285 10.81-3.05 2.137 1.551 4.033 3.365 6.259 4.818-5.124-.458-11.574-3.64-16.648-1.379zm30.606-9.282c-.146 3.053-.948 9.332-2.835 10.431-3.961 2.312-11.002-4.668-13.984-5.732.324-.934.86-1.674.901-2.868 1.764.434 3.912.137 5.44-.615-1.767-.198-3.727-.185-4.897-1.027-.429-1.239.105-2.927-.18-4.647 4.196-1.184 8.989-1.814 14.294-1.97 1.032 1.341 1.383 3.896 1.261 6.429zM47.777 24.24c-.85.606-6.6 8.087-7.388 7.777-10.405-4.103-20.134-11.199-28.828-17.91 8.29-17.787 11.635-39.579 12.227-60.582 9.496-4.441 17.836-10.844 30.722-11.512-1.491 10.55-2.852 19.962-3.699 29.895-3.237 1.365-7.882-.062-10.913.423-.025 3.651 4.628 1.6 5.015 4.054.292 1.858-2.56 1.998-1.631 4.923 2.368-.861 3.612-2.763 6.138-3.477 2.309 5.05-.032 13.985.3 18.205.064.792.397 4.39 2.172 3.759 1.57-.559-.09-9.569.082-13.563.157-3.68-.444-7.242 1.046-9.552a355.817 355.817 0 0 0 38.576 3.16c-2.964 1.272-6.485 2.475-10.345 4.651-2.093 1.18-8.69 3.635-9.293 5.622-.964 3.167 2.528 4.855 3.125 7.57-6.285-3.428-7.511 3.286-8.998 8.042-1.347 4.308-2.114 7.526-2.445 10.01-5.414 2.581-11.203 5.195-15.863 8.505zm63.009 6.872c8.67 4.204 10.232-15.711 6.834-22.127.525-1.914 2.331-2.646 3.069-4.366-4.838-8.667-10.211-16.756-15.148-25.32 3.672 2.286 8.917.409 13.238 2.12 1.58.624 2.722 4.24 3.918 7.133 3.29 7.958 6.743 17.99 8.28 25.586.346 1.73 1.292 5.5 1.08 7.04-.378 2.758-4.12 4.803-6.022 6.508-3.506 3.15-5.714 5.921-9.371 8.866-1.483-2.189-4.666-3.66-5.878-5.44zM27.95 107.99c-4.13-4.545-3.266-13.062-2.766-19.121 7.467 4.697 17.377-.372 17.284-8.36 3.565.094 1.332 4.452.687 7.259-2.107 9.169 3.55 19.13.256 27.516-6.395-.485-11.649-3.097-15.46-7.294zm29.558 26.38c-9.352-2.65-21.337-9.446-25.18-17.847 2.976.432 5.041 1.933 7.977 2.119 1.11.072 2.563-.466 3.838-.148 2.54.63 4.685 6.327 6.602 8.447 1.868 2.07 4.114 2.954 5.651 4.841.988.477 2.448.444 2.504 1.927-.428.457-.879.806-1.392.66zm48.681-2.493c-9.707 5.477-26.136 9.596-36.462 4.449-8.331-4.155-19.593-11.027-23.433-19.737 3.587-8.405-1.062-16.106-1.36-24.64-.157-4.54 2.139-8.504 2.315-13.446-1.228-2.025-4.978-2.275-7.574-2.136-.873 4.372-2.403 9.287-6.906 9.78-6.371.697-11.03-4.576-11.319-10.085-.342-6.48 4.978-17.22 12.517-16.475 2.913.287 3.629 3.207 6.802 3.177 1.72-3.432-2.653-4.51-3.103-6.964-.117-.634.363-3.112.642-4.274 1.37-5.658 4.422-12.982 7.427-17.29 3.814-5.464 11.307-6.288 19.37-6.823 1.44 3.101 6.743 2.846 10.2 2.035-4.143 1.64-7.993 5.617-11.185 9.137-3.665 4.039-7.378 8.371-7.566 13.65 6.927-9.61 12.65-18.003 25.246-22.23 9.53-3.196 20.662 1.465 27.986 6.608 3.039 2.137 4.853 5.529 7.013 8.634 8.082 11.626 11.854 28.219 11.024 44.303-.342 6.633-.327 13.244-2.552 17.706-2.326 4.666-10.193 8.84-14.8 4.62-.853 4.537 3.83 7.344 9.331 5.71-3.922 5.063-8.039 11.145-13.614 14.29zm18.084-149.66c7.585 3.77 21.757 10.149 26.512-.014 1.755-3.746 3.814-10.079 4.723-13.946 1.284-5.456-1.392-16.923-7-18.754-4.953-1.617-10.733-1.518-16.7-.32-.702.585-1.484 1.603-2.03 2.665-4.261.165-8.25-.229-11.615-1.98.319-3.15-1.812-3.656-3.81-4.305-1.48-5.872 2.963-13.541 1.9-18.896-.76-3.815-5.453-4.405-8.902-5.118-.113-2.12.15-3.89.386-5.683-.789-2.907-4.327-4.561-7.679-4.967-11.029-1.326-27.775-1.922-38.384 1.893-2.96 7.261-5.292 16.093-7.758 24.384-10.346-1.105-18.715 4.464-26.603 8.113-2.731 1.266-6.51 1.964-7.53 4.138-.99 2.105-.584 6.14-.83 9.95-.625 9.733-1.16 19.12-3.73 29.086-1.154 4.472-3.165 8.418-4.568 12.727C9.358 5.184 7.092 10.12 6.5 14.1c-.877 5.903 4.681 6.232 8.235 8.79 5.494 3.954 9.806 6.142 15.756 9.711 1.762 1.057 7.077 3.733 7.681 4.966 1.202 2.443-2.062 5.888-2.935 7.803-1.38 3.03-2.1 5.602-2.298 8.59-4.992.789-8.775 3.76-11.06 7.109-3.781 5.543-6.403 15.798-3.132 23.599.257.614 1.536 1.822 1.725 2.765.372 1.858-.7 4.329-.768 6.305-.343 10.14 1.716 18.875 8.541 21.932 2.771 11.038 12.688 14.71 22.032 20.195 3.493 2.05 7.343 3.36 11.32 4.824 14.263 5.25 36.15 4.261 47.987-4.692 5.02-3.797 13.044-11.813 15.914-17.617 7.58-15.323 7.042-40.931 1.74-59.571-.712-2.503-1.746-6.181-3.19-9.187-1.006-2.1-4.134-6.3-3.754-8.153.391-1.916 7.132-7.034 8.577-8.428 2.603-2.51 7.548-5.843 7.948-9.012.43-3.372-1.485-7.984-2.456-11.238-3.245-10.858-6.412-20.895-10.091-30.576" fill="#231f20"/><path d="M73.674 57.38c.411.548 2.674 1.38 5.84-.144 0 0-3.752-.626-3.44-6.881l-1.564.313s-1.615 5.672-.836 6.712" fill="#f7e4cd"/><path d="M101.09 3.617a1.72 1.72 0 1 0-3.44.001 1.72 1.72 0 0 0 3.44-.001M102.81-4.355a1.72 1.72 0 1 0-3.44 0 1.72 1.72 0 0 0 3.44 0" fill="#1d1919"/></g><g><rect transform="matrix(.8 0 0 -.8 0 144)" x="16.854" y="177.38" width="70.412" height="4.12" rx=".983" ry=".983"/><rect transform="scale(1 -1)" x="78.502" y="-2.097" width="50.037" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="13.483" y="-3.697" width="54.831" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="83.296" y="-3.697" width="45.243" height="3.296" rx=".786" ry=".786"/></g></g></symbol><symbol viewBox="0 0 24 24" id="json" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#fbc02d"/></symbol><symbol viewBox="0 0 50 50" id="julia" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" stroke-width="5.673"><circle cx="13.497" cy="281.632" r="9.555" fill="#bc342d"/><circle cx="36.081" cy="281.632" r="9.555" fill="#864e9f"/><circle cx="24.722" cy="262.389" r="9.555" fill="#328a22"/></g></symbol><symbol viewBox="0 0 64 64" id="karma" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -233)"><path d="M38.556 288.413l-20.29-26.687 9.532-7.246 20.29 26.686h-.001.002l5.527 7.247z" fill="#359b8b" stroke-width=".173"/><path d="M35.681 241.172L24.92 255.327v-14.13H12.947v13.817l7.84 33.235h4.132v-13.147l.003.003 20.29-26.686-.008-.006 5.504-7.24H35.84v.12z" fill="#3cbeae" stroke-width=".206"/></g></symbol><symbol viewBox="0 0 24 24" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7 14a2 2 0 0 1-2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1-2 2m5.65-4A5.99 5.99 0 0 0 7 6a6 6 0 0 0-6 6 6 6 0 0 0 6 6 5.99 5.99 0 0 0 5.65-4H17v4h4v-4h2v-4H12.65z" fill="#26a69a"/></symbol><symbol viewBox="0 0 24 24" id="kivy" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.89 0 0 1.89 -12.157 -11.429)" fill="#90a4ae"><path d="M7.026 8.63v4.474l1.928-1.928a.437.437 0 0 0 0-.619zM9.38 16.072v-4.474l-1.927 1.927a.437.437 0 0 0 0 .62zM18.576 10.412l-5.346.564-.017.018 2.39 2.39zM9.922 8.502s.023 3.304-.003 4.452c-.02.856.371 1.114.746 1.507.538.564 1.599 1.57 1.599 1.57a.53.53 0 0 0 .75 0l1.843-1.844a.53.53 0 0 0 0-.75z"/></g></symbol><symbol viewBox="0 0 24 24" id="kl" xmlns="http://www.w3.org/2000/svg"><defs><style>.a{fill:#3aaae1}.b{fill:#fdfeff}</style></defs><title>kl</title><path d="M12.033 1.737c-.25-.003-.5.11-.729.337C8.225 5.15 5.15 8.227 2.078 11.31c-.144.144-.229.346-.341.521v.41c.16.223.294.474.485.666a3259.51 3259.51 0 0 0 8.936 8.937c.193.192.443.325.666.486h.41c.205-.142.436-.256.609-.428 3.046-3.041 6.09-6.083 9.133-9.127.47-.47.472-1.005.006-1.472l-9.218-9.217c-.23-.23-.48-.347-.731-.35zm-1.062 4.545l1.386.832c.702.422 1.403.846 2.109 1.262a.544.544 0 0 1 .04.026l.016.013.017.013c.061.056.089.123.088.224a510.281 510.281 0 0 0 0 3.794.463.463 0 0 1-.007.094c-.015.069-.054.103-.142.109a.464.464 0 0 1-.044.002c-.045-.002-.09-.002-.136-.003-.323-.006-.648-.001-.998-.001v-.527-1.34-.671-.003l.004-.668c0-.147-.039-.231-.17-.308-.893-.528-1.78-1.066-2.67-1.6-.051-.03-.101-.065-.173-.111l.001-.003h-.001zm.362 3.39c.068-.003.119.043.173.138.085.148.174.293.264.44l.015.025c.096.154.194.31.292.47l-1.915 1.176c-.337.207-.673.417-1.014.617-.113.067-.154.143-.154.277.01.977.01 1.955.014 2.932V16H7.7V16h-.002c-.004-.053-.014-.112-.014-.17-.005-1.25-.006-2.501-.015-3.751 0-.142.045-.222.164-.294a467.13 467.13 0 0 0 3.353-2.054l.016-.01a.606.606 0 0 1 .032-.017l.016-.008a.308.308 0 0 1 .033-.013l.012-.004a.157.157 0 0 1 .028-.005l.01-.001zm5.677 3.126l.314.54.346.594v.001c-.158.094-.298.178-.438.259l-3.097 1.798c-.106.062-.189.071-.3.01l-.893-.496-1.524-.843-.895-.493c-.035-.02-.068-.044-.129-.085h.001l.137-.25.495-.902 1.446.795c.442.243.886.483 1.323.734.121.07.212.072.334 0 .894-.525 1.792-1.043 2.689-1.563.057-.034.118-.061.191-.1z" fill="#29b6f6" stroke-width=".041"/></symbol><symbol viewBox="0 0 24 24" id="kotlin" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="gpb"><stop offset="0" stop-color="#cb55c0"/><stop offset="1" stop-color="#f28e0e"/></linearGradient><linearGradient id="gpa"><stop offset="0" stop-color="#0296d8"/><stop offset="1" stop-color="#8371d9"/></linearGradient><linearGradient xlink:href="#gpa" id="gpc" x1="1.725" y1="22.67" x2="22.185" y2="1.982" gradientUnits="userSpaceOnUse" gradientTransform="translate(1.638 1.155) scale(.89324)"/><linearGradient xlink:href="#gpb" id="gpd" x1="1.869" y1="22.382" x2="22.798" y2="3.377" gradientUnits="userSpaceOnUse" gradientTransform="translate(1.638 1.155) scale(.89324)"/></defs><path d="M3.307 3.003v18.048h18.05v-.03L16.88 16.51l-4.48-4.515 4.48-4.515 4.443-4.477H3.307z" fill="url(#gpc)"/><path d="M12.538 3.003l-9.23 9.23v8.818h.083l9.032-9.032-.025-.024 4.48-4.515 4.444-4.477h-8.784z" fill="url(#gpd)"/></symbol><symbol viewBox="0 0 240 240" id="laravel" xmlns="http://www.w3.org/2000/svg"><path d="M216.05 119.036c-1.433.343-24.945 6.673-24.945 6.673l-19.227-28.622c-.537-.828-.99-1.656.359-1.849 1.345-.196 23.195-4.477 24.182-4.723.99-.245 1.837-.536 3.053 1.267 1.21 1.8 17.836 24.626 18.464 25.506.627.877-.447 1.41-1.883 1.748m-4.101 49.326c.588 1.003 1.176 1.64-.67 2.367-1.843.73-62.243 22.847-63.418 23.39-1.173.546-2.092.73-3.607-1.637-1.51-2.362-21.16-39.264-21.16-39.264l64.03-18.075c1.876-.644 2.317-.405 3.103.822 1.074 1.68 21.143 31.403 21.726 32.4m-103.7-21.087c-.78.202-37.566 9.733-39.525 10.22-1.965.485-1.965.246-2.188-.49-.226-.727-43.728-98.053-44.333-99.271-.605-1.214-.574-2.177 0-2.177.571 0 34.734-3.313 35.944-3.383 1.207-.07 1.08.205 1.526 1.033l49.025 91.818c.84 1.58 1.239 1.81-.452 2.248m94.588-59.77c-3.5-4.58-5.2-3.751-7.357-3.41-2.154.336-27.277 4.915-30.194 5.449-2.918.536-4.758 1.803-2.963 4.53 1.597 2.422 18.113 27.824 21.751 33.42l-65.663 17.066L66.18 49.832c-2.075-3.342-2.507-4.514-7.236-4.28-4.735.23-40.969 3.495-43.55 3.731-2.58.233-5.416 1.479-2.835 8.09 2.583 6.612 43.734 102.82 44.88 105.62 1.149 2.803 4.128 7.345 11.11 5.527 7.157-1.871 31.969-8.894 45.52-12.742 7.163 14.07 21.77 42.619 24.473 46.707 3.607 5.459 6.089 4.56 11.626 2.738 4.325-1.42 67.65-26.129 70.502-27.4 2.855-1.273 4.613-2.184 2.685-5.275-1.419-2.28-18.124-26.558-26.876-39.26 5.993-1.733 27.305-7.888 29.575-8.557 2.646-.779 3.008-2.19 1.572-3.94-1.436-1.755-21.293-28.72-24.79-33.296z" fill="#ff5722" stroke="#ff5722" stroke-width="8.852" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="less" xmlns="http://www.w3.org/2000/svg"><path d="M13.696 2.999V5h2.002v5a2 2 0 0 0 1.999 2 2 2 0 0 0-2 2v5h-2v2h2a2 2 0 0 0 2-2v-4a2 2 0 0 1 2-2h1V11h-1a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2.001zm-.03 12.766v.47a1 1 0 0 0 .03-.236 1 1 0 0 0-.03-.234zM10.566 21v-2.001H8.565v-5a2 2 0 0 0-2-2 2 2 0 0 0 2-2V5h2.001v-2H8.565a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-.999V13h1a2 2 0 0 1 2 2v3.999A2 2 0 0 0 8.564 21zm.03-12.766v-.47a1 1 0 0 0-.03.236 1 1 0 0 0 .03.234z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="lib" xmlns="http://www.w3.org/2000/svg"><path d="M19 7H9V5h10m-4 10H9v-2h6m4-2H9V9h10m1-7H8a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2M4 6H2v14a2 2 0 0 0 2 2h14v-2H4V6z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 40 40" id="livescript" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -257)" fill="#317eac"><path stroke-width="3.299" d="M5.419 260.18h3.685v34.207H5.419z"/><path stroke-width="3.299" d="M37.074 288.197v3.685H2.867v-3.685z"/><path stroke-width="2.894" d="M29.612 265.658l2.004 2.005L7.428 291.85l-2.004-2.005z"/><path stroke-width="2.325" d="M10.73 262.471h2.835v22.08H10.73z"/><path stroke-width="2.063" d="M15.36 262.519h2.835v17.382H15.36z"/><path stroke-width="1.77" d="M19.99 262.471h2.835v12.802H19.99z"/><path stroke-width="1.422" d="M24.526 262.491h2.835v8.254h-2.835z"/><path stroke-width="1.128" d="M28.783 262.463h2.835v5.197h-2.835z"/><path stroke-width="2.325" d="M34.801 286.545v-2.835h-22.08v2.835z"/><path stroke-width="2.063" d="M34.753 281.914v-2.835H17.371v2.835z"/><path stroke-width="1.77" d="M34.801 277.284v-2.835H21.999v2.835z"/><path stroke-width="1.422" d="M34.781 272.749v-2.835h-8.254v2.835z"/><path stroke-width="1.128" d="M34.809 268.492v-2.835h-5.197v2.835z"/></g></symbol><symbol viewBox="0 0 24 24" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="lua" xmlns="http://www.w3.org/2000/svg"><circle cx="12.203" cy="12.102" r="10.322" fill="none" stroke="#42a5f5"/><path d="M12.33 5.746a6.483 6.381 0 0 0-6.482 6.381 6.483 6.381 0 0 0 6.482 6.38 6.483 6.381 0 0 0 6.484-6.38 6.483 6.381 0 0 0-6.484-6.38zm1.86 1.916a2.329 2.292 0 0 1 2.33 2.293 2.329 2.292 0 0 1-2.33 2.291 2.329 2.292 0 0 1-2.329-2.29 2.329 2.292 0 0 1 2.328-2.294z" fill="#42a5f5" fill-rule="evenodd"/><ellipse cy="4.615" cx="19.631" rx="2.329" ry="2.292" fill="#42a5f5" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="markdown" xmlns="http://www.w3.org/2000/svg"><path d="M2 16V8h2l3 3 3-3h2v8h-2v-5.17l-3 3-3-3V16H2m14-8h3v4h2.5l-4 4.5-4-4.5H16V8z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" preserveAspectRatio="xMidYMid" id="markojs" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -120.96)" stroke-width=".984"><path d="M4.002 126.482c-.655 1.07-1.32 2.14-1.976 3.21-.655 1.06-1.308 2.142-1.963 3.212l.002.002-.002.002c.655 1.07 1.308 2.15 1.963 3.211.655 1.07 1.32 2.141 1.976 3.211h3.33c-.664-1.07-1.318-2.14-1.974-3.21-.653-1.069-1.307-2.145-1.961-3.214.654-1.068 1.308-2.146 1.961-3.215a601.93 601.93 0 0 1 1.974-3.209z" fill="#2196f3"/><path d="M3.999 126.482l-.002.002c.655 1.07 1.31 2.15 1.964 3.212.655 1.07 1.32 2.14 1.974 3.21h3.331c-.664-1.07-1.319-2.14-1.974-3.21-.653-1.068-1.306-2.146-1.96-3.214z" fill="#26a69a"/><path d="M15.203 126.482l.002.002c-.655 1.07-1.31 2.15-1.965 3.212-.655 1.07-1.319 2.14-1.974 3.21h-3.33c.664-1.07 1.318-2.14 1.973-3.21.654-1.069 1.307-2.146 1.961-3.214z" fill="#8bc34a"/><path d="M11.874 126.484c.664 1.07 1.318 2.14 1.974 3.21.653 1.068 1.307 2.146 1.961 3.214-.654 1.069-1.308 2.145-1.961 3.213-.656 1.07-1.31 2.14-1.974 3.21h3.33c.655-1.07 1.319-2.14 1.974-3.21.655-1.06 1.31-2.14 1.966-3.21l-.002-.003.002-.002c-.656-1.07-1.311-2.152-1.966-3.213-.655-1.07-1.319-2.138-1.974-3.209z" fill="#ffc107"/><path d="M16.74 126.482c.665 1.07 1.319 2.14 1.974 3.21.654 1.068 1.306 2.146 1.96 3.214-.654 1.069-1.306 2.145-1.96 3.213-.655 1.07-1.31 2.141-1.974 3.211h3.33c.656-1.07 1.32-2.14 1.974-3.21.655-1.062 1.31-2.141 1.966-3.212l-.002-.002.002-.002c-.655-1.07-1.31-2.152-1.966-3.213-.655-1.07-1.318-2.138-1.973-3.209z" fill="#f44336"/></g></symbol><symbol viewBox="0 0 23 24" id="mathematica" xmlns="http://www.w3.org/2000/svg"><path d="M11.512 1.523l-.073.025-.46.794-.454.763-1.217 2.09H9.29L5.435 3.5l-.1-.047h-.018v.092l.025.163v.086l.132 1.226v.082l.032.252v.082l.22 2.137v.075l.018.082v.06l-2.348.507-.04.015-.457.1-.025.01h-.042l-1.096.244-.04.007-.17.036v.082l.018.01 1.859 2.086.053.052.114.132.804.909v.005l-.053.05-.22.257-2.564 2.875-.01.007v.082l.071.006.295.075 1.697.366v.006l2.139.472h.015v.047l-.036.252v.08l-.046.412v.082l-.036.244v.082l-.045.412v.08l-.05.41v.08l-.036.244v.082l-.046.412v.082l-.05.407v.082l-.032.248V20l-.05.407v.104h.037l3.642-1.6.294-.134h.018l.177.312.539.911.015.032.854 1.465.16.262.404.695.007.022h.092l.005-.022.017-.025.56-.947.014-.042.6-1.033.316-.539.644-1.091.05.013 3.906 1.721h.035v-.085l-.138-1.32v-.082l-.032-.244v-.082l-.035-.245v-.085l-.033-.244v-.081l-.032-.245v-.082l-.032-.244v-.085l-.035-.245v-.082l-.032-.245v-.082l-.033-.244v-.085l-.025-.17v-.053l1.632-.354.043-.008.458-.107h.028v-.01l.23-.05.03-.01h.042l.382-.09.025-.01h.043l.194-.05h.033l1.015-.23.07-.007v-.064l-.015-.013-1.19-1.342-.028-.028-.197-.22-1.428-1.604v-.006l.295-.323.4-.457 2.148-2.408.015-.01v-.065l-.035-.008-1.288-.28-.372-.084-.047-.01-2.481-.544v-.045l.432-4.265v-.02h-.042l-.302.135-.01.014h-.025l-3.307 1.45-.297.135h-.015l-2.028-3.483-.099-.145-.014-.045zm-.001 1.114l1.365 2.323.34.592-.008.025-1.18 1.511-.517.66-.012-.01-.258-.335-.04-.05-1.397-1.787.03-.063 1.378-2.365.287-.491zm4.908 2.039l-.007.025-.168.225-.538.066zm-9.817.004l.053.02.677.3h-.499l-.224-.3zM16.947 5l-.123 1.248-.113-.928.226-.307zm-9.26.156l.053.024.705.309-.757-.175zm7.388.116l.02.168-1.318.403.003-.003.16-.071 1.015-.444zM9.669 6.388l.944 1.204v.01L9.483 7.2zm3.55.172l.21.682-.234.084-.089.022-.702.255.008-.022.776-.982zm-5 .836l.986.356.898.312.048.02 1.054.373.011 3.086-.362-.117-.67-.224-.081-.038-.735-.245-.77-.256-.29-.1-.011-.255-.032-1.195-.01-.287-.015-.894-.013-.297zm6.583 0l-.011.227-.028.9-.008.303-.032 1.475-.01.262-.337.117-.734.245-.77.256-.712.245-.355.117.01-3.086 1.632-.578zm.585.437l.09.735.79-.097-.915 1.302-.018.006.01-.183.018-.877zm-9.451.536l.152.22 1.447 2.049-2.607.968-.05.015-1.972-2.214-.28-.312.003-.01.115-.018.424-.1.14-.021.337-.078.042-.01zm11.146.003l3.284.713.029.01-.022.025-1.954 2.192-.277.312-.092-.036-2.564-.95.475-.681.152-.216zM6.787 8.52h.86l.036 1.258-.013-.006-.763-1.078zm1.358 2.625l.152.06.77.252.712.245.746.247.49.167-.065.092-1.723 2.334-1.015-.302-.082-.017-.035-.015-1.902-.56.938-1.22.981-1.277zm6.73 0l.033.006 1.787 2.327.132.17-.128.036-.032.014-2.196.642-.105.032-.564.17-.018-.003-1.053-1.44-.174-.239-.547-.726-.007-.018.469-.16.769-.254.713-.245.77-.252zm-7.766.305l-.007.02-.405.523-.291-.291.657-.245zm8.802 0l.043.007.578.212.714.27-.661.394-.375-.479-.03-.042-.262-.342zm-10.843.75l-.67.668.355-.397.207-.23zm12.911.016l.068.025.045.042.554.627.042.043.204.228-.255.135zm-6.473.265l.022.015 1.38 1.872.032.05.343.465.008.031-.088.117-.422.629-.047.074-.245.343-.97 1.43-.013.007-1.18-1.72-.096-.16-.493-.708-.008-.037 1.618-2.191.007-.01zm7.827 1.194l.565.633.063.082-.272-.093-.037-.013zm-15.785.148l.297.299-.637.218-.152.05.038-.058zm13.224.47l-.855.448.346.66-.185-.058-.27-.088-1.092-.348.012-.01zm-9.687.255l1.222.356-.006.007-.458.145-.443.135-.032.01-.49.157zm-2.765.048l.318.32 2.007.517-.567.18-.055.004-2.103-.469-.744-.156.007-.006zm14.966.205l.548.188v.003l-.457.1-.043.014-1.069.23zm-10.23.507l.007.227.01.347.025 1.363.025.691-.007.255-.24.107-2.863 1.255.032-.372.033-.255.017-.227.031-.256.037-.407.045-.42.018-.23.032-.251.032-.412.05-.414.013-.14 1.455-.457.003-.014.301-.098zm4.908 0l1.245.39v.014l.312.1 1.146.362.022.23.03.255.043.408.04.42.017.23.033.251.032.412.042.325.078.848-.078-.04-3.025-1.322-.004-.305.06-2.368zm-4.295.617l.015.007.067.107.6.875-.64.531-.034-1.438zm3.671 0h.008l-.005.06-.02.678-.005.214-.479-.223zm-2.888 3.605l.763.915.001.37-.017-.006-.025-.05-.464-.791-.012-.018zm1.53.61l.184.083-.343.586-.018.007.002-.532z" fill="#f44336" fill-rule="evenodd" stroke="#f44336" stroke-width=".7747499999999999" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 720 720" id="matlab" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><path d="M209.247 329.98L52.368 387.638l121.325 85.822 96.752-95.804-61.198-47.674z" fill="#4db6ac" fill-rule="evenodd" stroke-width=".3"/><path d="M480.193 71.446c-13.123 1.784-9.565 1.013-28.4 16.09-18.008 14.418-69.925 100.347-97.673 129.256-24.688 25.722-34.46 12.199-60.102 33.661-25.68 21.494-65.273 64.464-65.273 64.464l63.978 47.32L394.15 222.754c23.948-32.932 23.694-37.266 36.744-71.82 6.384-16.907 17.76-29.9 27.756-45.809 12.488-19.874 30.186-34.855 21.543-33.68z" fill="#00897b" fill-rule="evenodd" stroke-width=".3"/><path d="M478.206 69.796c-31.268-.189-62.068 137.245-115.56 242.691-54.543 107.519-162.235 176.82-162.235 176.82 18.156 8.243 34.681 4.91 54.236 23.394 13.375 16.164 52.09 95.976 75.174 146.117 0 0 18.964-10.297 42.994-27.695 24.03-17.397 53.124-41.896 73.384-70.3 26.883-37.692 47.897-61.043 65.703-75.271 17.806-14.23 32.404-19.336 46.458-20.54 50.238-4.305 124.582 85.792 124.582 85.792S527.267 70.09 478.206 69.796z" fill="#ffb74d" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 24 24" id="merlin" xmlns="http://www.w3.org/2000/svg"><text style="line-height:1.25;-inkscape-font-specification:'Century Gothic Bold'" x="1.953" y="21.178" transform="scale(.99582 1.0042)" font-weight="700" font-size="30.255" font-family="Century Gothic" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-width=".756"><tspan x="1.953" y="21.178" style="-inkscape-font-specification:'Century Gothic Bold'" font-size="22.745">M</tspan></text></symbol><symbol viewBox="0 0 192 191.99999" id="mocha" xmlns="http://www.w3.org/2000/svg"><title>Mocha Logo</title><g transform="translate(-354.75 -262.42) scale(4.835)" fill="#a1887f"><path d="M103.6 69.6c0-.5-.4-1-1-1H83.8c-.5 0-1 .4-1 1 0 3.4.5 15.1 5.5 20.8.2.2.4.3.7.3h8.4c.3 0 .5-.1.7-.3 5-5.6 5.5-17.3 5.5-20.8zm-7.4 18.2h-5.9c-.3 0-.5-.1-.7-.3-3.4-4-3.8-12-3.9-14.8 0-.5.4-1 1-1h13.2c.5 0 1 .4 1 1 0 2.8-.5 10.7-3.9 14.8-.3.2-.5.3-.8.3zM95.1 66.6s3.6-2.1 1.4-5.9c-1.3-2-1.9-3.7-1.4-4.4-1.3 1.6-3.5 3.3-1.1 6.9.8.9 1.2 2.8 1.1 3.4zM91.1 66.9s2.4-1.4.9-4c-.9-1.3-1.3-2.5-.9-2.9-.9 1.1-2.3 2.2-.7 4.7.5.5.7 1.8.7 2.2z"/><path d="M99.3 78.5c-.4 2.7-1.2 5.8-2.9 7.8-.2.2-.4.3-.6.3h-5c-.2 0-.5-.1-.6-.3-1.2-1.5-2-3.5-2.5-5.6 0 0 5.8.8 9.1-.4 2.4-.9 2.5-1.8 2.5-1.8z"/></g></symbol><symbol viewBox="0 0 24 24" id="movie" xmlns="http://www.w3.org/2000/svg"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V4h-4z" fill="#ff9800"/></symbol><symbol viewBox="0 0 24 24" id="music" xmlns="http://www.w3.org/2000/svg"><path d="M16 9V7h-4v5.5c-.42-.31-.93-.5-1.5-.5A2.5 2.5 0 0 0 8 14.5a2.5 2.5 0 0 0 2.5 2.5 2.5 2.5 0 0 0 2.5-2.5V9h3m-4-7a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2z" fill="#ef5350"/></symbol><symbol viewBox="0 0 24 24" id="mxml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#ffa726"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#ab47bc" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#26c6da" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#e53935" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#9ccc65" stroke-width="12.914"/></symbol><symbol viewBox="0 0 24 24" id="nim" xmlns="http://www.w3.org/2000/svg"><path d="M4.464 15.75L2.288 3.78l5.985 7.617L12.08 3.78l3.809 7.617 5.985-7.617-2.177 11.97H4.464m15.234 3.264a1.088 1.088 0 0 1-1.088 1.088H5.553a1.088 1.088 0 0 1-1.089-1.088v-1.089h15.234z" stroke-width="1.088" fill="#ffca28"/></symbol><symbol viewBox="0 0 500 500" id="nix" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-1.965 36.302)" stroke-width=".395"><path d="M135.59 415.7c0-.295-2.752-5.283-6.116-11.084-3.364-5.801-6.116-10.776-6.116-11.055s9.514-16.889 21.143-36.912c11.629-20.022 21.323-36.798 21.542-37.279.346-.76-1.608-4.363-14.896-27.466-8.412-14.625-15.294-26.785-15.294-27.023 0-.5 24.46-43.501 25.206-44.31.414-.45.592-.384 1.078.395.32.513 16.876 29.256 36.791 63.87 62.62 108.85 74.852 130.01 75.41 130.46.3.242.544.554.544.694 0 .14-11.836.21-26.302.154-23.023-.09-26.313-.175-26.393-.694-.11-.714-27.662-48.825-28.86-50.392-.746-.978-.906-1.035-1.426-.51-.688.696-28.954 49.323-29.49 50.733l-.365.96h-13.229c-10.896 0-13.229-.095-13.229-.538zm167.58-125.61c-.134-.216 1.188-2.863 2.938-5.882 6.924-11.944 84.291-145.75 96.491-166.88 7.143-12.371 13.142-22.465 13.333-22.433.363.062 25.861 43.105 25.861 43.655 0 .174-6.761 11.952-15.026 26.173-8.46 14.557-14.932 26.104-14.81 26.421.185.483 4.564.564 30.213.564h29.996l.958 1.48c.526.814 3.296 5.547 6.155 10.518 2.859 4.971 5.45 9.29 5.756 9.597.706.705.704.724-.16 1.572-.395.388-3.36 5.323-6.587 10.965-3.228 5.643-6.056 10.387-6.285 10.543-.23.156-19.695.171-43.256.034l-42.84-.249-.804 1.15c-.441.632-7.504 12.736-15.696 26.897l-14.892 25.747H339.03c-8.517 0-20.015.116-25.55.259-6.55.168-10.15.121-10.309-.135zM169.42 132.23c-56.373-.055-102.5-.182-102.5-.282 0-.1 5.617-10.132 12.481-22.294l12.481-22.112h30.332c27.113 0 30.332-.065 30.332-.611 0-.336-6.659-12.228-14.797-26.427-8.139-14.199-14.797-25.917-14.797-26.04 0-.123 2.682-4.853 5.96-10.51s6.003-10.578 6.055-10.934c.086-.586 1.376-.648 13.572-.648 7.413 0 13.463.143 13.446.317-.017.174.222.707.531 1.184.31.476 9.763 16.937 21.007 36.578 11.244 19.64 20.71 36.022 21.036 36.4.554.647 2.549.691 31.428.691h30.837l12.896 22.145c7.093 12.18 12.8 22.301 12.682 22.492-.118.19-4.776.303-10.352.249-5.575-.054-56.26-.143-112.63-.198z" fill="#5075c1"/><path d="M25.289 203.14c-6.098 10.563-6.69 11.711-6.225 12.078.283.224 3.18 5.044 6.44 10.712 3.261 5.668 6.017 10.355 6.124 10.417.106.061 13.585.153 29.95.204 16.367.052 29.994.23 30.285.399.472.273-1.08 3.094-14.637 26.574L62.06 289.793l12.907 21.865c7.1 12.026 12.982 21.906 13.068 21.956.086.05 23.257-39.831 51.492-88.624 11.352-19.617 21.214-36.64 30.37-52.442 23.308-40.452 30.68-53.468 30.73-54.132-1.097-.11-6.141-.187-13.006-.216-3.945-.01-7.82-.02-12.75-.002l-25.341.092-15.42 26.706c-14.256 24.693-15.445 26.663-16.278 26.86l-.024.037c-.011.003-1.62-.001-1.825 0-4.29.062-20.453.063-40.226-.01-22.632-.082-41.615-.125-42.183-.096-.568.03-1.147-.03-1.29-.132-.142-.102-3.29 5.066-6.996 11.485zm205.16-190.3c-.123.149 5.62 10.392 12.761 22.763 12.199 21.131 89.393 155.03 96.276 167 1.502 2.613 2.92 4.803 3.443 5.348.9-1.249 3.531-5.63 7.954-13.219a1342.88 1342.88 0 0 1 10.049-17.76l6.606-11.443c.692-1.403.754-1.818.653-2.117-.162-.48-6.904-12.332-14.982-26.337-8.078-14.005-14.824-25.849-14.991-26.32a.73.73 0 0 1-.009-.366l-.426-.913L359.42 72.5c3.69-6.307 6.425-11.042 9.47-16.29 9.159-15.948 12.037-21.189 11.896-21.55-.126-.324-2.7-4.83-5.72-10.017-3.021-5.185-5.845-10.148-6.275-11.026-.483-.987-.734-1.364-1.1-1.456-.054.014-.083.018-.145.035-.42.112-5.454.195-11.189.185-5.734-.01-11.22.024-12.188.073l-1.76.089-14.997 25.978c-12.824 22.212-15.084 25.964-15.595 25.883-.024-.004-.15-.189-.235-.301-.109.066-.2.09-.272.05-.255-.148-7.143-11.902-15.306-26.119l-14.36-25.016c-.115-.186-.444-.744-.457-.752-.477-.275-50.502.287-50.737.57zm-18.646 283.09c-.047.109-.026.262.042.48.329 1.05 25.338 43.735 25.772 43.985.207.119 14.178.239 31.05.266 26.651.044 30.75.152 31.234.832.308.43 9.988 17.214 21.513 37.296s21.152 36.627 21.394 36.767c.242.14 5.927.243 12.633.23 6.706-.013 12.401.099 12.657.246.132.076.382-.141.852-.795l6.008-10.406c5.234-9.065 6.62-11.684 6.294-11.888-.575-.36-15.597-26.643-23.859-41.482-3.09-5.45-5.37-9.516-5.441-9.774-.195-.712-.065-.822 1.156-.98 1.956-.252 57.397-.057 58.07.205.238.092.79-.569 2.594-3.497 1.866-3.067 5.03-8.524 11-18.866 7.22-12.505 13.044-22.784 12.942-22.843-.102-.059-.771-.051-1.489.016l-.046.001c-4.452.204-33.918.203-149.74.025-38.96-.06-69.786-.09-71.912-.072-1.121.01-2.095.076-2.66.172a.25.25 0 0 0-.062.083z" fill="#7db7e1"/></g></symbol><symbol viewBox="0 0 24 24" id="nodejs" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.85c-.27 0-.55.07-.78.2l-7.44 4.3c-.48.28-.78.8-.78 1.36v8.58c0 .56.3 1.08.78 1.36l1.95 1.12c.95.46 1.27.47 1.71.47 1.4 0 2.21-.85 2.21-2.33V8.44c0-.12-.1-.22-.22-.22H8.5c-.13 0-.23.1-.23.22v8.47c0 .66-.68 1.31-1.77.76L4.45 16.5a.26.26 0 0 1-.11-.21V7.71c0-.09.04-.17.11-.21l7.44-4.29c.06-.04.16-.04.22 0l7.44 4.29c.07.04.11.12.11.21v8.58c0 .08-.04.16-.11.21l-7.44 4.29c-.06.04-.16.04-.23 0L10 19.65c-.08-.03-.16-.04-.21-.01-.53.3-.63.36-1.12.51-.12.04-.31.11.07.32l2.48 1.47c.24.14.5.21.78.21s.54-.07.78-.21l7.44-4.29c.48-.28.78-.8.78-1.36V7.71c0-.56-.3-1.08-.78-1.36l-7.44-4.3c-.23-.13-.5-.2-.78-.2M14 8c-2.12 0-3.39.89-3.39 2.39 0 1.61 1.26 2.08 3.3 2.28 2.43.24 2.62.6 2.62 1.08 0 .83-.67 1.18-2.23 1.18-1.98 0-2.4-.49-2.55-1.47a.226.226 0 0 0-.22-.18h-.96c-.12 0-.21.09-.21.22 0 1.24.68 2.74 3.94 2.74 2.35 0 3.7-.93 3.7-2.55 0-1.61-1.08-2.03-3.37-2.34-2.31-.3-2.54-.46-2.54-1 0-.45.2-1.05 1.91-1.05 1.5 0 2.09.33 2.32 1.36.02.1.11.17.21.17h.97c.05 0 .11-.02.15-.07.04-.04.07-.1.05-.16C17.56 8.82 16.38 8 14 8z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 300 300" id="nodemon" xmlns="http://www.w3.org/2000/svg"><title>nodemon</title><path d="M149.868 20.62c-2.124 0-4.25.55-6.154 1.648L41.899 81.083a12.306 12.306 0 0 0-6.15 10.652v117.633a12.29 12.29 0 0 0 6.152 10.646l101.815 58.766h.001a12.282 12.282 0 0 0 12.291 0l101.84-58.766a12.29 12.29 0 0 0 6.153-10.652V91.738a12.31 12.31 0 0 0-6.146-10.652L156.015 22.27a12.302 12.302 0 0 0-6.153-1.648zM83.303 70.93s11.789 33.031 35.477 31.934l27.74-15.961a7.348 7.348 0 0 1 3.414-.99h.641a7.233 7.233 0 0 1 3.404.99l27.738 15.961c23.69 1.094 35.475-31.934 35.475-31.934 5.233 23.154 1.06 38.641-5.924 48.942l4.541 2.614h.002c2.321 1.327 3.734 3.795 3.737 6.49l-.12 95.811a3.724 3.724 0 0 1-1.855 3.227 3.624 3.624 0 0 1-3.735 0L177.1 206.971c-2.311-1.363-3.742-3.818-3.742-6.48v-44.763a7.44 7.44 0 0 0-3.737-6.465l-15.642-9.01a7.28 7.28 0 0 0-3.715-1.01 7.378 7.378 0 0 0-3.742 1.01l-15.648 9.01c-2.316 1.323-3.729 3.798-3.729 6.467v44.762c0 2.663-1.413 5.1-3.738 6.48l-36.748 21.041a3.571 3.571 0 0 1-3.71 0c-1.173-.65-1.864-1.887-1.864-3.224l-.137-95.812a7.483 7.483 0 0 1 3.74-6.49l4.541-2.615c-6.982-10.302-11.16-25.79-5.925-48.942z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 990 990" id="npm" xmlns="http://www.w3.org/2000/svg"><defs><style>.hncls-1{fill:#cb3837}.cls-2{fill:#fff}</style></defs><title>n</title><path class="hncls-1" d="M113.26 876.74V113.27h763.47v763.47zm143.59-620.4v476.18h240.61V355.63h140.21v376.96h95.457V256.34z" fill="#e53935" stroke-width=".771"/></symbol><symbol id="nunjucks" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.host0{fill:#388e3c}</style><path class="host0" d="M11.2 21.1H8.1l-2.3-7.9v7.9H2.7V2.9h3.1l2.3 7.4V2.9h3.1zM21.3 19.2c0 1-.8 1.9-1.9 1.9h-4.8c-1 0-1.9-.8-1.9-1.9v-3.8l3.2-.7V18h2.3V7.2h3.1v12z"/></symbol><symbol viewBox="0 0 150 150.00001" id="ocaml" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.76136 0 0 .76136 11.616 19.98)"><path d="M83.02 101.645l.023-.062c-.035-.159-.047-.195-.024.062z" fill="none" stroke-width="1.028"/><linearGradient id="hpa" gradientUnits="userSpaceOnUse" x1="-696.735" y1="97.7" x2="-696.735" y2="142.997" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M82.313 138.79c-.471-1.004-1.904-3.621-2.624-4.46-1.562-1.828-1.927-1.966-2.386-4.275-.799-4.02-2.913-11.31-5.405-16.341-1.286-2.596-3.426-4.777-5.385-6.66-1.71-1.652-5.565-4.431-6.237-4.294-6.296 1.257-8.249 7.432-11.21 12.323-1.638 2.705-3.374 5.007-4.665 7.885-1.192 2.646-1.087 5.577-3.128 7.849-2.093 2.333-3.454 4.814-4.48 7.829-.194.574-.747 6.596-1.348 8.015l9.357-.659c8.719.594 6.2 3.936 19.81 3.208l21.487-.665c-.666-1.97-1.584-4.25-1.938-4.991-.599-1.248-1.352-3.69-1.848-4.763z" fill="url(#hpa)" stroke-width="1.028"/><linearGradient id="hpb" gradientUnits="userSpaceOnUse" x1="-666.972" y1="142.12" x2="-666.972" y2="142.12" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><linearGradient id="hpc" gradientUnits="userSpaceOnUse" x1="-675.228" y1="-1.28" x2="-675.228" y2="142.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M109.553 94.296c-1.652 1.193-4.88 4.06-11.902 5.145-3.152.487-6.1.527-9.335.365-1.584-.076-3.077-.157-4.665-.177-.936-.008-4.074-.107-3.919.193l-.349.871c.054.287.169 1.004.2 1.177.129.704.165 1.265.192 1.912.048 1.331-.11 2.719-.043 4.062.141 2.787 1.175 5.326 1.306 8.137.143 3.13 1.69 6.442 3.188 8.998.569.973 1.434 1.084 1.811 2.283.442 1.373.024 2.83.239 4.293.842 5.675 2.477 11.606 5.032 16.728.018.043.038.09.06.128 3.156-.53 6.318-1.665 10.418-2.271 7.517-1.115 17.972-.54 24.688-1.17 16.993-1.597 26.216 6.97 41.478 3.459V22.459c0-11.84-9.594-21.438-21.435-21.438H19.239C7.4 1.021-2.197 10.62-2.197 22.458v46.774c3.067-1.11 7.479-7.635 8.861-9.222 2.419-2.775 2.858-6.315 4.062-8.544 2.743-5.078 3.215-8.57 9.451-8.57 2.907 0 4.061.67 6.027 3.31 1.368 1.834 3.731 5.224 4.837 7.49 1.277 2.615 3.357 6.153 4.272 6.867.677.53 1.35.928 1.976 1.163 1.012.38 1.848-.316 2.525-.855.863-.687 1.235-2.088 2.035-3.957 1.152-2.696 2.408-5.926 3.122-7.054 1.237-1.949 1.658-4.261 2.993-5.381 1.97-1.652 4.54-1.768 5.246-1.908 3.957-.781 5.755 1.906 7.704 3.645 1.276 1.138 3.019 3.432 4.256 6.507.967 2.4 2.199 4.622 2.714 6.008.497 1.339 1.725 3.484 2.453 6.055.661 2.336 2.43 4.125 3.102 5.235 0 0 1.029 2.882 7.285 5.516 1.357.572 4.1 1.501 5.736 2.096 2.718.988 5.351.86 8.704.458 2.391 0 3.686-3.462 4.772-6.234.643-1.639 1.259-6.334 1.678-7.667.406-1.297-.544-2.3.265-3.437.946-1.327 1.508-1.399 2.054-3.129 1.172-3.704 7.95-3.89 11.761-3.89 3.176 0 2.772 3.083 8.16 2.028 3.086-.605 6.059.398 9.335 1.265 2.758.732 5.352 1.566 6.906 3.385 1.005 1.178 3.5 7.08.958 7.331.244.3.423.84.88 1.135-.566 2.226-3.03.64-4.4.355-1.845-.383-3.147.057-4.952.856-3.085 1.374-7.598 1.214-10.286 3.452-2.281 1.898-2.277 6.133-3.34 8.507-.002-.001-2.955 7.6-9.402 12.248z" fill="url(#hpc)" stroke-width="1.028"/><linearGradient id="hpd" gradientUnits="userSpaceOnUse" x1="-735.137" y1="90.833" x2="-735.137" y2="141.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M38.247 105.09c-1.467-.15-2.83-.317-4.256-.605-2.662-.536-5.57-1.06-8.193-1.688-1.592-.385-6.895-2.263-8.048-2.792-2.702-1.246-4.496-4.63-6.609-4.282-1.348.22-2.662.682-3.5 2.042-.685 1.11-.917 3.016-1.391 4.294-.55 1.485-1.5 2.87-2.331 4.284-1.53 2.595-4.282 4.941-5.468 7.469-.239.52-.45 1.101-.649 1.708V144.415a48.57 48.57 0 0 1 4.45.96c11.955 3.19 14.872 3.46 26.598 2.119l1.1-.146c.897-1.867 1.59-8.227 2.171-10.195.454-1.51 1.077-2.712 1.313-4.253.223-1.463-.02-2.858-.146-4.188-.329-3.332 2.427-4.522 3.742-7.384 1.186-2.589 1.871-5.535 2.853-8.181.941-2.54 2.41-6.13 4.918-7.408-.305-.355-5.237-.518-6.554-.65z" fill="url(#hpd)" stroke-width="1.028"/></g></symbol><symbol viewBox="0 0 24 24" id="pdf" xmlns="http://www.w3.org/2000/svg"><path d="M14 9h5.5L14 3.5V9M7 2h8l6 6v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m4.93 10.44c.41.9.93 1.64 1.53 2.15l.41.32c-.87.16-2.07.44-3.34.93l-.11.04.5-1.04c.45-.87.78-1.66 1.01-2.4m6.48 3.81c.18-.18.27-.41.28-.66.03-.2-.02-.39-.12-.55-.29-.47-1.04-.69-2.28-.69l-1.29.07-.87-.58c-.63-.52-1.2-1.43-1.6-2.56l.04-.14c.33-1.33.64-2.94-.02-3.6a.853.853 0 0 0-.61-.24h-.24c-.37 0-.7.39-.79.77-.37 1.33-.15 2.06.22 3.27v.01c-.25.88-.57 1.9-1.08 2.93l-.96 1.8-.89.49c-1.2.75-1.77 1.59-1.88 2.12-.04.19-.02.36.05.54l.03.05.48.31.44.11c.81 0 1.73-.95 2.97-3.07l.18-.07c1.03-.33 2.31-.56 4.03-.75 1.03.51 2.24.74 3 .74.44 0 .74-.11.91-.3m-.41-.71l.09.11c-.01.1-.04.11-.09.13h-.04l-.19.02c-.46 0-1.17-.19-1.9-.51.09-.1.13-.1.23-.1 1.4 0 1.8.25 1.9.35M8.83 17c-.65 1.19-1.24 1.85-1.69 2 .05-.38.5-1.04 1.21-1.69l.48-.31m3.02-6.91c-.23-.9-.24-1.63-.07-2.05l.07-.12.15.05c.17.24.19.56.09 1.1l-.03.16-.16.82-.05.04z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="perl" xmlns="http://www.w3.org/2000/svg"><path d="M12 14c-1 0-3 1-3 2 0 2 3 2 3 2v-1a1 1 0 0 1-1-1 1 1 0 0 1 1-1v-1m0 5s-4-.5-4-2.5c0-3 3-3.75 4-3.75V11.5c-1 0-5 1.5-5 4.5 0 4 5 4 5 4v-1M10.07 7.03l1.19.53c.43-2.44 1.58-4.06 1.58-4.06-.43 1.03-.71 1.88-.89 2.55C13.16 3.55 15.61 2 15.61 2a15.916 15.916 0 0 0-2.64 3.53c1.58-1.68 3.77-2.78 3.77-2.78-2.69 1.72-3.9 4.45-4.2 5.21l.55.08c0 .52 0 1 .25 1.38C14.1 11.31 18 11.47 18 16s-4.03 6-6.17 6C9.69 22 5 21.03 5 16s4.95-5.07 5.83-7.08c.12-.38-.76-1.89-.76-1.89z" fill="#9575cd"/></symbol><symbol viewBox="0 0 24 24" id="php" xmlns="http://www.w3.org/2000/svg"><path d="M12 18.08c-6.63 0-12-2.72-12-6.08s5.37-6.08 12-6.08S24 8.64 24 12s-5.37 6.08-12 6.08m-5.19-7.95c.54 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.58 1.09-.28.22-.71.33-1.29.33h-.87l.53-2.76h.99m-3.5 5.55h1.44l.34-1.75h1.23c.54 0 .98-.06 1.33-.17.35-.12.67-.31.96-.58.24-.22.43-.46.58-.73.15-.26.26-.56.31-.88.16-.78.05-1.39-.33-1.82-.39-.44-.99-.65-1.82-.65H4.59l-1.28 6.58m7.25-8.33l-1.28 6.58h1.42l.74-3.77h1.14c.36 0 .6.06.71.18.11.12.13.34.07.66l-.57 2.93h1.45l.59-3.07c.13-.62.03-1.07-.27-1.36-.3-.27-.85-.4-1.65-.4h-1.27L12 7.35h-1.44M18 10.13c.55 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.57 1.09-.29.22-.72.33-1.3.33h-.85l.5-2.76h1m-3.5 5.55h1.44l.34-1.75h1.22c.55 0 1-.06 1.35-.17.35-.12.65-.31.95-.58.24-.22.44-.46.58-.73.15-.26.26-.56.32-.88.15-.78.04-1.39-.34-1.82-.36-.44-.99-.65-1.82-.65h-2.75l-1.29 6.58z" fill="#1E88E5"/></symbol><symbol viewBox="0 0 79 78" id="postcss" xmlns="http://www.w3.org/2000/svg"><title>postcss-logo-symbol</title><g transform="translate(5.48 5.52) scale(.85425)" fill="#e53935" fill-rule="evenodd" stroke="#e53935" stroke-width="1.519"><path d="M15.447 32.623c.106.08.29.132.106.29-.132.184-.29.342-.395.553-.105.185-.184.237-.342.106.21-.343.42-.66.63-.95zM68.342 60.24c0 .078.026.13.026.21.053-.105.053-.158.08-.21zm0 .236v-.026zm-5.368 10.277l-4.58-25.402c-.078-.025-.183-.077-.368-.13.053.105.08.184.106.263.13-.026.184-.026.236-.052 0-.026 0-.052.027-.08l4.58 25.404zm-4.737-31.12c-.026.078-.026.158-.026.237 0-.08 0-.16.028-.238zm.026.526c-.026 0-.026 0-.052-.028v.026c.028.026.028.026.054 0zm-.052.21v-.185c-.077.026-.156.026-.262.053.132.05.264.078.264.13z"/><path d="M78.71 33.967c-.052-1.028-.078-2.056-.184-3.083-.184-1.397-.368-2.82-.684-4.19-.237-1.133-.63-2.214-1.026-3.294-.5-1.265-1-2.556-1.632-3.768-1.026-1.95-2.368-3.69-3.605-5.508-.818-1.16-1.87-2.108-2.66-3.294-.447-.685-1.105-1.264-1.763-1.79-1.053-.845-2.158-1.61-3.263-2.347a32.525 32.525 0 0 0-2.58-1.634c-.71-.397-1.473-.713-2.21-1.056-.842-.395-1.658-.87-2.605-1.054-.238-.05-.448-.13-.685-.21-.605-.21-1.184-.447-1.79-.632-.92-.29-1.815-.632-2.763-.87C50.342 1 49.394.843 48.446.71 47.394.555 46.316.5 45.262.397a26.83 26.83 0 0 0-2.026-.184C42.236.16 41.21.16 40.21.134c-.5-.027-1.026-.08-1.526-.053-.763.026-1.526.105-2.29.21-.736.08-1.473.21-2.183.317-.867.105-1.735.158-2.604.264-.816.106-1.658.264-2.473.396-.29.053-.58.158-.87.21-.63.132-1.288.185-1.92.396-1.13.344-2.263.74-3.368 1.16-1.027.422-2.027.87-3 1.397-1 .552-1.948 1.21-2.895 1.844a45.325 45.325 0 0 0-2.66 1.923c-.84.66-1.63 1.397-2.394 2.135-.42.42-.763.922-1.158 1.396-.657.765-1.315 1.502-1.947 2.293-.524.66-1 1.344-1.5 2.03-.893 1.21-1.656 2.502-2.366 3.794-.29.527-.553 1.054-.816 1.58-.395.79-.816 1.555-1.184 2.372-.264.554-.474 1.16-.632 1.766-.367 1.292-.736 2.61-1.078 3.9-.316 1.16-.395 2.372-.42 3.558-.027 1.054.078 2.082.183 3.136.027.264-.13.58.184.79-.105.29-.026.45.13.5-.182.29.08.476-.024.74-.027.052.08.157.13.236 0 .08-.025.185 0 .264.028.237.133.474.133.738 0 .184.157.395.21.58.026.078 0 .21-.053.263-.158.184-.132.342.105.448.133.342.08.5.054.66.052.236-.027.315 0 .368.21.422.29.896.315 1.37 0 .106.053.212.106.343.026 0 0 .5 0 .5.13-.078.237-.104.368-.157.08.342.158.66.263.95.132.21.132.314.08.34.105.474.157.922.34 1.37 0-.5-.05-1-.13-1.475.368.132.684.263.895.263.027-.08.053-.184.08-.237-.158-.157-.29-.394-.448-.552.053.21 0 .29 0 .37-.105-.054-.237-.107-.368-.16.105-.13.21-.263.368-.42 0-.238-.13-.45-.5-.423.158-.052.316-.13.5-.184.29-.157-.026-.447-.026-.816.026-.447-.237-.895-.316-1.37-.132-.737-.105-1.844-.184-2.582-.158-.132-.29.21-.316.237.08.632.158 1.264.21 1.897-.157-.527-.263-1.107-.394-1.74-.027.185-.053.264-.053.37-.13.13-.026.29.053.474-.184-.08-.395-.052-.395-.052v.738c-.262-.264-.34-.474-.473-.66-.052-.21-.08-.42-.13-.63.05-.133 0-.212 0-.29a15.968 15.968 0 0 1-.08-.634c.026-.026-.026-.42-.026-.42.21.025.343.05.474.05-.263-.34-.08-.552.027-.763.053-.106.237-.13.29-.238.21-.395.553-.71.553-1.212 0-.237.08-.5.105-.738.053-.448.105-.896.13-1.344.054-.58 0-1.16.133-1.713.212-.92.475-1.843.764-2.766.21-.66.448-1.29.71-1.95.395-1.028.764-2.056 1.264-3.03.71-1.424 1.526-2.794 2.316-4.19.5-.87 1.026-1.687 1.58-2.53.525-.817 1.05-1.66 1.657-2.425a21.452 21.452 0 0 1 2.79-2.978c1.053-.948 2.053-1.923 3.184-2.793a32.218 32.218 0 0 1 4.685-3.005c1.343-.71 2.737-1.266 4.132-1.793.895-.342 1.868-.5 2.79-.79 1.052-.343 2.105-.5 3.21-.527.71-.027 1.395-.106 2.105-.185.632-.05 1.263-.104 1.948-.183-.08.105-.106.158-.132.21-.288.422-.604.844-.894 1.265-.237.343-.5.712-.737 1.054-.422.555-.87 1.108-1.264 1.688-.605.87-1.158 1.766-1.79 2.635-.63.843-1.315 1.634-1.973 2.45-.868 1.134-1.684 2.293-2.552 3.426-.79 1.08-1.63 2.11-2.394 3.19-.684.947-1.29 1.95-1.948 2.923-.973 1.45-1.947 2.872-2.92 4.322a271.93 271.93 0 0 1-2.316 3.294c-.053.08-.132.104-.21.157-.21.342-.21.527-.29.685-.21.395-.42.79-.658 1.16-.132.21-.316.394-.474.605-.026-.316.42-.474.21-.87-.13.212-.263.396-.394.607l-.316.63c.105.08.29.133.105.29-.08.133-.158.29-.237.423a.954.954 0 0 0 .29-.264c0 .29-.158.526-.29.763-.105.21-.368.37-.552.527.026.027.21.106.237.132.237-.08.316-.21.343-.132.08-.105.158-.184.184-.263.104-.264.262-.474.525-.58.106-.053.184-.132.263-.21.79-.818 1.606-1.608 2.316-2.478 1.106-1.345 2.106-2.74 3.16-4.11.446-.58.973-1.16 1.446-1.714.078.606.026 1.185 0 1.74-.08.974-.132 1.95-.21 2.95-.027.395 0 .79-.027 1.186 0 .105-.08.184-.08.29 0 .263.08.553.08.817-.08.975-.186 1.923-.265 2.898-.027.21.078.422.13.607-.13 1.422.16 2.925-.078 4.427.184-.29.237-.474.237-.658.025-.158 0-.316 0-.5v-.264c.025-.475.13-.975.078-1.45-.053-.527-.053-1.027.053-1.528.053-.21-.026-.474.106-.738v.395c-.026 1.5.027 3.003-.183 4.505-.027.132.08.37-.21.343-.238.474.052.817-.21 1.08-.054.053.05.29.077.448-.106.317-.106.317.052.343.026.58.08 1.106.105 1.66.42-1 .21-2.03.396-3.058.026.422.053.844.026 1.29 0 .687-.026 1.345-.052 2.03 0 .132-.027.264-.053.396-.08.37-.105.738-.237 1.08-.105.264-.052.66-.052.975v1.003c.105.448-.027.685.052.948-.08.265-.105.344-.08.423l.08.395c.527-.053.29.343.5.553-.158.212-.105.29-.105.397 0 .237-.025.448-.052.685 0 .606-.026 1.212-.026 1.792 0 .08.026.157.026.236 0 .054-.026.74-.026.74.053.078 0 .157-.08.236-.025 0-.104-3.347-.104-3.347h-.395c-.052 1.58.08 3.003-.21 4.48-.316.025-.42.078-.764.078-.816 0-1.632 0-2.448.026-.974 0-1.92.026-2.895.026-.472 0-.972.054-1.446.054-.632 0-1.29-.08-1.92-.08-.975 0-1.922.08-2.896.106-.71.026-1.42.026-2.13.053-.475.025-.95.05-1.422.104-.21.026-.395.105-.658.184-.08 0-.263-.026-.42 0-.265.053-.5.21-.765.264-.395.08-.5.184-.448.58v.263c-.026.052.58-.08.58-.08-.054 0-.08.158-.16.29.212-.08.343-.132.475-.184.395.185.737.08 1.052.16 1.026.262 2.078.37 3.13.473.685.053 1.343.08 2.027.105.973.053 1.947.106 2.92.106.816 0 1.606-.08 2.42-.08 1.13 0 2.264.052 3.395.08.237 0 .5-.028.763-.028h1.92c1.712-.052 3.422-.08 5.133-.13.975-.028 1.975-.08 2.948-.107l3-.08c1.158-.026 2.316-.026 3.448-.05.868 0 1.71-.03 2.58-.055.972-.026 1.972-.105 2.946-.157.527-.027 1.054-.08 1.58-.132.632-.052 1.29-.13 1.92-.157.948-.054 1.922-.08 2.87-.133 1.184-.078 2.368-.183 3.578-.21 1.106-.052 2.237-.026 3.343-.052.974-.027 1.948-.08 2.948-.106l1.66-.08s1.104-.026 1.657-.08c.947-.052 1.894-.157 2.842-.183.604-.027 1.21 0 1.815-.027.973-.026 1.973-.08 2.947-.08.367 0 .762.054 1.236.08-.21.185-.342.29-.5.422.105.026.21.08.316.132a.71.71 0 0 1-.42.13c-.054.133-.107.186-.16.45h.474c-.184 0-.342.237-.526.395-.21-.054-.395 0-.5.29.184.104.158.183.132.29-.316.104-.553.21-.42.552-.107.052-.238.105-.37.184-.13.21-.368.263-.316.553.106.025.21.08.29.104-.132.053-.263.132-.395.184-.473.29-.262.422-.157.554-.08.053-.158.105-.237.132.052.237.13.29.157.29a9.3 9.3 0 0 0-.395.316c-.08.237-.185.342-.29.5s-.158.37-.29.527c-.552.607-.947 1.32-1.657 1.793-.264.185-.5.422-.737.66-.474.447-.895.948-1.395 1.37a29.595 29.595 0 0 1-2.052 1.554 151.56 151.56 0 0 1-2.604 1.792c-.474.315-1 .552-1.5.842s-.974.554-1.474.843c-.316.21-.606.5-.948.66-.868.37-1.79.685-2.684 1.028-.87.37-1.5.685-2.158.922-.605.21-1.237.37-1.868.5-.21.054-.448 0-.685.027-.448.08-.895.186-1.343.238-1.158.158-2.316.264-3.473.422-.685.08-1.343.21-2.027.29-.473.026-.973-.026-1.447-.026-.342 0-.71.08-1.053.027-.552-.08-1.105-.21-1.658-.316-.13-.026-.316-.08-.42-.026-.21.106-.396-.052-.607 0-.13.027-.262-.08-.394-.08-.106-.025-.238.028-.37 0-.29-.078-.552-.183-.87-.157-.313.026-.63-.132-.97-.21-.475-.106-.92-.21-1.396-.317a2.38 2.38 0 0 1-.525-.237c-.685 0-1.133-.026-1.554-.185-.368-.13-.71-.315-1.105-.262-.104.026-.183-.026-.29-.026-.08-.106-.157-.317-.235-.317-.526.027-.842-.42-1.29-.553-.236-.08-.42-.343-.657-.422-.58-.237-1.052-.737-1.71-.816-.21-.027-.42-.132-.658-.21.08.104.13.183.21.262-.763-.37-1.473-.79-2.184-1.186-.104-.026-.183-.13-.262-.184l-.71-.474c-.395.08-.553-.08-.66-.132-.71-.5-1.525-.817-2.21-1.37-.29-.238-.63-.396-.84-.686-.37-.448-.817-.764-1.317-1.027-.394-.21-.762-.448-1.13-.685-.185-.132-.37-.29-.37-.58 0-.185-.078-.37-.315-.264-.105-.158-.21-.342-.342-.395-.316-.13-.526-.37-.763-.58s-.42-.5-.71-.605c-.527-.21-.843-.658-1.158-1.027-.738-.87-1.396-1.82-2.08-2.74-.053-.08-.158-.133-.237-.212.105.29.237.527.368.79-.262-.105-.446-.29-.604-.474-.027.027 1.815 3.057 1.815 3.057.16.237.29.475.448.712a.813.813 0 0 1-.79-.422c-.236-.42-.5-.684-1.026-.63a4.588 4.588 0 0 1-.13-.58c-.107 0-.185 0-.37-.027.37.58.685 1.08 1.027 1.66-.133-.08-.21-.132-.265-.158.473.5.815 1.133 1.42 1.45.132.605.816.895.974 1.475-.13-.027-.238-.053-.37-.08-.21-.263-.447-.526-.683-.816.052.184.13.342.236.474.316.395.606.79.974 1.133.132.134.316.187.316.424.21.105.29.13.368.13.054.16-.025.397.29.344.21.395.42.395.71.264.343.343.528.37.764.16 0 .13.026.262.026.368.105-.053.08-.132.08-.264.13.105.21.158.262.21.263.37.5.712.868 1.002.5.422.948.87 1.42 1.265.922.765 1.95 1.398 2.975 1.977 1.264.712 2.475 1.476 3.764 2.16 1.552.818 3.21 1.372 4.92 1.767.632.132 1.237.263 1.87.42.55.16 1.104.397 1.657.528.842.185 1.71.343 2.552.5.183.027.37.054.58.08.235.053.524-.053.577.027.132.21.237.104.395.078.184-.053.395-.053.605-.053.737.026 1.447.184 2.184.132.16 0 .396-.133.528.13.236-.105.368-.105.473-.13.028.236 0 .236-.05.262-.054.026-.133.053-.238.132.947.184 1.842.21 2.63 0 1.37.105 2.554-.053 3.686-.448.105.132.184.316.342.053.052-.08.184-.107.29-.133.236-.053.526-.158.736-.08.238.08.317-.13.5-.13.317 0 .606-.027.896-.08.158-.026.316-.105.5-.158a1.285 1.285 0 0 0-.58-.133c.317-.158.606-.29.896-.42-.053.078-.106.183-.21.183h.367c-.08 0-.185.237-.316.395.946-.237 1.814-.448 2.657-.66-.29-.552.315-.367.526-.684-.263.08-.526.158-.79.21.895-.447 1.816-.842 2.71-1.237-.13.158-.29.237-.525.37.158.025.263.025.342.05.42.133.316-.262.447-.5.5 0 .71-.078.947-.158.263-.08.526-.158.79-.263.42-.184.815-.42 1.236-.63.08-.028.21 0 .316 0 .29-.186.394-.344.473-.318.37.053.63-.08.736-.42.184-.133.316-.238.447-.318.578-.316 1.13-.632 1.71-.948.21 0 .316 0 .368-.027.344-.16.66-.342.975-.527a2.258 2.258 0 0 1-.263-.13c.262-.054.34-.08.5-.133.63-.74 1.5-1.24 2.157-1.82.29-.026.29-.105.29-.157.104-.132.21-.29.34-.396.58-.527 1.21-.975 1.737-1.528a37.16 37.16 0 0 0 2.184-2.374c.63-.738 1.264-1.475 1.79-2.292.737-1.133 1.368-2.293 2.026-3.48.474-.842.895-1.685 1.37-2.528.05-.08.157-.185.236-.185.71-.08 1.422-.13 2.106-.21.158-.026.342-.13.5-.21-.08-.132-.132-.29-.21-.422-.106-.16-.264-.29-.37-.45-.104-.13-.183-.29-.262-.447-.08-.13-.158-.236-.237-.37a9.7 9.7 0 0 1-.45-.894c-.026-.08-.08-.21-.052-.29.474-1.027.658-2.134 1.105-3.162.447-1.054.58-2.24.79-3.373.184-1.08.29-2.16.42-3.24.08-.764.185-1.502.21-2.266.16-1.212.106-2.346.08-3.48-.026-1-.08-2.028-.13-3.03zM12.685 66.405c-.184-.21-.342-.448-.526-.658l.08-.08c.287.317.577.633.866.976-.158-.08-.342-.132-.42-.238zm.42.238c.08-.027.16-.027.238-.053.08.132.132.29.21.448-.368-.027-.552-.185-.447-.395zm27.37 10.883v-.08c.5-.052.973-.105 1.473-.157v.077c-.5.08-.973.13-1.473.158zm6.63-.685c-.367.08-.762.133-1.13.186-.132.026-.29.158-.342-.08-.053.027-.106.027-.158.054.13.394.447.078.71.236-.58.08-1.13.132-1.684.21v-.052c.16-.026.343-.053.5-.08v-.078a7.743 7.743 0 0 0-.79-.053c-.077 0-.183.106-.262.132-.105.026-.21.053-.342.053-.447.026-.894.026-1.316.052-.027 0-.08-.026-.106-.026v-.08c1.763-.236 3.5-.473 5.263-.71.027.052.027.105.053.157-.158 0-.263.055-.395.08zm.396-.262c.606-.08 1.16-.132 1.738-.21-1.21.342-1.605.394-1.737.21zM24.58 23.374c.84-1.16 1.71-2.32 2.552-3.505.263-.345.473-.714.736-1.056.08-.106.185-.158.316-.264l-.026-.05c.105-.133.21-.24.263-.344.134-.21.213-.448.318-.685a.385.385 0 0 1 .105-.103c.37.184.37-.21.5-.343.237-.264.474-.553.684-.817.158-.21.316-.395.448-.632.026-.08-.053-.21-.08-.317h-.078c.08-.052.158-.13.237-.184.026 0 .026 0 .052-.026.158-.238.316-.475.474-.686.315-.42.657-.842 1.025-1.21-.052.13-.105.263-.158.368.027 0 .027.027.053.027.316-.422.658-.817.974-1.24-.027-.025-.053-.052-.08-.052-.13.132-.236.264-.368.396-.026-.027-.052-.053-.08-.053.265-.343.528-.685.79-1.08.053.08.106.184.21.395.107-.263.212-.447.29-.632-.078.08-.183.158-.262.238l-.08-.08.474-.71c.5-.712 1-1.45 1.5-2.162.185-.263.42-.474.58-.738.5-1 1.29-1.792 1.894-2.714.132-.184.316-.342.474-.5.13-.16.237-.106.342.026.71.896 1.42 1.818 2.13 2.714.528.66 1.054 1.29 1.554 1.976.605.844 1.184 1.687 1.79 2.53.684.975 1.368 1.95 2.026 2.95 1 1.477 1.947 2.953 2.947 4.428.737 1.08 1.474 2.135 2.184 3.215h-1.344c-1.236-.025-2.5-.13-3.736-.078-1.684.08-3.394.264-5.078.396-2.132.185-4.29.21-6.42.21-.765 0-1.528.107-2.29.16-.922.052-1.817.105-2.738.13-1.08.054-2.13.08-3.21.107-.606.026-1.237 0-1.895 0zm30.183 12.12v.238c-.026 0-.052.027-.105.027-.105-.37-.21-.766-.342-1.135-.263-.765-.553-1.53-1.027-2.214-.528-.737-1-1.5-1.528-2.265-.13-.185-.316-.343-.474-.5-.553-.607-1.106-1.24-1.816-1.687a21.485 21.485 0 0 0-3.29-1.688 7.374 7.374 0 0 1-.92-.474h.63l4.5-.08c.974-.025 1.922-.025 2.895-.078.236 0 .368.08.5.29.236.395.473.79.736 1.186.027.052.08.13.08.21 0 .58 0 1.186.026 1.766.025.606.08 1.186.104 1.792 0 .606-.053 1.238-.026 1.87.027.897.053 1.82.053 2.74zM26.447 26.67c1.237-.053 2.42-.132 3.632-.185.945-.053 1.92-.08 2.866-.132.395-.025.764-.05 1.158 0-.42.212-.842.423-1.21.686-.474.316-.92.737-1.395 1.08-.475.342-.896.764-1.29 1.212-.5.605-1.053 1.132-1.58 1.712-.37.422-.79.817-1.105 1.265-.447.58-.842 1.21-1.263 1.87.132-2.504.29-4.98.184-7.51zm17.185 25.35c-.843.21-1.71.448-2.58.553-.736.106-1.5.08-2.263.08a25.42 25.42 0 0 1-2.028-.08c-.763-.078-1.526-.157-2.263-.5-.633-.29-1.29-.553-1.92-.87-.634-.316-1.265-.684-1.74-1.264-.34-.423-.815-.765-1.236-1.134.08.316.263.58.553.764-.132.158-.316.08-.58-.343-.078.053-.157.08-.21.106.08-.185.158-.37.237-.527-.105-.21-.237-.448-.342-.66-.21-.342-.42-.71-.605-1.053-.053-.08-.053-.158-.105-.237a5.893 5.893 0 0 1-.37-.475c-.21-.315-.394-.657-.657-.974 0 .08.027.158.027.264-.027 0-.053.026-.053.026l-.554-1.344c-.026 0-.026 0-.052.026l.473 1.74c-.026 0-.052.025-.08.025-.077-.104-.156-.21-.21-.34-.052-.212-.21-.212-.34-.133-.08.053-.133.237-.106.316.185.448.395.896.606 1.344.052.158.105.29.184.448.027.053.106.105.106.184.106.21.185.42.316.606.237.316.5.632.737.948.235.316.445.66.656.975.026.053.105.053.13.08.133.395.58.684.896.526.08.606.737.817 1 1.397a11.957 11.957 0 0 1-.763-.343c-.027.026-.027.052-.054.105.316.158.632.316.92.5.265.16.528.317.765.5.316.29.685.45 1.13.554a.282.282 0 0 0-.05-.107c.736.343 1.5.712 2.078 1-2.737.054-5.658.107-8.685.16 0-.5-.026-.975-.026-1.476 0-.21.052-.395.025-.606-.08-1.21-.08-2.424-.237-3.61-.157-1.264-.157-2.503-.13-3.77.025-.683-.027-1.394-.054-2.08 0-.922 0-1.82.028-2.74 0-.132.053-.237.106-.37h.08c.025.054 0 .133.05.16.08.08.212.21.265.184.157-.106.394-.21.447-.37.13-.315.184-.658.184-.974 0-.236.106-.394.21-.553.054-.08.08-.158.133-.263-.105-.08-.21-.132-.342-.237.106-.29.08-.633.475-.79.052-.027.052-.16.08-.238.025-.213.05-.45.078-.66.052.08.08.105.13.157a.42.42 0 0 1 .054-.08c0-.104-.026-.315 0-.315.316-.053.184-.395.342-.553.025-.028-.027-.107-.027-.16 0-.052 0-.13.026-.13.367-.08.315-.475.552-.66.08-.053.105-.13.21-.263.21.368-.158.553-.184.816.446-.263.578-.895.315-1.08.105-.08.21-.184.29-.29.29-.316.604-.606.868-.922.185-.236.29-.526.474-.763.106-.132.316-.237.474-.317.474-.262.92-.552 1.21-1 .053-.053.132-.105.21-.158.08-.053.238-.053.264-.132.027-.052-.052-.184-.105-.263.104-.053.21-.158.42-.264-.08.158-.105.264-.158.37l.13.13c.238-.184.606-.394.843-.552 0-.025-.132-.13-.132-.13-.157.08-.394.21-.63.316.05-.08.05-.132.08-.158.367-.237.735-.474 1.13-.66.92-.42 1.842-.842 2.763-1.237.158-.08.37-.026.553-.026.078 0 .13 0 .21-.026.42-.132.842-.264 1.263-.37.183-.052.393-.078.58-.078.787.025 1.577.025 2.366.078.342.026.658.105.974.21a9.88 9.88 0 0 1 1.184.5c.447.24.868.502 1.29.792.763.5 1.473 1.054 2.236 1.502.737.448 1.316 1.054 1.79 1.74.58.816 1.237 1.554 1.5 2.555l.394 1.74c.08.316.264.632.185 1-.133.66-.238 1.345-.343 2.004-.052.265-.105.53-.078.79.05.82-.265 1.53-.58 2.268-.106.237-.264.475-.395.738a.798.798 0 0 0 .21.106l.237-.474c.027 0 .027 0 .053.027-.132.368-.237.764-.37 1.133-.314.817-.63 1.66-1.025 2.45-.21.448-.58.817-.842 1.24-.262.368-.473.763-.736 1.106-.237.29-.473.58-.79.79-.71.527-1.447 1.054-2.21 1.476-.473.29-1.026.448-1.552.58zm-14.027-1.4l-.026.027c-.055-.026-.134-.052-.186-.105l-.632-.95c-.052-.078-.08-.157-.052-.262.29.448.58.87.895 1.29zm16.37 3.61c1.183-.5 2.157-1.21 3.05-2.028.133-.132.264-.263.422-.37 1.106-.684 1.92-1.633 2.658-2.687.842-1.212 1.395-2.582 2.08-3.873a2.73 2.73 0 0 1 .157-.29c-.053 3.004.29 5.955.684 8.933-2.973.105-6 .21-9.052.316zm26.683-.79c-.026.053-.08.106-.105.16-.027-.054-.027-.133-.053-.24-.158.423-.5.212-.737.212-1.42.027-2.868.027-4.29.027-1.368 0-2.762 0-4.13.024-.448 0-.922.105-1.37.132-1.078.052-2.157.08-3.236.105-.08 0-.158-.13-.29-.236a1.81 1.81 0 0 1-.158.237c-.028-.052-.08-.104-.133-.183-.026.08-.053.158-.08.21H58c-.053-.368-.158-.71-.158-1.08 0-.79.08-1.58.105-2.372.027-.368 0-.71 0-1.054.106.08.185.133.29.21.052-.103.105-.182.158-.26 0 0-.053-.028-.106-.08.05-.027.104-.08.104-.106.026-.08.08-.158.08-.21 0-.185-.054-.343-.08-.5.026 0 .052 0 .08-.028l.157.79h.08c-.106-.183.236-.342-.053-.552-.026-.027.026-.185.026-.264-.08-.157-.13-.315-.21-.526.026-.026.105-.053.184-.08-.105-.052-.184-.104-.263-.13.263-.238.263-.37.026-.633.054-.025.106-.025.106-.05 0-.238 0-.475-.052-.71-.053-.266.08-.58-.316-.74a.79.79 0 0 0 .105.21s-.08.027-.158.08c-.342-.317-.13-.74-.21-1.213.184.053.316.106.447.16-.053-.186-.184-.397-.263-.634h-.107v-1.74c0 .027.184.027.29.054 0-.027.025-.053.025-.08-.08-.105-.185-.21-.29-.342l.053-.053c-.21-.262-.105-.63-.105-.71V39.4c.264.264-.13.606.264.764v-.263h-.027c-.026-.395-.026-.79-.052-1.186h-.052c-.027.054-.027.08-.054.133h-.052l.158-6.298c.263.342.552.66.736 1 .606 1.108 1.395 2.057 2.132 3.058.632.87 1.21 1.818 1.79 2.714.71 1.08 1.394 2.16 2.105 3.24a81.41 81.41 0 0 0 1.63 2.426c.5.71 1.028 1.396 1.554 2.082.446.606.92 1.212 1.367 1.818.527.738 1.053 1.475 1.58 2.187.262.368.552.737.84 1.106.16.21.396.37.554.5-.025 0-.052 0-.104-.026.08.105.13.184.184.237.29.158.316.316.158.554zM74 46.854v-.185c0 .052.026.13 0 .184zm.895-11.62c-.027 0-.184-.16-.21-.186-.027.08 0 .158-.053.264-.027-.078-.21-.052-.21-.13-.027.368.157.737.13 1.106.08-.053.395-.08.474-.158.027.026.08.052.106.052-.527.396-.395.79-.158 1.24.052.104.21.315.052.526-.052.053.027.21.053.343h.077v.05l-.237.08c-.052-.08-.367-.236-.367-.37v1.346c.263.08.263.448.368.633a.768.768 0 0 0 .107-.21l.027.024c-.027.158-.053.316-.106.475-.052.236-.105.447-.13.684 0 .026.05.08.05.105-.288.66-.13 1.396-.235 2.08-.08.5 0 1.03-.053 1.556-.054.448-.16.922-.264 1.37-.027.08-.08.105-.21.158.052-.316.026-.527-.027-.817-.028 0-.37-.184-.397-.184 0 .37.21.87.29 1.29-.08-.026-.395-.21-.42-.21-.054.316-.054.738-.08 1.08-.027.264-.263.5-.29.79 0 .16.184.264.158.528h.21c0-.526.238-1 .238-1.554h.078c.027.053.106.106.08.132-.053.29-.16.606-.132.896 0 .158.13.316.08.5-.054.16-.08.317-.107.554-.027-.132-.053-.184-.053-.263-.026 0-.263-.027-.29-.027-.026.158.185.316.158.448-.026.026-.052.026-.105.053l-.868-1.266c-.686-1-1.37-2.003-2.054-3.03a6.312 6.312 0 0 1-.475-.79 37.09 37.09 0 0 0-2.71-4.033c-.762-.974-1.37-2.03-2.08-3.055-.656-.975-1.314-1.924-1.972-2.9-.237-.315-.526-.605-.737-.948-.683-1.08-1.29-2.187-1.972-3.267-.58-.897-1.21-1.767-1.816-2.636-.21-.29-.42-.607-.632-.923a.37.37 0 0 1-.052-.182c-.053-.58-.106-1.16-.132-1.713 0-.527.053-1.054.053-1.608v-.474c0-.132.025-.237.025-.37.025-.025.052-.078.078-.104-.763 0-1.553-.028-2.316 0-.5.025-.763-.186-1.105-.555-1-1.133-1.737-2.424-2.605-3.636a162.42 162.42 0 0 0-2.5-3.427c-.685-.922-1.37-1.818-2.053-2.74-.764-1.054-1.5-2.108-2.29-3.162a381.983 381.983 0 0 0-2.895-3.794c-.45-.58-.95-1.133-1.45-1.74.343.054.66.106.975.133l1.264.08c.947.077 1.894.13 2.84.26.79.107 1.58.265 2.396.396 1.738.29 3.448.765 5.106 1.318.974.316 1.92.738 2.87 1.133 2.13.87 4.157 1.924 6.157 3.03.63.343 1 .896 1.472 1.397.685.712 1.37 1.423 2.027 2.16.762.87 1.472 1.766 2.21 2.662.657.79 1.34 1.58 2 2.372.21.237.37.527.552.79.42.633.895 1.24 1.263 1.924.262.502.42 1.082.604 1.635.262.817.526 1.607.79 2.424.183.606.34 1.24.472 1.87.106.423.08.87.21 1.29.16.556 0 1.16.16 1.715.025.053.05.132.078.185.105.104.184.21.026.368-.025.026-.025.13 0 .21.054-.052.08-.105.133-.184 0 .053.025.08.025.105 0 .104-.027.21 0 .315 0 .052.052.13.078.184.053-.054.105-.08.21-.16.237.897.264 1.793.264 2.715 0 .87.157 1.74-.21 2.583.078-.29-.106-.555-.027-.818z"/><path d="M58.08 45.482c.025 0 .052.027.052.027l-.027-.03c0-.025 0-.025-.026 0zm4.157 26.036c-.29.21-.58.395-.948.474-.028-.026-.028-.053-.054-.08.29-.184.605-.368.895-.553.027.05.08.104.106.157zM12.895 35.81c.29-.367.58-.736.894-1.105.025.026.235.08.262.105-.29.37-.685.87-.974 1.265-.054-.053-.133-.237-.185-.264zM5.42 48.725c-.21-.448-.42-.923-.63-1.37a.91.91 0 0 1 .236-.106c.29.42.42.92.632 1.37 0 0-.21.105-.237.105zm6.712-12.65c-.158.238-.316.502-.474.74-.026-.028-.316.104-.342.078.158-.237.552-.66.71-.896.027.026.053.053.106.08zM59.422 72.6c.025 0 .025-.026.052-.026.184.026.394.052.605.052-.344.237-.555.21-.66-.026zm-47.24-35.418c.028-.08.08-.158.133-.237.052 0 .13-.027.13-.027.107-.184.107-.316.212-.474-.026-.026-.053-.026-.08-.053-.157.108-.315.24-.473.345.053.052.053.08.053.132-.21-.027-.29.08-.395.368-.026.08-.158.106-.29.21-.026.054-.052.186-.105.317l.027.028c-.053.053-.132.08-.132.08-.158.157-.342.29-.5.447-.026.08-.052.158-.052.237.185-.184.5-.527.737-.738l.027.027c.105-.158.184-.316.29-.474.025.026.025.052.052.08-.08.21-.158.446-.237.657-.055.026-.134.08-.134.053-.105.08-.184.184-.29.263l-.473.316c-.263.237-.526.447-.816.685-.184.29-.368.553-.58.896.317-.08.396.053.37.317.368.052.395-.237.5-.448.026-.054.053-.16.105-.186.237-.21.5-.394.763-.605.053-.053.053-.16.053-.238 0-.026-.133-.026-.212-.053.237-.264.58-.71.816-1 .132-.08.263-.186.263-.265-.026-.29.158-.368.37-.474-.106-.08-.133-.157-.133-.183z"/><path d="M12.71 36.892c-.105.184-.21.342-.315.527l-.158-.08c-.105.605-.474 1.132-.842 1.237.105.053.21.106.29.08.078-.027.13-.16.183-.238l.71-1.028.238-.396-.105-.105zM3.948 48.46c.132 0 .264.026.42.026 0-.105.133-.08.133-.184h.08c0 .132.026.237.026.37h-.552c-.027-.027-.132-.186-.106-.212zm-.21-1.212c-.08-.08-.21-.158-.21-.237-.027-.104.052-.235.13-.367.054.184.08.342.132.527-.027.025-.053.052-.053.078zm.658-1.687c.105.266.21.556.316.82a.798.798 0 0 0-.21.105c-.105-.264-.237-.554-.342-.817a.652.652 0 0 1 .237-.106zm58.58 25.194c.13-.052.288-.08.5-.13-.238.183-.422.315-.58.473-.027-.026-.053-.053-.08-.053.053-.105.106-.184.16-.29zM30.63 15.074c.157-.106.29-.185.447-.29l.052.052c-.16.21-.29.42-.475.685-.026-.183-.026-.29-.053-.42-.026 0 0 0 .027-.026zm7.71 13.333c.237-.106.474-.21.763-.343-.026.158-.026.264-.026.37a.927.927 0 0 0-.264-.054c-.158.027-.448.238-.58.264-.025 0 .106-.21.106-.237zm19.74 22.346c.052.263.552.395.052.658.08.055.157.08.236.134a.2.2 0 0 1-.052.106c-.053.025-.158.078-.21.05-.027 0-.08-.104-.08-.157 0-.237.027-.474.053-.79z"/></g></symbol><symbol viewBox="0 0 24 24" id="powerpoint" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M8 11v2h1v6H8v1h4v-1h-1v-2h2a3 3 0 0 0 3-3 3 3 0 0 0-3-3H8m5 2a1 1 0 0 1 1 1 1 1 0 0 1-1 1h-2v-2h2z" fill="#d14524"/></symbol><symbol viewBox="0 0 67.47 70" id="powershell" xmlns="http://www.w3.org/2000/svg"><path d="M18.545 12.4c-3.014 0-6.08 2.34-6.873 5.248L1.91 53.438c-.793 2.908.996 5.248 4.01 5.248h42.887c3.014 0 6.08-2.34 6.873-5.248l9.761-35.79c.794-2.908-.993-5.248-4.007-5.248h-42.89zm4.848 6.243c.652.04 1.29.33 1.76.86l7.96 9.013-3.957 3.246 3.957-3.244 4.832 5.47c.037.042.06.088.094.131.026.034.057.06.082.096.02.028.032.057.05.086.057.087.105.176.15.267.028.06.055.117.08.178a2.546 2.546 0 0 1 .171.764c.005.073.01.146.008.219-.002.09-.01.178-.021.267a2.53 2.53 0 0 1-.036.217 2.56 2.56 0 0 1-.07.252c-.024.076-.048.15-.08.224a2.547 2.547 0 0 1-.111.22 2.503 2.503 0 0 1-.133.218 2.546 2.546 0 0 1-.147.187c-.058.07-.118.137-.185.202-.027.026-.048.057-.076.082-.037.032-.077.054-.116.084-.038.03-.07.065-.11.093L16.8 52.271a2.552 2.552 0 0 1-3.563-.626 2.553 2.553 0 0 1 .63-3.563l18.349-12.853-3.06-3.467-7.839-8.873a2.549 2.549 0 0 1 .225-3.608 2.546 2.546 0 0 1 1.85-.638zm22.441 28.214c1.377 0 2.255 1.083 1.969 2.43-.287 1.347-1.627 2.433-3.004 2.434l-9.957.006c-1.378 0-2.256-1.083-1.969-2.43.287-1.347 1.626-2.433 3.004-2.434l9.957-.006z" fill="#03a9f4" stroke-width="5.342" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 210 210" id="prettier" xmlns="http://www.w3.org/2000/svg"><title>prettier-icon-dark</title><g transform="matrix(.9 0 0 .9 10.5 10.5)" fill="none" fill-rule="evenodd"><rect fill="#56B3B4" x="165" y="40" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="200" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="135" y="120" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="75" y="120" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="120" width="50" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="160" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="80" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="65" y="20" width="110" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="20" width="40" height="10" rx="5"/><rect fill="#F7BA3E" x="55" y="180" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="55" y="60" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="180" width="30" height="10" rx="5"/><rect fill="#F7BA3E" x="15" y="60" width="30" height="10" rx="5"/><rect fill="#56B3B4" x="95" y="100" width="90" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="100" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="100" width="20" height="10" rx="5"/><rect fill="#BF85BF" x="105" y="40" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="40" width="80" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="140" width="100" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="140" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="135" y="60" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="135" y="80" width="60" height="10" rx="5"/><rect fill="#56B3B4" x="15" width="130" height="10" rx="5"/></g></symbol><symbol viewBox="0 0 80 80" id="protractor" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="hxa"><path transform="scale(1 -1)" fill="#564b55" stroke-width="27.224" d="M-2.983-69.251h69.412v67.108H-2.983z"/></clipPath></defs><g transform="matrix(1.13039 0 0 -1.13039 5.714 82.137)" clip-path="url(#hxa)"><g transform="scale(.1)"><path d="M1180.54 92.324c-5.53 0-9.93-1.797-13.23-5.39-3.29-3.614-5.22-8.594-5.81-14.97h36.02c0 6.583-1.47 11.622-4.4 15.126-2.93 3.496-7.12 5.234-12.58 5.234zm2.84-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.88 6.21-8.83 14.824-8.83 25.84 0 11.101 2.73 19.922 8.21 26.464 5.45 6.524 12.81 9.805 22.02 9.805 8.63 0 15.46-2.851 20.48-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.45c.21-8.086 2.26-14.22 6.12-18.418 3.89-4.18 9.34-6.29 16.38-6.29 7.42 0 14.76 1.563 22 4.669V34.14c-3.68-1.602-7.18-2.746-10.48-3.438-3.28-.684-7.24-1.035-11.89-1.035M1272.34 30.918v44.57c0 5.606-1.28 9.805-3.82 12.559-2.56 2.773-6.56 4.16-12.02 4.16-7.2 0-12.49-1.953-15.84-5.851-3.34-3.895-5.03-10.32-5.03-19.286V30.918h-10.42v68.887h8.47l1.71-9.422h.5c2.14 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.15 2.804 12.88 2.804 8.29 0 14.54-2.011 18.73-6.015 4.19-3.985 6.28-10.391 6.28-19.192V30.918h-10.43M1328.96 38.406c7.1 0 12.27 1.938 15.48 5.813 3.22 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.44 6.25-15.56 6.25-6.11 0-10.79-2.383-14.04-7.129-3.26-4.746-4.88-11.472-4.88-20.136 0-8.797 1.61-15.45 4.84-19.93 3.23-4.484 7.97-6.723 14.22-6.723zm20.85 1.762h-.56c-4.83-7.004-12.02-10.5-21.62-10.5-9.01 0-16.03 3.066-21.04 9.238-5 6.153-7.5 14.922-7.5 26.27 0 11.355 2.51 20.176 7.54 26.465 5.03 6.289 12.03 9.433 21 9.433 9.34 0 16.5-3.398 21.49-10.195h.81l-.43 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.38 9.25M1434.91 38.27c1.85 0 3.63.136 5.34.421 1.72.274 3.09.547 4.1.84v-7.976c-1.15-.559-2.81-.996-5.01-1.36-2.18-.351-4.17-.527-5.94-.527-13.32 0-19.97 7.012-19.97 21.055V91.71h-9.88v5.027l9.88 4.336 4.38 14.707h6.04V99.805h20V91.71h-20V51.16c0-4.15.98-7.333 2.96-9.56 1.97-2.206 4.67-3.331 8.1-3.331M1463.81 65.43c0-8.809 1.76-15.508 5.27-20.118 3.53-4.609 8.69-6.906 15.53-6.906s12.01 2.297 15.56 6.875c3.53 4.602 5.3 11.301 5.3 20.149 0 8.75-1.77 15.41-5.3 19.953-3.55 4.539-8.77 6.824-15.69 6.824-6.82 0-11.99-2.246-15.47-6.73-3.46-4.48-5.2-11.16-5.2-20.047zm52.47 0c0-11.23-2.83-20-8.48-26.309-5.66-6.309-13.47-9.453-23.44-9.453-6.17 0-11.64 1.445-16.42 4.336-4.78 2.89-8.46 7.031-11.06 12.45-2.59 5.401-3.88 11.73-3.88 18.976 0 11.23 2.8 19.968 8.41 26.242 5.61 6.258 13.4 9.402 23.38 9.402 9.64 0 17.3-3.222 22.97-9.62 5.69-6.415 8.52-15.087 8.52-26.024M1591.71 92.324c-5.54 0-9.94-1.797-13.23-5.39-3.3-3.614-5.24-8.594-5.81-14.97h36c0 6.583-1.46 11.622-4.39 15.126-2.93 3.496-7.13 5.234-12.57 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.89 6.21-8.83 14.824-8.83 25.84 0 11.101 2.74 19.922 8.2 26.464 5.46 6.524 12.81 9.805 22.04 9.805 8.62 0 15.45-2.851 20.48-8.523 5.03-5.676 7.54-13.157 7.54-22.461v-6.613h-47.45c.21-8.086 2.25-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.36-6.29 7.43 0 14.77 1.563 22.01 4.669V34.14c-3.69-1.602-7.17-2.746-10.46-3.438-3.3-.684-7.27-1.035-11.91-1.035M1683.5 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12.01 4.16-7.2 0-12.48-1.953-15.83-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M1740.11 38.406c7.12 0 12.28 1.938 15.49 5.813 3.21 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.43 6.25-15.56 6.25-6.12 0-10.8-2.383-14.05-7.129-3.24-4.746-4.88-11.472-4.88-20.136 0-8.797 1.64-15.45 4.85-19.93 3.22-4.484 7.96-6.723 14.21-6.723zm20.87 1.762h-.57c-4.82-7.004-12.03-10.5-21.62-10.5-9.01 0-16.02 3.066-21.03 9.238-5 6.153-7.52 14.922-7.52 26.27 0 11.355 2.52 20.176 7.55 26.465 5.02 6.289 12.02 9.433 21 9.433 9.34 0 16.5-3.398 21.48-10.195h.83l-.44 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.37 9.25M1846.07 38.27c1.85 0 3.64.136 5.36.421 1.7.274 3.07.547 4.08.84v-7.976c-1.13-.559-2.8-.996-5-1.36-2.2-.351-4.18-.527-5.94-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.86v5.027l9.86 4.336 4.4 14.707h6.04V99.805H1855V91.71h-19.98V51.16c0-4.15.98-7.333 2.95-9.56 1.97-2.206 4.68-3.331 8.1-3.331M1894.26 92.324c-5.53 0-9.94-1.797-13.22-5.39-3.31-3.614-5.25-8.594-5.83-14.97h36.01c0 6.583-1.45 11.622-4.38 15.126-2.95 3.496-7.13 5.234-12.58 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.1 9.297-5.9 6.21-8.84 14.824-8.84 25.84 0 11.101 2.73 19.922 8.2 26.464 5.47 6.524 12.81 9.805 22.03 9.805 8.63 0 15.46-2.851 20.49-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.46c.22-8.086 2.26-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.37-6.29 7.42 0 14.75 1.563 22 4.669V34.14c-3.7-1.602-7.17-2.746-10.47-3.438-3.28-.684-7.25-1.035-11.9-1.035M1983.36 49.727c0-6.426-2.4-11.368-7.18-14.844-4.77-3.477-11.47-5.215-20.11-5.215-9.13 0-16.26 1.445-21.37 4.336v9.687a51.32 51.32 0 0 1 10.65-3.964c3.79-.977 7.45-1.457 10.97-1.457 5.46 0 9.64.87 12.57 2.609 2.95 1.738 4.41 4.394 4.41 7.95 0 2.694-1.17 4.98-3.5 6.894-2.32 1.914-6.85 4.152-13.6 6.757-6.41 2.383-10.97 4.473-13.67 6.25-2.71 1.778-4.72 3.81-6.04 6.067-1.31 2.254-1.98 4.96-1.98 8.113 0 5.606 2.29 10.04 6.86 13.281 4.57 3.25 10.84 4.883 18.79 4.883 7.42 0 14.66-1.515 21.74-4.531l-3.71-8.496c-6.9 2.851-13.17 4.277-18.79 4.277-4.94 0-8.67-.77-11.18-2.324-2.52-1.543-3.78-3.691-3.78-6.406 0-1.844.48-3.418 1.42-4.707.95-1.309 2.46-2.54 4.56-3.711 2.09-1.184 6.11-2.871 12.07-5.086 8.16-2.98 13.69-5.98 16.55-8.996 2.87-3.02 4.32-6.809 4.32-11.367M2021.28 38.27c1.85 0 3.64.136 5.35.421 1.71.274 3.09.547 4.09.84v-7.976c-1.14-.559-2.81-.996-5.01-1.36-2.18-.351-4.18-.527-5.93-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.87v5.027l9.87 4.336 4.4 14.707h6.02V99.805h20V91.71h-20V51.16c0-4.15 1-7.333 2.97-9.56 1.98-2.206 4.67-3.331 8.1-3.331M2053.61 30.918h-10.42v68.887h10.42zm-11.31 87.559c0 2.39.59 4.14 1.76 5.253 1.18 1.106 2.65 1.661 4.42 1.661 1.67 0 3.1-.567 4.32-1.7 1.22-1.132 1.82-2.871 1.82-5.214 0-2.344-.6-4.09-1.82-5.247-1.22-1.16-2.65-1.726-4.32-1.726-1.77 0-3.24.566-4.42 1.726-1.17 1.157-1.76 2.903-1.76 5.247M2121.59 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.49l1.69-9.422h.5c2.15 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.16 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2159.29 77.742c0-4.812 1.35-8.465 4.08-10.926 2.72-2.48 6.51-3.71 11.37-3.71 10.19 0 15.28 4.953 15.28 14.831 0 10.344-5.16 15.532-15.47 15.532-4.9 0-8.67-1.32-11.31-3.965-2.63-2.649-3.95-6.555-3.95-11.762zm-5.67-58.387c0-3.73 1.58-6.55 4.72-8.488 3.14-1.922 7.65-2.879 13.52-2.879 8.75 0 15.24 1.309 19.45 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.15 6.32-3.45 7.754-2.31 1.457-6.65 2.168-13.01 2.168h-12.51c-4.74 0-8.43-1.12-11.06-3.386-2.65-2.266-3.97-5.508-3.97-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.23-3.39 3.15-5.754.91-2.371 1.37-5.039 1.37-8.02 0-6.746-2.29-12.128-6.91-16.152-4.61-4.012-10.93-6.023-18.98-6.023-2.05 0-3.98.156-5.78.5-4.45-2.356-6.67-5.305-6.67-8.871 0-1.883.77-3.282 2.34-4.176 1.54-.902 4.21-1.36 7.97-1.36h12.2c7.46 0 13.19-1.574 17.19-4.707 4-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2192.38 2.004 2183.46 0 2171.72 0c-9 0-15.95 1.68-20.82 5.027-4.88 3.352-7.34 8.079-7.34 14.211 0 4.18 1.35 7.813 4.03 10.88 2.68 3.046 6.45 5.116 11.32 6.21-1.77.8-3.24 2.031-4.44 3.711-1.19 1.68-1.78 3.633-1.78 5.84 0 2.52.66 4.707 2.01 6.602 1.34 1.882 3.44 3.71 6.34 5.468-3.56 1.465-6.46 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.26 13.37 6.79 17.452 4.52 4.082 10.93 6.133 19.22 6.133 3.6 0 6.86-.429 9.75-1.27h23.82M2284.61 91.71h-17.54V30.919h-10.43v60.793h-12.31v4.707l12.31 3.766v3.839c0 16.922 7.4 25.391 22.19 25.391 3.65 0 7.93-.73 12.82-2.195l-2.7-8.364c-4.03 1.301-7.46 1.946-10.31 1.946-3.93 0-6.85-1.309-8.73-3.926-1.89-2.617-2.84-6.816-2.84-12.598v-4.472h17.54V91.71M2302.87 65.43c0-8.809 1.76-15.508 5.28-20.118 3.52-4.609 8.7-6.906 15.52-6.906 6.84 0 12.02 2.297 15.57 6.875 3.54 4.602 5.3 11.301 5.3 20.149 0 8.75-1.76 15.41-5.3 19.953-3.55 4.539-8.78 6.824-15.69 6.824-6.83 0-11.99-2.246-15.46-6.73-3.48-4.48-5.22-11.16-5.22-20.047zm52.48 0c0-11.23-2.82-20-8.47-26.309-5.67-6.309-13.48-9.453-23.46-9.453-6.15 0-11.62 1.445-16.4 4.336-4.77 2.89-8.47 7.031-11.06 12.45-2.59 5.401-3.9 11.73-3.9 18.976 0 11.23 2.81 19.968 8.43 26.242 5.6 6.258 13.4 9.402 23.38 9.402 9.63 0 17.28-3.222 22.97-9.62 5.68-6.415 8.51-15.087 8.51-26.024M2403.79 101.074c3.07 0 5.8-.254 8.22-.761l-1.43-9.676c-2.86.633-5.37.933-7.55.933-5.58 0-10.33-2.261-14.3-6.785-3.95-4.531-5.94-10.156-5.94-16.902V30.918h-10.43v68.887h8.62l1.19-12.754h.5c2.56 4.48 5.63 7.949 9.23 10.37 3.61 2.423 7.56 3.653 11.89 3.653M2500.33 69.766l-10.68 28.476c-1.39 3.594-2.81 8.028-4.28 13.262-.93-4.024-2.24-8.438-3.96-13.262l-10.81-28.476zm14.77-38.848l-11.44 29.227h-36.83l-11.32-29.227h-10.81l36.34 92.273h8.98l36.13-92.273h-11.05M2583.07 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2620.76 77.742c0-4.812 1.36-8.465 4.08-10.926 2.73-2.48 6.53-3.71 11.37-3.71 10.2 0 15.28 4.953 15.28 14.831 0 10.344-5.15 15.532-15.45 15.532-4.91 0-8.68-1.32-11.32-3.965-2.64-2.649-3.96-6.555-3.96-11.762zm-5.66-58.387c0-3.73 1.57-6.55 4.71-8.488 3.15-1.922 7.65-2.879 13.53-2.879 8.75 0 15.23 1.309 19.44 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.14 6.32-3.45 7.754-2.31 1.457-6.64 2.168-13 2.168h-12.51c-4.74 0-8.43-1.12-11.07-3.386-2.63-2.266-3.96-5.508-3.96-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.22-3.39 3.14-5.754.92-2.371 1.38-5.039 1.38-8.02 0-6.746-2.3-12.128-6.92-16.152-4.61-4.012-10.92-6.023-18.97-6.023-2.05 0-3.99.156-5.78.5-4.46-2.356-6.67-5.305-6.67-8.871 0-1.883.78-3.282 2.33-4.176 1.55-.902 4.21-1.36 7.98-1.36h12.2c7.46 0 13.18-1.574 17.18-4.707 4.01-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2653.87 2.004 2644.94 0 2633.2 0c-9 0-15.95 1.68-20.83 5.027-4.88 3.352-7.33 8.079-7.33 14.211 0 4.18 1.35 7.813 4.02 10.88 2.69 3.046 6.47 5.116 11.32 6.21-1.77.8-3.23 2.031-4.43 3.711-1.19 1.68-1.79 3.633-1.79 5.84 0 2.52.66 4.707 2.01 6.602 1.35 1.882 3.45 3.71 6.35 5.468-3.56 1.465-6.47 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.25 13.37 6.79 17.452 4.52 4.082 10.92 6.133 19.21 6.133 3.62 0 6.86-.429 9.75-1.27h23.83M2692.7 99.805V55.117c0-5.605 1.27-9.805 3.83-12.566 2.56-2.766 6.57-4.145 12.01-4.145 7.2 0 12.47 1.965 15.81 5.903 3.33 3.945 4.99 10.379 4.99 19.304v36.192h10.44V30.918h-8.62l-1.5 9.25h-.58c-2.13-3.41-5.1-5.988-8.88-7.793-3.8-1.809-8.13-2.707-12.99-2.707-8.37 0-14.65 1.992-18.81 5.977-4.18 3.964-6.26 10.351-6.26 19.101v45.059h10.56M2760.61 30.918h10.43v97.805h-10.43zM2810.67 38.27c6.5 0 11.6 1.789 15.31 5.343 3.71 3.575 5.56 8.555 5.56 14.961v6.23l-10.44-.448c-8.3-.286-14.27-1.583-17.94-3.868-3.66-2.273-5.5-5.82-5.5-10.644 0-3.781 1.14-6.64 3.42-8.613 2.29-1.973 5.48-2.961 9.59-2.961zm23.57-7.352l-2.07 9.805h-.51c-3.44-4.305-6.86-7.227-10.27-8.77-3.42-1.523-7.68-2.285-12.8-2.285-6.83 0-12.17 1.758-16.05 5.273-3.87 3.528-5.81 8.536-5.81 15.032 0 13.906 11.12 21.199 33.37 21.875l11.7.359v4.277c0 5.418-1.17 9.395-3.5 11.985-2.32 2.566-6.03 3.855-11.15 3.855-5.74 0-12.24-1.758-19.49-5.273l-3.21 7.988c3.4 1.836 7.11 3.281 11.16 4.324a47.81 47.81 0 0 0 12.16 1.575c8.23 0 14.3-1.817 18.27-5.461 3.96-3.66 5.93-9.5 5.93-17.54V30.919h-7.73M2893.6 101.074c3.07 0 5.8-.254 8.25-.761l-1.46-9.676c-2.84.633-5.35.933-7.54.933-5.56 0-10.33-2.261-14.3-6.785-3.96-4.531-5.93-10.156-5.93-16.902V30.918h-10.44v68.887h8.61l1.19-12.754h.5c2.57 4.48 5.65 7.949 9.25 10.37 3.6 2.423 7.56 3.653 11.87 3.653M2901.63 6.727c-3.94 0-7.04.558-9.31 1.691v9.121c2.97-.84 6.08-1.25 9.31-1.25 4.14 0 7.3 1.25 9.45 3.77 2.16 2.507 3.24 6.132 3.24 10.859v91.895h10.69V31.797c0-7.95-2.01-14.121-6.04-18.496-4.02-4.383-9.8-6.574-17.34-6.574M2999.96 55.371c0-8.086-2.93-14.394-8.8-18.918-5.87-4.52-13.83-6.785-23.88-6.785-10.9 0-19.27 1.406-25.14 4.219v10.3c3.77-1.59 7.88-2.847 12.31-3.765 4.45-.93 8.85-1.399 13.21-1.399 7.12 0 12.49 1.36 16.09 4.063 3.59 2.695 5.4 6.465 5.4 11.277 0 3.196-.63 5.805-1.91 7.832-1.29 2.024-3.42 3.907-6.42 5.625-2.99 1.711-7.56 3.664-13.67 5.84-8.55 3.059-14.66 6.692-18.32 10.871-3.66 4.2-5.51 9.668-5.51 16.407 0 7.089 2.68 12.714 7.99 16.914 5.32 4.191 12.36 6.289 21.12 6.289 9.13 0 17.54-1.68 25.2-5.032l-3.32-9.304c-7.59 3.183-14.96 4.785-22.13 4.785-5.66 0-10.07-1.223-13.26-3.652-3.19-2.43-4.78-5.809-4.78-10.118 0-3.191.59-5.8 1.76-7.832 1.17-2.031 3.14-3.886 5.95-5.597 2.78-1.688 7.04-3.563 12.79-5.625 9.63-3.426 16.26-7.118 19.89-11.063 3.62-3.937 5.43-9.043 5.43-15.332M741.648 375.406h30c28.965 0 50.227 5.039 63.774 15.117 13.531 10.079 20.32 25.821 20.32 47.247 0 19.832-6.074 34.628-18.191 44.402-12.141 9.758-31.028 14.641-56.692 14.641h-39.211zm172.192 64.246c0-36.062-11.809-63.691-35.434-82.898-23.621-19.219-57.234-28.82-100.847-28.82h-35.911V198.73h-56.445v345.329h99.438c43.14 0 75.457-8.829 96.961-26.465 21.496-17.637 32.238-43.614 32.238-77.942M1099.26 464.691c11.17 0 20.39-.789 27.63-2.371l-5.43-51.718c-7.88 1.894-16.07 2.832-24.57 2.832-22.2 0-40.19-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.514v261.227h43.464l7.32-46.055h2.83c8.66 15.594 19.96 27.95 33.9 37.09 13.93 9.141 28.93 13.699 45 13.699M1206.88 329.82c0-60.308 22.28-90.465 66.85-90.465 44.08 0 66.13 30.157 66.13 90.465 0 59.688-22.21 89.512-66.61 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.95-75.972-32.83-99.898-21.89-23.945-52.35-35.918-91.41-35.918-24.41 0-45.97 5.508-64.7 16.543-18.75 11.016-33.16 26.836-43.23 47.48-10.08 20.625-15.11 44.551-15.11 71.793 0 42.364 10.86 75.43 32.58 99.2 21.73 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.05-24.328 33.06-57.121 33.06-98.379M1558.11 238.887c13.54 0 27.07 2.129 40.62 6.386v-41.816c-6.13-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.59 0-78.88 27.715-78.88 83.144v140.778h-35.68v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.48-9.57 26.34-9.57M1783.44 464.691c11.17 0 20.38-.789 27.62-2.371l-5.43-51.718c-7.88 1.894-16.06 2.832-24.56 2.832-22.2 0-40.2-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.52v261.227h43.46l7.34-46.055h2.82c8.66 15.594 19.95 27.95 33.9 37.09 13.92 9.141 28.93 13.699 45 13.699M1925.05 236.523c20.15 0 36.32 5.625 48.52 16.895 12.21 11.25 18.31 27.051 18.31 47.344v22.676l-33.54-1.407c-26.13-.937-45.16-5.312-57.04-13.105-11.89-7.793-17.82-19.727-17.82-35.781 0-11.661 3.45-20.665 10.39-27.051 6.91-6.387 17.32-9.571 31.18-9.571zm82.66-37.793l-11.11 36.387h-1.87c-12.62-15.918-25.29-26.738-38.04-32.48-12.74-5.742-29.13-8.633-49.13-8.633-25.67 0-45.7 6.934-60.1 20.801-14.41 13.847-21.62 33.457-21.62 58.808 0 26.934 10 47.246 30 60.934 19.99 13.691 50.45 21.172 91.41 22.441l45.09 1.414v13.938c0 16.699-3.88 29.16-11.68 37.441-7.79 8.262-19.88 12.383-36.25 12.383-13.39 0-26.23-1.953-38.5-5.891a294.638 294.638 0 0 1-35.44-13.933l-17.94 39.668c14.17 7.41 29.68 13.035 46.52 16.894 16.85 3.868 32.77 5.789 47.72 5.789 33.22 0 58.31-7.246 75.22-21.726 16.94-14.492 25.4-37.246 25.4-68.262V198.73h-39.68M2220.04 194.004c-39.52 0-69.55 11.543-90.1 34.609-20.55 23.067-30.82 56.172-30.82 99.321 0 43.925 10.74 77.707 32.23 101.339 21.5 23.614 52.56 35.418 93.18 35.418 27.56 0 52.35-5.117 74.41-15.359l-16.78-44.641c-23.46 9.133-42.82 13.704-58.1 13.704-45.19 0-67.79-29.993-67.79-89.981 0-29.293 5.63-51.305 16.89-66.031 11.26-14.707 27.76-22.09 49.48-22.09 24.72 0 48.11 6.152 70.15 18.437v-48.417c-9.92-5.84-20.5-10-31.76-12.52-11.26-2.52-24.93-3.789-40.99-3.789M2451.52 238.887c13.54 0 27.08 2.129 40.63 6.386v-41.816c-6.15-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.6 0-78.9 27.715-78.9 83.144v140.778h-35.66v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.47-9.57 26.33-9.57M2585.92 329.82c0-60.308 22.28-90.465 66.84-90.465 44.09 0 66.15 30.157 66.15 90.465 0 59.688-22.22 89.512-66.62 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.94-75.972-32.83-99.898-21.89-23.945-52.36-35.918-91.4-35.918-24.42 0-45.98 5.508-64.72 16.543-18.74 11.016-33.14 26.836-43.22 47.48-10.07 20.625-15.12 44.551-15.12 71.793 0 42.364 10.87 75.43 32.59 99.2 21.74 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.04-24.328 33.06-57.121 33.06-98.379M2972.33 464.691c11.18 0 20.38-.789 27.63-2.371l-5.43-51.718c-7.87 1.894-16.05 2.832-24.57 2.832-22.2 0-40.19-7.246-53.96-21.731-13.78-14.48-20.67-33.301-20.67-56.453V198.73h-55.51v261.227h43.46l7.33-46.055h2.83c8.66 15.594 19.96 27.95 33.89 37.09 13.94 9.141 28.94 13.699 45 13.699" fill="#100f0d"/><path d="M610.11 372.83c0-170.584-138.257-308.862-308.846-308.862-170.602 0-308.846 138.278-308.846 308.863 0 170.576 138.244 308.846 308.846 308.846 170.59 0 308.846-138.27 308.846-308.846" fill="#e53935" stroke-width="1.029"/><path d="M460.694 521.792l-105.04.958-61.415 61.415-72.096-47.883 12.445-12.438-29.207.26-99.129-166.817H67.357l24.39-24.402-24.57-41.363L294.66 64.049c2.192-.04 4.399-.08 6.603-.08 170.416 0 308.585 138.055 308.846 308.408L460.694 521.792" fill="#d51c2f" stroke-width="1.029"/><path d="M149.093 350.258c0 84.048 68.13 152.151 152.171 152.151 84.028 0 152.139-68.103 152.139-152.151zm342.063-7.017v14.046h44.015c-1.75 59.337-25.556 113.104-63.54 153.419L438.75 477.81l-9.925 9.94 32.875 32.887c-40.314 37.983-94.081 61.79-153.41 63.527l-.015-44.003h-14.035v44.003c-59.34-1.737-113.096-25.556-153.41-63.527l32.887-32.887-9.945-9.92-32.883 32.875c-37.975-40.315-61.781-94.082-63.53-153.419h44.002l-.008-14.034H67.176v-51.511h468.176v51.5h-44.196" fill="#f5f5f5" stroke-width="1.029"/></g></g></symbol><symbol id="pug" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:#c1272d}.hyst1{fill:#efcca3}.st2{fill:#ed1c24}.hyst3{fill:#ccac8d}.hyst4{fill:#fff}.st5{fill:#ff931e}.st6{fill:#ffb81e}.hyst7{fill:#56332b}.hyst8{fill:#442823}.hyst9{fill:#7f4a41}.hyst10{fill:#331712}.st11{fill:#fc6}.st12{fill:#ccc}.st13{fill:#b3b3b3}.st14{fill:#989898}.st15{fill:#323232}.st16{fill:#1e1e1e}.st17{fill:#4c4c4c}.st18{fill:#e6e6e6}.st19{fill:#606060}</style><path class="hyst1" d="M107.4 50.9c-.2-4.4.4-8.3-1.6-11.6-4.8-8.2-16.8-13-40.8-13v.7h-.5.5v-.7c-24 0-36.6 4.8-41.4 13.1-1.9 3.4-1.7 7.2-2 11.6-.2 3.5-1.8 7.2-1.1 11.2.8 5.2 1.1 10.4 1.9 15.2.6 3.9 6 7.2 6.5 10.9 1.4 10.2 12 14.9 36 14.9v.8h-.6.7v-.8c24 0 34.2-4.7 35.5-14.9.5-3.8 5.5-7 6.1-10.9.8-4.8 1.1-10 1.9-15.2.7-4-.9-7.8-1.1-11.3z"/><path class="hyst3" d="M64.6 54.5c4.3.1 7.3 2.8 10.1 5.3 3.3 2.9 8.9 4.9 11.2 7.4 2.3 2.5 5.3 5 6.4 8.9 1.1 3.9 1.4 8.9 1.4 10.2 0 1.3.7 1 2.7 0 4.7-2.3 9.9-8.5 9.9-8.5-.6 3.9-5.7 7.4-6.2 11.1C98.9 99.1 89 104 64.5 104h-.1.6"/><path class="hyst3" d="M80.4 46.7c.9 3.1 4.1 13.6-2.1 10.1 0 0 2.6 1.5 4.2 7.2 1.7 5.7 5.8 6.4 5.8 6.4s6.7 1.3 11.7-3c4.2-3.6 4.9-10 3.1-14.9-1.8-4.8-5-6.3-9.7-7.3-4.7-1.1-14.1-2-13 1.5z"/><circle cx="92.3" cy="58.1" r="8.8"/><circle class="hyst4" cx="90" cy="54.2" r="2.3"/><path class="hyst1" d="M78.9 57.7s7.9 5.4 12.2 10.7c4.3 5.3 4.2 6.3 4.2 6.3l-3.1 1.4s-4.4-8.3-9.8-11.4c-5.5-3.1-6.1-5.7-6.1-5.7l2.6-1.3z"/><path class="hyst3" d="M64.9 54.5c-4.3.1-7.5 2.8-10.4 5.3-3.3 2.9-9.1 4.9-11.4 7.4-2.3 2.5-5.4 5-6.5 8.9-1.1 3.9-1.5 8.9-1.5 10.2 0 1.3.2 1.4-2.7 0-4.7-2.2-9.9-8.5-9.9-8.5.6 3.9 5.7 7.4 6.2 11.1C30.1 99.1 40 104 64.5 104h.5"/><path class="hyst7" d="M88.1 71.4C83.3 65.5 75.6 60 64.9 60h-.1c-10.7 0-18.4 5.5-23.2 11.4-5 6.1-4.6 8.5-4.6 14.3 0 21 7.4 15 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.7 12.3-17.3.1-5.8.4-8.4-4.6-14.5z"/><path class="hyst8" d="M64.4 65.2s-.7 9.7-2.1 11.6l2.6-.6-.5-11z"/><path class="hyst8" d="M65.1 65.2s.7 9.7 2.1 11.6l-2.6-.6.5-11z"/><path class="hyst7" d="M56.7 62.9c-1-2.3 2.6-6 8.3-6.1 5.7 0 9.3 3.7 8.3 6.1-1 2.4-4.6 3.1-8.3 3.2-3.6-.1-7.3-.8-8.3-3.2z"/><path d="M65 65.2c0-.4 3.4-.5 5.2-1.7 0 0-3.7 1.2-4.5.7-.8-.4-1-1.6-1-1.6s-.3 1.2-.9 1.6c-.7.4-4.9-.7-4.9-.7s5.6 1.4 5.6 1.7c0 .3-.1 1.3-.1 2 0 2.5 0 8.7.4 9.2.6.9.4-6.7.4-9.2-.1-.8-.1-1.6-.2-2z"/><path class="hyst9" d="M65.2 78.6c1.7 0 4.7 1.2 7.4 3.1-2.6-2.9-5.7-4.9-7.4-4.9-1.8 0-5.6 2.2-8.3 5.4 2.8-2.2 6.4-3.6 8.3-3.6z"/><path class="hyst8" d="M64.5 96.3c-3.8 0-7.5-1.2-10.9-2.1-.7-.2-1.4.3-2.1.1-6.3-2-11.4-5.4-14.5-9.7v1c0 21 7.4 15.1 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.6 12.3-17.4 0-.8 0-1.6.1-2.3-2.9 4.7-8.2 8.4-14.8 10.6-.6.2-2-.3-2.6-.2-3.6 1.2-6.8 2.5-10.9 2.5z"/><path class="hyst8" d="M55 85s-2.5 7.5-.8 10.8l-2.3-1s1.7-7.6 3.1-9.8zM74.8 85s2.5 7.5.8 10.8l2.3-1s-1.8-7.6-3.1-9.8z"/><path class="hyst3" d="M48.6 46.7c-.9 3.1-4.1 13.6 2.1 10.1 0 0-2.6 1.5-4.2 7.2s-5.8 6.4-5.8 6.4-6.7 1.3-11.7-3c-4.2-3.6-4.9-10-3.1-14.9s5-6.3 9.7-7.3c4.7-1.1 14-2 13 1.5z"/><path d="M64.9 76.8c2.7 0 11.1 5.8 11.2 12.9v-.4c0-7.4-6.8-13.3-11.2-13.3-4.4 0-11.2 6-11.2 13.3v.4c.1-7.1 8.5-12.9 11.2-12.9z"/><ellipse transform="rotate(-14.465 66.712 61.468)" class="hyst10" cx="66.7" cy="61.5" rx=".8" ry="1.5"/><ellipse transform="rotate(17.235 62.371 61.462)" class="hyst10" cx="62.4" cy="61.5" rx=".8" ry="1.5"/><circle cx="37.2" cy="58.1" r="8.8"/><circle class="hyst4" cx="39.5" cy="54.2" r="2.3"/><path class="hyst9" d="M67.5 58.2c0-.1-2.3 1-2.9 1.1-.6-.1-2.9-1.2-2.9-1.1h5.8z"/><path class="hyst1" d="M50 57.7s-7.9 5.4-12.2 10.7c-4.3 5.3-4.2 6.3-4.2 6.3l3.1 1.4s4.4-8.3 9.8-11.4 6.1-5.7 6.1-5.7L50 57.7z"/><path class="hyst3" d="M32.7 41.7S30 49.1 24 52.2c0 0 9.4-1.1 8.7-10.5zM95.8 41.7s2.7 7.4 8.7 10.5c0 0-9.4-1.1-8.7-10.5zM78.7 55.5s-5.9-6.2-13.8-6.4h.1.1c-8 .2-13.8 6.4-13.8 6.4 6.9-4.8 12.8-4.7 13.8-4.7-.1 0 6.7-.1 13.6 4.7zM71.8 42.5s-3-4.2-7-4.3h.2c-3 .1-6.9 4.3-6.9 4.3 3.4-3.3 6.9-3.2 6.9-3.2s3.3-.1 6.8 3.2zM37.2 73.2s-4.7 2.3-8.1.9H29c-3-1.7-4.5-6.8-4.5-6.8s3 9 12.7 5.9zM92 73.2s4.7 2.3 8.1.9c4-1.7 4.6-6.8 4.6-6.8s-3 9-12.7 5.9z"/><path class="hyst3" d="M42.6 41.2c2.6-.5 6.9-.6 10.3.5 4.3 1.5.8 7 1.7 7.3.9.3 2.1-3.8 10.1-3.4 8.1.4 9 4 10.1 3.4s-1.1-10 11-7.8c0 0-12.7-3.4-12.1 5.8 0 0-7.3-5.6-17.5-.6.1 0 2.7-8.6-13.6-5.2zM86.9 41.2c.2 0 .3.1.4.1.1 0-.1-.1-.4-.1zM86.9 41.2zM39.1 28.9S28.3 42.5 26.7 47.7c-1.6 5.3-2.8 27-4.2 30.1l-5-21.4 9.2-22.3 12.4-5.2zM89.9 28.9s10.8 13.6 12.4 18.8c1.6 5.3 2.8 27 4.2 30.1l5-21.4-9.2-22.3-12.4-5.2z"/><path class="hyst7" d="M89.4 28.9s11.6 9.7 15 20.9c3.4 11.2 2 24.8 4.6 26.5 3.7 2.4 7.9-11.9 9.3-13.4 2.2-2.4 9.5-8.5 10-9.6.5-1.1-14.8-17.8-21.5-21.1-8.1-3.8-18.1-4.1-17.4-3.3z"/><path class="hyst8" d="M99.3 34.9s13.7 17.5 13.5 39.3l5.5-11.2c-.1 0-4.9-14.3-19-28.1z"/><path class="hyst7" d="M39.1 28.9s-11.6 9.7-15 20.9-2 24.8-4.6 26.5c-3.7 2.4-7.9-11.9-9.3-13.4C8 60.5.7 54.4.2 53.3-.3 52.2 15 35.5 21.7 32.2c8.1-3.8 18.1-4.1 17.4-3.3z"/><path class="hyst8" d="M29.2 34.9S15.5 52.4 15.7 74.2L10.3 63s4.8-14.3 18.9-28.1z"/><path class="hyst3" d="M21.8 74.6s1 5.4 2.6 7.1.5-1.3.5-1.3-1.7-.9-1.4-7.8-1.7 2-1.7 2zM107.1 74.6s-1 5.4-2.6 7.1-.5-1.3-.5-1.3 1.7-.9 1.4-7.8 1.7 2 1.7 2z"/><g><circle class="hyst8" cx="54.5" cy="70.5" r=".8"/><circle class="hyst8" cx="49.9" cy="75.3" r=".8"/><circle class="hyst8" cx="48.4" cy="70.5" r=".8"/></g><g><circle class="hyst8" cx="74" cy="70.5" r=".8"/><circle class="hyst8" cx="78.6" cy="75.3" r=".8"/><circle class="hyst8" cx="80.1" cy="70.5" r=".8"/></g></symbol><symbol viewBox="0 0 50 50" id="puppet" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" fill="#fbc02d"><path stroke-width=".283" d="M11.559 249.467h13.587v13.587H11.559zM27.435 265.056h13.587v13.587H27.435zM11.559 281.074h13.587v13.587H11.559z"/><path stroke-width=".256" d="M16.62 251.615l18.305 18.305-3.236 3.236-18.305-18.305z"/><path stroke-width=".256" d="M37.834 271.331L19.53 289.636l-3.237-3.237 18.305-18.304z"/></g></symbol><symbol viewBox="0 0 100 99.999997" id="purescript" xmlns="http://www.w3.org/2000/svg"><path clip-path="url(#SVGID_2_)" d="M98.079 38.548L79.22 19.68l-5.087 5.088L90.447 41.09 74.134 57.41l5.087 5.087 18.858-18.86a3.59 3.59 0 0 0 1.055-2.55 3.578 3.578 0 0 0-1.055-2.54M25.483 42.794l-5.09-5.089L1.53 56.568a3.566 3.566 0 0 0-1.05 2.545c0 .961.373 1.863 1.05 2.542L20.394 80.52l5.089-5.086L9.162 59.113z" fill="#42a5f5" stroke-width="1.192"/><path clip-path="url(#SVGID_2_)" transform="matrix(1.19175 0 0 1.19175 -306.84 -629.047)" fill="#42a5f5" d="M281.841 551.736l6.461 6.037h28.379l-6.461-6.037zM288.302 566.861l-6.463 6.035h28.381l6.463-6.035zM281.838 581.982l6.464 6.035h28.381l-6.463-6.035z"/></symbol><symbol viewBox="0 0 24 24" id="python" xmlns="http://www.w3.org/2000/svg"><path d="M19.14 7.5A2.86 2.86 0 0 1 22 10.36v3.78A2.86 2.86 0 0 1 19.14 17H12c0 .39.32.96.71.96H17v1.68a2.86 2.86 0 0 1-2.86 2.86H9.86A2.86 2.86 0 0 1 7 19.64v-3.75a2.85 2.85 0 0 1 2.86-2.85h5.25a2.85 2.85 0 0 0 2.85-2.86V7.5h1.18m-4.28 11.79c-.4 0-.72.3-.72.89 0 .59.32.71.72.71a.71.71 0 0 0 .71-.71c0-.59-.32-.89-.71-.89m-10-1.79A2.86 2.86 0 0 1 2 14.64v-3.78A2.86 2.86 0 0 1 4.86 8H12c0-.39-.32-.96-.71-.96H7V5.36A2.86 2.86 0 0 1 9.86 2.5h4.28A2.86 2.86 0 0 1 17 5.36v3.75a2.85 2.85 0 0 1-2.86 2.85H8.89a2.85 2.85 0 0 0-2.85 2.86v2.68H4.86M9.14 5.71c.4 0 .72-.3.72-.89 0-.59-.32-.71-.72-.71-.39 0-.71.12-.71.71s.32.89.71.89z"/><path d="M9.264 22.379c-.895-.24-1.581-.799-1.947-1.582-.228-.489-.237-.606-.238-2.957-.001-2.745.057-3.074.666-3.785.193-.226.568-.517.833-.648.47-.23.579-.239 3.839-.288 3.131-.048 3.386-.065 3.814-.264.626-.291 1.07-.687 1.4-1.247.27-.46.278-.522.311-2.29l.034-1.82.932.051c1.075.058 1.504.211 2.098.748.853.77.869.841.869 3.957 0 2.434-.02 2.783-.18 3.075a3.365 3.365 0 0 1-1.337 1.33l-.517.273-3.95.031-3.951.031.068.274c.037.151.164.377.282.503.209.224.262.229 2.433.229h2.22v1.05c0 1.653-.394 2.437-1.54 3.072l-.545.302-2.644.018c-1.455.01-2.782-.018-2.95-.063zm6.12-1.692c.22-.222.253-.325.206-.675-.07-.523-.278-.73-.732-.73-.467 0-.672.217-.735.78-.042.372-.012.496.163.672.3.3.77.28 1.097-.047z" fill="#fc0" stroke="#fc0" stroke-width=".102"/><path d="M9.349 22.38c-.911-.15-1.936-1.074-2.176-1.963-.073-.273-.101-1.279-.079-2.868.033-2.317.047-2.473.27-2.926.13-.263.401-.623.603-.8.674-.592.87-.63 3.484-.675 4.399-.076 4.927-.166 5.705-.967.642-.662.706-.9.774-2.883l.061-1.784.951.055c.523.031 1.11.122 1.304.204.54.225 1.358 1.042 1.472 1.47.153.572.243 3.18.16 4.617-.071 1.23-.093 1.327-.395 1.78-.193.288-.577.647-.966.903l-.647.425-3.922.008c-2.157.004-3.942.028-3.966.052-.115.115.354.82.587.883.14.038 1.181.073 2.314.079l2.06.01v.91c0 1.739-.326 2.446-1.454 3.162l-.631.4-2.543-.011c-1.398-.007-2.733-.043-2.966-.081zm5.98-1.718c.285-.256.313-.328.251-.658-.09-.483-.301-.682-.722-.682-.436 0-.625.193-.715.73-.065.384-.044.453.2.663.358.308.595.295.985-.053z" fill="#fdd835" stroke-width=".102"/><path d="M4.281 17.396c-.88-.215-1.714-.935-2.024-1.747-.149-.389-.168-.804-.142-3.041.027-2.26.054-2.638.215-2.962.259-.519.851-1.092 1.392-1.346.437-.206.632-.217 4.408-.245l3.95-.03-.067-.275a1.367 1.367 0 0 0-.282-.504c-.21-.224-.263-.23-2.433-.23h-2.22l.002-1.143c.003-1.338.157-1.795.84-2.493.746-.763 1.103-.838 4.025-.838 2.961 0 3.28.06 4.067.768.37.333.572.621.728 1.037.201.539.213.735.183 3.072-.035 2.777-.045 2.824-.78 3.598-.787.829-.76.824-4.59.883-3.812.06-3.797.057-4.61.806-.765.706-.917 1.2-.964 3.133l-.04 1.653-.677-.01c-.371-.007-.813-.045-.98-.086zM9.59 5.551c.237-.204.286-.326.286-.72 0-.547-.201-.763-.71-.763-.502 0-.765.248-.765.724 0 .492.141.782.439.902.345.14.444.12.75-.143z" fill="#3c78aa"/></symbol><symbol viewBox="0 0 24 24" id="r" xmlns="http://www.w3.org/2000/svg"><path d="M11.956 4.05c-5.694 0-10.354 3.106-10.354 6.947 0 3.396 3.686 6.212 8.531 6.813v2.205h3.53V17.82c.88-.093 1.699-.259 2.475-.497l1.43 2.692h3.996l-2.402-4.048c1.936-1.263 3.147-3.034 3.147-4.97 0-3.841-4.659-6.947-10.354-6.947m1.584 2.712c4.349 0 7.558 1.45 7.558 4.753 0 1.77-.952 3.013-2.505 3.779a1.081 1.081 0 0 1-.228-.156c-.373-.165-.994-.352-.994-.352s3.085-.227 3.085-3.302-3.23-3.127-3.23-3.127h-7.092v7.413c-2.64-.766-4.462-2.392-4.462-4.255 0-2.63 3.52-4.753 7.868-4.753m.156 4.12h2.143s.983-.05.983.974c0 1.004-.983 1.004-.983 1.004h-2.143v-1.977m-.031 4.566h.952c.186 0 .28.052.445.207.135.103.28.3.404.476-.57.073-1.17.104-1.801.104z" fill="#1976d2" stroke-width="1.035"/></symbol><symbol viewBox="0 0 24 24" id="raml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="razor" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 11.91c-.11-2.21-1.75-3.54-3.73-3.54h-.08c-2.29 0-3.55 1.8-3.55 3.84 0 2.29 1.53 3.74 3.54 3.74 2.25 0 3.72-1.65 3.83-3.59m-3.81-5.97c1.53 0 2.97.68 4.02 1.74 0-.51.33-.89.83-.89h.11c.74 0 .89.7.89.92v7.9c-.04.52.54.78.87.44 1.27-1.29 2.78-6.69-.79-9.81-3.33-2.92-7.8-2.44-10.18-.8-2.52 1.74-4.14 5.61-2.57 9.22 1.71 3.95 6.61 5.13 9.52 3.95 1.48-.59 2.15 1.4.65 2.05-2.34.99-8.77.89-11.78-4.32-2.03-3.52-1.93-9.71 3.46-12.92C10.81 1.42 16.24 2.1 19.5 5.5c3.45 3.6 3.25 10.3-.1 12.91-1.51 1.18-3.76.03-3.74-1.7l-.02-.56a5.611 5.611 0 0 1-3.99 1.66C8.63 17.81 6 15.15 6 12.13c0-3.05 2.63-5.74 5.65-5.74z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="react" xmlns="http://www.w3.org/2000/svg"><path d="M12 10.11c1.03 0 1.87.84 1.87 1.89 0 1-.84 1.85-1.87 1.85-1.03 0-1.87-.85-1.87-1.85 0-1.05.84-1.89 1.87-1.89M7.37 20c.63.38 2.01-.2 3.6-1.7-.52-.59-1.03-1.23-1.51-1.9a22.7 22.7 0 0 1-2.4-.36c-.51 2.14-.32 3.61.31 3.96m.71-5.74l-.29-.51c-.11.29-.22.58-.29.86.27.06.57.11.88.16l-.3-.51m6.54-.76l.81-1.5-.81-1.5c-.3-.53-.62-1-.91-1.47C13.17 9 12.6 9 12 9c-.6 0-1.17 0-1.71.03-.29.47-.61.94-.91 1.47L8.57 12l.81 1.5c.3.53.62 1 .91 1.47.54.03 1.11.03 1.71.03.6 0 1.17 0 1.71-.03.29-.47.61-.94.91-1.47M12 6.78c-.19.22-.39.45-.59.72h1.18c-.2-.27-.4-.5-.59-.72m0 10.44c.19-.22.39-.45.59-.72h-1.18c.2.27.4.5.59.72M16.62 4c-.62-.38-2 .2-3.59 1.7.52.59 1.03 1.23 1.51 1.9.82.08 1.63.2 2.4.36.51-2.14.32-3.61-.32-3.96m-.7 5.74l.29.51c.11-.29.22-.58.29-.86-.27-.06-.57-.11-.88-.16l.3.51m1.45-7.05c1.47.84 1.63 3.05 1.01 5.63 2.54.75 4.37 1.99 4.37 3.68 0 1.69-1.83 2.93-4.37 3.68.62 2.58.46 4.79-1.01 5.63-1.46.84-3.45-.12-5.37-1.95-1.92 1.83-3.91 2.79-5.38 1.95-1.46-.84-1.62-3.05-1-5.63-2.54-.75-4.37-1.99-4.37-3.68 0-1.69 1.83-2.93 4.37-3.68-.62-2.58-.46-4.79 1-5.63 1.47-.84 3.46.12 5.38 1.95 1.92-1.83 3.91-2.79 5.37-1.95M17.08 12c.34.75.64 1.5.89 2.26 2.1-.63 3.28-1.53 3.28-2.26 0-.73-1.18-1.63-3.28-2.26-.25.76-.55 1.51-.89 2.26M6.92 12c-.34-.75-.64-1.5-.89-2.26-2.1.63-3.28 1.53-3.28 2.26 0 .73 1.18 1.63 3.28 2.26.25-.76.55-1.51.89-2.26m9 2.26l-.3.51c.31-.05.61-.1.88-.16-.07-.28-.18-.57-.29-.86l-.29.51m-2.89 4.04c1.59 1.5 2.97 2.08 3.59 1.7.64-.35.83-1.82.32-3.96-.77.16-1.58.28-2.4.36-.48.67-.99 1.31-1.51 1.9M8.08 9.74l.3-.51c-.31.05-.61.1-.88.16.07.28.18.57.29.86l.29-.51m2.89-4.04C9.38 4.2 8 3.62 7.37 4c-.63.35-.82 1.82-.31 3.96a22.7 22.7 0 0 1 2.4-.36c.48-.67.99-1.31 1.51-1.9z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="readme" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="reason" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm5.119 8.993h2.798c.382 0 .71.025.985.075.275.05.534.159.774.326.244.168.435.386.577.654.145.265.218.598.218 1 0 .552-.112 1.001-.335 1.35-.22.348-.536.638-.947.87l2.16 3.203H12.31l-1.763-2.742h-.77v2.742H8.12v-7.478zm6.594 0h4.676v1.447h-3.018v1.29h2.802v1.447h-2.802v1.848h3.018v1.446h-4.676v-7.478zM9.778 13.37v2.014h.513c.266 0 .49-.014.67-.044.18-.03.329-.1.45-.207a.96.96 0 0 0 .253-.34c.055-.128.082-.297.082-.508 0-.187-.034-.35-.1-.483a.698.698 0 0 0-.343-.317 1.086 1.086 0 0 0-.395-.095 6.012 6.012 0 0 0-.526-.02h-.604z" fill="#f44336" stroke-width="1.067"/></symbol><symbol viewBox="0 0 172 193" id="restql" xmlns="http://www.w3.org/2000/svg"><title>Group</title><g transform="translate(14.767 16.713) scale(.82795)" fill="none"><path d="M171.39 55.799c-.975-6.147-4.673-11.642-10.15-14.805L96.381 3.546C93.217 1.72 89.615.756 85.964.756s-7.253.964-10.415 2.788L10.69 40.992A20.896 20.896 0 0 0 .272 59.035v74.89a20.894 20.894 0 0 0 10.416 18.042l64.859 37.446c3.165 1.827 6.767 2.791 10.417 2.791s7.252-.964 10.415-2.79l64.859-37.445c5.479-3.166 9.178-8.66 10.152-14.808zm-16.516 85.147L90.017 178.39a8.104 8.104 0 0 1-8.108 0l-64.857-37.444a8.109 8.109 0 0 1-4.053-7.021v-74.89a8.109 8.109 0 0 1 4.053-7.021l64.857-37.446c1.254-.725 2.654-1.086 4.054-1.086s2.8.361 4.054 1.086l64.857 37.446a8.106 8.106 0 0 1 4.053 7.021v74.89a8.109 8.109 0 0 1-4.053 7.021z" fill="#83e8c2"/><path d="M158.93 59.035a8.109 8.109 0 0 0-4.053-7.021L90.02 14.568c-1.254-.725-2.654-1.086-4.054-1.086s-2.8.361-4.054 1.086L17.055 52.014a8.106 8.106 0 0 0-4.053 7.021v74.89a8.109 8.109 0 0 0 4.053 7.021l64.857 37.444a8.104 8.104 0 0 0 8.108 0l64.857-37.444a8.109 8.109 0 0 0 4.053-7.021zm-46.766 31.681c.119-.069.242-.118.365-.149.044-.012.088-.01.131-.018.076-.012.152-.029.228-.029l.015.001c.02.001.038.005.059.006.093.005.184.019.273.04l.1.03c.077.025.15.057.223.095.028.014.057.027.084.043.094.057.184.122.263.199.007.008.013.017.021.024.07.071.133.15.188.235.018.029.033.059.05.09.04.072.072.148.099.229a1.512 1.512 0 0 1 .081.46v16.209l-3.278 1.893a1.548 1.548 0 0 0-.678.83 1.533 1.533 0 0 0-.098.514v3.785l-14.038 8.104-.01.004a1.55 1.55 0 0 1-.354.146c-.045.012-.09.011-.135.018-.074.012-.15.029-.225.029l-.014-.001c-.02-.001-.039-.005-.059-.006a1.463 1.463 0 0 1-.273-.041c-.034-.008-.066-.019-.1-.03a1.318 1.318 0 0 1-.223-.094c-.029-.015-.057-.027-.084-.044a1.45 1.45 0 0 1-.263-.198c-.009-.008-.015-.019-.023-.027a1.495 1.495 0 0 1-.185-.232c-.019-.029-.034-.06-.051-.09a1.422 1.422 0 0 1-.098-.229 1.702 1.702 0 0 1-.033-.101 1.487 1.487 0 0 1-.048-.358l-.001-.002v-20.053a1.446 1.446 0 0 1 .727-1.255zM85.24 31.369a1.449 1.449 0 0 1 1.452 0l45.741 26.41a1.45 1.45 0 0 1 0 2.512l-17.366 10.027a1.457 1.457 0 0 1-1.452 0l-15.49-8.943 1.727-.996a1.552 1.552 0 0 0 0-2.688l-13.111-7.57c-.239-.139-.508-.207-.775-.207s-.535.068-.775.207l-3.278 1.893-14.038-8.104a1.451 1.451 0 0 1 0-2.513zM57.59 47.558c.251 0 .501.065.726.194l15.489 8.942-1.727.997a1.552 1.552 0 0 0 0 2.688l1.727.996-15.488 8.943a1.457 1.457 0 0 1-1.452 0L39.499 60.291a1.45 1.45 0 0 1 0-2.512l17.366-10.027c.225-.129.475-.194.725-.194zm-9.56 92.328c-.241 0-.489-.062-.724-.196l-17.365-10.026a1.45 1.45 0 0 1-.726-1.256V75.59c0-.847.694-1.453 1.452-1.453.242 0 .49.062.724.197l17.366 10.025c.449.26.726.738.726 1.257v17.886l-1.727-.997a1.552 1.552 0 0 0-2.327 1.344v15.139c0 .555.295 1.067.775 1.344l3.278 1.894v16.209a1.45 1.45 0 0 1-1.452 1.451zm29.828 14.929a1.452 1.452 0 0 1-2.177 1.257l-17.365-10.026a1.452 1.452 0 0 1-.726-1.257v-17.885l1.726.996c.25.145.515.211.773.211.811 0 1.554-.648 1.554-1.555v-1.993l15.489 8.942c.449.26.726.738.726 1.257zm0-32.768c0 .127-.02.246-.049.36-.009.035-.021.067-.032.101-.026.08-.059.157-.099.229-.017.03-.032.061-.05.09a1.48 1.48 0 0 1-.188.235l-.021.025a1.51 1.51 0 0 1-.264.199c-.026.016-.055.028-.082.043a1.597 1.597 0 0 1-.324.124 1.362 1.362 0 0 1-.278.041c-.018.001-.036.006-.055.006l-.015.001c-.077 0-.155-.018-.233-.03-.043-.007-.084-.005-.125-.017a1.484 1.484 0 0 1-.366-.149l-14.035-8.104v-3.784a1.545 1.545 0 0 0-.776-1.343l-3.276-1.892V91.976c0-.127.02-.246.049-.361.009-.034.021-.066.032-.1a1.33 1.33 0 0 1 .099-.229c.017-.03.032-.062.051-.091.054-.084.116-.163.187-.234l.021-.025c.079-.076.168-.142.263-.199.027-.016.056-.029.084-.043a1.476 1.476 0 0 1 .601-.166c.019 0 .036-.005.055-.005l.015-.001c.078 0 .157.018.236.03.04.007.081.005.122.017.124.031.246.08.366.149l17.361 10.023a1.456 1.456 0 0 1 .726 1.259zm-9.984-45.373a1.448 1.448 0 0 1-.544-.55 1.466 1.466 0 0 1 0-1.413c.121-.219.303-.41.544-.55l14.038-8.104 3.277 1.892c.48.276 1.071.276 1.551 0l3.278-1.893 14.038 8.105a1.45 1.45 0 0 1 0 2.513L86.691 86.7a1.447 1.447 0 0 1-1.452 0zm74.842 51.733c0 .518-.276.997-.726 1.256l-45.741 26.409a1.452 1.452 0 0 1-2.177-1.257v-20.053c0-.519.277-.997.727-1.257l15.488-8.941v1.992c0 .906.743 1.555 1.553 1.555.26 0 .523-.066.774-.21l13.11-7.57a1.55 1.55 0 0 0 .776-1.344v-3.784l14.038-8.105a1.452 1.452 0 0 1 2.177 1.257v20.052zm0-32.764c0 .519-.276.997-.726 1.256l-15.489 8.943v-1.993c0-.906-.744-1.554-1.554-1.554a1.519 1.519 0 0 0-.773.21l-1.727.996V85.616c0-.519.277-.997.727-1.257l17.365-10.025c.234-.135.482-.197.724-.197.758 0 1.453.606 1.453 1.453z" fill="#111d5a"/><g fill="#83e8c2"><path d="M59.402 90.568zM94.485 123.06zM94.771 123.29zM77.775 122.51zM77.072 123.33zM77.418 123.09zM77.856 122.05zM76.749 123.45zM94.119 122.41zM77.131 133.51l-15.489-8.942v1.993c0 .906-.743 1.555-1.554 1.555a1.53 1.53 0 0 1-.773-.211l-1.726-.996v17.885c0 .519.276.997.726 1.257l17.365 10.026a1.452 1.452 0 0 0 2.177-1.257v-20.053a1.454 1.454 0 0 0-.726-1.257zM94.25 122.74zM110.28 111.42zM94.494 100.98c.088-.089.189-.168.303-.232l17.365-10.026-17.365 10.026a1.392 1.392 0 0 0-.303.232zM77.627 122.83zM58.027 90.936zM58.374 90.693zM59.044 90.521l-.015.001c.083-.001.167.015.251.029-.079-.012-.158-.03-.236-.03zM57.819 91.195zM58.696 90.568zM57.589 91.977zM76.043 123.46zM57.67 91.516zM75.677 123.31l-14.035-8.11zM76.401 123.5l.015-.001c-.082.001-.166-.016-.248-.029.078.012.156.03.233.03zM112.16 90.716zM77.662 101.27zM113.64 90.734zM96.237 123.31zM113.33 90.597zM112.89 90.52c-.075 0-.151.018-.228.029.081-.014.162-.029.242-.028l-.014-.001zM141.26 74.137c-.241 0-.489.062-.724.197l-17.365 10.025c-.449.26-.727.738-.727 1.257v17.885l1.727-.996c.25-.145.515-.211.773-.21.81 0 1.554.647 1.554 1.554v1.993l15.489-8.943a1.45 1.45 0 0 0 .726-1.256V75.59c0-.847-.695-1.453-1.453-1.453zM112.96 90.526zM95.523 123.5c.074 0 .15-.018.225-.029-.08.013-.159.028-.238.028l.013.001zM95.451 123.5zM85.238 86.7zM95.078 123.43zM141.26 106.9c-.241 0-.489.062-.724.196l-14.038 8.105v3.784c0 .555-.296 1.067-.776 1.344l-13.11 7.57c-.251.144-.515.21-.774.21-.81 0-1.553-.648-1.553-1.555v-1.992l-15.488 8.941c-.449.26-.727.738-.727 1.257v20.053a1.452 1.452 0 0 0 2.177 1.257l45.741-26.409a1.45 1.45 0 0 0 .726-1.256v-20.053a1.454 1.454 0 0 0-1.454-1.452zM67.871 41.396a1.451 1.451 0 0 0 0 2.513l14.038 8.104 3.278-1.893c.24-.139.508-.207.775-.207s.536.068.775.207l13.111 7.57a1.552 1.552 0 0 1 0 2.688l-1.727.996 15.49 8.943a1.457 1.457 0 0 0 1.452 0l17.366-10.027a1.45 1.45 0 0 0 0-2.512l-45.741-26.41a1.449 1.449 0 0 0-1.452 0zM39.497 57.779a1.45 1.45 0 0 0 0 2.512l17.366 10.027a1.457 1.457 0 0 0 1.452 0l15.488-8.943-1.727-.996a1.552 1.552 0 0 1 0-2.688l1.727-.997-15.489-8.942a1.458 1.458 0 0 0-1.451 0zM49.481 138.43v-16.209l-3.278-1.894a1.55 1.55 0 0 1-.775-1.344v-15.139c0-.906.743-1.555 1.554-1.554.259 0 .523.065.773.21l1.727.997V85.611a1.45 1.45 0 0 0-.726-1.257L31.39 74.33a1.436 1.436 0 0 0-.724-.197c-.758 0-1.452.606-1.452 1.453v52.817c0 .518.276.997.726 1.256l17.365 10.026a1.45 1.45 0 0 0 2.176-1.255zM114.34 108.18l-3.278 1.893 3.278-1.893V91.971zM114.11 91.193zM114.16 91.283z"/></g><g fill="#de5941"><path d="M94.494 100.98a1.45 1.45 0 0 0-.424 1.023v20.053l.001.002c0 .126.02.244.048.358.01.034.021.066.033.101.026.08.059.156.098.229.017.03.032.061.051.09.055.084.115.162.185.232.009.009.015.02.023.027.079.077.169.142.263.198.027.017.055.029.084.044a1.46 1.46 0 0 0 .596.165c.02.001.039.005.059.006.079 0 .158-.016.238-.028.045-.007.09-.006.135-.018.119-.031.238-.08.354-.146l.01-.004 14.038-8.104v-3.785c0-.18.04-.35.098-.514.122-.343.353-.643.678-.83l3.278-1.893V91.977c0-.127-.021-.246-.049-.361-.009-.033-.021-.065-.032-.099a1.266 1.266 0 0 0-.099-.229c-.017-.031-.032-.061-.05-.09a1.425 1.425 0 0 0-.188-.235l-.021-.024a1.41 1.41 0 0 0-.263-.199c-.027-.016-.056-.029-.084-.043a1.509 1.509 0 0 0-.323-.125 1.591 1.591 0 0 0-.273-.04c-.021-.001-.039-.005-.059-.006-.08-.001-.161.015-.242.028-.043.008-.087.006-.131.018-.123.031-.246.08-.365.149l-17.365 10.026a1.447 1.447 0 0 0-.302.233zM77.13 100.74L59.769 90.717a1.424 1.424 0 0 0-.366-.149c-.041-.012-.082-.01-.122-.017-.084-.015-.168-.03-.251-.029-.019 0-.036.005-.055.005-.095.005-.188.02-.278.041-.034.009-.065.02-.099.03a1.406 1.406 0 0 0-.224.095c-.028.014-.057.027-.084.043a1.515 1.515 0 0 0-.263.199l-.021.025c-.07.071-.133.15-.187.234-.019.029-.034.061-.051.091-.04.073-.072.149-.099.229a1.463 1.463 0 0 0-.081.461v16.206l3.276 1.892a1.547 1.547 0 0 1 .776 1.343v3.784l14.035 8.104c.119.068.242.117.366.149.041.012.082.01.125.017.082.014.166.03.248.029.019 0 .037-.005.055-.006.095-.004.188-.019.278-.041.034-.008.065-.019.099-.029.077-.025.152-.058.225-.095.027-.015.056-.027.082-.043.095-.058.185-.123.264-.199l.021-.025c.07-.071.133-.15.188-.235.018-.029.033-.06.05-.09.04-.072.072-.149.099-.229a1.448 1.448 0 0 0 .081-.461v-20.047a1.456 1.456 0 0 0-.726-1.259zM86.689 86.7l17.365-10.026a1.45 1.45 0 0 0 0-2.513l-14.038-8.105-3.278 1.893a1.556 1.556 0 0 1-1.551 0l-3.277-1.892-14.038 8.104c-.241.14-.423.331-.544.55a1.466 1.466 0 0 0 0 1.413c.121.218.303.41.544.55L85.238 86.7a1.447 1.447 0 0 0 1.451 0z"/></g></g></symbol><symbol viewBox="0 0 24 24" id="riot" xmlns="http://www.w3.org/2000/svg"><defs><path d="M13.26 3.04l.58.05.54.07.52.09.49.11.46.13.44.14.41.16.39.17.36.19.33.21.32.22.29.23.26.25.22.22.2.22.19.24.17.24.15.25.15.26.12.27.12.28.1.29.08.31.07.31.05.32.04.34.02.35.01.37v.05l-.02.51-.05.49-.09.48-.13.45-.15.43-.19.4-.22.39-.26.37-.28.34-.31.33-.33.3-.37.28-.39.27-.41.24-.44.22L21 21h-7.04l-3.48-5.14H9.17V21H3V3h9.01l.64.01.61.03zm-4.09 8.52h2.66l.99-.11.75-.35.47-.55.16-.74v-.05l-.17-.75-.47-.54-.74-.32-.96-.11H9.17v3.52z" id="ija"/></defs><use xlink:href="#ija" fill="#ff1744"/><use xlink:href="#ija" fill-opacity="0" stroke="#000" stroke-opacity="0"/></symbol><symbol viewBox="0 0 24 24" id="robot" xmlns="http://www.w3.org/2000/svg"><path d="M12.05 2.804a1.787 1.787 0 0 1 1.788 1.788c0 .661-.357 1.242-.893 1.546v1.135h.893a6.256 6.256 0 0 1 6.256 6.256h.894a.894.894 0 0 1 .893.893v2.681a.894.894 0 0 1-.893.894h-.894v.894a1.787 1.787 0 0 1-1.787 1.787H5.795a1.787 1.787 0 0 1-1.787-1.787v-.894h-.894a.894.894 0 0 1-.894-.894v-2.68a.894.894 0 0 1 .894-.894h.894a6.256 6.256 0 0 1 6.255-6.256h.894V6.138a1.773 1.773 0 0 1-.894-1.546 1.787 1.787 0 0 1 1.788-1.788m-4.022 9.83a2.234 2.234 0 0 0-2.234 2.235 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.234-2.234 2.234 2.234 0 0 0-2.234-2.234m8.043 0a2.234 2.234 0 0 0-2.234 2.234 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.235-2.234 2.234 2.234 0 0 0-2.235-2.234z" fill="#ff5722" stroke-width=".894"/></symbol><symbol viewBox="100 100 800 800" id="rollup" xmlns="http://www.w3.org/2000/svg"><style>.ilst0{fill:url(#ilXMLID_4_)}.ilst1{fill:url(#ilXMLID_5_)}.ilst2{fill:url(#ilXMLID_8_)}.ilst3{fill:url(#ilXMLID_9_)}.ilst4{fill:url(#ilXMLID_11_)}.ilst5{opacity:.3;fill:url(#ilXMLID_16_)}</style><g id="ilXMLID_14_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_4_" x1="444.47" x2="598.47" y1="526.05" y2="562.05" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_15_" class="ilst0" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_4_)"/></g><g id="ilXMLID_2_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_5_" x1="420.38" x2="696.38" y1="475" y2="689" gradientUnits="userSpaceOnUse"><stop stop-color="#BF3338" offset="0"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_10_" class="ilst1" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_5_)"/></g><linearGradient id="ilXMLID_8_" x1="429.39" x2="469.39" y1="517.16" y2="559.16" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_3_" class="ilst2" d="M329.82 813.46c15.58-8.903 122.41-220.34 227.02-320.5s117.96-66.771 60.094-175.83c0 0-221.46 310.49-301.58 464.06" fill="url(#ilXMLID_8_)" stroke-width="1.113"/><g id="ilXMLID_7_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_9_" x1="502.11" x2="490.11" y1="589.46" y2="417.46" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_12_" class="ilst3" d="M373 537c134.4-247.1 152-272 222-272 36.8 0 73.9 16.6 97.9 46.1-32.7-52.7-90.6-88-156.9-89H307.7c-4.8 0-8.7 3.9-8.7 8.7V691c13.6-35.1 36.7-85.3 74-154z" fill="url(#ilXMLID_9_)"/></g><linearGradient id="ilXMLID_11_" x1="450.12" x2="506.94" y1="514.21" y2="552.85" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FBB040" offset="0"/><stop stop-color="#FB8840" offset="1"/></linearGradient><path id="ilXMLID_6_" class="ilst4" d="M556.84 492.96c-104.61 100.16-211.44 311.6-227.02 320.5s-41.732 10.016-55.643-5.564c-14.801-16.582-37.837-43.401 86.802-272.65 149.57-274.99 169.15-302.7 247.05-302.7 40.953 0 82.24 18.473 108.95 51.302 1.447 2.337 2.893 4.785 4.34 7.233-45.738-47.074-145.23-57.98-169.93-.222-25.373 59.204 42.622 125.08 72.335 119.85 37.837-6.677-6.677-93.48-6.677-93.48 57.757 108.95 44.403 75.563-60.205 175.72z" fill="url(#ilXMLID_11_)" stroke-width="1.113"/><linearGradient id="ilXMLID_16_" x1="508.33" x2="450.33" y1="295.76" y2="933.76" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FFF" offset="0"/><stop stop-color="#FFF" stop-opacity="0" offset="1"/></linearGradient><path id="ilXMLID_13_" class="ilst5" d="M373.22 547.49c149.57-274.99 169.15-302.7 247.05-302.7 33.719 0 67.661 12.575 93.48 35.277-26.708-30.492-66.326-47.519-105.72-47.519-77.9 0-97.486 27.71-247.05 302.7-124.64 229.25-101.6 256.07-86.802 272.65 2.114 2.337 4.563 4.34 7.122 6.01-13.02-18.919-18.807-62.877 91.922-266.42z" fill="url(#ilXMLID_16_)" opacity=".3" stroke-width="1.113"/></symbol><symbol viewBox="0 0 24 24" id="ruby" xmlns="http://www.w3.org/2000/svg"><path d="M16 9h3l-5 7m-4-7h4l-2 8M5 9h3l2 7m5-12h2l2 3h-3m-5-3h2l1 3h-4M7 4h2L8 7H5m1-5L2 8l10 14L22 8l-4-6H6z" fill="#f44336"/></symbol><symbol viewBox="0 0 144 144" id="rust" xmlns="http://www.w3.org/2000/svg"><path d="M68.252 26.206a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0M25.766 58.451a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m84.97.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m-74.661 4.88a3.252 3.252 0 0 0 1.651-4.29l-1.58-3.574h6.214v28.01H29.823a43.847 43.847 0 0 1-1.42-16.738zm25.994.688v-8.256h14.798c.764 0 5.397.883 5.397 4.347 0 2.877-3.553 3.908-6.475 3.908zm-20.203 44.452a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m52.769.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m1.101-8.076a3.246 3.246 0 0 0-3.856 2.498l-1.787 8.342a43.847 43.847 0 0 1-36.566-.175l-1.787-8.342a3.246 3.246 0 0 0-3.854-2.497l-7.365 1.581a43.847 43.847 0 0 1-3.808-4.488h35.834c.406 0 .676-.074.676-.443V84.527c0-.369-.27-.442-.676-.442h-10.48V76.05h11.335c1.035 0 5.532.296 6.97 6.045.45 1.768 1.44 7.519 2.116 9.36.674 2.065 3.417 6.19 6.34 6.19h18.501a43.847 43.847 0 0 1-4.06 4.7zm19.898-33.468a43.847 43.847 0 0 1 .093 7.612h-4.499c-.45 0-.631.296-.631.737v2.066c0 4.863-2.742 5.92-5.145 6.19-2.288.258-4.825-.958-5.138-2.358-1.35-7.593-3.6-9.214-7.152-12.016 4.409-2.8 8.996-6.93 8.996-12.457 0-5.97-4.092-9.729-6.881-11.572-3.914-2.58-8.246-3.096-9.415-3.096H39.336A43.847 43.847 0 0 1 63.867 28.52l5.484 5.753a3.243 3.243 0 0 0 4.59.105l6.137-5.869a43.847 43.847 0 0 1 30.017 21.38l-4.201 9.487a3.256 3.256 0 0 0 1.652 4.29zm10.477.154l-.143-1.467 4.327-4.036c.88-.82.55-2.472-.574-2.891l-5.532-2.068-.433-1.428 3.45-4.792c.704-.974.058-2.53-1.127-2.724l-5.833-.949-.7-1.31 2.45-5.38c.502-1.095-.43-2.496-1.636-2.45l-5.92.206-.935-1.135 1.36-5.766c.275-1.17-.913-2.36-2.084-2.085l-5.765 1.359-1.136-.935.207-5.92c.046-1.198-1.357-2.135-2.45-1.637l-5.379 2.452-1.31-.703-.95-5.833c-.193-1.183-1.75-1.83-2.723-1.128l-4.796 3.45-1.425-.432-2.068-5.532c-.42-1.127-2.072-1.452-2.89-.576l-4.036 4.33-1.467-.143-3.117-5.036c-.63-1.02-2.318-1.02-2.946 0l-3.117 5.036-1.467.143-4.037-4.33c-.819-.876-2.47-.551-2.89.576l-2.069 5.532-1.426.432-4.795-3.45c-.974-.703-2.53-.055-2.723 1.128l-.951 5.833-1.31.703-5.379-2.452c-1.093-.5-2.496.439-2.45 1.637l.206 5.92-1.136.935-5.765-1.36c-1.171-.272-2.36.915-2.086 2.086l1.358 5.766-.933 1.135-5.92-.206c-1.193-.035-2.134 1.355-1.637 2.45l2.453 5.38-.703 1.31-5.832.949c-1.185.192-1.827 1.75-1.128 2.724l3.45 4.792-.433 1.428-5.532 2.068c-1.123.42-1.452 2.07-.574 2.891l4.328 4.036-.143 1.467-5.035 3.116c-1.02.63-1.02 2.318 0 2.946l5.035 3.117.143 1.467-4.328 4.037c-.878.818-.549 2.468.574 2.89l5.532 2.068.433 1.428-3.45 4.793c-.701.976-.056 2.532 1.129 2.723l5.831.948.703 1.312-2.453 5.378c-.5 1.093.444 2.5 1.638 2.451l5.917-.207.935 1.136-1.358 5.768c-.275 1.168.915 2.355 2.086 2.08l5.765-1.357 1.137.932-.207 5.921c-.046 1.199 1.357 2.136 2.45 1.636l5.379-2.45 1.31.702.95 5.83c.193 1.187 1.75 1.829 2.725 1.13l4.792-3.453 1.427.435 2.069 5.53c.42 1.123 2.072 1.454 2.89.574l4.037-4.328 1.467.146 3.117 5.035c.628 1.016 2.316 1.018 2.946 0l3.117-5.035 1.467-.146 4.036 4.328c.818.88 2.47.549 2.89-.574l2.068-5.53 1.428-.435 4.793 3.453c.974.699 2.53.055 2.722-1.13l.952-5.83 1.31-.703 5.378 2.451c1.093.5 2.493-.435 2.45-1.636l-.206-5.92 1.135-.933 5.765 1.357c1.171.275 2.36-.912 2.085-2.08l-1.358-5.768.932-1.136 5.92.207c1.194.048 2.138-1.358 1.636-2.451l-2.45-5.378.7-1.312 5.833-.948c1.187-.19 1.831-1.747 1.127-2.723l-3.45-4.793.433-1.428 5.532-2.068c1.125-.422 1.454-2.072.574-2.89l-4.327-4.037.143-1.467 5.035-3.117c1.02-.628 1.021-2.315.001-2.946z" fill="#ff7043" stroke-width="1.146"/></symbol><symbol viewBox="0 0 500 500" id="sass" xmlns="http://www.w3.org/2000/svg"><path d="M422.676 96.573c-12.192-47.839-91.508-63.557-166.575-36.892-44.68 15.877-93.029 40.786-127.81 73.311-41.349 38.675-47.943 72.328-45.216 86.395 9.583 49.622 77.585 82.069 105.535 106.126v.144c-8.246 4.05-68.565 34.584-82.684 65.799-14.893 32.932 2.372 56.556 13.804 59.742 35.424 9.859 71.764-7.866 91.311-37.01 18.853-28.12 17.28-64.422 9.086-82.487 11.3-2.976 24.476-4.314 41.218-2.36 47.248 5.52 56.517 35.017 54.747 47.366-1.77 12.35-11.681 19.14-14.998 21.186-3.317 2.045-4.326 2.766-4.05 4.287.405 2.215 1.94 2.137 4.758 1.652 3.894-.656 24.804-10.042 25.709-32.828 1.14-28.933-26.587-61.302-75.684-60.45-20.216.354-32.933 2.268-42.123 5.69-.681-.774-1.363-1.547-2.084-2.307-30.35-32.382-86.46-55.285-84.088-98.824.866-15.823 6.372-57.5 107.817-108.052 83.104-41.415 149.637-30.009 161.135-4.76 16.427 36.08-35.554 103.137-121.858 112.812-32.88 3.684-50.198-9.059-54.498-13.804-4.536-4.995-5.204-5.218-6.909-4.287-2.753 1.533-1.01 5.938 0 8.574 2.583 6.712 13.15 18.603 31.176 24.515 15.863 5.205 54.459 8.063 101.156-9.99 52.283-20.255 93.12-76.523 81.125-123.548zM200.213 340.34c3.92 14.5 3.487 28.016-.564 40.248a65.289 65.289 0 0 1-3.225 7.97c-3.12 6.477-7.316 12.534-12.442 18.132-15.653 17.069-37.507 23.532-46.88 18.092-10.122-5.874-5.048-29.944 13.083-49.11 19.52-20.636 47.602-33.903 47.602-33.903l-.039-.079 2.465-1.35z" fill="#ec407a" stroke="#ec407a" stroke-width="16.286552999999998"/></symbol><symbol viewBox="0 0 300 300" id="sbt" xmlns="http://www.w3.org/2000/svg"><path d="M105.46 209.517c-7.875 0-13.452-7.521-13.452-15.37v-.327c0-7.848 5.578-13.735 13.452-13.735h164.05c1.476-4.905 2.625-11.446 3.281-17.986h-137.81c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h137.31c-.82-6.54-1.969-13.081-3.773-17.986h-104.01c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h91.87c-21.327-37.607-60.864-61.315-106.14-61.315-67.918 0-123.04 54.448-123.04 122.3 0 67.856 55.122 123.28 123.04 123.28 46.59 0 87.112-25.507 107.95-63.114h-152.73z" fill="#0277bd" stroke-width="1.638"/></symbol><symbol viewBox="0 0 256 256" id="scala" xmlns="http://www.w3.org/2000/svg"><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M59.607 50.647l149.097-21.982v49.488L59.607 100.135zM59.593 114.08L208.69 92.098v49.488L59.593 163.568zM59.587 177.358l149.097-21.982v49.488L59.587 226.846z"/><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M62.425 91.414l95.605 30.923-2.832 8.757-95.605-30.922zM113.084 61.13l95.604 30.922-2.832 8.757-95.605-30.922zM62.425 154.79l95.605 30.922-2.833 8.758-95.604-30.923zM113.097 124.408l95.604 30.923-2.832 8.757-95.605-30.922z"/></symbol><symbol viewBox="0 0 24 24" id="settings" xmlns="http://www.w3.org/2000/svg"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="shaderlab" xmlns="http://www.w3.org/2000/svg"><path d="M9.11 17H6.5l-4.91-5L6.5 7h2.61l1.31-2.26L17.21 3l1.87 6.74L17.77 12l1.31 2.26L17.21 21l-6.79-1.74L9.11 17m.14-.25l5.13 1.38L11.42 13H5.5l3.75 3.75m6.87.38L17.5 12l-1.38-5.13L13.15 12l2.97 5.13M9.25 7.25L5.5 11h5.92l2.96-5.13-5.13 1.38z" fill="#1976d2"/></symbol><symbol viewBox="0 0 24 24" id="slim" xmlns="http://www.w3.org/2000/svg"><path d="M6.959 2.5a4.605 4.605 0 0 0-4.615 4.615v9.957a4.605 4.605 0 0 0 4.615 4.615h9.957a4.605 4.605 0 0 0 4.615-4.615V7.115A4.605 4.605 0 0 0 16.916 2.5zm4.938 2.691a6.811 6.811 0 0 1 6.81 6.813H13.43L9.938 7.287l.699 4.717H5.086a6.811 6.811 0 0 1 6.81-6.813z" fill="#f57f17"/></symbol><symbol id="smarty" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.iust0{fill:#ffce00}</style><path class="iust0" d="M9.14 20.606c0 .556.398.953.954.953h3.812c.556 0 .953-.397.953-.953v-.953H9.141zM12 2.5c-3.653 0-6.671 3.018-6.671 6.671 0 2.303 1.112 4.289 2.859 5.48v2.144c0 .556.397.953.953.953h5.718c.556 0 .953-.397.953-.953V14.65c1.747-1.191 2.86-3.177 2.86-5.48 0-3.653-3.019-6.671-6.672-6.671zm2.7 10.563l-.794.555v2.224h-3.812v-2.224l-.794-.555A4.712 4.712 0 0 1 7.235 9.17 4.78 4.78 0 0 1 12 4.405a4.78 4.78 0 0 1 4.765 4.765 4.712 4.712 0 0 1-2.065 3.892z"/></symbol><symbol viewBox="0 0 200 200" id="snyk" xmlns="http://www.w3.org/2000/svg"><title>Group 2</title><g transform="translate(15.255 18.22) scale(1.8477)" fill="none" fill-rule="evenodd"><path d="M65.161 24.997c-1.656 5.974-5.255 23.587-5.255 23.587s-6.618-2.464-14.148-2.476h-.055c-.413.002-.822.012-1.23.026v41.649h6.677v.003h5.815v-.003h20.858c.111-8.177-2.036-27.066-2.036-27.066-1.088-2.279.46-7.668.46-7.668-8.869-9.092-11.086-28.051-11.086-28.051zm-3.357 43.958c5.476 0 1.381 4.64.9 5.168H52.35c.944-1.18 4.504-5.168 9.453-5.168z" fill="#607d8b" stroke-width="1.6"/><path d="M26.366 24.995s-2.217 18.961-11.087 28.053c0 0 1.548 5.391.46 7.669 0 0-2.15 18.895-2.038 27.066h19.273v.003h7.079v-.003h5.744V46.107h-.025c-7.532.013-14.151 2.478-14.151 2.478s-3.6-17.615-5.255-23.59zm3.264 43.96c4.95 0 8.51 3.987 9.452 5.168H28.73c-.479-.528-4.573-5.168.9-5.168z" fill="#90a4ae" stroke-width="1.6"/><g transform="translate(23.76 77.45) scale(1.5998)"><g transform="translate(17.526)"><path d="M7.357.06H.177v.075C.177 2.64 2.345 4.67 4.89 4.67 7.431 4.67 9.6 2.64 9.6.135V.059z" fill="#455a64"/><path d="M1.972.06v.075a2.692 2.692 0 1 0 5.386 0V.059z" fill="#fff"/><path d="M5.496.06H4.234c-.012 0-.023.005-.034.007.157.033.243.388.21.624a.721.721 0 0 1-.71.617c.102.471.487.85.997.922a1.188 1.188 0 0 0 1.35-1.007C6.112.743 5.881.06 5.495.06z" fill="#37474f"/></g><path d="M7.552.06H.372v.075c0 2.505 2.17 4.535 4.712 4.535 2.544 0 4.712-2.03 4.712-4.535V.059z" fill="#455a64"/><path d="M2.168.06v.075a2.692 2.692 0 1 0 5.385 0V.059z" fill="#fff"/><path d="M5.692.06H4.428c-.01 0-.022.005-.032.007.156.033.242.388.21.624a.72.72 0 0 1-.712.617c.104.471.488.85.999.922A1.187 1.187 0 0 0 6.24 1.223C6.308.743 6.078.06 5.69.06z" fill="#37474f"/></g><path d="M25.514-.27l-4.202 7.697C19.838 10.17 6.858 34.465 6.858 43.243v.516L12.8 59.573c-.8 7.258-2.203 21.643-1.78 28.21h5.73c-.354-3.787.648-17.008 1.903-28.25l.076-.677-1.075-2.892c3.694-3.868 6.285-9.193 8.073-14.261l.174 1.235 5.869 9.629 2.291-.983c.058-.024 5.935-2.523 11.643-2.523 5.672 0 11.646 2.5 11.702 2.525l2.29.976 5.86-9.626.23-1.608c1.769 5.117 4.358 10.536 8.07 14.49l-1.127 3.035.076.678c1.259 11.286 2.266 24.564 1.916 28.252h5.677c.406-6.567-1.05-20.952-1.848-28.208l5.838-15.817v-.514c0-8.779-12.876-33.074-14.347-35.816L65.923-.27l-5.897 41.229-2.723 4.478c-2.628-.882-7.1-2.11-11.603-2.11-4.498 0-8.94 1.225-11.557 2.108l-2.722-4.476-2.07-14.452a.832.832 0 0 0 .006-.071l-.016-.004zm-3.166 18.39l1.206 8.407c-.46 3.143-2.561 15.47-8.198 23.24l-2.598-6.99c.325-4.554 5.067-15.462 9.59-24.656zm46.763 0c4.523 9.194 9.267 20.104 9.592 24.657L76.166 49.6c-6.09-8.553-8-22.459-8.166-23.73z" fill="#607d8b" stroke-width="1.6"/></g></symbol><symbol viewBox="0 0 24 24" id="solidity" xmlns="http://www.w3.org/2000/svg"><path d="M5.8 14.05l6.253 8.61 6.252-8.61-6.254 3.807z" fill="#0288d1" stroke-width="4.553" stroke-linejoin="round"/><path d="M12.051 1.347L5.8 11.833l6.252 3.807 6.254-3.807z" fill="#0288d1" stroke-width="5.025" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 120 120" id="sonar" xmlns="http://www.w3.org/2000/svg"><style>.a,.b{fill:#fff}.b{stroke:#fff;stroke-miterlimit:10}</style><path d="M115.45 23.033S97.961 33.27 97.534 33.412c-.427.284-.852.57-1.137.854-1.422 1.421-1.848 3.41-1.422 5.26.285.852.711 1.849 1.422 2.56.711.71 1.564 1.137 2.559 1.422 1.848.426 3.84 0 5.262-1.422.426-.427.709-.853.851-1.28l.143-.427 2.56-4.692zm-39.102 9.242c-27.441 0-31.99 13.08-31.99 29.29 0 3.838.569 7.962-1.99 11.942-3.84 5.972-8.957 5.828-10.236 5.828-1.706 0-7.962-.993-8.246-2.841h.994c6.682 0 11.658-5.404 11.658-12.655v-2.56h-5.686c-4.123 0-7.82 1.849-10.238 5.12-2.417-3.271-6.113-5.12-10.236-5.12h-5.83v2.56c0 7.11 5.688 12.795 12.797 12.795h1.848c0 4.124 5.687 20.332 47.63 20.332 16.352 0 40.665-2.843 40.665-33.697 0-5.829-1.848-11.23-4.691-15.78-.996.284-1.992.568-3.13.568a8.92 8.92 0 0 1-8.956-8.957c0-.995.141-1.991.425-2.986-4.265-2.702-8.53-3.838-14.787-3.838z" fill="#1e88e5" stroke-width="1.422"/></symbol><symbol viewBox="0 0 412 395" id="stylelint" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-white</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#cfd8dc" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 412 395" id="stylelint_light" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-black</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#546e7a" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 200.00001 200.00001" id="stylus" xmlns="http://www.w3.org/2000/svg"><path d="M126.814 155.9c14.64-17.51 16.362-35.595 5.024-69.18-7.177-21.24-19.09-37.602-10.334-50.807 9.329-14.065 29.135-.43 12.63 18.371l3.301 2.297c19.806 2.296 29.566-24.83 14.783-32.58C113.179 3.621 79.02 42.803 94.09 88.156c6.458 19.232 15.5 39.613 8.18 55.83-6.314 13.923-18.514 22.103-26.695 22.39-17.079.862-5.74-38.32 13.922-48.08 1.722-.861 4.162-2.01 1.866-4.88-24.256-2.727-38.464 8.468-46.645 24.112-23.825 45.497 45.21 62.29 82.095 18.371z" fill="#c0ca33" stroke-width="1.435"/></symbol><symbol viewBox="0 0 24 24" id="swc" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="jba"><stop offset="0" stop-color="#791223"/><stop offset="1" stop-color="#d92f3c"/></linearGradient><linearGradient xlink:href="#jba" id="jbb" x1="12.356" y1="21.559" x2="12.356" y2="2.949" gradientUnits="userSpaceOnUse"/></defs><path d="M6 3c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6 3 6.5V19a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6.5c0-.5-.17-.93-.46-1.27l-1.39-1.68C18.88 3.21 18.47 3 18 3H6zm-.07 1h12l.94 1H5.12l.81-1z" fill="url(#jbb)"/><path style="line-height:125%" d="M11.053 11.918h-.008c-.244.022-.475.054-.676.11a2.9 2.9 0 0 0-.856.412 3.399 3.399 0 0 0-.67.683 9.36 9.36 0 0 0-.586.95c-.07.131-.134.244-.201.365v.001h-.002l-.768 1.372-.003-.001c-.136.253-.264.485-.38.686-.123.212-.26.39-.411.539a1.599 1.599 0 0 1-.52.34c-.04.016-.092.024-.138.036h-.567v1.383H5.834v-.001c.245-.02.477-.053.679-.11a2.9 2.9 0 0 0 .856-.411c.245-.185.469-.413.67-.683.195-.275.39-.591.585-.95.07-.131.135-.244.202-.366l.004.001.002-.002.02-.038H10.948v-1.378h-.19v-.001H9.624c.125-.234.246-.452.355-.64.123-.21.259-.39.41-.538.152-.148.325-.26.52-.34.04-.015.091-.024.136-.035h.57V13.3h-.002v-1.381h-.56v-.001z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol viewBox="0 0 24 24" id="swift" xmlns="http://www.w3.org/2000/svg"><path d="M17.09 19.72c-2.36 1.36-5.59 1.5-8.86.1A13.807 13.807 0 0 1 2 14.5c.67.55 1.46 1 2.3 1.4 3.37 1.57 6.73 1.46 9.1 0-3.37-2.59-6.24-5.96-8.37-8.71-.45-.45-.78-1.01-1.12-1.51 8.28 6.05 7.92 7.59 2.41-1.01 4.89 4.94 9.43 7.74 9.43 7.74.16.09.25.16.36.22.1-.25.19-.51.26-.78.79-2.85-.11-6.12-2.08-8.81 4.55 2.75 7.25 7.91 6.12 12.24-.03.11-.06.22-.05.39 2.24 2.83 1.64 5.78 1.35 5.22-1.21-2.39-3.48-1.65-4.62-1.17z" fill="#fe5e2f"/></symbol><symbol viewBox="0 0 24 24" id="table" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5m4 7.5h-4v2h1l-2 1.67L10 13h1v-2H7v2h1l3 2.5L8 18H7v2h4v-2h-1l2-1.67L14 18h-1v2h4v-2h-1l-3-2.5 3-2.5h1v-2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 200 200" id="terraform" xmlns="http://www.w3.org/2000/svg"><g transform="translate(177.03 -58.705) scale(.92881)" fill="#5c6bc0" stroke="#b0aff5" stroke-linejoin="round"><g stroke-width=".288"><path transform="skewY(26.439) scale(.89541 1)" d="M-203.8 170.95h64.714v51.88H-203.8zM-124.37 171.04h64.714v51.88h-64.714zM-124.37 236.09h64.714v51.88h-64.714z"/></g><path transform="skewY(-22.59) scale(-.92328 1)" stroke-width=".284" d="M-19.172 128.27h62.76v51.88h-62.76z"/></g></symbol><symbol viewBox="0 0 24 24" id="test-js" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="test-jsx" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="test-ts" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tex" xmlns="http://www.w3.org/2000/svg"><g font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-linejoin="miter"><text style="line-height:125%" x="9.914" y="364.919"><tspan x="9.914" y="364.919" font-size="287.5">T</tspan></text><text style="line-height:125%" x="136.374" y="435.558"><tspan x="136.374" y="435.558" font-size="287.5">E</tspan></text><text style="line-height:125%" x="307.819" y="361.201"><tspan x="307.819" y="361.201" font-size="287.5">X</tspan></text></g></symbol><symbol viewBox="0 0 24 24" id="todo" xmlns="http://www.w3.org/2000/svg"><path d="M3 5h6v6H3V5m2 2v2h2V7H5m6 0h10v2H11V7m0 8h10v2H11v-2m-6 5l-3.5-3.5 1.41-1.41L5 17.17l4.59-4.58L11 14l-6 6z" fill="#42a5f5"/></symbol><symbol id="travis" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style id="jkstyle2">.jkst0{fill:#cb3349}.jkst1{fill:#f4edae}.jkst2{fill:#e6ccad}.jkst3{fill:#656c67}.jkst4{fill:#e5caa3}.jkst5{fill:#c7b39a}.jkst6{fill:#ebd599}.jkst7{fill:#2d3136}.jkst8{fill:#edf6fa}.jkst9{opacity:.8}.jkst10{opacity:.75;fill:#ebd599}</style><g id="jkg99" transform="translate(11.017 12.484) scale(.8858)"><g id="jkg10"><path class="jkst0" d="M47.781 86.572s-31.118 21.903-32.335 30.247l2.335-.48S55.045 91.64 84.584 88.628l.669-3.749z" id="jkpath4" fill="#cb3349"/><path class="jkst0" d="M96.629 83.442l-24.511 17.385 1.325 1.063c.999-.806 43.539-13.798 43.539-13.798l8.969-5.623c-6.018.749-29.322.973-29.322.973z" id="jkpath6" fill="#cb3349"/><path class="jkst0" d="M117.932 104.469c17.405 0 43.495-17.046 43.495-17.046l-8.434-1.605c-.417.417-13.6-.462-13.6-.462l-6.258-1.738-14.951 17.036-1.217 2.956c1.075-.437.965.859.965.859z" id="jkpath8" fill="#cb3349"/></g><path class="jkst0" d="M174.728 158.832l-5.377 1.514-24.843-.537-15.541-12.085-18.784 4.7-21.726-1.88-12.166 13.294-22.828 6.819-11.398-3.534-.574-.494 5.116 12.527s11.588 12.424 18.061 13.885c6.472 1.461 18.165-.105 26.935-1.463 8.769-1.357 15.764-4.489 18.582-9.603 2.818-5.117 3.236-6.578 3.236-6.578s8.353 11.797 15.556 13.155c7.203 1.357 28.605-5.952 28.605-5.952s13.051-3.549 15.346-8.038c2.297-4.489 8.353-19.209 8.353-19.209zM44.456 169.038l-.361-.166-2.013-1.736z" id="jkpath12" fill="#cb3349"/><g id="jkg97"><path class="jkst1" d="M195.832 70.085a48.125 48.125 0 0 0-.21-2.009 26.472 26.472 0 0 0-.215-1.424c-1.793-1.509-3.831-2.851-5.952-4.071-2.299-1.343-4.704-2.546-7.159-3.663-2.438-1.15-4.942-2.191-7.461-3.207a134.313 134.313 0 0 0-3.798-1.477c-1.269-.495-2.55-.956-3.835-1.424 2.697.447 5.366 1.059 8.015 1.741 1.723.446 3.437.945 5.14 1.477-12.112-31.655-41.07-52.27-72.687-52.27-31.622 0-60.577 20.615-72.686 52.27a109.044 109.044 0 0 1 5.137-1.477c2.653-.682 5.323-1.294 8.018-1.741-1.289.468-2.567.929-3.84 1.424-1.267.472-2.536.967-3.798 1.477-2.519 1.016-5.016 2.057-7.46 3.207-2.45 1.117-4.857 2.32-7.156 3.663-2.121 1.219-4.157 2.562-5.957 4.071-.075.457-.151.951-.21 1.424a51.768 51.768 0 0 0-.21 2.009 51.354 51.354 0 0 0-.177 4.061 59.216 59.216 0 0 0 .5 8.11c.37 2.692.864 5.366 1.595 7.951.36 1.295.768 2.572 1.24 3.808.237.617.495 1.225.764 1.816.134.294.274.585.413.864l.172.328c.199.101.408.204.607.3l1.204.575c.671.305 1.6.746 2.368 1.09.043-.037.086-.075.123-.114l-2.235-8.513c.474-.13 4.718-1.225 12.032-2.617a38.816 38.816 0 0 1-1.772-.381c-1.665-.414-3.309-.919-4.899-1.564a22.415 22.415 0 0 1-2.309-1.115c-.742-.426-1.472-.908-2.037-1.548 8.036 2.622 24.64 1.434 39.399-.091 13.499-1.391 27.029-2.293 40.63-2.32 13.602.027 27.137.929 40.63 2.32 14.766 1.525 31.37 2.713 39.405.091-.564.64-1.293 1.123-2.035 1.548a22.5 22.5 0 0 1-2.308 1.115c-1.592.645-3.234 1.15-4.899 1.564-.247.059-.496.113-.743.166 8.02 1.488 12.689 2.697 13.188 2.831l-2.138 8.11c.43-.194.864-.381 1.29-.574l1.202-.575c.2-.097.403-.199.607-.3l.166-.328c.146-.279.286-.57.419-.864.27-.591.528-1.199.764-1.816a42.235 42.235 0 0 0 1.241-3.808c.731-2.585 1.225-5.259 1.595-7.951.345-2.685.526-5.398.501-8.11a50.874 50.874 0 0 0-.179-4.059z" id="jkpath14" fill="#f4edae"/><path class="jkst2" d="M116.787 182.661c-1.064.16-2.128.295-3.186.375-.682.033-1.404.102-2.059.102l-.242.005c.822-1.837 1.446-3.26 1.919-4.339.963 1.08 2.188 2.417 3.568 3.857z" id="jkpath16" fill="#e6ccad"/><path class="jkst2" d="M119.101 185.018c3.304 3.272 7.398 5.146 11.904 5.479-7.569 3.074-14.702 4.26-20.197 4.63-5.478.367-11.032-.279-16.474-1.771.456-.082.79-.14 1.193-.189.447-.054 10.206-1.327 14.605-7.868l.413.009 1.08-.009c.731 0 1.395-.06 2.094-.087a43.69 43.69 0 0 0 4.878-.703c.167.171.333.338.504.509z" id="jkpath18" fill="#e6ccad"/><path class="jkst3" d="M128.464 87.071a98.82 98.82 0 0 1-1.048 1.343c-1.933 2.444-4.614 5.57-7.794 8.627a369.585 369.585 0 0 0-11.404-.177c-6.46 0-12.655.171-18.537.457 8.311-3.449 18.296-6.818 29.109-8.842a113.323 113.323 0 0 1 9.674-1.408z" id="jkpath20" fill="#656c67"/><path class="jkst3" d="M79.821 90.792c-2.966 2.084-6.317 4.744-9.566 7.971a360.155 360.155 0 0 0-21.567 2.81c9.207-4.232 19.713-8.127 31.133-10.781z" id="jkpath22" fill="#656c67"/><path class="jkst3" d="M181.48 107.969l-3.384 23.679-16.212 11.355-42.283-4.807-6.365-20.961a1.383 1.383 0 0 0-1.108-.971c-1.567-.253-2.953-.382-4.108-.382-1.16 0-2.541.129-4.115.382-.522.086-.95.461-1.106.971l-6.209 20.45-42.047 9.357-16.662-11.672-3.283-26.572c.715-.404 1.441-.806 2.176-1.209 1.031-.222 2.191-.457 3.475-.704l3.094 25.073c.048.392.264.741.586.967l11.462 8.032a1.425 1.425 0 0 0 1.101.213l34.57-7.692c.119-.027.237-.069.344-.124a1.39 1.39 0 0 0 .682-.827l6.225-20.498c1.67-.43 5.947-1.429 9.706-1.429 3.749 0 8.03.999 9.701 1.429l6.225 20.498c.161.532.624.912 1.176.977l34.57 3.927c.335.037.677-.05.952-.242l11.469-8.025c.31-.22.52-.566.573-.946l3.062-21.421c2.301.444 4.224.846 5.733 1.172z" id="jkpath24" fill="#656c67"/><path class="jkst3" d="M185.751 93.119l-2.976 11.29c-6.086-1.342-19.456-3.975-37.654-5.747 5.946-2.535 12-5.715 17.531-9.69 10.829 1.53 18.78 3.169 23.099 4.147z" id="jkpath26" fill="#656c67"/><g id="jkg32"><path class="jkst4" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath28" fill="#e5caa3"/><path class="jkst4" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath30" fill="#e5caa3"/></g><g id="jkg38"><path class="jkst5" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath34" fill="#c7b39a"/><path class="jkst5" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath36" fill="#c7b39a"/></g><path class="jkst2" d="M187.481 115.502c.508.419.911 1.504.456 6.558-.559 6.188-3.16 17.049-4.771 18.8-1.778.344-5.505-.064-7.778-.595.393-1.559.505-2.306.822-3.9l3.975-2.781c.317-.22.526-.566.58-.941l2.778-19.466c1.686.912 3.421 1.899 3.938 2.325z" id="jkpath40" fill="#e6ccad"/><path class="jkst2" d="M40.937 140.908c.199.704.408 1.407.624 2.1-2.139.628-6.495 1.23-8.465.886-1.633-1.645-4.679-12.966-5.345-18.978-.543-4.871-.162-5.924.333-6.334.575-.483 2.728-1.708 4.593-2.707l2.519 20.449c.048.393.257.741.586.967z" id="jkpath42" fill="#e6ccad"/><path class="jkst2" d="M121.347 141.194l-.151 1.305s-4.581 4.248-11.956 5.199c-7.375.95-13.171-3.582-13.171-3.582.242.788.586 2.567 2.256 4.086a53.184 53.184 0 0 0-6.313-.393c-.804 0-1.616.023-2.401.061-4.539.237-10.924 7.1-15.414 14.014-2.203.697-9.089 2.883-17.06 5.237-7.44-10.309-11.098-20.842-11.469-21.932l.005-.006c-.15-.419-.301-.839-.441-1.268l1.913 1.338v.005l4.726 3.309 1.58 1.101c.236.167.515.253.794.253.102 0 .204-.011.305-.031l43.435-9.67a1.385 1.385 0 0 0 1.025-.95l6.194-20.39c1.069-.145 2.008-.22 2.814-.22.801 0 1.746.075 2.815.22l6.374 20.997c.162.532.624.919 1.171.977z" id="jkpath44" fill="#e6ccad"/><path class="jkst2" d="M170.926 140.066l1.402-.984c-.232.973-.484 1.94-.747 2.896-1.949 6.248-4.25 11.774-6.805 16.656-.565.039-1.161.061-1.8.061-1.972 0-3.986-.167-6.215-.371-3.868-.355-10.007-1.058-11.946-1.283-1.67-1.332-7.385-5.873-12.14-9.615-.187-.151-.348-.291-.505-.42-.837-.708-1.789-1.513-3.717-1.513-1.751 0-4.308.638-10.489 2.508 3.212-2.401 3.233-5.5 3.233-5.5l.151-1.305 40.748 4.629a1.41 1.41 0 0 0 .955-.241l4.094-2.868z" id="jkpath46" fill="#e6ccad"/><path class="jkst6" d="M140.937 54.337c.124 3.625.033 10.194-1.655 16.345a1.335 1.335 0 0 0 0 .704 259.298 259.298 0 0 0-6.446-.591c2.412-5.054 2.938-10.436 3.052-12.332 1.852-1.317 3.696-2.896 5.049-4.126z" id="jkpath48" fill="#ebd599"/><path class="jkst6" d="M79.456 58.462c.112 1.896.638 7.267 3.046 12.317-2.149.171-4.297.37-6.441.596a1.328 1.328 0 0 0 0-.694c-1.686-6.139-1.772-12.714-1.654-16.345 1.353 1.231 3.19 2.81 5.049 4.126z" id="jkpath50" fill="#ebd599"/><path class="jkst7" d="M151.835 125.675c-2.89-1.396-6.059.377-11.828.484-4.292.151-7.896.198-8.132-6.543-.237-6.747 2.513-12.326 6.805-12.478 4.292-.15 8.207 5.2 8.735 11.931.145 1.854-.06 3.207-.521 4.21 3.996-.477 4.899 2.235 4.941 2.396zm-13.488-9.878a2.203 2.203 0 0 0 2.154-2.235 2.186 2.186 0 0 0-2.235-2.153 2.194 2.194 0 0 0 .081 4.388z" id="jkpath52" fill="#2d3136"/><circle transform="rotate(-1.049 138.093 113.428)" class="jkst8" cx="138.307" cy="113.602" id="jkellipse54" r="2.194" fill="#edf6fa"/><path class="jkst7" d="M83.484 120.953c.063 6.747-3.509 6.339-7.806 6.381-.435.011-.848.016-1.258.022-.482.011-.944.016-1.39.005-4.168-.005-6.833-.194-9.19 1.079.058-.145 1.09-2.461 4.835-3.4-.414-.914-.673-2.181-.742-3.937-.257-6.741 3.9-12.269 8.197-12.306 4.292-.042 7.289 5.411 7.354 12.156zm-6.634-3.529a2.195 2.195 0 1 0-.122-4.388 2.195 2.195 0 0 0 .122 4.388z" id="jkpath56" fill="#2d3136"/><circle transform="rotate(-1.473 76.78 115.216)" class="jkst8" cx="76.79" cy="115.23" id="jkellipse58" r="2.195" fill="#edf6fa"/><g class="jkst9" id="jkg64" opacity=".8"><path class="jkst6" d="M50.691 75.155s.667-8.692 2.03-12.023c.702-1.717 4.996-2.81 8.276-3.591 3.278-.78 8.508-2.342 9.524 2.264 1.015 4.606 2.653 7.963 3.746 9.446l-1.404-18.97-22.562 5.464-1.484 16.786.703 1.327 1.171-.703" id="jkpath60" fill="#ebd599"/><path class="jkst6" d="M164.855 75.155s-.666-8.692-2.029-12.023c-.703-1.717-4.997-2.81-8.275-3.591-3.28-.78-8.51-2.342-9.526 2.264-1.013 4.606-2.654 7.963-3.748 9.446l1.407-18.97 22.562 5.464 1.483 16.786-.703 1.327-1.171-.703" id="jkpath62" fill="#ebd599"/></g><path class="jkst10" d="M132.965 18.378s-.598 45.49-11.224 45.49h-14.875-12.752c-10.626 0-11.484-45.47-11.484-45.47l-5.22 15.438.085 21.183 3.707 2.947 1.685 9.096 2.357 5.307 45.482.084 2.105-3.791 1.769-6.4.254-4.043 5.023-14.341z" id="jkpath66" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M166.429 60.794s2.187 15.692 7.974 18.522c5.788 2.829 0 0 0 0l-8.103-2.444z" id="jkpath68" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M48.908 60.794s-2.187 15.692-7.975 18.522c-5.788 2.829 0 0 0 0l8.104-2.444z" id="jkpath70" opacity=".75" fill="#ebd599"/><path class="jkst7" d="M167.987 76.8c2.755.902 5.526 1.858 8.036 3.325-1.343-.532-2.729-.913-4.126-1.257a70.385 70.385 0 0 0-4.201-.924c-2.82-.531-5.65-.982-8.498-1.327-2.841-.37-5.687-.682-8.546-.924-2.858-.241-5.709-.483-8.573-.65-11.446-.704-22.924-.88-34.41-.892-11.483.006-22.962.221-34.409.897-2.862.166-5.715.409-8.572.651-2.857.241-5.71.548-8.546.923-2.847.345-5.678.796-8.498 1.327-1.407.264-2.81.57-4.206.919-1.391.344-2.783.725-4.126 1.257 2.509-1.466 5.28-2.427 8.041-3.331.232-.075.467-.139.703-.214-.015-.059-.032-.113-.043-.177-.048-.317-1.069-7.859.709-18.645.086-.516.456-.935.962-1.075l2.917-.831c.634-22.625 9.952-33.266 10.243-33.594-8.326 13.397-8.25 29.286-8.106 32.986l18.128-5.152c.016-.005.026-.005.042-.01.076-.016.151-.027.226-.032.021 0 .049-.006.075-.006a1.19 1.19 0 0 1 .297.027c.015 0 .031.011.053.016.075.016.145.042.224.075.033.016.054.033.086.049.058.033.119.07.177.112.016.011.034.016.049.033l.032.032c.016.016.037.027.054.044.012.016.494.493 1.262 1.209-.182-5.973.102-23.108 8.262-37.31-.172.498-6.646 19.428-4.415 40.645.724.58 1.486 1.149 2.229 1.649.359.247.58.655.585 1.09.006.07.161 6.833 3.148 12.586.042.086.074.177.102.268 7.429-.505 14.878-.709 22.312-.714 7.436.005 14.88.22 22.307.731.027-.097.06-.193.109-.285 2.986-5.753 3.142-12.516 3.142-12.586.01-.436.231-.843.591-1.09.741-.5 1.493-1.069 2.224-1.649 2.234-21.217-4.24-40.147-4.411-40.645 8.153 14.201 8.444 31.336 8.262 37.31a62.536 62.536 0 0 0 1.261-1.209c.016-.016.039-.027.053-.044.012-.01.018-.021.033-.032.016-.016.033-.022.049-.033.06-.042.119-.079.177-.118.028-.01.054-.027.081-.043.081-.033.155-.059.236-.08.016 0 .033-.011.049-.011.096-.021.2-.032.296-.027.027 0 .049.006.07.006.075.005.156.016.231.032.012.006.028.006.042.01l18.129 5.152c.146-3.7.221-19.59-8.104-32.986.289.328 9.609 10.969 10.237 33.594l2.922.831c.499.14.875.559.962 1.075 1.777 10.786.752 18.328.708 18.645-.01.065-.026.124-.042.182.239.07.47.139.707.215zm-3.297-.968c.14-1.207.789-7.809-.591-16.801l-20.52-5.833c.184 3.475.265 11.012-1.707 18.199a1.619 1.619 0 0 1-.101.258c.203.021.408.037.606.064 5.769.661 11.511 1.584 17.189 2.83 1.712.398 3.426.823 5.124 1.283zm-25.409-5.151c1.688-6.15 1.779-12.72 1.655-16.345-1.353 1.23-3.197 2.809-5.049 4.125-.114 1.896-.64 7.278-3.052 12.332 2.149.173 4.298.366 6.446.591a1.33 1.33 0 0 1 0-.703zm-56.78.098c-2.408-5.05-2.934-10.422-3.046-12.317-1.858-1.316-3.696-2.895-5.049-4.125-.119 3.631-.032 10.206 1.654 16.345.065.237.058.473 0 .694 2.145-.227 4.292-.425 6.441-.597zm-8.933.864a1.65 1.65 0 0 1-.098-.247c-1.975-7.187-1.889-14.723-1.712-18.199L51.244 59.03c-1.38 8.982-.736 15.583-.597 16.797 1.703-.462 3.411-.887 5.131-1.284 2.835-.628 5.693-1.154 8.556-1.638 2.869-.478 5.747-.843 8.626-1.192.205-.027.404-.042.608-.07z" id="jkpath72" fill="#2d3136"/><g id="jkXMLID_1_"><g id="jkg78"><path class="jkst7" d="M129.293 18.973v17.025h-12.068v-4.974h-2.72v22.981h4.109v12.85H97.505v-12.85h4.092v-22.98h-2.711v4.974h-12.06V18.973zm-3.626 13.408v-9.789H90.443v9.789h4.816v-4.974h9.964v30.225h-4.1v5.606h13.865v-5.606h-4.1V27.407h9.964v4.974z" id="jkpath74" fill="#2d3136"/><path class="jkst0" id="jkpolygon76" fill="#cb3349" d="M101.123 57.632h4.1V27.407h-9.964v4.974h-4.816v-9.79h35.224v9.79h-4.816v-4.974h-9.964v30.225h4.1v5.606h-13.864z"/></g></g><path class="jkst3" d="M30.694 93.119c1.759-.399 4.136-.907 7.051-1.47a104.37 104.37 0 0 0-6.222 4.597z" id="jkpath83" fill="#656c67"/><path class="jkst5" d="M95.111 139.78s.492 3.165-3.938 4.519c-4.428 1.355-32.482 9.716-35.682 9.263-3.199-.451-11.319-5.874-11.319-5.874l-1.969-7.004 12.016 7.492z" id="jkpath85" fill="#c7b39a"/><path class="jkst5" d="M120.242 139.167s-.354 3.182 4.131 4.345c4.484 1.161 32.875 8.295 36.05 7.704 3.176-.591 11.053-6.361 11.053-6.361l1.663-7.084-11.045 6.588z" id="jkpath87" fill="#c7b39a"/><path class="jkst5" d="M28.412 133.956s3.887 7.775 10.166 5.083l4.485 1.645-.448 3.29-9.419 1.195-2.541-1.494z" id="jkpath89" fill="#c7b39a"/><path class="jkst5" d="M187.551 131.822s-6.353 8.115-12.632 5.424l-2.019 1.302.448 3.289 9.419 1.196 2.54-1.495z" id="jkpath91" fill="#c7b39a"/><path class="jkst5" d="M89.279 192.904s23.03 11.611 49.106-4.188l-8.374-.571s-18.272 7.232-32.738 3.235z" id="jkpath93" fill="#c7b39a"/><path class="jkst7" d="M112.626 171.509l1.594 1.899c.036.046 3.577 4.26 7.906 8.552 2.879 2.853 6.357 4.297 10.343 4.297 1.361 0 2.791-.175 4.235-.523 1.34-.326 2.796-.673 4.287-1.03 5.384-1.287 11.482-2.749 14.438-3.577.585-.166 1.238-.315 1.925-.472 3.935-.909 9.329-2.163 12.187-7.889 2.149-4.297 5.047-9.874 7.197-13.961-1.863.859-3.816 1.79-5.203 2.52-2.138 1.123-4.938 1.667-8.558 1.667-2.152 0-4.266-.181-6.605-.389-4.675-.43-12.586-1.361-12.667-1.372l-.606-.067-.478-.383c-.071-.052-7.003-5.575-12.606-9.981-.227-.186-.434-.358-.621-.513-.59-.503-.59-.503-.942-.503-1.797 0-7.02 1.62-18.462 5.167l-.703.223-.689-.26c-.078-.026-7.585-2.81-16.581-2.81-.736 0-1.47.019-2.185.056-.901.046-5.958 2.448-12.425 12.68l-.419.657-.741.238c-.107.037-11.238 3.63-23.042 7.005l-.766.218-.725-.337c-.077-.031-4.696-2.174-9.091-4.194 2.397 3.541 5.462 7.958 8.159 11.422 4.711 6.067 10.649 11.674 22.034 11.674 1.428 0 2.945-.088 4.503-.265 11.581-1.309 14.563-1.837 16.168-2.117.543-.092.973-.171 1.522-.238.088-.011 9.571-1.237 12.232-7.206 2.744-6.134 3.298-7.595 3.319-7.651l.968-2.583s.12-.669.317-.877c0 .005 0 .005.005.005l.019.016c.305.219.757.902.757.902zM40.499 55.71c-2.516 1.014-5.016 2.06-7.46 3.209-2.449 1.119-4.856 2.32-7.155 3.66-2.121 1.222-4.157 2.563-5.954 4.076-.077.455-.149.952-.211 1.423a51.357 51.357 0 0 0-.388 6.068c-.026 2.713.16 5.426.502 8.112.372 2.692.864 5.369 1.594 7.952a41.963 41.963 0 0 0 1.243 3.804c.233.623.492 1.228.762 1.818.134.294.274.585.413.864l.172.326c.201.104.409.207.605.3l1.206.574c.673.311 1.6.751 2.366 1.093.046-.037.088-.078.124-.114l-2.231-8.511c.471-.129 4.717-1.227 12.032-2.619a33.744 33.744 0 0 1-1.775-.379 36.704 36.704 0 0 1-4.898-1.563 22.857 22.857 0 0 1-2.309-1.119c-.741-.425-1.471-.905-2.035-1.547 8.035 2.624 24.637 1.433 39.398-.088 13.501-1.393 27.028-2.293 40.628-2.325 13.6.031 27.138.931 40.63 2.325 14.77 1.522 31.374 2.713 39.406.088-.564.642-1.293 1.122-2.034 1.547-.739.42-1.522.782-2.309 1.119a36.965 36.965 0 0 1-4.903 1.563c-.244.056-.492.114-.741.166 8.02 1.486 12.689 2.697 13.186 2.832l-2.138 8.107c.43-.192.864-.377 1.288-.574l1.207-.574c.196-.094.404-.196.606-.3l.166-.326c.144-.279.284-.57.419-.864.27-.591.528-1.196.767-1.818.471-1.231.879-2.51 1.236-3.804.731-2.583 1.228-5.26 1.595-7.952.346-2.686.528-5.4.502-8.112a52.755 52.755 0 0 0-.176-4.059 51.573 51.573 0 0 0-.213-2.009 29.83 29.83 0 0 0-.213-1.423c-1.797-1.513-3.831-2.853-5.954-4.076-2.299-1.34-4.704-2.541-7.159-3.66-2.438-1.149-4.943-2.195-7.46-3.209a140.105 140.105 0 0 0-3.801-1.476c-1.267-.491-2.552-.956-3.835-1.423 2.696.445 5.369 1.06 8.013 1.739 1.724.446 3.444.948 5.141 1.481-12.11-31.658-41.07-52.272-72.685-52.272-31.622 0-60.576 20.614-72.684 52.272a107.832 107.832 0 0 1 5.135-1.481c2.651-.678 5.322-1.294 8.02-1.739-1.29.466-2.568.931-3.842 1.423-1.268.47-2.535.967-3.799 1.475zm159.43 18.316a53.972 53.972 0 0 1-.258 8.733 55.462 55.462 0 0 1-1.619 8.605c-.4 1.414-.86 2.811-1.404 4.198a38.295 38.295 0 0 1-.89 2.071c-.161.341-.331.678-.523 1.025l-.284.512a8.975 8.975 0 0 1-.348.574l-.294.457-.461.237c-.492.254-.895.445-1.342.653l-1.298.585a88.22 88.22 0 0 1-2.62 1.065c-.611.239-1.15.457-1.662.674l-1.444 5.487c-.036-.009-.471-.12-1.283-.315l-.078.574c1.594.833 4.726 2.522 5.793 3.403 2.148 1.775 2.299 4.587 1.823 9.841-.244 2.697-1.139 7.946-2.381 12.767-2.144 8.298-3.283 9.273-4.753 9.649-.746.192-1.894.383-3.008.383-2.266 0-5.353.063-7.429-.439-.533 1.888-2.055 6.812-5.068 12.962.151-.073.3-.135.435-.207 3.717-1.952 10.861-5.064 11.162-5.199l5.643-2.452-2.89 5.435c-.067.118-6.264 11.773-10.059 19.383-3.769 7.538-10.835 9.179-15.065 10.151-.637.151-1.241.291-1.733.425-3.035.854-9.18 2.319-14.599 3.623-.064.016-.13.033-.197.042a64.057 64.057 0 0 1-10.955 5.411c-14.568 5.518-29.923 5.208-43.844.092a647.05 647.05 0 0 1-9.193 1.097 45.12 45.12 0 0 1-4.985.291c-13.264 0-20.294-6.736-25.425-13.331-5.493-7.062-12.212-17.546-12.497-17.985L31 158.426l6.585 2.961c3.152 1.419 12.524 5.757 15.205 7 .217-.061.43-.124.642-.186-4.457-6.357-8.112-13.605-10.695-21.634-2.195.662-5.576 1.175-8.206 1.175-.961 0-1.822-.072-2.484-.228-1.471-.336-3.148-1.754-5.431-9.795-1.325-4.668-2.314-9.764-2.603-12.387-.57-5.121-.466-7.864 1.662-9.636 1.283-1.071 5.611-3.344 6.507-3.809l-.192-1.58c-13.75 8.08-21.991 15.22-22.157 15.366L0 134.302l7.005-11.047c5.544-8.755 11.948-15.832 17.84-21.284-.244-.098-.471-.196-.71-.294l-1.299-.585a34.907 34.907 0 0 1-1.34-.653l-.461-.237-.295-.457c-.166-.249-.238-.388-.347-.574l-.29-.512c-.181-.347-.358-.684-.518-1.025a30.878 30.878 0 0 1-.89-2.071 44.74 44.74 0 0 1-1.404-4.198 54.745 54.745 0 0 1-1.62-8.605 54.664 54.664 0 0 1-.259-8.733c.078-1.455.218-2.909.419-4.354.104-.725.213-1.45.358-2.17.15-.734.296-1.418.518-2.221l.155-.564.404-.317c2.294-1.802 4.768-3.163 7.284-4.369a78.87 78.87 0 0 1 6.311-2.616c5.943-16.493 16.162-31.118 29.591-41.311C74.337 5.57 90.664 0 107.671 0s33.334 5.57 47.218 16.106c13.43 10.193 23.649 24.819 29.588 41.307a78.282 78.282 0 0 1 6.316 2.62c2.515 1.206 4.99 2.567 7.283 4.369l.404.317.156.564c.227.803.372 1.487.517 2.221.146.72.26 1.445.357 2.17.203 1.443.348 2.897.419 4.352zm-11.995 48.031c.456-5.052.058-6.139-.455-6.554-.513-.43-2.247-1.412-3.935-2.329l-2.779 19.464a1.39 1.39 0 0 1-.58.942l-3.977 2.781c-.315 1.593-.429 2.345-.817 3.903 2.273.528 5.999.938 7.775.595 1.612-1.748 4.214-12.61 4.768-18.802zm-5.161-17.648l2.977-11.29c-4.318-.978-12.27-2.615-23.1-4.148-5.53 3.976-11.582 7.155-17.53 9.691 18.199 1.771 31.57 4.406 37.653 5.747zm-4.68 27.237l3.385-23.676a240.127 240.127 0 0 0-5.731-1.169l-3.059 21.422a1.415 1.415 0 0 1-.575.943l-11.472 8.023c-.27.192-.616.28-.947.243l-34.572-3.929a1.391 1.391 0 0 1-1.176-.973l-6.227-20.5c-1.668-.431-5.949-1.43-9.696-1.43-3.764 0-8.041.999-9.708 1.43l-6.228 20.5a1.388 1.388 0 0 1-1.025.947l-34.572 7.692a1.483 1.483 0 0 1-.306.033 1.36 1.36 0 0 1-.792-.25l-11.467-8.029a1.396 1.396 0 0 1-.585-.968l-3.091-25.072c-1.284.249-2.443.487-3.479.703-.734.405-1.46.809-2.174 1.213l3.281 26.568 16.666 11.675 42.047-9.354 6.207-20.449a1.389 1.389 0 0 1 1.108-.975c1.574-.253 2.95-.382 4.116-.382 1.153 0 2.536.129 4.105.382.528.083.957.461 1.108.975l6.366 20.956 42.282 4.808zm-8.07-4.411l2.992-20.948c-8.439-1.536-20.78-3.394-35.897-4.554-13.647 4.707-25.077 6.108-25.766 6.155l-.797.057c4.353.374 8.454 1.544 8.66 1.605.452.135.804.481.944.933l6.186 20.366 33.138 3.764zm2.303 11.845l-1.404.983-3.779 2.651-4.095 2.868c-.279.192-.621.28-.954.243l-40.746-4.633-2.966-.337a1.39 1.39 0 0 1-1.171-.977l-6.377-20.998c-1.066-.145-2.014-.219-2.81-.219-.809 0-1.751.073-2.817.219l-6.192 20.392a1.383 1.383 0 0 1-1.025.946l-43.435 9.672c-.103.02-.206.03-.305.03-.279 0-.559-.083-.798-.253l-1.578-1.098-4.726-3.307v-.011l-1.91-1.335c.135.43.289.85.441 1.268l-.006.006c.368 1.092 4.028 11.622 11.467 21.929a873.96 873.96 0 0 0 17.057-5.234c4.488-6.917 10.877-13.777 15.418-14.014a51.12 51.12 0 0 1 2.402-.061c2.221 0 4.344.16 6.31.393-1.671-1.517-2.013-3.298-2.256-4.085 0 0 5.793 4.53 13.17 3.584 7.378-.953 11.959-5.204 11.959-5.204s-.021 3.102-3.236 5.503c6.182-1.869 8.739-2.511 10.489-2.511 1.931 0 2.883.808 3.717 1.519.161.129.322.268.507.419a3519.302 3519.302 0 0 1 12.141 9.614c1.936.227 8.075.926 11.943 1.283 2.23.201 4.245.372 6.217.372.637 0 1.233-.026 1.797-.063 2.558-4.88 4.857-10.411 6.808-16.653.261-.96.516-1.928.743-2.901zm-15.034-51.593c-.01-.006-.02-.012-.031-.012a551.624 551.624 0 0 0-9.826-.651 905.6 905.6 0 0 0-13.667-.668 72.95 72.95 0 0 1-1.574 2.225c-2.479 3.355-7.398 9.51-13.704 14.729 8.926-1.6 24.409-5.56 37.803-14.905.336-.238.668-.486.999-.718zm-29.876.926c.377-.471.729-.926 1.044-1.34-3.281.331-6.512.808-9.67 1.408-10.814 2.024-20.801 5.389-29.11 8.837a383.259 383.259 0 0 1 18.54-.455c3.908 0 7.708.067 11.404.176 3.179-3.056 5.861-6.182 7.792-8.626zm3.587 102.085c-4.503-.332-8.598-2.205-11.903-5.477a271.86 271.86 0 0 0-.502-.512 44.25 44.25 0 0 1-4.881.704c-.698.026-1.361.087-2.091.087l-1.083.011-.413-.011c-4.396 6.539-14.159 7.813-14.605 7.87-.403.046-.734.103-1.191.186 5.442 1.491 10.996 2.138 16.474 1.77 5.492-.367 12.627-1.558 20.195-4.628zm-17.4-7.461a45.604 45.604 0 0 0 3.184-.378 138.958 138.958 0 0 1-3.568-3.857 398.441 398.441 0 0 1-1.92 4.339h.243c.658.001 1.378-.071 2.061-.104zm-3.354-78.632c1.827-1.103 3.582-2.366 5.249-3.712a422.33 422.33 0 0 0-7.278-.072c-10.137 0-19.606.415-28.189 1.061-8.61 4.209-13.875 7.672-13.998 7.76l-8.268 5.514 5.679-8.149a52.452 52.452 0 0 1 2.956-3.857c-9.536 1.066-17.477 2.329-23.41 3.422l3.038 24.632 10.453 7.321 33.184-7.378 6.212-20.464c.104-.337.331-.621.627-.793.098-.063.202-.109.315-.14.192-.052 3.51-.999 7.336-1.465zm3.816-18.788c-2.31-.036-4.623-.057-6.933-.062h-.005c-3.39.005-6.787.041-10.189.109l-6.269 2.971c-.005.005-.041.021-.088.048-.942.46-9.174 4.613-16.919 12.021 6.943-3.65 17.146-8.418 29.153-12.115a144.186 144.186 0 0 1 11.25-2.972zM70.251 98.761c3.251-3.225 6.605-5.886 9.567-7.967-11.415 2.651-21.923 6.543-31.128 10.778a360.846 360.846 0 0 1 21.561-2.811zm2.159-9.949a150.122 150.122 0 0 1 11.813-2.796c-5.798.212-11.6.481-17.393.808-3.366.186-6.715.414-10.065.667-1.678.129-3.345.263-5.007.445-.476.046-.942.098-1.418.16-4.369 2.614-21.127 13.134-32.631 26.889 11.179-7.769 30.654-19.443 54.701-26.173zm-30.85 54.197a68.861 68.861 0 0 1-.621-2.102l-5.162-3.612a1.391 1.391 0 0 1-.586-.969l-2.516-20.449c-1.864.999-4.017 2.225-4.592 2.707-.497.409-.875 1.46-.336 6.332.668 6.01 3.712 17.333 5.348 18.979 1.968.347 6.327-.258 8.465-.886zm-3.815-51.36a229.005 229.005 0 0 0-7.051 1.47l.829 3.127a103.93 103.93 0 0 1 6.222-4.597z" id="jkpath95" fill="#2d3136"/></g></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tune" xmlns="http://www.w3.org/2000/svg"><path d="M6.85 2.852h-2v6h2v-6m12 0h-2v10h2v-10m-16 10h2v8h2v-8h2v-2h-6v2m12-6h-2v-4h-2v4h-2v2h6v-2m-4 14h2v-10h-2v10m4-6v2h2v4h2v-4h2v-2h-6z" fill="#fbc02d" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 50 50" id="twig" xmlns="http://www.w3.org/2000/svg"><path d="M9.727 47.556c-.125-.223-.297-2.168-.183-2.087.034.025.171.267.304.537.132.27.282.487.332.482.123-.011.075-1.196-.1-2.454-.331-2.398-1.176-4.435-2.358-5.69-.2-.212-.344-.4-.319-.419.093-.067 1.327.843 1.842 1.359.293.293.735.825.981 1.181.328.474.465.618.51.534.078-.147-.21-9.903-.376-12.701-.074-1.255.063-1.023.61 1.035 1.064 4.006 1.858 7.922 2.342 11.55.086.637.173 1.172.195 1.19.022.016.092.001.157-.034.888-.483 1.524-.667 2.55-.736.727-.048.945.062.35.178-1.15.222-1.99 1.013-2.344 2.201-.315 1.061-.327 2.707-.024 3.434.152.366.037.426-1.067.56-.716.088-.977.096-1.202.037-.356-.092-1.118-.098-1.195-.008-.031.036-.243.066-.47.066-.38 0-.423-.017-.535-.215zm1.974-3.233c.152-.205.072-.41-.204-.522-.225-.09-.263-.088-.437.025-.21.137-.252.43-.08.554.18.13.607.096.72-.057zm1.248.086a.763.763 0 0 0 .214-.203c.241-.33-.352-.622-.745-.366-.406.265.08.785.531.569zm2.288 3.094c-.033-.039.117-.387.334-.775.216-.387.411-.665.433-.618.07.152-.201 1.28-.33 1.372-.15.108-.354.117-.437.02zM8.2 47.092c-.29-.343-.221-.434.14-.182.176.123.321.263.321.31 0 .165-.279.087-.46-.128zm8.649-.145c0-.053.102-.18.227-.282.25-.204.312-.113.143.207-.095.18-.37.236-.37.075zm8.065-.827c-.243-.025-.48-.088-.527-.141-.11-.125-.114-3.043-.004-3.043.045 0 .132.149.193.331.127.38.228.42.31.124.094-.337.065-3.472-.039-4.297-.449-3.55-1.865-6.124-4.342-7.89-1.086-.774-2.653-1.436-4.047-1.711-.764-.15-.522-.224.598-.182 2.364.089 4.167.706 5.847 2.001a11.046 11.046 0 0 1 2.32 2.502c.453.682.64.854.64.584 0-.07.063-.882.139-1.805.679-8.26 2.396-15.1 4.984-19.86 1.86-3.422 5.108-6.817 7.885-8.244 1.397-.718 2.539-.988 4.02-.952.933.023 1.01.036 1.77.307a6.822 6.822 0 0 1 1.363.662c.612.407 1.309 1.004 1.235 1.058-.026.018-.343-.165-.705-.407-2.657-1.771-5.062-1.52-7.12.742-1.108 1.22-2.651 3.53-3.634 5.443-2.828 5.503-4.541 11.464-5.291 18.413-.163 1.509-.282 3.76-.195 3.703.032-.022.266-.52.518-1.108 1.597-3.723 3.578-6.428 5.79-7.908.672-.449 1.612-.904 1.715-.83.022.016-.172.22-.432.454-1.957 1.754-3.248 3.76-4.232 6.572-.938 2.68-1.366 5.588-1.368 9.3-.002 1.741.188 4.385.366 5.101.125.505.08.546-.585.546-.55 0-2.306.138-3.416.27-.414.05-.817.04-1.609-.036-.58-.056-1.129-.119-1.218-.14-.165-.037-.18-.014-.2.302-.01.186-.098.203-.728.139zm2.507-6.725c.294-.11.375-.22.375-.517 0-.63-1.309-.706-1.524-.088-.074.211.13.51.42.616.297.108.413.106.73-.011zm2.369-.052c.277-.222.318-.364.174-.611-.4-.691-1.755-.307-1.428.404.121.266.299.35.738.354.227 0 .387-.045.516-.147zm3.011 6.681c-.027-.05.088-.268.256-.484.879-1.135 1.22-1.544 1.284-1.544.04 0 .056.037.036.082l-.423.964c-.212.485-.445.924-.519.977-.169.122-.57.125-.634.005zm2.446-.596c0-.121.853-.683.896-.59.018.04-.056.209-.166.376-.168.259-.238.305-.464.305-.164 0-.266-.035-.266-.091zm-13.04-.124c-.177-.159-.493-.656-.462-.725.018-.038.248.1.512.309.264.207.457.405.428.438-.075.088-.371.074-.478-.022z" fill="#9bb92f" stroke-width=".078"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript" xmlns="http://www.w3.org/2000/svg"><path d="M49 51h408v408H49V51zm246.669 314.879l19.463-1.702c.922 7.8 3.067 14.199 6.435 19.198 3.368 4.998 8.597 9.04 15.688 12.124 7.09 3.085 15.067 4.627 23.93 4.627 7.87 0 14.819-1.17 20.845-3.51 6.027-2.34 10.512-5.548 13.455-9.625 2.942-4.077 4.413-8.526 4.413-13.348 0-4.892-1.418-9.164-4.254-12.816-2.836-3.651-7.516-6.718-14.039-9.2-4.183-1.63-13.436-4.165-27.759-7.604s-24.355-6.683-30.099-9.732c-7.445-3.899-12.993-8.739-16.644-14.517-3.652-5.779-5.478-12.249-5.478-19.41 0-7.871 2.234-15.227 6.701-22.069 4.467-6.842 10.99-12.036 19.569-15.581 8.58-3.546 18.116-5.318 28.61-5.318 11.557 0 21.75 1.861 30.577 5.584 8.828 3.722 15.617 9.199 20.368 16.432 4.75 7.232 7.303 15.421 7.657 24.568l-19.782 1.489c-1.064-9.856-4.662-17.301-10.795-22.335-6.133-5.034-15.191-7.551-27.174-7.551-12.479 0-21.573 2.286-27.281 6.86-5.707 4.573-8.561 10.086-8.561 16.538 0 5.602 2.021 10.21 6.062 13.826 3.971 3.617 14.34 7.321 31.109 11.115 16.769 3.793 28.273 7.108 34.513 9.944 9.076 4.183 15.776 9.483 20.101 15.9 4.325 6.417 6.488 13.809 6.488 22.175 0 8.296-2.375 16.113-7.126 23.452-4.751 7.338-11.575 13.046-20.474 17.123-8.898 4.077-18.913 6.116-30.045 6.116-14.11 0-25.933-2.056-35.47-6.169-9.537-4.112-17.017-10.299-22.441-18.559-5.424-8.26-8.278-17.602-8.562-28.025zm-65.728 50.094V278.454h51.583v-18.399H157.938v18.399h51.37v137.519h20.633z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript-def" xmlns="http://www.w3.org/2000/svg"><path d="M457 459H49V51h408v408zM69 71v368h368V71H69z" fill="#0288d1"/><text x="342.219" y="344.544" font-family="ArialMT" font-size="12" fill="#0288d1" transform="translate(-6058.94 -5838) scale(18.1514)"><tspan style="-inkscape-font-specification:sans-serif" font-family="sans-serif" font-weight="400">TS</tspan></text></symbol><symbol viewBox="0 0 24 24" id="url" xmlns="http://www.w3.org/2000/svg"><path d="M16 6h-3v1.9h3a4.1 4.1 0 0 1 4.1 4.1 4.1 4.1 0 0 1-4.1 4.1h-3V18h3a6 6 0 0 0 6-6c0-3.32-2.69-6-6-6M3.9 12A4.1 4.1 0 0 1 8 7.9h3V6H8a6 6 0 0 0-6 6 6 6 0 0 0 6 6h3v-1.9H8c-2.26 0-4.1-1.84-4.1-4.1M8 13h8v-2H8v2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="verilog" xmlns="http://www.w3.org/2000/svg"><path d="M17.282 17.08H6.718V6.513h10.564m4.226 4.226V8.627h-2.113V6.514c0-1.173-.95-2.113-2.113-2.113H15.17V2.288h-2.113v2.113h-2.112V2.288H8.83v2.113H6.718c-1.173 0-2.113.94-2.113 2.113v2.113H2.492v2.113h2.113v2.113H2.492v2.113h2.113v2.113a2.113 2.113 0 0 0 2.113 2.113H8.83v2.113h2.113v-2.113h2.112v2.113h2.113v-2.113h2.113a2.113 2.113 0 0 0 2.113-2.113v-2.113h2.113v-2.113h-2.113V10.74m-6.339 2.113h-2.112V10.74h2.112m2.113-2.113H8.831v6.34h6.338z" fill="#ff7043" stroke-width="1.056"/></symbol><symbol viewBox="0 0 24 23.999999" id="vfl" xmlns="http://www.w3.org/2000/svg"><defs><style>.jra{fill:#f05223}.jrb{fill:url(#jra)}</style><radialGradient id="jra" cx="205.45" cy="208.29" r="225.35" gradientTransform="matrix(.04556 0 0 .0456 2.888 2.88)" gradientUnits="userSpaceOnUse"><stop stop-color="#ffd104" offset="0"/><stop stop-color="#faa60e" offset=".35"/><stop stop-color="#f05023" offset="1"/></radialGradient></defs><title>houdinibadge</title><g stroke-width=".046"><path class="jra" d="M19.97 3H4.03A1.03 1.031 0 0 0 3 4.031v4.135C4.548 6.977 6.563 6.21 8.948 6.21c5.107.003 8.35 3.574 8.348 8.081 0 3.13-1.46 5.485-3.746 6.71h6.42A1.03 1.031 0 0 0 21 19.968V4.031a1.03 1.031 0 0 0-1.03-1.03z" fill="#f4511e"/><path class="jrb" d="M3 17.722v2.247A1.03 1.031 0 0 0 4.03 21h1.837C4.474 20.21 3.49 19 3 17.722z" fill="url(#jra)"/><path class="jra" d="M8.948 8.231c-2.586-.09-4.598.86-5.948 2.264v3.163c.918-2.654 3.447-3.87 5.565-3.85 2.647.027 4.689 2.025 4.7 4.284.012 2.159-.892 3.748-3.33 4.14-1.33.213-3.411-.567-3.318-2.578.046-1.037.854-1.622 1.777-1.58-.905 1.213.293 2.102 1.139 1.921 1.048-.224 1.475-1.156 1.475-1.878 0-.762-.718-1.994-2.498-1.951-2.204.052-3.591 1.639-3.638 3.602-.056 2.468 2.253 4.091 4.622 4.121 3.48.046 5.543-2.24 5.539-5.586-.005-3.029-2.434-5.946-6.085-6.072z" fill="#f05223"/></g></symbol><symbol viewBox="0 0 24 24" id="virtual" xmlns="http://www.w3.org/2000/svg"><path d="M21 14H3V4h18m0-2H3c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h7l-2 3v1h8v-1l-2-3h7a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 281.25 281.25" id="visualstudio" xmlns="http://www.w3.org/2000/svg"><path d="M196.18 101.74l-52.778 42.444 52.778 40.889V101.74m-136.67 110l-30-18.889v-100L62.843 81.74l47.778 37 96.666-89.222 44.444 27.778v172.22l-55.555 22.222-85.111-81.555-51.555 41.555m3.333-48.889l20.667-19.111-20.667-19.778z" fill="#ab47bc" stroke-width="11.111"/></symbol><symbol viewBox="0 0 300 300" id="vscode" xmlns="http://www.w3.org/2000/svg"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0}.icon-white{fill:#fff}</style></defs><title>BrandVisualStudioCode</title><path d="M218.62 29.953l-105.41 96.92L54.301 82.47 29.955 96.64l58.068 53.359-58.068 53.359 24.346 14.212 58.909-44.402 105.41 96.878 51.424-24.976V54.93zm0 63.744v112.6l-74.719-56.302z" fill="#2196f3" stroke-width="17.15"/></symbol><symbol viewBox="0 0 24 24" id="vue" xmlns="http://www.w3.org/2000/svg"><path d="M1.821 4.15l10.21 17.618L22.24 4.235V4.15h-7.692L12.113 8.33 9.691 4.15H1.82z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179H5.937z" fill="#35495e"/></symbol><symbol viewBox="0 0 420 419" id="watchman" xmlns="http://www.w3.org/2000/svg"><g stroke="#fff" stroke-linecap="round" stroke-linejoin="bevel"><path d="M166.95 145.32a93.935 123.23 0 0 1 92.934 3.263" fill="none" stroke-width="18.467"/><path d="M162.92 137.96L44.63 256.25a174.07 173.93 0 0 0 5.705 16.486l123.68-123.68-11.096-11.096zM266.54 144.04l-11.096 11.096 117.16 117.16a174.07 173.93 0 0 0 5.691-16.5l-111.76-111.76zm170.65 170.65v22.193l17.1 17.1 11.096-11.098-28.195-28.195z" fill="#fff" stroke-width="1.963"/><path d="M167.52 273.36a93.935 123.23 0 0 1 92.934-3.263" fill="none" stroke-width="18.467"/><path d="M49.516 144.56a174.07 173.93 0 0 0-.809 2.213 174.07 173.93 0 0 0-4.757 14.344 174.07 173.93 0 0 0-.016.055l119.56 119.56 11.098-11.096-125.07-125.07zM454.87 64.703l-17.668 17.668v22.191l28.764-28.764-11.096-11.096zm-80.984 80.984l-117.86 117.86 11.098 11.096 112.18-112.18a174.07 173.93 0 0 0-5.416-16.777z" fill="#fff" stroke-width="1.963"/></g><image x="21.229" y="20.262" width="378" height="377.1" preserveAspectRatio="none" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAaQAAAGjCAYAAABjSWGNAAAgAElEQVR4AeydB3hUVdrH/+fOpNMF JAFUmivFXtZuIBRRQUUTil1RV3et6Lr6rSu6rg3B1dXVtXeBCCioCASIimLDhkFsgAIJSAkhPZmZ 8z3vjReGMJlMueeWmfc88MzMLaf8zp3855zznvcV4MQEkoxA7sVr01PrUrv4pdZNBNA1AHQRkJ0g ZAcJrYOA7Aipv8/Q6JgUKdBkG0iZISDSDVwSaAfAY3z+/bVSAD56L+mfwA5INAqgSkrUCKA2IOQO IcQOIUU5pNwhNZQLia2Q2OyH3OJBxqaiwk4VzfLlj0wg4QmIhG8hNzDJCEgxJH9DjgfevgGBAyCx vxSyh5DoAWA/CHQH0MEFUOoBbJbAeiHEBiED6wMSv0DgF02In1J2Vq+ZP78fXcOJCSQMARakhOnK 5GrICaO3tE3P8B0kERgkgP6Qop8E+gqgL4Bdo5gEphKAwHpIrIHAjxL4DpCrAo1yVfHsHhsSuN3c tAQmwIKUwJ2bGE2TYsjY33qLQOBwCHk4JA6HhgE08kmM9ilpRQUgvwPE1xD4MiC0r2R67dfFz/eq U1IaZ8oETCLAgmQSSM7GHAIj8td38sN7HAROkpDHATgMTWs15hSQvLn4JLBaQHwqEPgQQiwvmpHz XfLi4JY7kQALkhN7JYnqlDd2w4GQnuMk5AkCOBHAQQD4ubTgGZDAFg1ieQCBj6Dhg7SKmhW8LmUB eC6iRQL8xW8RDZ8wn4AUQ8eVHib9YjCELj4nAOhqfjmcY4wE6iGxAkIsCwDvSel5v7iwa1WMefFt TCBqAixIUSPjG6IhkDtmQw/N4xkKTQ4TEkNZgKKhZ/u1DZD4CAKLIMSik/p3WzF5sgjYXiuuQMIS YEFK2K61p2GTJ0tt2Xebj5EyMArAab+vAdlTGS7VVAI0xSeAd6QQb2uBtIW8V8pUvJwZz9XzM2AG AdpoKqpTh3uEdlYA8gwBdDEjX87D0QQaALlMCLzha5Rz2NTc0X3lmsrxCMk1XeWsig6/YFOWr0Ge JoBzICWNhNo6q4ZcGwsJkFOKT4TUZiOgzSqate8aC8vmohKIAAtSAnWm6qaMHPljWl2bzBGapk2A lKMBZKguk/N3JYHPpMCrHuGbuWj6fqWubAFX2hYCLEi2YHdToVIMKSg7SQAXABgDoJObas91tZWA H5DvCeCV2rrUwg/ndqm0tTZcuOMJsCA5vovsqeDwszd19af4LwbEZQAOtKcWXGoCEaiCwAwB7emi Gd0+TqB2cVNMJMCCZCJMt2dFFnIflJQNh8BEgKbkRIrb28T1dySBlULIpzyBwCsLCntud2QNuVK2 EGBBsgW7swqlvUIer+cSQE7UPWI7q3pcm8QlUAtgtibFU4sKu70PCJm4TeWWRUKABSkSSgl5DTkt 3XSqJuVfJDAiRFyfhGw1N8qxBH4AxJP+hrpnit/otcOxteSKKSXAgqQUr/My18216wMXCOB6AH9w Xg25RklOoEoI8bzw+R5ZNKvnj0nOIumaz4KUJF0+YsyWbL/Xdx0gr5BAxyRpNjfTvQQCkPIdDdqD iwqz33NvM7jm0RBgQYqGlguvbfKmrd2MJrPtNBc2gauc5ASElJ8EIO4/eWD2m+xLL7EfBhakBO3f wfmlR2oCtwI4G4CWoM3kZiURASmwGpAPVLTPeXnFk6IxiZqeNE1lQUqwrs4bW3Y0AoF/QIjT2Vdh gnUuN8cgsFYK3LNPoPzFwsKBDcZBfnU/ARYk9/eh3oIh+WXHaELeIZs8bCdIq7gZTCAsgXUQuK9T oPw5FqawnFxzkgXJNV0VuqJD8zcdDCHvlvpG1tDX8FEmkOAE1gohJp/Yv9vLvMbk7p5mQXJp/w0Z u7mPkP7JACbwGpFLO5GrbS4BiRII/H3xzOw3eZOtuWityo0FySrSJpWTO760s8cv7wQEeVVINSlb zoYJJBAB+bGQ4uaiwpxlCdSopGgKC5JLuvm4/PUZmcJ7NSD/DqCDS6rN1WQCdhKY45f+vxYX9vzJ zkpw2ZETYEGKnJVNV1L4h9JxAuI+9jNnUxdwsW4mQFZ4//E31N/NLomc340sSA7uo2H5Gw7zC+3f AjjFwdXkqjEBNxDYKgVuPbl/9rNs+ODc7mJBcmDf5J61toMnNfVfgLgCgNeBVeQqMQF3EpD4XGja NRyTyZndx4LkqH6RYkj+pouEkFMAdHZU1bgyTCBxCEgIPFvvabx52av7lydOs9zfEhYkh/Th0HM2 95Ye//8ADHVIlbgaTCDRCWwWENcWzcyemegNdUv7WJBs7qncXOnVum66XoBMuZFpc3W4eCaQdAQE xFyfz//n4tk9NiRd4x3WYBYkGzuEjBak0J6WwJE2VoOLZgJMANgJIW49qX+3J9jowb7HgQXJBva0 pyhLeO+QkJPYaMGGDuAimUDLBJYJgSuKZuR81/IlfEYVARYkVWRbyHfouLJcBPCUhOzbwiV8mAkw AXsJ1EOKe3Z07HYvh7mwtiNYkCzinXvx2nRvTdq9EriOw0JYBJ2LYQLxEfgCUlywuDB7VXzZ8N2R EmBBipRUHNcNGVt6hJB4CcCAOLLhW5kAE7CeQB1tqF0yI/thdtiqHj4LkkLG+fnSUy7KbpWQ/wBE isKiOGsmwASUEpBLPBouXji9+3qlxSR55ixIih6A3DEbemhe7WV2+6MIMGfLBKwnsA3AxMUzc96w vujkKJEFSUE/D87fOFoT4lkA+yjInrNkAkzAVgLyv/7MhknFz/eqs7UaCVg4C5KJnTpy5I9p9W3b TBGQf2HDBRPBclZMwHkEVnr8KFg4K2e186rm3hqxIJnUd0PGlO4vPHgdAkeZlCVnwwSYgLMJVEHI yxfP6D7d2dV0T+1YkEzoq6H5ZadLIV/gKToTYHIWTMBlBIQQj6bsrLpp/vx+9S6ruuOqy4IUR5fo VnRa2Z1S4jaeoosDJN/KBNxP4DMhcW5RYc6v7m+KfS1gQYqRvR6zKCXtVQiMjDELvo0JMIEEIiCB LR4p8hcVZr+XQM2ytCmapaUlSGF5+WUDvKnpn7EYJUiHcjOYgAkEBNAlIAKLho7deI0J2SVlFjxC irLbh+SXni0EXgTQJspb+XImwASShICAeC6lsuoqXleKrsNZkKLglVdQeiuAf/F6URTQ+FImkLwE lnkatXMWzun2W/IiiK7lLEgR8KL9RQ1tsyia60URXM6XMAEmwAQMAmsDHjFq6WvZJcYBfm2ZAAtS y2z0M7njSzt7/JgD4MRWLuXTTIAJMIFQBCqkkAVLZnRfGOokH9tNgI0adrPY611u/vq+Xr9YzmK0 Fxo+wASYQOQE2gsp3h4ytnRi5Lck55UsSC30+5D8smM8wvMhB9JrARAfZgJMIBoCXiHxVN7YjXcC kmemWiDHYEKAyRu76QzIALkDyQpxmg8xASbABGImQBZ4vt+6XVFcLHwxZ5KgN7IgNevYvPyNl0EI MmDwNDvFH5kAE2AC5hCQmJ9Zh3PnzcupMSfDxMiFp+yC+nFIQdlNEOIpFqMgKPyWCTAB8wkIjKxJ xyLy+GJ+5u7NkUdIet9JkVdQRvuLaJ8RJybABJiAVQS+9kvvqcWFXTdZVaCTy0l6QZo8WWrLVpU9 JoE/ObmjuG5MgAkkJgEB8ZNPw7Di6dnrErOFkbcqqQWJvHVvF2UU2fXCyJHxlUyACTAB0wn86pf+ vOLCnj+ZnrOLMkzaNaTcXOndLja9zGLkoqeVq8oEEpfAfh7hfW/4OaUHJW4TW29ZUo6Q8vNLUreh 42tCYEzriPgKJsAEmIBlBDZDiiGLC7NXWVaigwpKOkEiMSoXnQol5GgH9QNXhQkwASagE6C4SprU 8ooKu61MNiRJNWVH03Q0MmIxSrbHnNvLBNxDgOIqSREoGjq2tL97am1OTZNmhEQGDNtE6asCosAc dJwLE2ACTEAlAVkGIXMXz+jxg8pSnJR3UoyQfreme57FyEmPHteFCTCB8ARENqS2mJw8h78ucc4m gSBJUS7KHgVwfuJ0G7eECTCBJCHQwyM8i3LHbOiRDO1NeEEaMrbsHt70mgyPMreRCSQsgQM8Hu3d kfllXRK2hb83LKEFaUjBxluExN8SvRO5fUyACSQ4AYGBjQjMG5q/vX0itzRhBSlvbOmVAuLeRO48 bhsTYALJQ0AK8UcpamfT1pVEbXVCCtLg/I2jIfEYgKSxIkzUB5TbxQSYQDABMWS76Phsogb5SzhB Gjxu0x81IV7jEBLBDzG/ZwJMIIEInJc3tuyhBGrPrqYklCCReaQIBOYByNzVQn7DBJgAE0g0AhLX Dc0vTbj18YSZ0iILlEaBjyRk0tjsJ9p3jNvDBJhAVAQkpBy/uLD7jKjucvDFCTFCokW+Bsg3WIwc /KRx1ZgAEzCbgIAQz9IyhdkZ25VfAgiSFPoin8DxdkHkcpkAE2ACNhHI1AKBuYmycdb1gjS0oOz/ AJxn08PAxTIBJsAE7CbQ1ePV3kmEPUquFqS8/LJzJXCX3U8Dl88EmAATsJnAwVLUv+R2c3CPzRBj Ln5o/qaDIeRbABJ2k1jMcPhGJsAEkpHAH3oPrNTWlkxb6tbGu9LKLu/sDfsgRfsEQB+3gud6MwEm wAQUEJBC4NyiGTmzFeStPEvXTdlRKAmkaLTxlcVI+ePBBTABJuAyAkJKvKDPILms4lRd1wlSudj0 LwDDXMiaq8wEmAATsIJAG6kFZrnRyMFVgkQ+6iTkX63oUS6DCTABJuBaAhL9Aqhznc871wjS0HM2 99aEeIEdprr2K8IVZwJMwEICQmBMXsGmGy0sMu6iXGHUkHvx2nRPddpHEDg87hZzBkyACTCBpCEg G4UUQ4oKc5a5ocmuGCF5a9IeYjFyw+PEdWQCTMBZBESKFGKGbpnsrIqFrI3jBWno2NIxHII8ZN/x QSbABJhABARkDlI8z7lh06yjBWnImNL9IfF0BMT5EibABJgAE2iRgByVl192bYunHXLCsWtItN9o O8reBztNdcijwtVgAkzA5QTqhSaPK5re/UuntsOxI6RyUXo7i5FTHxuuFxNgAi4kkBaQ4tVRo0od G8DUkYI0JL/sGAlBXrw5MQEmwASYgEkEhMRBtZnifpOyMz0bx03ZDb9gU5a/PvAFgANNby1nyASY ABNgAlIKeeqSGd0XOg2F40ZI/nr/AyxGTntMuD5MgAkkEAEhpHh2RP76Tk5rk6MEacjYjcMBcZXT IHF9mAATYAIJRqC7X3gedVqbHDNld8LoLW3T0xu/BbCf0yBxfZgAE2ACiUhASoxZUpgzxyltc8wI KT29cQqLkVMeC64HE2ACyUBACPnYiRN+6eiUtjpCkIbll50C4AqnQOF6MAEmwASSg4DITvN7pzml rbZP2ZFNfE0GvuGAe055JLgeTIAJJB0BiZGLC3Petbvdto+QajPEP1iM7H4MuHwmwASSmoDA407Y MGurIOWNX3+IRMBV8TqS+qHlxjMBJpCoBA6oyRB32N042wRp8mSpwe99AhApdkPg8pkAE2ACTEDe OHjshkPt5GCbIC0r2XQlII+zs/FcNhNgAkyACewi4NWkeEIfLOw6ZO0bWwRpZH5ZFynkPdY2lUtj AkyACTCB8ATEse9/V3Zp+GvUnbVFkBqaxKiDumZxzkyACTABJhATAYl7cs9aa8vfZ8sFadi40qMA 2KbAMXUQ38QEmAATSBICAuiipabfZUdzLRYkKQIB+R8AFpdrB1oukwkwASbgTgIC8uph+RsOs7r2 lgrDkPxNFwHiWKsbyeUxASbABJhAVAQ8AWgPRXWHCRdb5qnhd48MPwDobkK9OQsmwASYABNQTEAI eVbRjO5vKi5mV/aWjZBq0sVNLEa7uPMbJsAEmIDjCUgpHjjyCmnZXlFLBGnEmC3ZEPJmx9PnCjIB JsAEmEAwgQM7lJddHXxA5XtLBMmX0vhPAG1UNoTzZgJMgAkwAQUEBG63KkSFckEafk7pQZC4SAEm zpIJMAEmwATUE9gn1Z/yV/XFWGB+7feARkdeKxrDZTABJsAEmID5BITENXnjN+9rfs575qh0hPT7 Jthz9iySPzEBJsAEmIDLCGSJQODvquusVJACAZC/OstMy1XD4vyZABNgAslKQEp5xfD8Tb1Utl+Z IA0pKD0ZwDCVlee8mQATYAJMwDICqX7NTwFVlSVlgiQgbQ/2pIwaZ8wEmAATSEYCUpw/ZOzmPqqa rkSQ8saVngSIIaoqzfkyASbABJiALQS8Aj5la0lKBAkBOdkWVFwoE2ACTIAJqCUgxflDz9ncW0Uh pgvS0PzSE3l0pKKrOE8mwASYgCMIeOFRY3FnuiBB4FZHIONKMAEmwASYgBICEvK84eM29jQ7c1MF KW/8+kMkMNLsSnJ+TIAJMAEm4CgCqb6AuMHsGpkqSPB7yL0E7zsyu5c4PybABJiAwwgI4PIR+es7 mVkt0wQpd1zZAQDGmlk5zosJMAEmwAQcS6CNT3j/bGbtTBMkLYAb2WedmV3DeTEBJsAEnE5AXntc /voMs2ppitPT3LPWdhCQl5hVKc6HCdhNwOsVaN9WQ1amQFaGQEaGhox0gdQUAaEBaali19x0Q6PU j1OdG30S/gDQ0CDh9wPVtQFUVUlUVgdQXRNATa20u2lcPhMwk0DnLOE5H8BTZmRqiiBpqekTAcnx jszoEc7DUgIkLL3396JvrxT03T8FPXK8yNnXgy77eJTUo7pG4tdSH9Zv9OGXDT78vK4Rq39uRFV1 QEl5nCkTUE1AAtcD8mlAxP1rK24DhNxc6fV0LfsZwH6qG875M4F4CXTqoOHQAWk4dGAqBv0hFQf0 NOU3WbzV0gXqu58asXJ1A75YWY/NW/xx58kZMAGrCAjg1KKZOQviLS/ub6O3a9mZksUo3n7g+xUR SEkROKR/Kv54eJr+v3t23I+8kpr27O4F/R9+StN0/MZNPnzxTQM++7oeK76pR31D3D8+ldSbM2UC REBKXAUgbkGKe4SUl1+6FAK53C1MwCkEaK3n2CPSMfiEdBx/VLpTqhVXPZZ9WocPP6vDxyvq9fWo uDLjm5mA+QQCwu/pVzRr3zXxZB2XIOXllw2AkCXxVIDvZQJmEKCR0PFHpWHw8Rk48ZjEEKGWuJAw LXq/Fp98WY/GRh45tcSJj1tLQEDcXzQz+2/xlBqfIBVsfBQQptqhx9MYvjf5CPQ5IAUjB2fgrFOz kq/xAN4qqsE7i2vww5rGpGw/N9pRBLZ2kuXdCwsHNsRaq5gFadSo0syaDGwE0CHWwvk+JhALAbKM yzsxA6OGZ6Jfr5RYski4e0iQ5i2qwdIPa1FXz6OmhOtglzRISHleUWH3V2OtbsyCNLRg46US4plY C+b7mEC0BPbt4sGoYZkYdybvMAjHbtbb1Zi7qAYby3zhLuNzTMB8AhIfLC7MoWjhMaWYBElKeeCM udULXp1TeQDtq+DEBFQSGHRQKkYPy8SQE03bEK6yuo7J+73ldZi7sBpfr4p5BsUxbeGKuIOApkE+ PLnTjQMOSv93LDWOWpBIjAB8T4XNW1iDp1/bCRalWNDzPa0ROO7IdH1a7pjD0lq7lM+HIfDNqgZ9 xFT8UW2Yq/gUE4iPAE2fP35fZyOTvwsh/mV8iPQ1FkG6F8AuSwoWpUhR83WREsg9PkMfER0yIDXS W/i6CAjQxtt5C6t1Cz3JExsREONLIiXQe78UPDlllxgZt3mFEFHt8I5KkKSU5E9lr4lpFiWDP7/G Q+CUY2lElIXDBrIQxcMxknsfeqoCbxfVRHIpX8MEwhJoQYzontOEEPPD3tzsZLSCdCqAkAWQKP3v 5Z1s4dMMMH9snQBNyZHFHE3RcbKOwKofGnTLPNrTxIkJxEIgjBhRdjOFEFGFJIpWkF4EcEFLFSdR eviZipZO83EmsAeBA3unYPTwTJw6OHOP4/zBWgLkmohMxskbBCcmECkB8gP59INdWru8vRBiZ2sX GecjFiQpJf3VqDZubOmVRaklMnzcINCxvaabbp9zuns3s1IYCWMdJjNDoLau6XNKCnaFojDa65ZX GimRVd53P/ImW7f0mV31pC0Y40a30Wc2WqnDJUKI51u5ZtfpaASJhl7Td90Z5g2LUhg4SXxKCGD8 WW1w6bi2jqZAISHWl/qwcZMf5OR0yzY/tu8IoKIygIqdgV1CFK4RXg/QsYMHnTtq6NTRg25dPdi/ hxf75XhBZuxOTrSPqfCtamzdHtV6tJObxHUzkUAUYkSlLhJCDI+0+GgE6U0AoyPNmEUpUlLJcR15 Vrj1Guc59aA/urRP59vVDbr7nTW/+pT7hyNh7pHtxYADU3BQ31TdiKNnjvO8kD8/sxKvzK6KSICT 4ynmVkYpRgawbCHEJuNDuNeIBElK2RHA9nAZhTrHohSKSnIdoz+8Z52a6Shfcx9/UY9PvqjDFysb 9BGQE3qka2cPjjg4DUcflgayNnRSuu/RHSj6gA0fnNQndtQlRjGiql4jhHg0kjpHKkgUnvzZSDJs fg2LUnMiyfP5vDFtcMlYZ0zP0R/UpR/V4qtvGxwfW4g8lx91SBpOOCYNp+Y6w+CDnLjSd/nnX3h9 KXm+wbtb2r6thosL2kayZrT7pt3vioUQg3d/bPldpIL0NtmUt5xN+DMsSuH5JNpZp3hYIKuxhe/V 6kHu3BqmgcSJggtS4D4nxHZ66fUqvPh6JU/jJdqXNkx7sjIFJo5vF6sYGTnvK4T4zfjQ0murgiSl bA9gR0sZRHqcRSlSUu69zuMBLhvXDgWj7bOeW7fBh/lLavQpJjJASKTUrq2GYSdl4KqL2tnerH8+ VI73PmYzcds7QnEFTBIjquWVQognW6tuJII0HkDM7sSDK8CiFEwjsd4POzkDt/zZPqMFGg3NXViD L1bWJxbYEK0ho4hDB6TqXi3sXG+a/U41XplTpVsehqgmH3I5ARPFiEgsFEKMaA1JJII0E0B+axlF ep5FKVJS7riOwoVfPLYtzjnNnlHRzHnVmLeoGmWbk9NEOWdfjy5M+WfYw5+e0keeqdB/DLjjieVa RkKA9tZdPiHuabrmRXUQQoT1nNCqIAUCcqYQ5gkS1ZBFqXk/ufOznaOiFwsrMXt+DaqqE2taLtYn gf6AnD0yyzYjkjcX1GD2/GqOwRRrBzrsvusuax/vmlGoFh0phPgi1AnjWKuCNOutquvHnJ71kHGD Wa8sSmaRtD4fWiuiX0/n2vCr/IXCSsycW+14Sznre6WpRK9XYMxpmbjiPHvWmZ54aSdef6tVhy52 4eFyIyCgSIyoZE0IEdbPPHnvDpu2+f5y7uqfGo/PO8nc4Gh/6JOCju09+OTLxJ/zDwvYZSfJ0uvZ aV0w4EBrvQ2QEP3ffeX63iF/cs7ORfSkBAJAyfeN+tpOba3EkYdYG0vqqEPT0KG9B6Wb/dhZyaPX iDrNQRepEqM/3bK14sWnMqesWnVnfILUa9Ckh0s3+Tuv/qkRLEoOenJsqArtKbpuIhldWpdmzK3C P6aU47Ov6sFCFDl38rNX8kMjXn2jGo0+icMHWSdM9GPzrFOzUF0r2S9e5F1m+5WqxOiav28lLyjp dahatGbV1F/DNTTsCGnI2M19hJR3UQbk14tFKRzKxD3X94AUfVOclc5Q58yvxgOPV2Dph3U8PRfH o0UjppXfNWDGvGpoAjjYQj96Rx+ahvbtPPh5nU93PhtHM/hWxQRUidH1d2zDqh9+30wtsGltydQl 4ZoSVpB6DbxxrADOMDIgUfplgw+nHMfTdwaTRH+lX7p33dwR/XqnWNLU5Svq8PgLO/HGuzU85WMi cRpdfvltAxYva9o71L+fNVOuB/VNAVkA/rYtgJ/WsZcHE7vUtKyuubQ9Ro8w3yMIiRH5iAxKaWtL pj4d9Hmvt2GNGobkl84SAmOa30V7H26/gdzbmZvY0MFcnvHmRhswrTTnfvQ5EiJnLoiTFdthA9NA 01H7dfcip5sHbTI1tMnSdMwNjVIfBZBn8A2lPtAG3a9K6rH2170CLMfbLabcT/uYRg/PwinHWec3 j4wdyOiBk3MIkBidaY0YUaP9XunvuqCwZ4t+UVsUpPx86dkuyrYCCLnbUZUokfnof54Na6runN5M 0JrQegMFzjvpj9b8saJ1ohdmVoH+qDsppaUKDD4hA8NPzsAhA2IbUWwvD+CDT+vwzmJn+oEbNSzT 0nXBjz6v04MB0pogJ3sJqBKjG+7YhpV7jox2N1SK/MWF2a/vPrDnuxYFKS9/w3EQ2kd7Xr7nJxal PXkkwieaXrnyAmtMht9bTt4VqvXwD05iRzvUzz29DS44t42p1fr863p9Ayn9UXZS0jToa4QTzja3 veHa+NyMptAW4a7hc+oIqBKj2+7djk/D/dgQeHLxjJwrW2pZi2tIvQbedJEQGNLSjXSc1pNUrCnR vDMthn7KJuHh8Jt+jqboLjjXGu/cT76yU18r2rzFOTbcNCKiP8r33baP7prHbMA53bz6iOsPfVJR XRvAxjJntJ0s8mh9icJy0B6zvr3UrxfSKJy+4zSlSdF3OVlH4M8Xt1MSDqZVMWpqYoe1JVMfaam1 LY6QhuaXLpACEUX645FSS3jdcZz2FNHUDXleUJ30Hf3vVDsmDhG1lzaTjh1tvZcDGimR/z0aOTkp 0ZoC/YK2Kt37nx1YvIzjLVnBm8SIPHqYnSIUI71YKf09lhT23BiqDiFHSLm50ivaVP0XQEQT5zxS CoXWHcdGDc/EnZM6os/+6n8VP/x0BV6aVYXKKmdsmKTRwLgz22DaHftYuk/HeDIoSuzQkzJ0I4kd lQE4ZbT4/c+NmPVONVJThCUboGmt0uMR+KpkD4ssAxO/mkTACWJETdEgVqxZNW1lqGaFFKRex151 tJDy6lA3tHSMRaklMs49ftn4tpg4Qf16EVnO0eZWChXuhETesmmt7N93ddajtNpdpwN6pmBEbia6 dfVie7kfW7fbL9iNjdBHbr+W+nGyBRFsDx63vWsAACAASURBVOmfqntuIevE6hqewjP7mVQlRpOn lmP5iihH+EJsXVsy9a1QbQwpSH37T5oAgWGhbgh3jEUpHB3nnOvYXsOV57eDFRtdyWKSgrrV1tn/ R4aEiPZVPfqvziAXN05LfQ5IwWl5mejU0YMt2wIor7BfmNat9+mjWlpfG/SHiCZMYsZKJvW0zWB9 qR9ULidzCKgSI4qJ9cEnMRnoZK0tmfpYqNaFXEPKKyibC8hRoW6I5BivKUVCyZ5rjjksDffc2kl5 4bQ2MnNuFTY5wGiBhIj+0N9wuXXrImYApvW2eQur9T1NZuQXbx4nHJ2OO28yf/9hqHqRN/cXX68K dYqPRUHgTxeocYIcb4DGVCm6zi/M3tK8KSFHSL0GTnpYADGvfPFIqTlmZ3ymxcxbrwm5rczUCj79 WiWeea0SVQ6YeqHQ3/+7vwsorLrbElmbjh6RpW++JWG321np+tKm0VJ6qoaBikdLhw5MA0XI/ea7 BvicYYzotscHThUjAtkoRPG6kqk/Noe6lyANz9/USwp5W/MLo/3MohQtMbXXTxzfFpeOU2vSvWRZ LR5+Zifo1e405MQMkH8uFRZFVreN3PzQVGN6uoYNZfavsaxYWY9fNvpwyrFqrTIP6puqm+GvXN2I Tb+xKkXz3CkTo3+bE7peCPnz2pJpxc3btJcgHXDwDacC4tzmF8bymUUpFmrm3pOWJnD1Re2Vxy4i bwsPP70Tv2219w8HucK59rL2utFC1857Pd7mwrU4NxqV0BoLWb+tXe9DXb1963L03SaHrRlpAqr9 4tEot6IyALL+49Q6AVViNOVxMs+Pac0oRKVFw9qSqS81P7HXN7b3oJsmAji2+YWxfmZRipVc/PfR lM+F57bF6UPNd5wYXLsHn6jQg+YFH7P6PcVposXb8We1Qbcuez3WVldHaXmDDkpFwag2ujB9v6YR ZBFnRyKHrZ99XY/ynQEce4TaKdE/Hp6u7xejDbycWiYwcUJbFIw23+MGidGCYlNnPjpflP/gA8XF e8ZH2suoIa9g43JAmCZIBjo2dDBIWPOad2KG8vUicoY7Y16VrdMpZC1HfvdIkJI1vTK7Cq+9UWXr iClnXw/yz2ijIuz1Ht1Khh5Pv7rTEVabe1TMAR9IjGhfndlJgRjpVfT40X/hrJzVwfXd46ckOVSt FVX/BmD6LkkeKQVjV/ueHkrVgfTojyB5bq6qtmfaiJydXj6haR8VbTBN5kR7eMjlEfXEdz82gmIg WZ0qq6Ue/ZnqQF7RVSUa9Xfr6sGOioDt08Oq2hhLvm4TI72NQi5vvkF2D0HKOfjKgwBcHwuQSO5h UYqEUnzXkPHChflqjRfuf2yHvpM/vprGdnf/vim4dFw7XH1RO9CGUjsTTR9R8Luff/FhQ5kfASn1 zZ121YmE4PwxbdDogx5M0w5h+mZVA35c68OQE9QZPPTaLwWnDs7E9h0BikRqF27HlKtKjMizyjtL TJ2m24OZEFi7pmTaouCDe0zZ5Y0tPQ8SLwdfoOL9iNwM3HyV+ebHyR66QtUGOOMZoCm619+2xw9d v14p+nTQaUPUrocZbW3p1fDYTYEEySlp89Q2S9MNSM4bY/7USfOyWvtMDmxnvV1tS+j3lBShj2DH nBbz7pHWmqeff3lWFZ6fWRnRtYl4kSoxeuSZCt3PokpmUorFSwqzhwaXsccIqffASRcBOD74AhXv KaTx5q1+0EY7M1Oyegnvso8Hl09ohzNPVfflnzm3Gv99YaflfugO6OnVR3yTrmwPEiW7Erk9euqV nfr+KtqP01KimE7kk418wdEIhabT7EpHHpKGC85pq6+30FSelYnaTgYPZAlI9VCVaOqW9iuFDXmg qnCb8724oK0+VWt2NawQI6qzEGi/tmTqA8H133OEVFBKw6c9FCv4YrPf80gpfqL0B2/a5H3izyhM DnZEcqV1IfJArvoXdphm66dKvm/QfynG6o2a3DSRqfa4s+wfMVE/vrmgOuTIrjUO8Zy3IuAjjd4L 36pC6WZ7tx3Ewymae0mMzj/H/GfKKjEy2iok9i8qzPl112fjDb3mFWwsBUR28DHV71WJ0pz51Xjs +cQOl2yFJd3N/9ymx8pR/RwY+dOUF5luF4xWN9ozymrt9YH/7sDC98yZQ9+3iwdnjshCwSj72/XQ UxV4u6imteabep6CAF51oZrQB8EVjSYMQvB9bnqfKGKkM5cYubgw512D/64puxMn/NLRG/DcbZyw 6lXV9B1t1mvbRkOihkomx6g3XKHONxv5orvlX9vx68aWp6fMfkbox8kjd3dW7pamtXrTH2zyTk7P plmJPFiv+KYeRR/U6lN5FIPKrkRulAb8IVWfygs3/Whm/Wi9jb6LZHBxxMHqpvDyTsrAth0B/Jig xg6qxOh/L+3EnPnW/kjRny8hvlpbMnW58aztEqQDD/rrURC41Dhh5SuLUnS06aGk0BGqEnldePyF naD1EKsSmS3/5RJ1AhtJO2hK6//u367UcovMo8kwYulHTUYRqr0ctNTunH29GHx8Bsj4wMrNpt+u brJKpLJVJRJcenLJ4i+REhnKXKTAgpbEqPCtaptQiQ1rS6bONQrfJUi9Dp40EsAZxgmrX1mUIiNO bkFUrkfQw0lB9KxMNNq74jz1cZlaahO1+dZ7t+um0i1dY/ZxcpRKI4YPP6unxV0c2Nseg42DD0rV nbeSAYJViUZlbxXVIC1NA4WcUJHIBD4zQ8Pn31jXLhXtMPIkMbpkrPk/Qu0VI2qdrF9bMu0Zo527 BKn3oEkXmOkyyCggmlcWpfC0/nJJO6WL/HdOK8f8peasmYRvye6zfQ9IsSykwe5Sm96RR3ISom+/ t9YCLbgeFPPo4y/qdXGiUOoUE8nqRKO01FSBL1ZaN6Kg+FiffFkP8rWoKs4STYt2bO9ByQ+Nlo72 ze6/xBUjIiXarS2Zep/BbJcg9Rk46RoA/YwTdr2yKO1Nnhb6aUGYFsVVJLJQuu/RHVi52ro/SEY7 aPqxn8Wjg+dmkBCV61M6ofYSGXWz8pWixH74WR2+WtUACoZn9aZf8o9nbFy3st0kghSm5OjD1Kwr 0QisXRtND3hIG2ndllSJ0bPTKzFjrl3TdHv0QlrvQ/76xNpvH9Qrs0uQeg2c9A8Anfe41KYPLEq7 we/X3YsJZ6nzETb7nWrQ2okdsXbItFvFBund9PZ890JhJf7vvnJ9zcQOLwZ71ib0p81b/Hj/kzpQ yIWMdIH9e1jnFql9O49pVoWhWxf6KO2RIiexZDWqItEPnjOGZeplbCxzj1m4KjGiH2SvzrF2Wj5c v2oy8Maakmnr6RpdkJp82FVOBcQugQqXgRXnWJSAwwel4rF7OiubZ6fQ4k+/at8ud/rCDein3tqM /O7d/sB2fP51gy1eC2L5vlD8n/eW12HVj43IzBCwwl8f+Yhb9UOjLXt5SCjeXlyL9FSh7Hknwftt WwA/rbNvijbSZyH/jCxMnGD+uiqJEX0fHJWE9t6akqlfU510Aeox8KoDpMCNjqokoJvdqvDo4AaT 8NzjM3D3X9WFGievC9PftPfBJFdH7dtqyh676W9UYfK0cny8ot62EA3xNo42epJF3k/rfPo2hpxu akdM6ekCxcvNinkTXeuNdSUKRKgqIi15hSfvEbSu5NREYnTlBUkiRmTWAJSsLZm6lPpDf7r98PcF 9nDa4Ji+MmJwmD21Y0QSdeLmWfJQoNJb9z8p6qNNf3SMB8vrgbJf/TPnVYOmIrdud8/0jMGlpdeP Pq8D/acfKqOHZYJc5qhIJx6Trlv92bm29uTLO1G+w6/kjzIxu+L8droFnhN94CWbGFF/aEAf41nW BUlqYn9h3ZYTo+yIX5NJlFQ9kAbsa2/fhlU/WG+8YJRvvHbsYP7sMDkSJQ8dm7YkjhAZvIzX4o9q Qf8piuqo4Vkg7+dmp306emwXc9oXQ/14x40dzW6enh+53UlPE3oIFSUFxJCpqu8+OaB13DRdEB8p sb/xUf+r0HvQjWcC4hTjoBNfk2FNidZUVMwbU3+SJR25VdlQZp73gXieE/pjkD/KHF9cNBp64L8V IH9zZLGVDIlCXsxfUqOviZgdnHDRB7Uod4BFGnkJ+fDzemiK9mmRWTgZcnz6pf17lc4ckYmrLzZ/ Y7grvKELYG3J1Ifoe6uPkITUekp9b7Ozv8qJPFJS5RKEetSJfv0aTJjCp82VJLQ//2JCZs5+9Fus 3btLa7CguAan5WXihsvN/4PWYsEWnfh5XaPuZd7nB+iPttmJ8iTBe+H1Sj3on9n5R5IfRTy+5lLz +84VYtQEKCc3V3qLi4VPHyEdMOCma4RA70jg2X1NIo6UKKYJuc5RkWio/uQr9lnStdQm2ogZT7hl cm1EFkO0sZQTdN9t5GFjZ6XEMYfHt6eHhN4JIySjX/1+6KMYVcYOtFcpI03Tpwgrdlr7PJEYXXtZ UosRdbOW0rby6Z+/nbZTN3ESQvY0Ot8NrzRSomiGZicydCDLLyuT7groTDVi9NQrlfofbSvbw2XZ R4AMEd54txpTHt9hXyUUlkzGDuRdQ0UaNTwTz0ztAnKlZFVSJUZkPetEg41wXGUA+9F5w+a2R7iL nXhu3qIaUOwOs5OVokTid+4Zarwv/PupCpCTVE5MIJEIvPZGFR58wvzvvcHooTv3gdlrckbewa8q xcjOvYXBbYzmfUBqetgjbdSoUpqYNX9yNpraxHgthUhwqyjRnLFheh5j81u87Z8PlevOK1u8gE8w ARcToHUzitOlKt11c0cMO1mN1wiqM4VZUTFNRyMjN4oRMZEIdKNXrTrTY2lAPrMfIpWiRNNpZidy B3PdZe2VLNBSXW+6axve+9iejY1ms+L8mEBLBChkxgXX/qYbtbR0TTzHb/lzB9AoxuykKiCpm8WI GAsNXejVK4Xs7OQ9SJE8ECRKlMz+1WFMpz3xkjmRZ7MyBSaObwearzY7kbXZ7Hersd7CgHpmt4Hz YwLRECjb7Mdjz1cgINVY4NHfE/JcMdMkJ6QsRmF6V4ocOuvVAgFXj5CMJjpdlLp29mD8mWqcpJIY Pf3aTlBUUk5MIF4CNbXWWprFU18yB//PsxXw+SQorpbZieJ0kfd18vsYT1IlRq+/Ve3aabpmPHXH 3l4BdEmUP2NOFaVuXTwYO1qNGJFVFXnr5sQEzCIg3aNHu5r8+Is7dR91tLnc7ERRWj2aiNlyjdw9 me36jNpIYmTW7I3ZzKLPT+xL93ghRQe4fc4uqPVOE6Xe+6XgySlqonok1gMZ1In8lgnEQID2pdXU Slx+nvmRVcnVkNeLqEcjpxybjr9f1yGG1oS/JfG++1KHpEnR9CZ88911lkRJhfUdrSlFY+jQr5c6 MaJFzMT5deSu54tr61wCtNVh2pNqzMJpI3c0338So9tvMN8XX+KJkf486aA0IPEEiZpntyhRBM7H 71MzMiKXIG4173TunzKuWaIQeGdxDf4xpVxJcyL9UapKjMgNWIL+EG0aIQkI8yVcyaMQfaZ2iRIF 1vv3nftEX+EI7qBpCbftwo6gWXwJEzCVAIXquO4favYqkSiF8+hysqKRkRN9UprYaem5F69NJ08N 5jtSMrGW8WZltSgdc1gaptyuRoz+99JOR7uRj7ev+H4mYCaBku8bcOmNW5TsVaJN7aEcotL3/x8K pukSXIyaur06q4NXAubv/jTzqTIhLxIlSqr3KdHDeM+taqK8PvxMhZIvlgl4OQsm4FgCFMLi+cIm /3dm7/8zPIXTd5OSqu9/UogRgFQEMryQSHdosFhTH3LVovTFynplYnT/Yzuw6P1aU3lwZkwgWQiQ B+9HnlWzgdYQueUr6pR8/5NFjOhZDABtvBCIz1e9i55qlaJkeHUwG8fkqeVY9im7AjKbK+eXXATI EzptoA0EpOk+JEmUDGEyk+qbC2rw2PPJs8fQ70EmBehL6DWk5g+IKlFqXo4ZnynC66df2R/N0oy2 cB5MwAkE6A88xVdS9QPSrDaSGJGAJlMSgUAaCVLSjJCMznWDKN04eRu++a7BqDK/MgEmYBIBMpuu b5BQ4dXBjComoxgRN02KtmRll24GRLflQaLk1OHw9XewGLnteeL6uosAbZ+g/XxOS8kqRkY/0Agp aRMtGHo9wJUKwkzECvWKm7diza+Nsd7O9zEBJhAhAdrP1+iTuGSs+a6GIqzCHpeRk+Rkm6YLBiAR aJPUgkQwCt+q1pnYLUr0ML65sBrr1vuC+4jfMwEmoJDAK7Or0NAgbf9RSt9/w3xcYXMdnbUAPCRI zvh5YCMqu0WJHsbpc6uweYvfRgpcNBNITgL0/acwFuG8L6gkw2K0my4JEq0jJX2yS5ToYXx5dhW2 lbMYJf1DyABsI0DT936/NH3zfGsNYjHak1DST9kF47BalOhh5MB6wT3A75mAfQTI0Ims71TELgrV Khajvanw6KgZExIl8hmnOrEYqSbM+TOB6AksKK7FPx9S4yk8uDb0/f/fy+r/zgSX6Yb3JEgujA+p Fq1qUWIxUtt/nDsTiIfAex/XKRUl4/tfV58osbrjob3nvSRITZ4H9zye1J+yMgVy9lU3m0luRkYO yUxqxtx4JuBkAocMUOcvgL7/+WeYH2rdyTwjrZu6v7qR1sBh15EYTRzfTolvquCmUuRJEWR2Hnwu Gd5npFPrOTEB5xG47rL2yr//FBKdEsc2293/EvCzIO3mAavEyCiS9j4JAcyc17QXyjieDK8eXr1M hm52VRszMwQun6D+x6gBhUXJINH0KqBV0Z8FdiUNWC5GRldccX47x/rUMurIr0wg0Ql0bK9ZKkYG TxKliwuSfiuogQM0Qkp6d9JWj4x20f/9DbkuSfEKHr43BxPmcy0vCIehw6eiIdCtiwdjR7dRPk3X Up14pNREJiBkJQlScvk4b/ZU0DDdijWjZsXu9ZEeSq8XePpVtjHZC06IA7SJkRMTiJdA7/1S8OSU zvFmE/f99P0nv3rkyihZk9S0eg0yuUdIVs4Zt/agjTuzDSZO4OF7a5z4PBMwg0D/fs4QI6MtNFPi 1JAYRh1Vvnr8qNEgkncNyQprmmg7kESJLPA4MQG7CGRkJL4F5CEDUvGfu+0fGTXv42QWJQ2o0gSQ lNuFnShGxsNJ0SztcvRo1IFf3UugPs64jhr9VUjgdPxR6Zh2xz6ObWGyilIDtNqkXENyshgZ35Kz R2aB/jAkc3wUgwW/RkegsZHX11oiNuzkDNzy5w4tnXbMcRIlSkm1ppRVvUOTkOodNzmmmwFVYvT6 73GVzGzqmSMy9fqmpyX2L1YzmXFeTKAlAqOHZyoTI4pAa3YiUco/I8vsbJ2aX13x873qvIDY4dQa ml0vVWJ0273b8elXTdbzNN1mZiI3I5QK36pC6WYOUWEmW84reQgUjMoC7flTkcgZK/m/o2SMbMwq xwgcakQiMCtfB+aj65AmZHIIkhVi9MRLO6FipESi9OIjXXFQ3xQHPkdcJSbgbAIXntvGEjGi6bWX Z5lvtk2ilAQjJX2mToOQCT9CskKMjK8kiZIqV0CP/qszjj5MndNHow38ygQShQBto7gwX81Witsf 2D0yMniRbzoWJYNGNK9NAyNNAluiuc1t11opRgabJ1/eielvmv9LifK/99ZOGHJChlEUvzIBJtAC Ado+QdsoVKS/3bMdy1eE9rqmUpTMXhJQwSa2POVmuk8LaFpZbBk4/y47xMigQh4XVPxSovxvu7YD Rg3j8BUGa35lAs0JXHNpe6j64339Hdvw+dfhPa6pEiUSWTLOSMC0ldrkFVLobxKtgXaKkcGSHkpy B2L2Qiflf93E9qANjDPnJp+ncIMvvzKB5gQ6ddBwwTltlfmlu/KWrfh5XWPzYkN+pu8/JcNXXciL Yjh47WXt9bso5HrCJCFLqS3erBp/WU2CzQA5QYyMB4UWOn0+4PLzzJ/HvuK8dsjK0KDC5NSoP78y AbcQ+EOfFDx2jxrvCxTldfa71Vi/0RcVDhIl8lFp9tRhoomSDDQtHWnz5uWQzCaM1DpJjIwnd8bc KvzvJTUOMcj31VUXqjFnNerPr0zA6QTI+4JKMZo+typqMTKY0fS9ijVlEqVEmb4T0DYRLyNM2gYD nptfad7Y2LdjZjuC9xnFmi/tI3jkGTWO1c85PQvU9pQU3kAba//wfe4lcFpeJu66uaOSBtDI6OnX dmLzlvj2AKoUpTOGun9NSRMB3ZZBjxgrpVgvhDxQSY9alCn9QSbPBmanux4q37XpNd68ac63vkHi 5qvMd11CbScXZLPnV2N9aXTTCvG2i+9nAnYRoDhGKqbDqT1vLqjBo89VQJrkickILWP29N31l7fX 16oXFNfa1Q1xlys0/EqZ6CMkoQXWx52jjRmoEiPagf3+7zuwzWoePTQkcioSjQ6fe6gLDh/Ee5VU 8OU8nUWAjIVUidGc+dW6H0mzxMggp2qkRD9yR+S61hjA37ApRx8hNU3ZSbhWkFSKkeEOxHiYzHol kbv5n9vNym6vfKbc3glDT3Ltw7lXe/gAE2hO4OqL2imLHUTeVh57Xs2aL7WDREmFRxcXi1JpcbHQ p3V0QZJC6MOl5p3u9M9uFCOD6Zff1uNPt2zFO4vV2JP87S8dcM5p5vrVM+ruhFdaxD7qEB4JOqEv rKxD504e3eHwGEXP9qtzqkDeVlQnVW7GXClKQQMifQ0JkL8C7loQd7MYGQ/7T+sa8eLrVfD7ocQY 46qL2qFDew3PvGa+J2KjDXa9nnB0Ouh/8Ue1mLuoBt+sijMIkF0NMbFcjwf6jxBVTkRNrGpMWR06 IBVTFcYxou0TVoZ7MITP7A28JEqNPmDJMpesKQUNiHRB8gY8P/pFIKaHxI6bEkGMDG5bt/vx3xeb fpGpsBAcf1YbZKQLPPqc+l99RpusfM09PgP0f+F7tZi3sBrf/RTZpkUr66i6LCEAGjEksvk/rY/Q H1pVibZl2OFRW5Uo3XZNB/h9cpcXclXczMlXrjHy8dCbIwZOqawVlbcCQv9snHTiayKJkcE3EAA+ +bIebdto6N8v1Ths2utBfVPRsb0Hm7b4UbHTGT88AhIgsTQr9TkgBWT+26mjB1u2BVBe4Yx2mtW+ UPmQEJ11ahZ0p7uHmjd9+eaCauxwyHNC7R53Vhtcc0mTd4JQHOI99u+nKvDGu2qmziOp2+ff1CMz Q8OAA8397p9yXAZ+2eDT/0dSD7uuEZDPrymZ9iWVrwvQqlV3yt4DbzofgHPj+gL6XhsVpt3B8Uzs 6hQq97Ov1DyYlDftYj9zRBbW/OrDr1HuNlfBhKYUzj2jjel7pw7snaL7+WvXVmsS4MrEFKbTh2bi v/d2xjGHmydERj/TNHJdvUm2zkamMb6SN5ILzjXvh0vzakyeWo6iD+yf2iJRUvGD1BWipImpa7+d qtsx7BoR9Rk4aSSAfs07zCmfE3FkFIotPZgej8Ah/c39tWSURdNbldUSqx0wtdW/Xwp65vy+jGlU 0KRXGhWSAGdladj0mx87qxJDmIafkoHrr1DnXLeqOoDnZ6rxVB9N12ZmCPzpwvagTd+q0rW3b8OK b8I7SVVVdqh86QdpMoqSP+C9bd2qKfpDt0uQeg2adAyAY0OBsvtYsoiRwfmrkgb9F+qRiqzIjjks TRc9KsfOJCFw8rHpSqswoF+qPq2Vnq5hQ5kP1TXO+OUfbaPph8S1l7bX14q6dNr1tY02m1avX/5F vel771ottNkFBx+UiosK2mLkYPM3ulNR5H2BRkY0neW0pFKUflzr078DDmvzzqWF3f5u1GnXk917 0KT9AZxhnHDKa7KJkcG95IdG/LY1gOOPVvMHm0ZgNK21YmW9aTvRjbpH+kpThxeca77T2VDlD/xD qm6BlpoisHa9zzFTUqHqGnzsxGPS8eeLm+L67Ntl19c1+BJT39Pifumm+NzkxFMhcoMzeVJH9NpP TXRk8r7wyLMVqKl17g8TVaJEcdRoZmSjjf0b4tn4Zm3J1KeM47ue8D4DJmVA4FLjhBNek1WMDPZk Fk4PUJ6iTa40rUW/trduD2BbufVTWrQLnkYsVkbBHXRQKgpGNa1d/bCmEY0ONcr74xFpuPrC9nro gpx91UxrGs+Z8frdj436pk3js9Wv5HlBpck6bUb97wvusDZVJUr0t8RhorR4bcnUN4xnbZcg9Rt4 fVVAaLcYJ+x+/csl7fSpFrPr4RQDhkjbRb9mPvi0DgJCN0yI9L5Ir+vbKwW0QL55qx8/r7N+CoP+ CLZv58FBfdX8Im6JA00LkZUfrddRHWgvmBPSUYem4U8XtsPFBW3RI9saITLa/ez0Sqz5xfpngJwC k+cF+qGgKlGwzKddth+PREnFd8NJoiQhX1tbMu1Do993CdLPqx6q7T3wxisBYc0cilGDEK80RXH2 SPMXM+9/bAeWfBg67HCIajjm0I6KAFaubkCbTE2JKFFDaZMppa9t2GBK0Tc7tPMoa1u4jqSpSwrh QRM4q35oBJng25EOGZCKyye0xcQJ7ZQZeoRr18x51SicZ32wx4EHpuLisW11k/1w9Yvn3JMv78Qr c+w31IilDZ9+mdiipEnt32tWTf3RYLNLkOhArwE3jxQCvY2TdryqEqMpj+/QN0/a0SYzyiQzaZV7 laiOhw5M09eVSJSsHDHQ1N2n9GuwrfUjJaNvDhuYpk+PEWea0rBKmPr3TcGl49rpI4QDelo7SjTa To5En/h9c7ZxzIrXkUOawkb03l9du+/9zw68VWTfHiMzOKoUpa9WNcQdWiOeNgrg1jWrpu6Ky7OH IPUeNOkQAMfHU0A896oUIze7Zg9mSsN4r1fgYEVm4bSuRCOGb75r0PfxBJet+j198eoaJFRZF0ZS /yMObhKm2jqJ739uVGbw0Wf/FN2SjEIH0KZeuxK5ynnyFetdS106ri2uPF9tYMnrbt+m/4izi62Z 5aoSpRG5mfji2wb8ttWWOettiwtzCjVkfQAAIABJREFU/i+Y056CNHBSVwBjgi+w6j2LUeSkv/y2 AVWKjQGGn5Jpy36lku8b8frb1boQqBLdSEjTWs4F57RFTZ3U15giuSeSa2jf1QXntMHNV3cAbeK1 Ky1fUYfHX9iJtxU5922pXZ06aLj8vHYw239bcHlk1v23e7Y70cQ5uJpRv1clSqcOtkeUpBTL166a +mIwiD0Eqc+gSWRz9OfgC6x4z2IUPWVaiCevC7Q/RVWi/UoZGZrlmwdp2oxEd96iGgT8AFnG2ZVI mC48ty12VjWNmGKtR86+Hpx/Tlvcek0HJe6hIq0X+fp7bnqlbk1ntfkvmbBTmHHyGqIqGaEjGhqc a9YdT9tJlMgNmNkM7RAlTcjZa0qmLQrmsYcgHTngwfJaUXUTAHVPTHDppH6KDBhozShRpumaIdv1 kfbxqLTAo4Jo0blrZ3tMw8l9DU0nLHivVl/Tor1EdiVy0UPCtG1HAD+uidxWvMs+TUL09+s7mu6r LFoW0/5XgUeeqcDPNljSnX9OG9D0pMr0YmElnplu/fSjyjaFypvWkhNBlITEY2tWTVsZ3Ma9Yk7k FWxcDghLPDawGAV3RezvyckmsSRHmyoT/TGjMOx2pe7ZXowelqnUnUwkbaNRBnkWX/R+bYtrTB3b a/pGXHIManci56E0NWd29NNI2kWbry/KbwsVPiiDy3/46Qp9RB18LNHfX3dZeyVha668ZSt+Xhf5 j65YOQuBAUUzcr4Lvn9vQRpb+m9IXBd8kYr3LEbmU504vq3uGdn8nHfnSFMihsv83Uetfdeze5Mw qdgaEG1LSJRopEpGEB4N6NhBQ98DUkBTfXYnCjlCnrvtECJqO7mF+scNHZVj+L/7tieM8UK0sFSJ 0hU3b8WaX5WKUsVJA7I7TZ68Z9yjEIK0cRykeC1aMNFcz2IUDa3orqXF4j9doNZ6iUK70wjBbl94 ZKlGMaTI3Qyn3QTI/Y9hGLL7qLXvaGMvTdOpTGS88PLsKmwrt8VCTGXTosrbpaK0aPHMnOHNG7rH GhKd7HPQLTXQpLIREotR8y4w9zNt7ly3wQdyO68qHdDDC7LCa2gEvv3ePgetFPPo4y/q9bAdZApv p/m0KtbR5EuRgW+9dzu+/V7pL9uwVdqvu1f3MpE/Su30MW3kJTdANDJN9kTfQRWb5unH3rJP69XE FhPylbUl04qb991eIyS6YEhB6W8C6NL84ng/sxjFSzDy+7t38+Lc07OUzDEH14KcVU5/swpbttn/ K5W8HdAak0rLw+C2O+X9C4WVeO2Navh89v5xpj9g9GtddbIruqvqdsWTf1amwMTx7ZR835VM3wlt 1OIZ3d5q3ua9Rkh0Qe+BN51EMd2aXxzPZxajeOhFf29lVUCfV8/KND8SZXBtyAcdCd9v2wIgZ7B2 ps1b/Hj/kzqQp3QK206/1hM5kX82GhF9sbLBMs8SoXimpQndKSo5R1Wd/jGlPOGtZ2NhSE6CVY6U Fr5Xq+99jKVuoe5Jlbj+p1VT97KQakGQbuwJiGGhMorlGDlOHHOa+UP4ZDDtjoV38D3kJ67BB5AH ApXp+KPS9TDMX5XUg8KT25nKNvtRvLwOZA1Hgd5UBQG0q43kXeH2B7brU5VWungK1V4ajf7v/s6g uFMqE60X3fVQue7WSWU5bs5bpSileIW+FGBSPLHvFhbmTAnFOqQg9Rn0Vz8gJ4a6IdpjtMCuIuoj mbLOX2J/6OFoedhx/berG/S9J4MVbqKldg04MFXf/PnLRp8jgp9RXJ+lH9Xhp3U+PRJnTjd3j5im v1GFO6eVY/mKekeEzZg4oa3ug0/1M134VjUee36na4MrquYTnD+JEq0jZ2WY64iZNuKmmidKs9eW TN1ruo7aEVKQ9usyZZOWVTUJQFw/e0iMVLgIof0wtIufU+QE1pf68M6SWqSlqgljEVyTU47NAE3j kLcFJyRqe9EHtfi11I8O7TRYEejOzHbTAv49/9mBDz6pc0RgwWOPSMNVF7XHqbnqrRtpi8FLr7vT U7eZz0A0eTU0Sn00QwJipkcHs0RJSPlQ8w2xRvtCCtK6dXcG+gyYdAoE+hgXRvuqUozs3JwZLQcn XU9RMmmXN8WfoXhAKtOgP6Tqng0oTLRTQkWvW+/TvT5s2uJHp44ePTihSgbx5j3r7Wrc/98KFH9U 65jRAe11u3Zie0tiNd3yr+1Y/AHPgsTyHNHUGlnbOlGU/H55/brvpoWMlBhSkAjAAQMn7S8EhsQC g8UoFmrW3UMjF4oSe9xRasKjB7eEzM8zMzR8/Z29C+/BdSLXOfOX1FjGILjsSN6/8W41pjxeoY/q yDjFCYnWCGktmLxDq07U/kl3bQeNbDnFTkC1KJERRdQRlwXWLH29+90ttapFQeo98AYfhLispRtb Os5i1BIZZx3/cW2jbpGmKhJtcGv1taUxbWyLShtcl+D3xOClWbSxMoDjjlQvzsFlh3pPcXumPlGh W5FV7HSGENH+LgoaSBGcaSuB6vTcjErQfiq7jTVUt9Oq/FWKEu19ilqUBApbWj8iJi0K0lEDp5bV iqprAUT8TWUxsuoxM6ecHTubTMPT0zRY4biUotKSb7MNZT49tIU5rYg/F3KWSsK0s1KCnKhancik lox0SJBos69T0ojcDDx+b2dLng1qM5l0v2NxOAynsFZZDxKlX0t9GGNyFG5aU4pWlATEA2tKppa0 1N6QG2ONi/MKSmcDONv4HO6VxSgcHeefOy0vEzdeoX5To0Hi6dcqQVZjTkvkqJb2VV2p2P0StZvW huYuqsE3NoSND8edfAWeOTxTubNeow40RUcjI/rDyUkdgd77peDJKZ1NL4BM8p9+LSIryIBX+rss KOy5vaVKtDhCohv6DLipEwROb+lm4ziLkUHCva80fbXkwzp4NHMtc1oiQvuiaCqPDC2ctlZAZrOv zKlCQ4Oa/VsffV6nmzFPf7Pa1vDRofrmgnPb4I4bO4IiB1uR6IfJs9Mro1+LsKJyCVYGjb7JFRB5 1DAzGSOllasbQLHMwqTPFxX2eCTMeYQdIeXmr+/rEZ4fw2XAYhSOjjvP0Y57CmNuVZozv1p3Bkqe FpyWPB6A9m+dPjQzbstEmpKjX5M//2KvR4tQjIeckKH/oVJtfWmU7RQHvUZ9kulV5Ujp4WcqwqG8 Z/HMPUOWN784rCDRxXkFpd8DOLD5jfSZxSgUlcQ4RtE9J09SHzogmBYFWCPvzQHnLKMEVw8d2ms4 8uA0HH5wKg7okYIe2R60ydL2uMb4QKbltFb2/U+NesTdVT80wOc8vdVDZYwengmasrUq0b6qJ18O afVrVRWSvhw7RElInFRUmLMsHPxIBOlBALRJdo/EYrQHjoT8QPuVaDf+OQrcPoUDZncgwHB1a36O XBNlpGtITxfw+yRq6qTugbqx0dnrISSk487MwrgzrRsJE7up/yMPK7ypvflzZMfnfr1S8Ph9ataU QoyUtneS2V0LC0XYn2Vh15AIUu+BN9H8wkXBwFiMgmkk7nsaqZAvvPKdARx7RMTGlnED+eMR6Ti4 f6rulYCC3zk50Zw5hUCg/UJVNRL1DdKxIzziqGnAhLPb4P7/64RBijdHB/cbGS7c8eAOfPOdM7x3 BNctWd9v3xHAF9824NTB5o6OaU2JQqzTJvygNOutwnazgj6HfNuqIHU64cGN6XW7zb9ZjEJyTOiD P/zciIXv18LjESDv3lak7K5e5B6Xgb69UnQT8dLNYX9YWVElV5dB1oMU4v4/d3fG4YOsNW0n9z/P z6zi2EUOfIJ+2+q3RJSkEHevLZm6qjUErU7ZUQZDC0pnSiCfpm9UDPEp0Nbsd6pbqyufdwABKyLS hmqmU02kQ9XVScdIiGh96IbLrTPpN9pPBhxvLqwGuWzi5GwCNFr+9537mF5Jipf2n2d3NAqZ0aWo sFNYiwcqPCJByhu7cdzE8e1eUyFGHGzL9GdAeYb79/DirBHqg/+FasiyT+t0x7orvtljOiDUpUl9 jIRoxCmZuOkq64WIwD/1SiVmzHXePrOkfihaabwqUVr6Ue36ISdk7tdK8frpiASp9LfGU7O7eOdH kmE017AYRUPLeddSWJGrLmxnS8U+/6ZeN6H+8LM6W8p3aqFkpj5qWJbu6seOOvKoyA7q5pV5+KBU TLnd/JESgH5CiJ9aq2lEgiSlfA3AuNYyi+Y8i1E0tJx7bbcuHj3ECK1P2JHW/NqoC9O7xbVwumWb Sj5tszR9H9Gl49RHbW2pHbRW9PpbPPXeEh+3HD/msDTcc2sns6vbTQixubVMIxWkCwG80FpmkZ5n MYqUlHuus9r1UCgy09+swluLakB7gJIl0X4S2nk/api5llLR8CMLOgqi58SNzdG0g6/dTcBkUSoW QgzenXvL7yIVpA4AylvOJvIzLEaRs3LblakpAhcXtEXBaHtGSwavT76ox4L3an6PrOrs/UBGnaN5 peCHucel47QhmZY5Pm2pfryvqCUy7j9uoihdI4R4NBIiEQkSZSSlfBPA6EgybekaFqOWyCTW8aMO SdN/tZN3b7sTWfkUfVCD1T81QrpYm8hI4ZD+qcg7McNSrwot9R+NRskHnVO9arRUbz4eHQGTRClb CLEpkpKjEaSxAKZHkmmoa1iMQlFJ7GMFo7Jwxfn2GD2EIkvrGxQGfNWPDa4QJxKhAf1SccIx6SCW TkhLljV5KP92NW9wdUJ/WFEH+mF5500xuxFbJIQYHmk9oxEkmqSOacWSxSjS7ki869q20XDe2W10 wwcnta7og1p8+mU9Vqysh1OC4RGfrEyBwwam4Y9HpOlTck5i9uhzO0HrRZySj8Apx6bj9htiEqVL hBDPR0osYkGiDKWULwK4INLM6ToWo2hoJe61hw5IxejhWTjlOPun8ZpTXrfBh5XfNej/v1/TiNJN PstGUB3bayBXKxQgkTwoWOUJozmDcJ9fnVOFFwo5ims4RslwLkZRai+EiNiTbrSCdCqAiPcjsRgl w2MaXRuHnZyBW/5MNjLOTuRzjTwMbNzkQ+kmP0o3+0ARdmMZTZGT2k4dNHTu6EGPHA96ZnuxXw+v 7mm7a+dWvXfZBmreoqZwGWRaz4kJEIEoRalQCFEQDbloBYm+PRH5AWExiqYbkutaWhspGNUGl59n 356ZWIn7/UBFZUDf80SOVBt9TZYSXo8Atcv4nJmu6Y5MMzMFaI+QmxJZKc5dVA165cQEmhOIQpRO F0K80/z+cJ+jEiTKSEp5L4C/hcuUxSgcHT5nEKCRw4Sz2oCilHJyBoEHn6jAu0s5PIQzesO5tYhw psMrRPhwE81bGIsg/QHA6uYZGZ/Zh5VBgl8jJdC+rYb8M7Iw7iwWpkiZmX0dGSy8uaDasrUzs+vP +VlPYERuBm6+qsXp9/uEELdGW6uoBYkKkFJ+AODE5oU9N6MSr8xmh4rNufDnyAjQesroYZksTJHh MuUqms14/W0WIlNgJmEmYUTpICEERRuPKsUqSJcCeCa4JBajYBr8Ph4CZHlGUWp5xBQPxfD3khDN nl8NWhPjxATiIRBClJYJIU6KJc9YBYl26e0aCrEYxYKe72mNAIXZPuvUTN0dUWvX8vnICDz8dAXe XlzDHhYiw8VXRUigmSidJ4R4NcJb97gsJkGiHKSU1wJ4+LHnd2LOfN4stwdV/mAqAa9X4PS8DFxz qT2xfUxtjA2Zfb2qAXMXVuP9j+t4jcgG/slSZJssbdurj+57aVaWmBtrm2MWJCrw7oe3n7j0wzpa T+LEBJQTILPqIw9Jw+jhmTj+KOdtsFUOIMoCFhTXYt6iat2PX5S38uVMIGoCAuL+opnZYS2wW8s0 LkGizPPyS5dCILe1gvg8EzCTABlAnDE0ExPOZsu85lzJ6ek7S2qwoyLQ/BR/ZgKqCASE39OvaNa+ a+IpwARBKjsXQhbGUwm+lwnESsDrAY45PB3DT8nAicck76jpvY/r9FhQX5XU87RcrA8T3xczAQEx t2hm9pkxZ/D7jXELUm6u9Hq6lv0MIKKY6fFWmO9nAi0RoP1Mg0/I0GMFDTootaXLEub4N6sasPjD Wn1tqLKKR0MJ07EubIgATi2ambMg3qrHLUhUgSEFZTcJyCnxVobvZwJmEdinowcn/TEdJx+brscR Mitfu/MhH3sffV6H95bXYcs2ttm2uz+4fJ3AqsUzswcBIu6IY6YIUu5Zazt4UtPWA+AJfX5CHUeA zMePPCQVfzw8HUcflgba5+SmRNNxH6+ow8df1INHQm7queSoqwCuKJqZ85QZrTVFkKgiQwrKHhGQ 15hRKc6DCagk0LO7F4f2T8XB/VP10VOXfZzjcZsct67+sRG0FvTFtw1Y84u7I92q7EfO2xEEttZI /37LC3vWmlEb0wRpeP6mXn4R+AGA14yKcR5MwCoC7dtpOLB3CvockIJ+vVLQM8eD7vt6kZZm2tcj ZFPKNvvx60Yfftnow/c/NYBiMW36jafhQsLig84kIMUdiwuz7zKrcqZ+4/IKSl8GcJ5ZleN8mICd BMhIonMnD9q1FaBpP4p+2yZT6EKV4hWgDbspv//8CgRoszjg+X2wVVsndW8IdQ0S9fUSOysD+nTb zqqAbo69dbsfPtYeO7uXy46fQJVX+vdfUNhze/xZNeVg7mjG438Afs8EAKYKnVmN5XyYQDQEaPqM /nNiAkxgbwISeMpMMaISTF3dXfxaz29EFBFl924iH2ECTIAJMAEXEGjwavIhs+tpqiBR5aSG+8yu JOfHBJgAE2ACziEgIF5ZOL07WVabmkwXpMXTcz4A5BJTa8mZMQEmwASYgFMI+ODX7lZRGdMFiSop Ie5UUVnOkwkwASbABGwmIOTL8fqsa6kFSgRpycyc9wEsbalQPs4EmAATYAKuJODzBwL/UlVzJYJE lZXAZFWV5nyZABNgAkzAFgKvFBf2/ElVycoE6fdR0iJVFed8mQATYAJMwFICDR6pKV2OUSZIhEnT cFvTYMlSaFwYE2ACTIAJmE5APrWwsNta07MNylCpIC2anvM5gFlB5fFbJsAEmAATcB+BGq8vVdna kYFDqSBRIR4/bgfgMwrkVybABJgAE3AXASnwyILZXcpU11q5IC2clbMaAi+obgjnzwSYABNgAkoI bGvwND6gJOdmmSoXJCrP25hCo6TqZmXzRybABJgAE3A4ASlw97JX9y+3opqWCBIN9SQER5S1oke5 DCbABJiAWQQEfqxon/2YWdm1lo8lgkSVyKqlEOeitLUK8XkmwASYABNwBgEZwC0rnhSNVtXGMkGa Ny+nRorA361qGJfDBJgAE2ACsROQwHtLCnPmxJ5D9HdaJkhUtZP757wACTIF58QEmAATYALOJeD3 yMD1VlfP8kB6eWPLjoaUH5sdi8lqcFweE2ACTCBRCUghH18yo/vVVrfP0hESNW7xjOzPADxrdUO5 PCbABJgAE2idgAS2BOobyMuO5clyQaIWpkpBjd1heWu5QCbABJgAEwhPQOC24jd62fL32RZBml+Y vQVC/F94KnyWCTABJsAErCQgpPzk5P7Zts1g2SJIBPik/t2eAPS1JCt5c1lMgAkwASYQmoDPr8kr J08WgdCn1R+13KghuEmDx244VJMaWd15g4/zeybABJgAE7CYgJBTFs/o/leLS92jONtGSFSLpTN6 fA2IaXvUiD8wASbABJiA1QTWeVI9SmMdRdIgWwWJKphZKwmC0hgbkYDga5gAE2ACyUpACnH1wpe6 2e5v1HZBIg8Omha4nAP5JetXgdvNBJiAzQReXjIje77NddCLt12QqBaLpvdYDOBJJwDhOjABJsAE kojAJq/0X+eU9jpCkAhGXV3KzQB+dQoYrgcTYAJMINEJSImrFxT23O6UdjpGkD6c26VSCslTd055 MrgeTIAJJDgBMd1q56mtAXWMIFFFl8zovhCQj7dWaT7PBJgAE2AC8RAQpV7p+3M8Oai411GCRA30 pHnIDv4HFY3lPJkAE2ACTABSQF7qpKk6o08cJ0hkehjQtAsB+IxK8isTYAJMgAmYRUD+t2hmzgKz cjMzH8cJEjVu6fRun0DIe8xsKOfFBJgAE0h2AlJgdWatsNUbQ7g+cKQgUYX9m3P+CYmPwlWezzEB JsAEmEDEBBoAnEd7PyO+w+ILHStIxcXCJ/2YIIByi5lwcUyACTCBxCMg8dclM3K+cHLDHCtIBG3J 7JxfpBRXOBkg140JMAEm4AICby8uzH7E6fV0tCARvMWF2a8L4Amng+T6MQEmwAScSUCU+j24GBDS mfXbXSvHCxJVtVr6b4TEl7urze+YABNgAkwgAgI+aHJc8Ws5WyO41vZLXCFIywt71krNkw+gwnZi XAEmwASYgEsISIhbF0/P+cAl1YUrBIlgLpmx789CyIvYK7hbHi2uJxNgAjYTmLNkZrepNtchquJd I0jUqqIZ3d+EkA9G1UK+mAkwASaQbAQEfhQy/RI3rBsFd42rBIkq3imQc6uUgsJVcGICTIAJMIG9 CVSLgHZOUWEn1y1xuE6QCguFPw0YD4Ff9u4HPsIEmAATSGoCUgAXFRV2W+lGCq4TJII8vzB7SwCB MwE4dsexGx8GrjMTYALuJiCBfxXNzJnl1la4UpAI9tIZPb6WQlzMRg5uffS43kyACZhKQMq3Th6Q fYepeVqcmbC4PNOLyysoux2Qd5meMWfIBJgAE3APgZVCpp/kxnWjYMSuFyRAiryCspfIaWBww/g9 E2ACTCAZCEhgS8AXOKJ4do8Nbm+va6fsdoMXspMsvxSQH+8+xu+YABNgAklBoEZq2qhEECPqrQQY ITU9dCPzy7o0CnwkIfsmxWPIjWQCTCDZCUgJed6Smd1fSxQQCTBCauoKsrzzSd9IGr4mSudwO5gA E2ACLREQErclkhhROxNmhGR02uBxm/6oBQJLAGQax/iVCTABJpBIBKSQjy+Z0f3qRGoTtSVhRkhG x1D484CU4ynorHGMX5kAE2ACCUTglX0COdckUHt2NSXhBIlatrSw+1wJ/IX3KO3qZ37DBJhAQhCQ S8iIizzWJERzmjUi4absgts3NL/0b1Lg3uBj/J4JMAEm4FICnwqZPtzte43CsU/IEZLR4KLCnPsE xP3GZ35lAkyACbiUwKpUKc5IZDGifknoEVLTg6dvnH0cwJUufRC52kyACSQzAYFf/I2BExNlr1G4 rkzoEVJTw2njbPafAbwSDgSfYwJMgAk4kMAGf8A/NBnEiNgnwQip6RHLz5eebaL0VQFR4MCHjqvE BJgAE2hGQJZByNzFM3r80OxEwn5MGkGiHjzyCpnSvrxsuhAYk7A9yg1jAkzA9QRog78mcErRjJzv XN+YKBqQBFN2u2mseFI07oPy8QJi7u6j/I4JMAEm4BwCuhhJLS/ZxIh6IKkEiRpcWDiwoaPcni8l ZjvnEeSaMAEmwAR0ApulRwx2a8TXePswqabsgmHl5kqvZ99NL0PKscHH+T0TYAJMwB4CotTjl3kL Z+Wstqd8+0tNuhGSgby4WPg6BbpRDKWXjWP8ygSYABOwicB6v/SdksxiRNyTVpCo8eR+o5PMpjDo /7PpIeRimQATSHICAuInvyZOLi7s+VOSo0ges+/wHS3FkLFl9wiJv4W/js8yASbABEwkIMU3fnhG FBd23WRirq7NKqlHSLt7TcglM3JuFVL8lR2y7qbC75gAE1BIQOKj+pSGXBaj3YyT1qhhN4I93w0Z WzpRSDwBwLPnGf7EBJgAEzCNwLuZtThn3rycGtNyTICMWJBCdOKQgo2jBASFBc4KcZoPMQEmwARi JiAgnivv0O1K2hcZcyYJeiMLUgsdS5FnRSAwTwBdWriEDzMBJsAEoiQg/7l4Zs4dgJBR3pgUl7Mg henm3Pz1fb3CO19C9g1zGZ9iAkyACbRGwC+Aq4pm5jzV2oXJfJ4FqZXezx1f2tnjxxwAJ7ZyKZ9m AkyACYQiUCGAsUUzcxaEOsnHdhNgK7vdLEK+K34tZ2tqZfVQ3kAbEg8fZAJMIDyBtQGPOIHFKDwk 4yyPkAwSEbzmFZTeCuBfyRS2IwIsfAkTYAIhCYjlnkZx1sI53X4LeZoP7kWABWkvJOEPDMkvPVsI vAigTfgr+SwTYAJJS0DI51N31vxp/vx+9UnLIIaGsyDFAG342E2DAlLOYWOHGODxLUwgsQn4hJA3 Fs3o/p/Ebqaa1rEgxcj1xAm/dEzzpbwK4NQYs+DbmAATSCACehwjTRQUTc8uTqBmWdoUNmqIEfey V/cv7ySzzwBwL7sbihEi38YEEoWAxOfw4WgWo/g6lEdI8fHT7x6aX3a6FPIFAPuYkB1nwQSYgIsI CCEeTdlZdROvF8XfaSxI8TPUcxiev6mXH4GZEDjKpCw5GybABJxNoAoCf1o8I+cVZ1fTPbXjKTuT +mphYbe1qVXVJ9KvJZ7CMwkqZ8MEnEtgpcePo1mMzO0gHiGZy1PPbejYjWdKKZ7hKTwFcDlLJmAz ASnk44GMhhuLn+9VZ3NVEq54FiRFXZo7ZkMPzau9LIBTFBXB2TIBJmAtge1SYuKSwhxyJcZJAQGe slMAlbIsnt1jwz4yOw8Q/wAku5lXxJmzZQIWEVgqJA5nMVJLm0dIavnquQ8bV3pUIACywhtgQXFc BBNgAuYRqBPAbScOyH548mQRMC9bzikUARakUFQUHDsuf31Gpua5FxLXsi88BYA5SyZgNgGJLwFx /uLC7FVmZ835hSbAghSai7KjeQVlgwXw5P+3d26xVVRRGP7/PbW2oqYgSIGC8a6QoPLgJYKppYL1 Fi+p0AcUrw/6oDHxHhU1XuOLiYmJqVGjRvAIGjERQ9GCGNEgmGgUlYhSORRRqQhKy5lZZho1EUs9 9zMz5386ObPXWnutb03yZ86Zvbe2HSoZYgUWgUIJDBj48K8NjQ/rVNdCUebmL0HKjVdRrAeflliz ALBbANQUJaiCiIAIFEzAgA/DPS8CAAAGbElEQVRovF5PRQWjzCuABCkvbMVxap275RQL2AlgWnEi KooIiECeBHYCvGvG5Man9V9RngSL4CZBKgLEQkI0N1uNO7z3ZsIeAFBfSCz5ioAI5EOAS/2Mf0P4 Zmw+3vIpHgEJUvFYFhSp9bJtRwUueIa0mQUFkrMIiEC2BLbB7KYVqQmLsnWQXWkJSJBKyzfH6MaW Oen5ND4OYHSOzjIXARHIjoABfK7GMre+k5r4S3YusioHAQlSOSjnOMfs9p5RGboHAV6vlx5yhCdz ERiewDrS3di1qHHN8GYarQQBCVIlqGc55+BLDz6fBDEjSxeZiYAIDE3gJxrunj5lXKdeWhgaUBSu SpCi0IVhczC2XJ6eS/BRAJOGNdWgCIjAvgQGQDzl9/c/2P3GkX37Dup7tAhIkKLVj/1mE65dGgHv JiNuB9CwX0MNiIAI/E3gdd/827pTEzf+fUGf0SYgQYp2f/6TXXNHerTn2/0ArgN4wH8MdEEEqpwA zT6C5+7QceLxuxEkSPHr2WDGze09x3j0FgDoAKBd22PaR6VdVAJfkLina9G41wFaUSMrWFkISJDK grl0k8zs6JmKjHsI5AWlm0WRRSDCBIjvYbxvlDW+lErRj3CmSu1/CEiQ/gdQXIZb5/SebkFwL4i2 uOSsPEWgQAKbaXx0JH55NpWaMlBgLLlHgIAEKQJNKGYKLe1bTyXtXgDnFzOuYolAhAh8R8MjI7Hj eQlRhLpShFQkSEWAGMUQ4aGAvo87SVys/5ii2CHllAeBrwh7fEfD+Bd1LEQe9GLgIkGKQZMKSfHs 9i3HO8dbYZgHoLaQWPIVgQoR+JjEY9NPHPeGFrVWqANlmlaCVCbQlZ7mnLmbxwfm3QzjdVrHVOlu aP4sCAQElhn4xIpXx72Xhb1MEkBAgpSAJuZSwqx5vSP8Absy3OUYwHG5+MpWBMpAYDdgL4D25IpF TV+XYT5NESECEqQINaOcqSxYYG7lF73nerAbDZgNwCvn/JpLBP5FgPjGjM8EA3s6tcXPv8hU1RcJ UlW1e+hiW9vTkwLyKsKu1n55QzPS1ZIQ6AfwGh07uxY2rtRi1pIwjlVQCVKs2lXaZMOnplVf9s5G YNeSdqG2Jiot7yqO/hkMz9bAf1HnEVXxXTBE6RKkIaDoEjCzY9tY8zPzSV4Dw7FiIgIFEtgF8FWY 37ki1fRhgbHknlACEqSENrZ4ZYXHX2ydQeIKGi41YGTxYitSwgkEAFbC7OXa2rrU2y8ftjPh9aq8 AglIkAoEWE3ubW3fHDhwyIg2g3UQvBBAfTXVr1qzJGBYC+AV52UWLl84KZ2ll8xEABIk3QR5ETjz ou2H1B04cB7Jyww4D8CIvALJKQkEDLCPaG4JAre4a/HYb5NQlGooPwEJUvmZJ27G8PDAg+iFr45f YkAbgTGJK1IF7UsgA9gqI5cGe4PXupc0/bCvgb6LQK4EJEi5EpP9sATa28372UufxoAXwHg+aFOH ddBgnAj8BGAZzN4i6pd1pUb9GqfklWv0CUiQot+jWGfYfOkPTTU1nGVw5xhspp6eYtXO8EiHNQSW +84tH+2PXavzhmLVv9glK0GKXcvim3C4zmn1hvRJ8F2rITgL5HTtqxepfvowrIfDKgZ8NwNvZXfq 8F2RylDJJJqABCnR7Y12ceHPezu89FTzeRaIGQBOBzAh2lknKrvdMKwDsdrI9/v/qFn9wZtjfktU hSomVgQkSLFqV/KTbWnvmeCcdxrMzjDwVAAnAzg0+ZWXvMKMARsIfGLAGs+CNXu3T/i8u5uZks+s CUQgSwISpCxByaxSBIwtc348CgimATaNNihQk7Xn3rD92EmzL438FMT6gO7Tg3cHny1dOv73Yb00 KAIVJiBBqnADNH1+BMJ1ULUH+ZO9wJ9ixAk0d7yZHQ3iaAB1+UWNlZcBCF+13mjARho2mLPPXcAN Xanxm2NViZIVgb8ISJB0KySMgHHW3HSTH7hjBp+iDEcYrYmGpsHvxMSY/AQY7oS9zYAewLYQDD83 G7DJkRsz9f2bup8/ck/CmqdyqpyABKnKb4BqLL95/qa62j21Y3xzjQDHGvwxNDfKYA2ObDCzBpg1 GFy9ozWY8QA4Oxhm9QT/efraz75+vxMIxQTh9gUg+mDYS2CXGcKxPwJaH8k+GnfArC8g+hyw3Rx+ 9PZie+DqtmqNTzXemar5T7boKrYfCqI6AAAAAElFTkSuQmCC"/></symbol><symbol viewBox="0 0 24 24" id="webpack" xmlns="http://www.w3.org/2000/svg"><path d="M19.376 15.988l-7.709 4.45-7.708-4.45V7.087l7.708-4.45 7.709 4.45z" fill="#fff" fill-opacity=".785" stroke-width="0"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18.21 0 .41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.939v2.104h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07l7-3.94zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#1c78c0"/></symbol><symbol viewBox="0 0 24 24" id="wolframlanguage" xmlns="http://www.w3.org/2000/svg"><title>wolframLanguage</title><g transform="scale(.12121)" fill="none" fill-rule="evenodd"><circle cx="99.197" cy="98.946" r="83.28" fill="#212121" stroke-width=".841"/><path d="M182.529 98.828a83.406 83.406 0 0 1-39.14 70.721.064.064 0 0 1-.038.019l-28.62-35.665 23.71 2.612s11.385 1.177 13.978 0c2.373-.938 15.175-18.963 15.175-18.963s-36.75-23.23-49.312-36.032c1.434-21.575-1.656-50.269-1.656-50.03-9.251 9.234-10.429 10.669-19.68 19.203-4.028-13.04-5.923-17.547-9.95-30.588-12.104 9.95-21.337 26.799-27.977 46.48a78.68 78.68 0 0 0-4.23 5.094 109.774 109.774 0 0 0-2.667 3.66 114.558 114.558 0 0 0-5.132 8.002 172.555 172.555 0 0 0-3.403 6.051c-7.706 14.475-14.034 31.066-19.515 46.001a.858.858 0 0 1-.092-.184c-14.988-30.912-9.502-67.85 13.822-93.072 23.325-25.223 59.723-33.575 91.71-21.045 31.988 12.53 53.029 43.382 53.017 77.736z" fill="#e53935"/><path d="M101.452 69.178s-1.416-8.295-2.373-11.367c6.401-6.18 7.357-7.118 13.52-13.04.477 11.845.238 18.006-.479 32.481-3.55-3.568-10.668-8.074-10.668-8.074zm-27.737 40.778s-6.64-4.029-11.624-4.728c1.435-3.329 5.223-7.596 6.18-8.773-1.913.699-15.653 6.86-17.087 12.084a74.804 74.804 0 0 1 11.385 3.79 35.993 35.993 0 0 0-8.774 20.158s21.815-3.33 38.185-1.196c.283.168.609.251.938.24l8.534.239 27.111 45.136.221.35c-.037.018-.055.037-.073.037-51.133 18.485-88.085-15.543-95.976-27.443.034-.102.058-.206.074-.313 7.1-30.017 15.855-65.939 30-76.552 7.356-12.82 9.49-31.783 22.751-41.734 3.33 9.951 8.553 30.588 12.103 40.539 15.653 15.652 39.361 35.094 55.234 43.15 1.656.956 3.79 7.596 3.79 7.596l-6.401 8.056-68.276-6.879a54.462 54.462 0 0 0-4.58-.183 86.848 86.848 0 0 0-14.144 1.36c3.311-8.295 10.43-14.935 10.43-14.935zm22.054-8.774c3.789-.46 7.817.956 12.323 3.568 4.267-1.195 4.745-1.434 9.013-2.612-5.463-4.028-11.386-8.295-19.442-7.118a47.249 47.249 0 0 0-1.894 6.162z" fill="#fff" stroke-width=".936"/></g></symbol><symbol viewBox="0 0 24 24" id="word" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M7 13l1.5 7h2l1.5-3 1.5 3h2l1.5-7h1v-2h-4v2h1l-.9 4.2L13 15h-2l-1.1 2.2L9 13h1v-2H6v2h1z" fill="#01579b"/></symbol><symbol viewBox="0 0 24 24" id="xaml" xmlns="http://www.w3.org/2000/svg"><path d="M18.93 12l-3.47 6H8.54l-3.47-6 3.47-6h6.92l3.47 6m4.84 0l-4.04 7L18 18l3.46-6L18 6l1.73-1 4.04 7M.23 12l4.04-7L6 6l-3.46 6L6 18l-1.73 1-4.04-7z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="xml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="yaml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="yang" xmlns="http://www.w3.org/2000/svg"><path d="M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2m0 2a8 8 0 0 0-8 8 8 8 0 0 0 8 8 4 4 0 0 1-4-4 4 4 0 0 1 4-4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 2.5A1.5 1.5 0 0 1 13.5 8 1.5 1.5 0 0 1 12 9.5 1.5 1.5 0 0 1 10.5 8 1.5 1.5 0 0 1 12 6.5m0 8a1.5 1.5 0 0 0-1.5 1.5 1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5 1.5 1.5 0 0 0-1.5-1.5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 289.99999 290.00001" id="yarn" xmlns="http://www.w3.org/2000/svg"><path d="M250.733 218.418c-12.39 2.943-18.661 5.653-33.993 15.641-24.004 15.487-50.176 22.688-50.176 22.688s-2.168 3.252-8.44 4.723c-10.84 2.633-51.647 4.878-55.364 4.956-9.988.077-16.105-2.555-17.809-6.66-5.188-12.388 7.434-17.809 7.434-17.809s-2.788-1.703-4.414-3.252c-1.471-1.47-3.02-4.413-3.484-3.33-1.936 4.724-2.943 16.261-8.13 21.45-7.125 7.2-20.598 4.8-28.573.619-8.75-4.646.62-15.564.62-15.564s-4.724 2.788-8.518-2.942c-3.407-5.266-6.582-14.248-5.73-25.32 1.084-12.777 15.176-25.011 15.176-25.011s-2.477-18.661 5.653-37.787c7.356-17.422 27.179-31.437 27.179-31.437s-16.648-18.352-10.454-35c4.027-10.84 5.653-10.763 6.97-11.227 4.645-1.781 9.136-3.717 12.466-7.356 16.648-17.964 37.864-14.557 37.864-14.557s9.911-30.431 19.203-24.469c2.865 1.859 13.163 24.778 13.163 24.778s10.996-6.426 12.235-4.026c6.659 12.931 7.433 37.632 4.49 52.654-4.955 24.778-17.344 38.096-22.3 46.459-1.161 1.936 13.319 8.053 22.456 33.373 8.44 23.152.929 42.587 2.245 44.756.232.387.31.542.31.542s9.679.774 29.114-11.228c10.376-6.427 22.688-13.628 36.703-13.783 13.55-.232 14.247 15.719 4.104 18.12z" fill="#2c8ebb" stroke-width=".774"/></symbol><symbol viewBox="0 0 24 24" id="zip" xmlns="http://www.w3.org/2000/svg"><path d="M14 17h-2v-2h-2v-2h2v2h2m0-6h-2v2h2v2h-2v-2h-2V9h2V7h-2V5h2v2h2m5-4H5c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#afb42b"/></symbol></svg>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 24 24" id="actionscript" xmlns="http://www.w3.org/2000/svg"><text style="line-height:113.99999857%" x="5.605" y="15.892" transform="scale(.91325 1.095)" font-weight="400" font-size="42.822" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/><path style="line-height:125%" d="M4.744 2.031c-1.157 0-1.994.31-2.51.93-.515.612-.771 1.678-.771 3.197v2.467c0 1.408-.402 2.111-1.201 2.111v2.035c.8 0 1.2.679 1.2 2.036v2.654c0 1.512.26 2.562.78 3.152.52.59 1.355.885 2.502.885V19.43c-.447 0-.77-.151-.97-.453-.195-.303-.292-.815-.292-1.538v-2.267c0-1.807-.404-2.937-1.214-3.395v-.045c.81-.464 1.214-1.581 1.214-3.351V6.025c0-1.283.42-1.925 1.262-1.925V2.03zm14.66 0V4.1c.842 0 1.262.642 1.262 1.925v2.268c0 1.843.402 2.996 1.207 3.46v.046c-.805.442-1.207 1.544-1.207 3.306v2.356c0 .715-.099 1.22-.299 1.516-.2.302-.52.453-.963.453v2.068c1.152 0 1.984-.295 2.494-.885.516-.59.772-1.663.772-3.218V14.84c0-1.379.404-2.069 1.209-2.069v-2.035c-.805 0-1.21-.696-1.21-2.09V6.113c0-1.49-.255-2.54-.77-3.152-.516-.62-1.348-.93-2.495-.93zm-3.054 4.46c-.455 0-.886.057-1.293.173a3.056 3.056 0 0 0-1.078.527c-.308.241-.551.549-.731.924-.18.37-.27.817-.27 1.336 0 .663.165 1.227.493 1.695.33.468.831.864 1.502 1.188.263.125.509.249.736.37.227.12.422.244.586.374.168.13.299.271.394.424a.963.963 0 0 1 .145.521c0 .144-.03.28-.09.405a.9.9 0 0 1-.275.318c-.12.088-.272.158-.455.21a2.34 2.34 0 0 1-.635.075c-.415 0-.825-.083-1.233-.25a3.644 3.644 0 0 1-1.13-.763v2.222a3.68 3.68 0 0 0 1.101.418c.427.093.875.139 1.346.139.459 0 .894-.05 1.305-.152a3.002 3.002 0 0 0 1.09-.5c.31-.237.556-.543.736-.918.183-.38.275-.849.275-1.405 0-.403-.052-.755-.156-1.056a2.542 2.542 0 0 0-.45-.813 3.295 3.295 0 0 0-.704-.633 6.754 6.754 0 0 0-.922-.535 12.4 12.4 0 0 1-.676-.348c-.2-.115-.37-.231-.51-.347a1.502 1.502 0 0 1-.322-.375.91.91 0 0 1-.115-.453c0-.153.033-.288.101-.408a.948.948 0 0 1 .29-.32c.123-.089.275-.156.454-.202a2.18 2.18 0 0 1 .598-.078c.16 0 .326.015.502.043.18.028.36.07.539.13.18.056.354.13.522.218.171.088.329.188.472.304V6.871a4.039 4.039 0 0 0-.957-.285 6.448 6.448 0 0 0-1.185-.096zm-8.774.165l-3.123 9.967h2.094l.605-2.217h3.053l.61 2.217h2.107L9.869 6.656H7.576zm1.072 1.78h.047c.028.347.077.646.145.896l.922 3.35H7.564l.934-3.377c.08-.288.13-.578.15-.87z" font-weight="400" font-size="51.019" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="android" xmlns="http://www.w3.org/2000/svg"><path d="M15 5h-1V4h1m-5 1H9V4h1m5.53-1.84L16.84.85c.19-.19.19-.51 0-.71a.513.513 0 0 0-.71 0l-1.48 1.48C13.85 1.23 12.95 1 12 1c-.96 0-1.86.23-2.66.63L7.85.14a.501.501 0 0 0-.7 0c-.2.2-.2.52 0 .71l1.31 1.31C6.97 3.26 6 5 6 7h12c0-2-1-3.75-2.47-4.84M20.5 8A1.5 1.5 0 0 0 19 9.5v7a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 20.5 8m-17 0A1.5 1.5 0 0 0 2 9.5v7A1.5 1.5 0 0 0 3.5 18 1.5 1.5 0 0 0 5 16.5v-7A1.5 1.5 0 0 0 3.5 8M6 18a1 1 0 0 0 1 1h1v3.5A1.5 1.5 0 0 0 9.5 24a1.5 1.5 0 0 0 1.5-1.5V19h2v3.5a1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5V19h1a1 1 0 0 0 1-1V8H6v10z" fill="#c0ca33"/></symbol><symbol viewBox="0 0 24 24" id="angular" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="angular-component" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#0288d1"/></symbol><symbol viewBox="0 0 24 24" id="angular-directive" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ab47bc"/></symbol><symbol viewBox="0 0 24 24" id="angular-guard" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-pipe" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#00897b"/></symbol><symbol viewBox="0 0 24 24" id="angular-resolver" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-routing" xmlns="http://www.w3.org/2000/svg"><path d="M11 10H5L3 8l2-2h6V3l1-1 1 1v1h6l2 2-2 2h-6v2h6l2 2-2 2h-6v6a2 2 0 0 1 2 2H9a2 2 0 0 1 2-2V10z" fill="#43a047"/></symbol><symbol viewBox="0 0 24 24" id="angular-service" xmlns="http://www.w3.org/2000/svg"><path d="M12.102 2.625l8.84 3.15-1.34 11.7-7.5 4.15-7.5-4.15-1.34-11.7 8.84-3.15m0 2.1l-5.53 12.4h2.06l1.11-2.78h4.7l1.11 2.78h2.05l-5.5-12.4m1.62 7.9h-3.23l1.61-3.87z" fill="#ffca28"/></symbol><symbol viewBox="0 0 100 100" id="apiblueprint" xmlns="http://www.w3.org/2000/svg"><title>api-blueprint</title><path d="M50.133 7.521A16.998 16.998 0 0 0 33.135 24.52a16.998 16.998 0 0 0 4.945 11.974L24.861 57.398a16.998 16.998 0 0 0-3.175-.308A16.998 16.998 0 0 0 4.688 74.088a16.998 16.998 0 0 0 16.998 16.998 16.998 16.998 0 0 0 16.998-16.998 16.998 16.998 0 0 0-7.063-13.773l12.576-19.89a16.998 16.998 0 0 0 5.936 1.093 16.998 16.998 0 0 0 6.154-1.155l12.537 19.83a16.998 16.998 0 0 0-7.244 13.895 16.998 16.998 0 0 0 16.998 17 16.998 16.998 0 0 0 16.998-17A16.998 16.998 0 0 0 78.578 57.09a16.998 16.998 0 0 0-2.95.262L62.337 36.327A16.998 16.998 0 0 0 67.13 24.52 16.998 16.998 0 0 0 50.132 7.522z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="applescript" xmlns="http://www.w3.org/2000/svg"><path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" fill="#78909c"/></symbol><symbol viewBox="0 0 24 24" id="appveyor" xmlns="http://www.w3.org/2000/svg"><path d="M12 2c-.084 0-.165.008-.248.01a10 10 0 0 0-.266.01 9.952 9.952 0 0 0-.754.066 10 10 0 0 0-.148.018 9.855 9.855 0 0 0-.93.177 10 10 0 0 0-.07.02c-.196.049-.392.1-.584.16v.012a10 10 0 0 0-2 .875V3.34c-.02.012-.038.027-.059.039a10 10 0 0 0-.953.635c-.09.067-.172.142-.26.213a10 10 0 0 0-.628.546c-.109.104-.211.211-.315.319a10 10 0 0 0-.476.539c-.1.12-.201.237-.295.361a10 10 0 0 0-.52.766c-.088.143-.17.288-.252.435a10 10 0 0 0-.363.723c-.072.161-.136.327-.2.492a10 10 0 0 0-.269.778c-.02.067-.044.131-.062.199a10 10 0 0 0-.008.027c-.098.364-.166.728-.22 1.09-.012.077-.024.153-.034.23a9.85 9.85 0 0 0-.08 1.182c0 .03-.006.057-.006.086a10 10 0 0 0 .008.148c.001.094-.002.188.002.282l.011.004a10 10 0 0 0 .333 2.158l-.012-.004c.012.047.033.091.047.139a10 10 0 0 0 .322.955c.02.052.037.106.059.158a10 10 0 0 0 .503 1.035c.065.116.14.226.21.34a10 10 0 0 0 .423.64c.092.128.187.252.285.375a10 10 0 0 0 .448.52c.112.123.222.248.341.365a10 10 0 0 0 .803.719 10 10 0 0 0 .01.006c.099.078.207.146.309.22a10 10 0 0 0 .648.442c.138.085.28.163.424.242a10 10 0 0 0 .715.358c.114.051.226.106.343.154a10 10 0 0 0 1.133.389c.016.004.031.01.047.015a10 10 0 0 0 .461.098 10 10 0 0 0 .482.103 10 10 0 0 0 .418.051 10 10 0 0 0 .575.065 10 10 0 0 0 .144.005A10 10 0 0 0 12 22a10 10 0 0 0 .197-.01 10 10 0 0 0 .496-.025 10 10 0 0 0 .49-.043 10 10 0 0 0 .489-.074 10 10 0 0 0 .51-.098 10 10 0 0 0 .47-.12 10 10 0 0 0 .477-.14 10 10 0 0 0 .47-.172 10 10 0 0 0 .481-.197 10 10 0 0 0 .414-.201 10 10 0 0 0 .475-.252 10 10 0 0 0 .39-.238 10 10 0 0 0 .452-.301 10 10 0 0 0 .38-.291 10 10 0 0 0 .385-.315 10 10 0 0 0 .375-.347 10 10 0 0 0 .36-.363 10 10 0 0 0 .293-.334 10 10 0 0 0 .353-.434 10 10 0 0 0 .28-.393 10 10 0 0 0 .263-.4 10 10 0 0 0 .264-.461 10 10 0 0 0 .228-.436 10 10 0 0 0 .195-.437 10 10 0 0 0 .196-.48 10 10 0 0 0 .228-.69 10 10 0 0 0 .028-.094 10 10 0 0 0 .021-.066 10 10 0 0 0 .098-.461 10 10 0 0 0 .103-.482 10 10 0 0 0 .051-.418 10 10 0 0 0 .065-.575 10 10 0 0 0 .005-.144A10 10 0 0 0 22 12a10 10 0 0 0-.01-.197 10 10 0 0 0-.025-.496 10 10 0 0 0-.043-.49 10 10 0 0 0-.074-.489 10 10 0 0 0-.098-.51 10 10 0 0 0-.12-.47 10 10 0 0 0-.14-.477 10 10 0 0 0-.172-.47 10 10 0 0 0-.197-.481 10 10 0 0 0-.201-.414 10 10 0 0 0-.252-.475 10 10 0 0 0-.238-.39 10 10 0 0 0-.301-.452 10 10 0 0 0-.291-.38 10 10 0 0 0-.315-.385 10 10 0 0 0-.347-.375 10 10 0 0 0-.363-.36 10 10 0 0 0-.334-.293 10 10 0 0 0-.434-.353 10 10 0 0 0-.393-.28 10 10 0 0 0-.4-.263 10 10 0 0 0-.461-.264 10 10 0 0 0-.436-.228 10 10 0 0 0-.437-.196 10 10 0 0 0-.48-.195 10 10 0 0 0-.69-.228 10 10 0 0 0-.094-.028 10 10 0 0 0-.066-.021 10 10 0 0 0-.461-.098 10 10 0 0 0-.482-.103 10 10 0 0 0-.418-.051 10 10 0 0 0-.575-.065 10 10 0 0 0-.144-.005A10 10 0 0 0 12 2zm-.016 5.002a5 5 0 0 1 .262.01 5 5 0 0 1 .227.011 5 5 0 0 1 .341.05 5 5 0 0 1 .135.019 5 5 0 0 1 .014.004 5 5 0 0 1 .115.025 5 5 0 0 1 .303.076 5 5 0 0 1 .265.086 5 5 0 0 1 .2.074 5 5 0 0 1 .242.106 5 5 0 0 1 .228.11 5 5 0 0 1 .196.109 5 5 0 0 1 .244.15 5 5 0 0 1 .17.12 5 5 0 0 1 .224.171 5 5 0 0 1 .186.16 5 5 0 0 1 .176.164 5 5 0 0 1 .172.18 5 5 0 0 1 .177.203 5 5 0 0 1 .133.172 5 5 0 0 1 .16.223 5 5 0 0 1 .133.214 5 5 0 0 1 .12.21 5 5 0 0 1 .107.216 5 5 0 0 1 .109.24 5 5 0 0 1 .084.223 5 5 0 0 1 .08.242 5 5 0 0 1 .07.264 5 5 0 0 1 .047.207 5 5 0 0 1 .045.277 5 5 0 0 1 .028.227 5 5 0 0 1 .02.351 5 5 0 0 1 .003.079 5 5 0 0 1-.012.271 5 5 0 0 1-.011.227 5 5 0 0 1-.05.341 5 5 0 0 1-.019.135 5 5 0 0 1-.004.014 5 5 0 0 1-.025.115 5 5 0 0 1-.076.303 5 5 0 0 1-.086.265 5 5 0 0 1-.074.2 5 5 0 0 1-.106.242 5 5 0 0 1-.11.228 5 5 0 0 1-.109.196 5 5 0 0 1-.15.244 5 5 0 0 1-.12.17 5 5 0 0 1-.171.224 5 5 0 0 1-.16.186 5 5 0 0 1-.164.176 5 5 0 0 1-.18.172 5 5 0 0 1-.203.177l-.002.002c-.018.019-.028.035-.047.053l-3.959 5.09-3.05-.979a141.684 141.684 0 0 0 3.177-3.084 5 5 0 0 1-.103-.015 5 5 0 0 1-.149-.024 5 5 0 0 1-.115-.025 5 5 0 0 1-3.57-3.04 5.072 5.072 0 0 1-.206-.661 5 5 0 0 1-.033-.147c-.025-.118-.036-.24-.054-.36-.987.993-1.964 1.993-2.954 3.05l-.98-3.053 5.092-3.957c.043-.044.082-.07.125-.11a5 5 0 0 1 .71-.634c.18-.13.367-.25.561-.356a5 5 0 0 1 .16-.08 4.94 4.94 0 0 1 .516-.222 5 5 0 0 1 .147-.057c.211-.07.43-.123.654-.164a5 5 0 0 1 .172-.027c.236-.035.476-.058.722-.059zM12 9a3 3 0 0 0-.053.002 3 3 0 0 0-.166.01 3 3 0 0 0-.133.011 3 3 0 0 0-.17.026 3 3 0 0 0-.113.021 3 3 0 0 0-.19.05 3 3 0 0 0-.103.03 3 3 0 0 0-.16.057 3 3 0 0 0-.129.053 3 3 0 0 0-.146.072 3 3 0 0 0-.12.063 3 3 0 0 0-.132.082 3 3 0 0 0-.123.08 3 3 0 0 0-.116.088 3 3 0 0 0-.126.105 3 3 0 0 0-.1.094 3 3 0 0 0-.111.111 3 3 0 0 0-.096.107 3 3 0 0 0-.094.116 3 3 0 0 0-.098.136 3 3 0 0 0-.072.11 3 3 0 0 0-.076.133 3 3 0 0 0-.07.132 3 3 0 0 0-.063.14 3 3 0 0 0-.054.14 3 3 0 0 0-.077.228 3 3 0 0 0-.007.026 3 3 0 0 0-.03.138 3 3 0 0 0-.031.149 3 3 0 0 0-.014.11 3 3 0 0 0-.02.183 3 3 0 0 0-.001.052A3 3 0 0 0 9 12a3 3 0 0 0 .002.053 3 3 0 0 0 .01.166 3 3 0 0 0 .011.133 3 3 0 0 0 .026.17 3 3 0 0 0 .021.113 3 3 0 0 0 .05.19 3 3 0 0 0 .03.103 3 3 0 0 0 .057.16 3 3 0 0 0 .053.129 3 3 0 0 0 .072.146 3 3 0 0 0 .063.12 3 3 0 0 0 .082.132 3 3 0 0 0 .08.123 3 3 0 0 0 .088.116 3 3 0 0 0 .105.126 3 3 0 0 0 .094.1 3 3 0 0 0 .111.111 3 3 0 0 0 .107.096 3 3 0 0 0 .116.094 3 3 0 0 0 .136.098 3 3 0 0 0 .11.072 3 3 0 0 0 .133.076 3 3 0 0 0 .132.07 3 3 0 0 0 .135.06 3 3 0 0 0 .153.061 3 3 0 0 0 .216.07 3 3 0 0 0 .004.003 3 3 0 0 0 .026.007 3 3 0 0 0 .138.03 3 3 0 0 0 .149.031 3 3 0 0 0 .11.014 3 3 0 0 0 .183.02 3 3 0 0 0 .011.001 3 3 0 0 0 .041 0A3 3 0 0 0 12 15a3 3 0 0 0 .053-.002 3 3 0 0 0 .166-.01 3 3 0 0 0 .133-.011 3 3 0 0 0 .17-.026 3 3 0 0 0 .113-.021 3 3 0 0 0 .19-.05 3 3 0 0 0 .103-.03 3 3 0 0 0 .16-.057 3 3 0 0 0 .129-.053 3 3 0 0 0 .146-.072 3 3 0 0 0 .12-.063 3 3 0 0 0 .132-.082 3 3 0 0 0 .123-.08 3 3 0 0 0 .116-.088 3 3 0 0 0 .126-.105 3 3 0 0 0 .1-.094 3 3 0 0 0 .111-.111 3 3 0 0 0 .096-.107 3 3 0 0 0 .094-.116 3 3 0 0 0 .098-.136 3 3 0 0 0 .072-.11 3 3 0 0 0 .076-.133 3 3 0 0 0 .07-.132 3 3 0 0 0 .06-.135 3 3 0 0 0 .061-.153 3 3 0 0 0 .07-.216 3 3 0 0 0 .003-.004 3 3 0 0 0 .007-.026 3 3 0 0 0 .03-.138 3 3 0 0 0 .031-.149 3 3 0 0 0 .002-.008 3 3 0 0 0 .012-.101 3 3 0 0 0 .02-.184 3 3 0 0 0 .001-.011 3 3 0 0 0 0-.041A3 3 0 0 0 15 12a3 3 0 0 0-.002-.053 3 3 0 0 0-.01-.166 3 3 0 0 0-.011-.133 3 3 0 0 0-.026-.17 3 3 0 0 0-.021-.113 3 3 0 0 0-.05-.19 3 3 0 0 0-.03-.103 3 3 0 0 0-.057-.16 3 3 0 0 0-.053-.129 3 3 0 0 0-.072-.146 3 3 0 0 0-.063-.12 3 3 0 0 0-.082-.132 3 3 0 0 0-.08-.123 3 3 0 0 0-.088-.116 3 3 0 0 0-.105-.126 3 3 0 0 0-.094-.1 3 3 0 0 0-.111-.111 3 3 0 0 0-.107-.096 3 3 0 0 0-.116-.094 3 3 0 0 0-.136-.098 3 3 0 0 0-.11-.072 3 3 0 0 0-.133-.076 3 3 0 0 0-.132-.07 3 3 0 0 0-.14-.063 3 3 0 0 0-.14-.054 3 3 0 0 0-.228-.077 3 3 0 0 0-.026-.007 3 3 0 0 0-.138-.03 3 3 0 0 0-.149-.031 3 3 0 0 0-.008-.002 3 3 0 0 0-.101-.012 3 3 0 0 0-.184-.02 3 3 0 0 0-.011-.001 3 3 0 0 0-.041 0A3 3 0 0 0 12 9z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 720 720" id="arduino" xmlns="http://www.w3.org/2000/svg"><defs><symbol id="ana" preserveAspectRatio="xMinYMin meet" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke-opacity="100%" stroke-width="60" stroke="#00979c" d="M174 30a10.5 10.1 0 0 0 0 280C364 320 344 30 544 30a10.5 10.1 0 0 1 0 280C354 320 374 30 174 30"/><path d="M528 205v-32.8h-32.5v-13.7H528V126h13.9v32.5h32.5v13.7h-32.5V205H528z" text-anchor="middle" fill="#00979c" stroke-width="20" stroke="#00979c" font-family="sans-serif" font-size="167"/><path fill="#00979c" stroke="#00979c" stroke-width="23.6" transform="matrix(1.56 0 0 .64 -366 .528)" d="M321 266v-17.4h53.3V266H321z"/></symbol></defs><title>Layer 1</title><use x="20.063" y="360.85" transform="matrix(.997 0 0 .997 -18.596 -159.19)" xlink:href="#ana"/></symbol><symbol viewBox="0 0 24 24" id="assembly" xmlns="http://www.w3.org/2000/svg"><path d="M1.746 1.566v20.905H5.13v-2.088H3.438V3.656h1.69v-2.09H1.747zm17.219 0v2.09h1.693v16.727h-1.693v2.09h3.383V1.566h-3.383zM15.196 3.988c-.5 0-.93.076-1.29.225-.359.15-.652.372-.877.671-.226.302-.39.673-.494 1.108a6.715 6.715 0 0 0-.155 1.54c0 .573.049 1.083.15 1.528.1.442.264.811.49 1.11.222.298.512.524.872.676.36.153.795.23 1.304.23.518 0 .954-.075 1.308-.224.353-.153.643-.376.869-.671.219-.29.38-.661.484-1.112.104-.454.156-.967.156-1.54 0-.573-.052-1.079-.152-1.515a2.92 2.92 0 0 0-.485-1.106 2.09 2.09 0 0 0-.868-.686c-.354-.155-.79-.234-1.312-.234zm-6.814.12a.941.941 0 0 1-.138.458.849.849 0 0 1-.356.296A1.71 1.71 0 0 1 7.385 5a5.244 5.244 0 0 1-.631.037v1.11H8.19v3.6H6.754v1.188h4.545V9.745H9.894V4.11H8.382zm6.814 1.138c.375 0 .643.176.805.527.161.348.241.933.241 1.756 0 .814-.082 1.399-.247 1.756-.164.356-.43.534-.799.534-.369 0-.636-.178-.8-.534-.165-.357-.248-.941-.248-1.749 0-.829.082-1.415.243-1.763.162-.35.43-.527.805-.527zm-6.33 7.64c-.5 0-.93.073-1.29.223-.359.15-.651.374-.877.673-.225.302-.39.67-.494 1.106a6.715 6.715 0 0 0-.155 1.54c0 .573.05 1.082.15 1.527.1.442.264.814.49 1.112.222.3.514.525.874.677.36.152.793.229 1.302.229.519 0 .954-.076 1.308-.225.354-.153.643-.376.869-.672.22-.29.38-.66.484-1.111.104-.455.156-.967.156-1.54 0-.573-.05-1.079-.15-1.515a2.923 2.923 0 0 0-.487-1.106 2.084 2.084 0 0 0-.867-.686c-.353-.156-.791-.232-1.313-.232zm5.846.119a.941.941 0 0 1-.138.457.85.85 0 0 1-.356.296 1.71 1.71 0 0 1-.503.137 5.245 5.245 0 0 1-.631.037v1.112h1.435v3.597h-1.435v1.189h4.545v-1.189h-1.405v-5.636h-1.512zm-5.846 1.137c.375 0 .643.176.805.527.162.347.241.933.241 1.756 0 .813-.08 1.399-.245 1.755-.164.357-.432.534-.8.534-.37 0-.637-.177-.802-.534-.164-.356-.245-.939-.245-1.746 0-.83.08-1.418.242-1.765.161-.35.43-.527.804-.527z" fill="#ff6e40"/></symbol><symbol viewBox="0 0 24 24" id="aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="api" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apa"/><linearGradient id="apa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#apb"/><linearGradient id="apb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#apc"/><linearGradient id="apc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#apd"/><linearGradient id="apd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#ape"/><linearGradient id="ape" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="apn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#apf"/><linearGradient id="apf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="apo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#apg"/><linearGradient id="apg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="app" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#aph"/><linearGradient id="aph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><g transform="rotate(11.282 -1.694 21.569) scale(.47102)" clip-rule="evenodd" fill="none" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#api)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#apj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#apk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#apl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#apm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#apn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#apo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#app)"/></g></symbol><symbol viewBox="0 0 24 24" id="autohotkey" xmlns="http://www.w3.org/2000/svg"><path d="M5 3c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H5zm3.668 3.447a.9.9 0 0 1 .652.256.84.84 0 0 1 .262.625c0 .34-.014.852-.041 1.537-.022.68-.033 1.19-.033 1.53 0 .111-.016.326-.047.644a6.149 6.149 0 0 0-.033.68l2.578-.485c1.007-.179 1.874-.281 2.603-.308.018-.3.048-1.105.088-2.416.01-.345.115-.742.317-1.19.25-.55.533-.826.851-.826.237 0 .448.08.631.236.197.17.295.382.295.637a.775.775 0 0 1-.025.201c-.09.327-.135.612-.135.854 0 .125-.014.32-.041.584-.023.26-.033.453-.033.578 0 .425-.022 1.056-.067 1.893a38.963 38.963 0 0 0-.068 1.892c0 .327.025.816.074 1.465.05.649.074 1.136.074 1.463a.84.84 0 0 1-.261.625.893.893 0 0 1-.65.254 1 1 0 0 1-.686-.254.777.777 0 0 1-.29-.611c0-.327-.015-.818-.046-1.471a39.552 39.552 0 0 1-.041-1.47c0-.256.004-.482.013-.679-.702.032-1.57.142-2.603.33-.86.157-1.719.316-2.578.477-.01.304-.042.812-.096 1.523a22.354 22.354 0 0 0-.066 1.538.84.84 0 0 1-.262.625.893.893 0 0 1-.65.253.898.898 0 0 1-.653-.253.84.84 0 0 1-.262-.625c0-.452.038-1.128.114-2.028.08-.9.12-1.575.12-2.027 0-.573.015-1.436.042-2.586.027-1.155.04-2.017.04-2.59a.84.84 0 0 1 .263-.625.895.895 0 0 1 .65-.256z" fill="#4caf50"/></symbol><symbol viewBox="0 0 24 24" id="autoit" xmlns="http://www.w3.org/2000/svg"><defs id="ardefs8"><style id="arstyle4482">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style><style id="arstyle4510">.cls-1{fill:#5d83ac}.cls-2{fill:#f0f0f0;fill-rule:evenodd}</style></defs><g id="arg4522" transform="translate(-59.538 -26.404) scale(.0555)"><path d="M12.8 2.133A10.666 10.666 0 0 0 2.136 12.799 10.666 10.666 0 0 0 12.8 23.465a10.666 10.666 0 0 0 10.668-10.666A10.666 10.666 0 0 0 12.8 2.133zm.15 4.713c.456 0 .836.105 1.142.314.306.21.565.469.78.78l6.089 8.812H9.627l1.82-2.506h3.36c.315 0 .589.01.822.03a11.93 11.93 0 0 1-.473-.663 39.13 39.13 0 0 0-.517-.75l-1.748-2.578-4.577 6.467H4.746l6.25-8.813c.204-.281.46-.534.772-.757.31-.224.705-.336 1.181-.336z" transform="matrix(16.89188 0 0 16.89188 1072.761 475.745)" id="arcircle4514" fill="#1976d2" stroke-width=".026"/></g></symbol><symbol viewBox="0 0 213.33333 213.33333" id="babel" xmlns="http://www.w3.org/2000/svg"><path d="M50.22 199.659c-.875-.406-1.261-1.6-.857-2.652.404-1.053.12-1.914-.63-1.914s-1.615.748-1.92 1.663c-.328.983-1.27.302-2.304-1.667-.962-1.831-3.718-5.533-6.126-8.226-9.418-10.535-7.71-27.444 5.432-53.77 12.459-24.96 23.117-39.033 45.966-60.696 30.229-28.66 52.679-46.223 70.587-55.22 10.98-5.518 13.025-5.059 2.778.624-11.004 6.102-11.378 6.359-10.512 7.226.33.33 7.306-2.67 15.504-6.667 15.87-7.737 16.34-7.912 16.34-6.082 0 .652-4.95 3.738-11 6.858-13.062 6.736-12.722 6.48-10.472 7.872 1.117.69 5.428-.582 11.54-3.406 5.367-2.48 10.397-4.508 11.179-4.508 2.755 0-3.928 5.302-11.541 9.157-20.437 10.35-68.937 46.043-68.07 50.097.166.777-5.792 7.639-13.241 15.248-15.257 15.587-26.14 30.002-33.748 44.706-6.379 12.326-7.457 17.734-5.385 26.996 3.482 15.56 11.592 18.366 31.482 10.895 28.228-10.603 45.758-28.704 47.022-48.556.602-9.442-1.317-13.479-8.52-17.93-4.01-2.48-5.268-2.621-12.065-1.365-4.173.771-10.153 2.906-13.289 4.744s-6.455 3.34-7.377 3.34c-.922 0-3.216 1.336-5.096 2.968-1.88 1.633.48-1.13 5.247-6.14 6.82-7.167 7.956-8.9 5.333-8.132-5.208 1.525-10.194 4.33-15.649 8.803-2.76 2.264-.923.175 4.08-4.641 11.565-11.131 21.183-15.97 33.088-16.641 17.097-.966 27.254 5.805 31.964 21.31 2.435 8.017 2.609 10.24 1.353 17.37-1.65 9.361-7.034 21.553-15.593 35.307-4.398 7.067-8.434 11.427-15.588 16.844-9.166 6.94-15.654 11.02-15.654 9.845 0-.295 2.455-2.161 5.455-4.147 8.818-5.835 5.075-5.377-8.326 1.02-6.854 3.27-15.199 6.593-18.542 7.38-7.106 1.675-30.527 3.164-32.846 2.089zm-8.408-19.899c0-1.1-.6-2-1.333-2-.734 0-1.334.9-1.334 2s.6 2 1.333 2c.734 0 1.334-.9 1.334-2zm89.255-8.204c1.53-1.945 2.473-3.845 2.097-4.222-.377-.377-.836-.435-1.02-.13-.182.306-1.787 2.206-3.565 4.223-1.778 2.016-2.571 3.666-1.763 3.666s2.72-1.591 4.25-3.536zm-77.644-1.745c-.82-2.172-1.74-3.7-2.045-3.396-.951.952 1.088 7.345 2.343 7.345.656 0 .522-1.777-.298-3.95zm82.303-27.915c-.837-.837-3.217 2.55-3.184 4.53.012.734.896.178 1.965-1.235 1.07-1.413 1.618-2.896 1.219-3.295zm-66.238-36.904c-1.312-1.312-3.676.702-3.676 3.133 0 2.035.175 2.031 2.254-.047 1.24-1.24 1.88-2.628 1.422-3.086zm39.657.768c4.403-2.196 6.8-3.986 5.333-3.982-2.838.01-16.667 6.028-16.667 7.254 0 1.6 3.717.527 11.333-3.272zm16.667-5.333c0-.733-.9-1.333-2-1.333s-2 .6-2 1.333.9 1.333 2 1.333 2-.6 2-1.333zm-3.334-3.923l5.334-1.104-7.334-.133c-4.033-.073-8.233.45-9.333 1.16-2.539 1.64 3.572 1.682 11.333.077zm35.738-63.976c2.788-1.69 4.765-3.376 4.393-3.748-.947-.947-11.942 5.654-14.237 8.548-1.792 2.258-1.714 2.276 1.44.329a1452.76 1452.76 0 0 1 8.403-5.13z" fill="#ffca28" stroke-width="1.333"/></symbol><symbol viewBox="0 0 400 400" fill-opacity=".05" id="bithound" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.88 0 0 .88 24.121 2.895)" fill="#e53935" fill-opacity="1"><path d="M370.5 207c-1.5-14.8-4.8-29.9-9.5-44-13.5-40.3-38.6-81.6-70.3-110.1-1.4-1.2-6.7-4.4-8.7-3.3-5.2 2.9 4.6 22.8 5.8 26.4 7.4 22 12.1 45.3 6.8 68.3-7.1 30.4-30.4 51.7-61.5 54.3-17.1 1.4-34.3-.5-51.4 1.5-25.6 3-51.7 11.8-68 32.8-1.9 2.4-3.6 5.1-5.2 7.9h-.4c-6.3.7-12.6-2-15.7-3.7-.8-.5-1.6-.9-2.2-1.2-19-10.5-33-34-41.6-53.4-3.9-9-7.2-18.4-9.3-27.9-1-4.3-1.1-8.8-1.3-13.2-.1-2.7.3-6.5-1.2-8.9-3.3-5.2-7.5-.2-8.2 4-1.1 6.9-2.1 13.7-1.8 20.7.5 11.8 3.8 23.5 8 34.5 6.2 16.2 14.9 31.1 26.2 44.4 4.7 5.5 9.7 10.6 15.1 15.3 4.8 4.3 10.9 7.7 14.5 13.2 4.2 6.3 4.9 14.1 4.5 21.4-1 19.3-1.6 37.4 3.9 56.2 4.8 16.7 10.8 33.8 20.8 48.1 5 7.1 11.2 14.6 18 19.9 4.6 3.6 13.3 4 8.3-9.2-11.1-29.3-12.1-59.7 5.2-87.1 14.5-22.8 40.1-43.1 69-39.5 42.5 5.3 72.1 44.3 70 86-.6 11.7-1 21.7-4.7 32.7-1.5 4.4-2.6 10-1.5 14.6 1.8 7.8 10.5 4.9 14.3-.2 10.3-14 21.1-27.6 30.8-42 31.6-47.2 47-101.8 41.3-158.5z"/><path d="M132.4 92.1c.7 2.3 1.4 4.8 1.9 7.5.1 1.1.4 2.3 1 3.4 2.6 6.8 8.9 10.5 14.8 14 3.6 2.2 10.1 4.3 14.1 5.9 5.2 2.1 16.4-.6 21.7-1 12.2-1 23.5-5.3 34.7 1.2-57.4 67.3-3.2 82.3 38.8 49.9 48-37 2.8-124.3 2.8-124.3s-1-6.8-19.2-10.8c-1.7-.9-3.4-1.7-5.1-2.4-18-8.3-34.2 5.3-47.2 16.4-3.8 3.2-7.5 6.4-11.5 9.4-5.4 4-11.2 7.3-17.3 10.2-6.4 3-14 6.4-21.1 6.7-1 0-2.9.2-4.9.6-3.1.3-4.7 1.1-5.4 2.5-1.2 1-2 2.4-1.8 4.2.2 2.5 1.4 4.6 2.7 6.2.4.1.7.3 1 .4z"/></g></symbol><symbol viewBox="0 0 400.00001 399.99999" id="bower" xmlns="http://www.w3.org/2000/svg"><g transform="translate(12.061 33.203) scale(.81733)"><path d="M447.61 200.08c-23.139-22.234-138.85-36.114-175.36-40.154a107.137 107.137 0 0 0 4.517-12.944 146.107 146.107 0 0 1 15.905-5.901c.677 1.997 3.865 9.648 5.682 13.279 73.415 2.025 77.184-54.557 80.17-70.058 2.92-15.157 2.771-29.802 27.953-56.575-37.516-10.933-91.467 16.945-109.54 58.437-6.79-2.545-13.597-4.424-20.328-5.586-4.824-19.46-29.944-73.672-95.863-73.672-83.46 0-174.43 68.853-174.43 185.41 0 97.976 66.891 183.84 104.68 183.84 16.505 0 30.703-12.36 34.036-23.44 2.795 7.597 11.368 31.213 14.184 37.225 4.162 8.89 23.41 16.583 31.833 7.357 10.83 6.017 30.703 9.641 41.534-6.405 20.86 4.412 39.3-8.026 39.702-22.868 10.235-.546 15.256-14.918 13.021-26.363-1.647-8.426-19.248-38.66-26.113-49.098 13.59 11.054 48.013 14.183 52.194.007 21.911 17.198 56.057 8.171 58.765-5.815 26.624 6.917 57.16-8.276 52.146-26.676 42.771-2.958 37.296-48.464 25.296-59.996z" fill="#543729" stroke-width=".973"/><path d="M328.514 103.025c9.212-18.277 20.788-38.234 35.409-50.58-16.093 6.485-31.981 25.873-41.375 46.595a144.914 144.914 0 0 0-14.552-8.132c13.105-27.972 43.555-51.332 77.112-53.157-22.477 20.385-14.498 62.754-32.979 85.183-5.288-5.311-17.43-15.562-23.615-19.909zm-14.53 29.762c.01-.7.272-6.094.763-8.557-1.288-.304-9.3-1.87-13.476-1.772-.304 5.245 2.204 14.17 4.684 19.541 17.075-.358 29.408-5.471 36.667-10.172-6.18-2.88-16.726-5.442-24.745-6.974-.894 1.851-3.097 6.568-3.892 7.934z" fill="#00acee"/><g stroke-width=".973"><path d="M250.54 277.39c.004.024.015.057.018.082-2.165-4.657-4.463-10.314-7.208-17.708 10.688 15.557 44.184 7.533 42.427-6.407 16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455 28 5.4 54.832 10.783 63.256 12.938-5.595 9.123-18.339 15.566-37.549 11.089 10.38 14.14-9.773 31.105-37.844 21.76 6.18 13.883-18.814 26.38-47.22 11.91.361 13.889-35.24 15.488-49.315.143zm55.543-70.194c32.497 2.495 86.238 7.34 119.51 11.997-2.102-10.828-7.844-13.921-25.905-18.772-19.425 2.072-68.706 6.913-93.604 6.776z" fill="#2baf2b"/><path d="M285.78 253.36c16.395 12.336 50.143-2.055 42.471-19.353 16.423 7.653 35.168-7.745 30.964-14.455-33.103-6.383-67.84-12.788-75.719-13.908 4.78.254 12.702.797 22.59 1.556 24.899.137 74.18-4.704 93.604-6.775-31.452-7.975-95.666-19.613-140.01-22.48-2.055 3.003-5.833 8.097-12.413 13.51-19.403 41.053-54.557 68.34-93.454 68.34-11.335 0-24.018-1.912-38.233-6.456-8.865 9.497-46.661 16.694-77.329 1.641 24.326 56.961 80.74 94.984 143.19 94.984 52.591 0 75.912-53.704 70.808-67.914-1.238-3.45-6.145-14.889-8.891-22.283 10.689 15.556 44.185 7.532 42.429-6.408z" fill="#ffcc2f"/><path d="M253.91 145.27c4.644-2.526 20.69-12.253 35.981-15.908a67.843 67.843 0 0 1-.536-5.12c-10.032 2.403-28.945 10.51-39.784-.661 22.866 6.9 34.283-6.149 51.09-6.149 10.014 0 24.305 2.798 35.57 7.22-9.061-8.37-38.772-33.63-75.558-33.717-8.213 9.957-17.09 31.526-6.764 54.334z" fill="#cecece"/><path d="M115.58 253.33c14.215 4.544 26.898 6.457 38.233 6.457 38.896 0 74.05-27.29 93.454-68.341-14.351 11.978-39.291 22.228-78.241 22.228 34.694-7.866 64.56-25.156 79.753-50.427-10.68-16.998-22.263-54.603 7.07-84.33-4.512-14.497-26.475-52.766-75.095-52.766-84.85 0-155.17 71.001-155.17 166.15 0 22.525 4.547 43.65 12.67 62.664 30.666 15.054 68.462 7.858 77.327-1.64z" fill="#ef5734"/><path d="M141.03 108.45c0 21.644 17.546 39.191 39.19 39.191s39.192-17.548 39.192-39.191c0-21.644-17.548-39.191-39.192-39.191-21.644 0-39.19 17.547-39.19 39.191z" fill="#ffcc2f"/><path d="M156.76 108.45c0 12.958 10.507 23.463 23.463 23.463 12.96 0 23.464-10.506 23.464-23.463 0-12.959-10.504-23.464-23.464-23.464-12.957 0-23.463 10.506-23.463 23.464z" fill="#543729"/><ellipse cx="180.22" cy="98.044" rx="13.673" ry="8.501" fill="#fff"/></g></g></symbol><symbol viewBox="0 0 140 140" id="browserlist" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><path d="M70.314 10.066a59.828 59.828 0 0 0-59.828 59.828 59.828 59.828 0 0 0 59.828 59.828 59.828 59.828 0 0 0 59.828-59.828 59.828 59.828 0 0 0-59.828-59.828zm-4.836 8.785c.496 4.043 1.352 7.322 2.572 10.223 4.779-4.287 10.265-7.546 16.041-9.02-.981 3.938-1.357 7.295-1.261 10.43 6.026-2.314 12.349-3.404 18.3-2.706-3.182 2.413-5.482 4.717-7.128 7.015-2.201 12.074 6.858 20.43 14.779 24.551a5.128 5.128 0 0 1 5.183-3.888 5.128 5.128 0 0 1 3.7 8.435v.002c-.487 1.055-2.002 2.343-3.497 3.219-4.075 2.39-11.172 5.736-20.914 7.39.045 1.214.077 2.453.077 3.747 0 4.817-.485 8.291-1.385 10.699-3.3 13.313-12.648 26.76-24.695 31.95.357-4.083.197-7.485-.402-10.591-5.582 3.218-11.646 5.278-17.623 5.52h-.002c1.785-3.662 2.855-6.878 3.412-9.976-6.347.996-12.727.742-18.377-1.17 2.93-2.732 5.054-5.314 6.673-7.96-6.292-1.344-12.169-3.87-16.766-7.686 3.822-1.544 6.795-3.239 9.3-5.197-5.426-3.517-10.034-7.998-12.972-13.23 4.012-.07 7.321-.568 10.3-1.453-3.786-5.215-6.468-11.032-7.333-16.951 3.861 1.405 7.196 2.133 10.36 2.355-1.662-6.22-2.081-12.605-.768-18.436 3.03 2.634 5.824 4.48 8.63 5.815.678-6.406 2.576-12.52 5.893-17.496 1.926 3.622 3.914 6.391 6.111 8.672 2.93-5.754 6.9-10.798 11.791-14.262zm26.465 19.557c-2.395 5.514-1.665 11.297-.555 18.732a2.138 2.138 0 0 0 .28-4.178 3.419 3.419 0 1 1 .092 6.704c.574 3.882 1.157 8.18 1.421 13.125a67.143 67.143 0 0 0 3.25-.649c6.616-1.487 12.258-3.801 16.871-6.506.45-.264.884-.563 1.276-.867.366-.557.333-.957.035-1.285-4.831-1.245-10.891-4.53-15.258-8.795-4.764-4.653-7.428-10.164-7.412-16.281z" fill="#ffca28" stroke-width=".855"/></symbol><symbol viewBox="0 0 140 140" id="browserlist_light" xmlns="http://www.w3.org/2000/svg"><title>Browserslist logo</title><g transform="translate(10.823 10.1)" stroke-width=".855"><circle cx="59.492" cy="59.795" r="59.828" fill="#ffca28"/><path d="M54.656 8.752c-4.89 3.464-8.862 8.508-11.791 14.262-2.198-2.28-4.185-5.05-6.111-8.672-3.318 4.976-5.216 11.09-5.893 17.496-2.807-1.335-5.6-3.18-8.63-5.814-1.314 5.83-.895 12.216.767 18.436-3.164-.223-6.498-.95-10.36-2.356.865 5.92 3.548 11.737 7.333 16.951-2.978.885-6.287 1.383-10.3 1.453 2.939 5.233 7.547 9.714 12.972 13.23-2.505 1.959-5.478 3.654-9.299 5.198 4.596 3.815 10.474 6.341 16.766 7.685-1.62 2.647-3.743 5.228-6.674 7.96 5.65 1.912 12.03 2.166 18.377 1.17-.556 3.098-1.626 6.314-3.412 9.975h.002c5.977-.24 12.042-2.3 17.623-5.52.6 3.108.76 6.51.402 10.593 12.047-5.19 21.395-18.638 24.695-31.951.9-2.408 1.385-5.881 1.385-10.7 0-1.293-.031-2.531-.076-3.745 9.742-1.655 16.839-5.001 20.914-7.39 1.494-.877 3.01-2.165 3.496-3.22v-.002a5.128 5.128 0 0 0-3.7-8.435 5.128 5.128 0 0 0-5.183 3.889c-7.92-4.122-16.98-12.477-14.779-24.551 1.646-2.299 3.947-4.603 7.13-7.016-5.952-.698-12.276.392-18.302 2.707-.095-3.135.28-6.492 1.262-10.43-5.776 1.473-11.262 4.733-16.041 9.02-1.22-2.902-2.076-6.18-2.572-10.223zm26.465 19.557c-.015 6.117 2.648 11.628 7.412 16.281 4.366 4.265 10.426 7.55 15.258 8.795.298.328.331.728-.035 1.285-.392.304-.825.603-1.275.867-4.613 2.704-10.256 5.019-16.871 6.506-1.071.24-2.154.458-3.25.649-.265-4.945-.848-9.243-1.422-13.125a3.419 3.419 0 1 0-.092-6.703 2.138 2.138 0 0 1-.28 4.177c-1.11-7.435-1.84-13.218.555-18.732z" fill="#37474f"/></g></symbol><symbol viewBox="0 0 24 24" id="bucklescript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm14.1 8.858a5.5 5.5 0 0 1 1.26.145c.417.093.778.213 1.082.357v1.723h-.18a3.281 3.281 0 0 0-.959-.603 2.867 2.867 0 0 0-1.155-.247c-.14 0-.277.011-.416.035a1.4 1.4 0 0 0-.395.12.756.756 0 0 0-.291.231.54.54 0 0 0-.123.348c0 .198.065.35.196.456.13.104.376.2.738.288.237.057.466.11.683.164.22.054.455.128.706.222.496.188.86.444 1.095.77.238.32.357.738.357 1.253 0 .737-.271 1.336-.813 1.798-.538.46-1.27.689-2.197.689a5.447 5.447 0 0 1-1.402-.161 6.725 6.725 0 0 1-1.117-.416v-1.794h.183c.344.318.73.563 1.155.734.429.17.839.256 1.233.256.1 0 .235-.01.4-.03.166-.02.3-.055.403-.102a.97.97 0 0 0 .313-.225c.084-.09.127-.223.127-.4a.568.568 0 0 0-.183-.424c-.119-.12-.294-.213-.526-.276-.243-.067-.5-.128-.773-.185a5.523 5.523 0 0 1-.76-.227c-.544-.204-.936-.48-1.177-.828-.237-.351-.357-.786-.357-1.305 0-.697.27-1.265.81-1.703.54-.442 1.235-.663 2.083-.663zm-8.981.135h2.51c.521 0 .903.02 1.143.06.243.041.484.13.721.266.246.144.43.338.548.583.121.24.181.518.181.83 0 .36-.082.68-.247.959a1.697 1.697 0 0 1-.7.642v.04c.423.098.758.298 1.004.603.249.305.373.706.373 1.205 0 .361-.063.686-.19.97-.125.285-.296.52-.516.707a2.31 2.31 0 0 1-.845.472c-.304.094-.69.141-1.159.141H8.12v-7.478zm1.659 1.372v1.582h.262c.263 0 .486-.007.672-.017.185-.01.332-.043.44-.1.15-.077.248-.175.294-.295.046-.124.07-.266.07-.427a.91.91 0 0 0-.083-.371.518.518 0 0 0-.282-.277 1.187 1.187 0 0 0-.456-.086c-.18-.007-.433-.01-.76-.01h-.157zm0 2.873V18.1H9.9c.469 0 .804-.002 1.007-.006.202-.003.39-.046.56-.13a.712.712 0 0 0 .357-.33c.067-.142.099-.302.099-.483 0-.237-.04-.42-.121-.547-.078-.13-.214-.228-.405-.291a1.842 1.842 0 0 0-.538-.072 49.47 49.47 0 0 0-.716-.003h-.366z" fill="#26a69a" stroke-width="1.067"/></symbol><symbol viewBox="0 0 24 24" id="c" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 15.97l.42 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96-1.14-1.27-1.68-2.88-1.68-4.83C6 9.9 6.68 8.13 8 6.89 9.28 5.64 10.92 5 12.9 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.6 2.49-1.04-.34c-.4-.1-.87-.15-1.4-.15-1.15-.01-2.11.36-2.86 1.1-.76.73-1.14 1.85-1.18 3.34.01 1.36.37 2.42 1.08 3.2.71.77 1.7 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.09-.32z" fill="#0277bd"/></symbol><symbol viewBox="0 0 300 300" id="cabal" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -822.52)" fill-rule="evenodd" color="#000"><rect transform="matrix(-.98339 .18149 .60192 .79856 0 0)" x="405.55" y="967.22" width="107.25" height="156.59" rx="12.306" ry="12.31" fill="#2d9bbd"/><rect transform="matrix(-.98528 .17093 -.59175 .80612 0 0)" x="-1156.5" y="1461.9" width="108.34" height="123.15" rx="10.69" ry="12.31" fill="#4a4bcd"/><path d="M52.112 965.158c-1.343 3.515-26.292 23.248-25.744 27.277.548 4.03 29.812 16.023 32.04 19.027s10.545 41.668 13.603 42.5 18.828-31.274 21.548-32.932c2.72-1.658 32.808 2.503 34.15-1.01 1.343-3.515-18.174-35.352-18.721-39.381-.548-4.03 9.732-40.12 7.502-43.125-2.229-3.005-30.06 9.427-33.118 8.594-3.059-.833-26.793-27.3-29.514-25.643-2.72 1.657-.405 41.177-1.747 44.693z" fill="#2e5bc1"/></g></symbol><symbol viewBox="0 0 24 24" id="cake" xmlns="http://www.w3.org/2000/svg"><path d="M12.254 6.621a1.807 1.807 0 0 0 1.808-1.807c0-.344-.09-.66-.262-.932l-1.546-2.684-1.546 2.684a1.72 1.72 0 0 0-.262.932 1.808 1.808 0 0 0 1.808 1.807m4.158 9.04l-.967-.976-.976.976c-1.175 1.166-3.236 1.175-4.42 0l-.959-.976-.994.976a3.134 3.134 0 0 1-3.977.353v4.167a.904.904 0 0 0 .904.904h14.463a.904.904 0 0 0 .904-.904v-4.167a3.134 3.134 0 0 1-3.977-.353m1.265-6.328h-4.52V7.525H11.35v1.808H6.83a2.712 2.712 0 0 0-2.711 2.712v1.392c0 .977.795 1.772 1.771 1.772.489 0 .94-.18 1.248-.515l1.952-1.926 1.908 1.926c.669.669 1.835.669 2.504 0l1.916-1.926 1.944 1.926c.316.334.768.515 1.247.515.976 0 1.78-.795 1.78-1.772v-1.392a2.712 2.712 0 0 0-2.711-2.712z" fill="#ff7043" stroke-width=".904"/></symbol><symbol viewBox="0 0 24 24" id="certificate" xmlns="http://www.w3.org/2000/svg"><path d="M4 3c-1.11 0-2 .89-2 2v10a2 2 0 0 0 2 2h8v5l3-3 3 3v-5h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2H4m8 2l3 2 3-2v3.5l3 1.5-3 1.5V15l-3-2-3 2v-3.5L9 10l3-1.5V5M4 5h5v2H4V5m0 4h3v2H4V9m0 4h5v2H4v-2z" fill="#ff5722"/></symbol><symbol viewBox="0 0 24 24" id="changelog" xmlns="http://www.w3.org/2000/svg"><path d="M11 7v5.11l4.71 2.79.79-1.28-4-2.37V7m0-5C8.97 2 5.91 3.92 4.27 6.77L2 4.5V11h6.5L5.75 8.25C6.96 5.73 9.5 4 12.5 4a7.5 7.5 0 0 1 7.5 7.5 7.5 7.5 0 0 1-7.5 7.5c-3.27 0-6.03-2.09-7.06-5h-2.1c1.1 4.03 4.77 7 9.16 7 5.24 0 9.5-4.25 9.5-9.5A9.5 9.5 0 0 0 12.5 2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="clojure" xmlns="http://www.w3.org/2000/svg"><path d="M3.355 1.78c-.845 0-1.525.68-1.525 1.525v17.441c0 .845.68 1.525 1.525 1.525h17.442c.845 0 1.525-.68 1.525-1.525V3.305c0-.845-.68-1.526-1.525-1.526H3.355zm6.168 2.572h1.963l6.368 14.931H15.93l-3.38-8.086-3.349 8.086H7.21l4.346-10.38-2.032-4.551z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="cmake" xmlns="http://www.w3.org/2000/svg"><path d="M11.99 2.965L2.977 20.999l9.874-8.47-.863-9.564z" fill="#1e88e5"/><path d="M12.007 2.963l.002.29 1.312 14.498-.001.006.023.26 7.362 2.979h.416l-.158-.311-.114-.228h-.002l-8.84-17.494z" fill="#e53935"/><path d="M8.607 16.11L2.98 20.995h17.743v-.016L8.607 16.11z" fill="#7cb342"/></symbol><symbol class="bfmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate" xmlns="http://www.w3.org/2000/svg"><path class="bfsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#eee" stroke-width="2.849"/></symbol><symbol class="bgmain_logo__svg" viewBox="0 0 300 300.00001" id="code-climate_light" xmlns="http://www.w3.org/2000/svg"><path class="bgsymbol" d="M196.19 75.562l-51.846 51.561 30.766 30.766 21.08-21.08 59.252 59.537 30.481-30.766zm-61.246 60.961l-30.481-30.481-78.053 78.053-11.964 11.964 30.766 30.766 11.964-12.249 39.596-39.312 7.691-7.691 30.481 30.48 28.772 28.773 30.766-30.766-28.772-28.772z" fill="#455a64" stroke-width="2.849"/></symbol><symbol viewBox="0 0 24 24" id="coffee" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="coldfusion" xmlns="http://www.w3.org/2000/svg"><rect transform="rotate(90)" x="2.283" y="-21.86" width="19.487" height="19.487" ry="0" fill="#0d3858" stroke="#4dd0e1" stroke-width=".7"/><text x="6.653" y="16.426" fill="#4dd0e1" font-family="Calibri" font-size="29.001" font-weight="bold" letter-spacing="0" stroke-width=".725" word-spacing="0" style="line-height:1.25"><tspan x="6.653" y="16.426" font-family="'Segoe UI'" font-size="10.634" font-weight="normal">C<tspan font-size="11.844">f</tspan></tspan></text></symbol><symbol viewBox="0 0 24 24" id="conduct" xmlns="http://www.w3.org/2000/svg"><path d="M10 17l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9m-6-6a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#cddc39"/></symbol><symbol viewBox="0 0 24 24" id="console" xmlns="http://www.w3.org/2000/svg"><path d="M20 19V7H4v12h16m0-16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h16m-7 14v-2h5v2h-5m-3.42-4L5.57 9H8.4l3.3 3.3c.39.39.39 1.03 0 1.42L8.42 17H5.59z" fill="#ff7043"/></symbol><symbol viewBox="0 0 24 24" id="contributing" xmlns="http://www.w3.org/2000/svg"><path d="M17 9H7V7h10m0 6H7v-2h10m-3 6H7v-2h7M12 3a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m7 0h-4.18C14.4 1.84 13.3 1 12 1c-1.3 0-2.4.84-2.82 2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="cpp" xmlns="http://www.w3.org/2000/svg"><path d="M10.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C1.56 15.77 1 14.16 1 12.21c.05-2.31.72-4.08 2-5.32C4.32 5.64 5.96 5 7.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M11 11h2V9h2v2h2v2h-2v2h-2v-2h-2v-2m7 0h2V9h2v2h2v2h-2v2h-2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="credits" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v2H3V3m4 4h10v2H7V7m-4 4h18v2H3v-2m4 4h10v2H7v-2m-4 4h18v2H3v-2z" fill="#9ccc65"/></symbol><symbol viewBox="0 0 200 200" id="crystal" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" stroke-width="1.153" fill="#cfd8dc"/></symbol><symbol viewBox="0 0 200 200" id="crystal_light" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:none}</style><path d="M179.363 121.67l-57.623 57.507c-.23.23-.576.346-.806.23l-78.713-21.09c-.346-.115-.577-.345-.577-.576L20.44 79.144c-.115-.345 0-.576.23-.806L78.294 20.83c.23-.23.576-.346.807-.23l78.713 21.09c.345.114.576.345.576.575l21.09 78.597c.23.346.115.577-.115.807zM102.148 59.09l-77.33 20.63c-.115 0-.23.23-.115.345l56.586 56.47c.115.115.346.115.346-.115l20.744-77.215c.115 0-.115-.23-.23-.116z" fill="#37474f" stroke-width="1.153"/></symbol><symbol viewBox="0 0 24 24" id="csharp" xmlns="http://www.w3.org/2000/svg"><path d="M11.5 15.97l.41 2.44c-.26.14-.68.27-1.24.39-.57.13-1.24.2-2.01.2-2.21-.04-3.87-.7-4.98-1.96C2.56 15.77 2 14.16 2 12.21c.05-2.31.72-4.08 2-5.32C5.32 5.64 6.96 5 8.94 5c.75 0 1.4.07 1.94.19s.94.25 1.2.4l-.58 2.49-1.06-.34c-.4-.1-.86-.15-1.39-.15-1.16-.01-2.12.36-2.87 1.1-.76.73-1.15 1.85-1.18 3.34 0 1.36.37 2.42 1.08 3.2.71.77 1.71 1.17 2.99 1.18l1.33-.12c.43-.08.79-.19 1.1-.32M13.89 19l.61-4H13l.34-2h1.5l.32-2h-1.5L14 9h1.5l.61-4h2l-.61 4h1l.61-4h2l-.61 4H22l-.34 2h-1.5l-.32 2h1.5L21 15h-1.5l-.61 4h-2l.61-4h-1l-.61 4h-2m2.95-6h1l.32-2h-1l-.32 2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="css" xmlns="http://www.w3.org/2000/svg"><path d="M5 3l-.65 3.34h13.59L17.5 8.5H3.92l-.66 3.33h13.59l-.76 3.81-5.48 1.81-4.75-1.81.33-1.64H2.85l-.79 4 7.85 3 9.05-3 1.2-6.03.24-1.21L21.94 3H5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="css-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#42a5f5"/><path d="M4.676 3l-.488 2.51h10.211l-.33 1.623H3.864l-.496 2.502H13.58l-.57 2.863-4.119 1.36-3.569-1.36.248-1.232H3.06l-.593 3.005 5.898 2.254 6.8-2.254.902-4.53.18-.91L17.406 3H4.675z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 33 33" id="cucumber" xmlns="http://www.w3.org/2000/svg"><title>cucumber-mark-transparent-pips</title><g transform="translate(0 -5)" fill="none" fill-rule="evenodd"><path d="M-4-1h40v40H-4z"/><path d="M16.641 7.092c-7.028 0-12.714 5.686-12.714 12.714 0 6.187 4.435 11.327 10.288 12.471v3.64C21.824 34.77 28.561 28.73 29.063 20.8c.303-4.772-2.076-9.644-6.09-12.01a10.575 10.575 0 0 0-1.455-.728l-.243-.097c-.223-.082-.448-.175-.68-.242a12.614 12.614 0 0 0-3.954-.632zm2.62 4.707a1.387 1.387 0 0 0-1.213.485c-.233.31-.379.611-.534.923-.466 1.087-.31 2.251.388 3.105 1.087-.233 2.01-.927 2.475-2.014a2.45 2.45 0 0 0 .243-1.02c.048-.824-.634-1.404-1.359-1.479zm-5.654.073c-.708.068-1.382.63-1.382 1.407 0 .31.087.709.243 1.02.466 1.086 1.46 1.78 2.546 2.013.621-.854.782-2.018.316-3.105-.155-.311-.3-.617-.534-.85a1.364 1.364 0 0 0-1.188-.485zm-3.809 3.735c-1.224.063-1.77 1.602-.752 2.402.31.233.612.403.922.559 1.087.466 2.344.306 3.275-.316-.233-1.009-1.023-1.936-2.11-2.402-.388-.155-.703-.243-1.092-.243-.087-.009-.161-.004-.243 0zm11.961 4.708a3.551 3.551 0 0 0-2.013.582c.233 1.01 1.023 1.936 2.11 2.401.389.156.705.244 1.093.244 1.397.077 2.08-1.65.994-2.427-.31-.233-.611-.379-.922-.534a3.354 3.354 0 0 0-1.262-.266zm-10.603.072a3.376 3.376 0 0 0-1.261.267c-.389.155-.69.325-.923.558-1.009.854-.33 2.48 1.068 2.402.388 0 .782-.087 1.092-.243 1.087-.465 1.859-1.392 2.014-2.401a3.474 3.474 0 0 0-1.99-.582zm3.931 2.378c-1.087.233-2.009.927-2.475 2.014-.155.31-.243.684-.243.995-.077 1.32 1.724 2.028 2.5 1.02.233-.312.378-.613.534-.923.466-1.01.306-2.174-.316-3.106zm2.887.073c-.621.854-.781 2.019-.315 3.106.155.31.3.615.534.848.854.932 2.65.243 2.572-.921 0-.31-.088-.71-.243-1.02-.466-1.087-1.46-1.78-2.547-2.013z" fill="#4caf50" stroke-width=".776"/></g></symbol><symbol id="cuda" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style>.bust0{fill:#76b900}</style><title>NVIDIA-Logo</title><path id="buEye_Mark" class="bust0" d="M76.362 75.199V64.116c1.095-.068 2.19-.137 3.284-.137 30.377-.958 50.286 26.135 50.286 26.135s-21.483 29.83-44.539 29.83c-3.079 0-6.089-.48-8.962-1.438v-33.66c11.836 1.436 14.23 6.636 21.277 18.471l15.804-13.273s-11.562-15.12-30.992-15.12c-2.053-.068-4.105.069-6.158.274m0-36.67v16.556l3.284-.205c42.213-1.437 69.784 34.618 69.784 34.618s-31.608 38.45-64.516 38.45c-2.873 0-5.678-.274-8.483-.753v10.262c2.326.274 4.72.48 7.046.48 30.65 0 52.817-15.668 74.3-34.14 3.558 2.874 18.13 9.784 21.14 12.794-20.388 17.104-67.937 30.856-94.893 30.856-2.6 0-5.062-.137-7.525-.41v14.436h116.44V38.532zm0 79.977v8.757C48.038 122.2 40.17 92.712 40.17 92.712s13.615-15.05 36.192-17.514v9.579h-.068c-11.836-1.437-21.14 9.646-21.14 9.646s5.268 18.678 21.209 24.082M26.077 91.481S42.839 66.714 76.43 64.115v-9.03C39.213 58.094 7.057 89.565 7.057 89.565s18.199 52.68 69.305 57.47v-9.579c-37.492-4.652-50.286-45.975-50.286-45.975z" fill="#8bc34a" stroke-width=".684"/></symbol><symbol viewBox="0 0 24 24" id="dart" xmlns="http://www.w3.org/2000/svg"><title>Dart</title><path d="M12.486 1.385a.978.978 0 0 0-.682.281l-.01.007-6.387 3.692 6.371 6.372v.004l7.659 7.659 1.46-2.63-5.265-12.64-2.456-2.457a.972.972 0 0 0-.69-.288z" fill="#00ca94"/><path d="M5.422 5.35L1.73 11.733l-.007.01a.967.967 0 0 0 .006 1.371l3.059 3.061 11.963 4.706 2.704-1.502-.073-.073-.018.002-7.5-7.512h-.01L5.423 5.35z" fill="#1565c0"/><path d="M5.405 5.353l6.518 6.525h.01l7.502 7.51 2.855-.544.005-8.449-3.016-2.955c-.66-.647-1.675-1.064-2.695-1.202l.002-.032-11.181-.853z" fill="#1565c0"/><path d="M5.414 5.361l6.521 6.522v.009l7.506 7.506-.546 2.855h-8.448l-2.954-3.017c-.647-.66-1.064-1.676-1.2-2.696l-.033.003L5.414 5.36z" fill="#00ee94"/></symbol><symbol viewBox="0 0 24 24" id="database" xmlns="http://www.w3.org/2000/svg"><path d="M12 3C7.58 3 4 4.79 4 7s3.58 4 8 4 8-1.79 8-4-3.58-4-8-4M4 9v3c0 2.21 3.58 4 8 4s8-1.79 8-4V9c0 2.21-3.58 4-8 4s-8-1.79-8-4m0 5v3c0 2.21 3.58 4 8 4s8-1.79 8-4v-3c0 2.21-3.58 4-8 4s-8-1.79-8-4z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="diff" xmlns="http://www.w3.org/2000/svg"><path d="M3 1c-1.11 0-2 .89-2 2v11c0 1.11.89 2 2 2h2v-2H3V3h11v2h2V3c0-1.11-.89-2-2-2H3m6 6c-1.11 0-2 .89-2 2v2h2V9h2V7H9m4 0v2h1v1h2V7h-3m5 0v2h2v11H9v-2H7v2c0 1.11.89 2 2 2h11c1.11 0 2-.89 2-2V9c0-1.11-.89-2-2-2h-2m-4 5v2h-2v2h2c1.11 0 2-.89 2-2v-2h-2m-7 1v3h3v-2H9v-1H7z" fill="#42a5f5"/></symbol><symbol id="docker" viewBox="0 0 41 34.5" xmlns="http://www.w3.org/2000/svg"><style id="bystyle2">.byst0{fill:#fff}.byst1{clip-path:url(#bySVGID_4_)}</style><g id="byg34" transform="translate(.292 1.9)" fill="#0087c9"><g id="byg32"><g id="byg30"><g id="byg28"><g id="byg26"><g id="byg9"><path id="bySVGID_1_" class="byst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2c1.2 0 2.1.9 2.1 2s-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g></g></g></g></g></g></symbol><symbol viewBox="0 0 24 24" id="document" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m9 16v-2H6v2h9m3-4v-2H6v2h12z" fill="#42a5f5"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone" xmlns="http://www.w3.org/2000/svg"><g fill="#e0e0e0" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 200 200" id="drone_light" xmlns="http://www.w3.org/2000/svg"><g fill="#424242" transform="translate(9.063 22.346) scale(.71044)"><path d="M128.22.723C32.095.723.39 84.566.39 115.222h77.928S89.36 75.275 128.22 75.275s49.906 39.947 49.906 39.947h77.476c0-30.66-31.257-114.5-127.38-114.5m98.82 134.45h-48.914s-8.55 39.946-49.906 39.946c-41.355 0-49.902-39.948-49.902-39.948H30.255c0 10.25 37.727 82.708 98.443 82.708 60.714 0 98.344-59.604 98.344-82.708"/><circle cx="128" cy="126.08" r="32.768"/></g></symbol><symbol viewBox="0 0 3473 3473" id="editorconfig" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" clip-rule="evenodd" xmlns="http://www.w3.org/2000/svg"><defs id="ccdefs4"><style id="ccstyle2">.ccfil2{fill:#020202}.ccfil0{fill:#e3e3f8}.ccfil5{fill:#efefef}.ccfil6{fill:#faf1f1}.ccfil3{fill:#fdf2f2}.ccfil1{fill:#fdfdfd}.ccfil4{fill:#fef3f3}</style></defs><g id="ccLayer_x0020_1" transform="matrix(.8945 0 0 .8945 138.649 275.985)"><g id="cc_631799120"><g id="ccg11"><path class="ccfil0" d="M967 1895c46-30 84-105 61-158-63 27-60 89-61 158z" id="ccpath7" fill="#e3e3f8"/><path class="ccfil0" d="M1679 2067c50-16 98-72 71-130-39 27-64 64-71 130z" id="ccpath9" fill="#e3e3f8"/></g><g id="ccg21"><path class="ccfil1" d="M280 2895c0 63 16 131 60 155 162 91 730 20 923-23 101-23 183-98 278-139 214-93 369-168 540-293 124-91 321-347 342-500l-169-38c-4 172-43 211-196 251-103 28-304 34-409 16-139-23-202-96-265-179-122-162 27-275-166-286-203 249-561 70-718 45-67 97-224 727-222 871 97-33 158 3 245 37 308 119 39 224-84 193-84-20-110-75-159-110z" id="ccpath13" fill="#fdfdfd"/><path class="ccfil1" d="M683 1458c125 24 236 76 342 129 173 86 204 74 220 194 2 22-2 34 61 54 106 33-61-26 223-25 169 1 556 69 681 148 52 33 42 75 218 70-2-207-57-516-138-706-99-230-230-265-497-351-156-50-614-105-756-17-133 83-158 182-282 356-36 51-49 90-72 148z" id="ccpath15" fill="#fdfdfd"/><path class="ccfil1" d="M1784 1883c100 41-5 306-144 242-45-127 62-199 91-256-60-9-231-36-282-17-66 25-81 166-47 232 160 314 867 247 792 3-30-99-58-115-159-149-81-27-162-55-251-55z" id="ccpath17" fill="#fdfdfd"/><path class="ccfil1" d="M527 1848c80 77 261 89 378 95 15-155 28-271 152-262 61 83 29 181-35 244 109-1 172-83 156-202-92-66-371-198-511-217-39 42-135 272-140 342z" id="ccpath19" fill="#fdfdfd"/></g><path class="ccfil2" d="M339 2838c66-6 238 44 252 100-107 13-243 3-252-100zm-59 57c49 35 75 90 159 110 123 31 392-74 84-193-87-34-148-70-245-37-2-144 155-774 222-871 157 25 515 204 718-45 193 11 44 124 166 286 63 83 126 156 265 179 105 18 306 12 409-16 153-40 192-79 196-251l169 38c-21 153-218 409-342 500-171 125-326 200-540 293-95 41-177 116-278 139-193 43-761 114-923 23-44-24-60-92-60-155zm1399-828c7-66 32-103 71-130 27 58-21 114-71 130zm105-184c89 0 170 28 251 55 101 34 129 50 159 149 75 244-632 311-792-3-34-66-19-207 47-232 51-19 222 8 282 17-29 57-136 129-91 256 139 64 244-201 144-242zm-817 12c1-69-2-131 61-158 23 53-15 128-61 158zm-440-47c5-70 101-300 140-342 140 19 419 151 511 217 16 119-47 201-156 202 64-63 96-161 35-244-124-9-137 107-152 262-117-6-298-18-378-95zm-100-80c-37-102-37-261 120-274l-80 223c-21 48-21 37-40 51zm256-310c23-58 36-97 72-148 124-174 149-273 282-356 142-88 600-33 756 17 267 86 398 121 497 351 81 190 136 499 138 706-176 5-166-37-218-70-125-79-512-147-681-148-284-1-117 58-223 25-63-20-59-32-61-54-16-120-47-108-220-194-106-53-217-105-342-129zm1770-49c-19-63 16-59 77-102 35-25 63-51 106-75 161-90 461-105 589 2 52 43 137 127 124 237-27 219-177 339-300 439-125 102-333 207-548 137-18-44-4-323-25-426-19-92-9-102 44-157 156-162 494-280 686-141 81 60 58 83 100 129 52-56-45-244-403-232-243 8-348 198-450 189zM997 840c5-139 133-427 261-527 155-120 317-233 555-98 59 33 56 50 62 132 5 79-2 108-22 172-158 510-290 217-796 338 19-166 163-314 243-391 137-133 236-219 442-191 57 95 63 155-6 266-92 148-115 139-101 240 72-18 94-88 127-158 201-420-91-471-270-394-120 51-334 287-404 429-14 28-29 64-42 95zm792 21c21-125 145-156 145-541 0-166-204-315-471-204-229 94-264 166-386 350-115 174-111 365-210 526-29 46-55 62-87 108-23 34-40 77-63 117-47 77-95 133-133 225-120 3-221 5-233 129-16 170 64 212 64 276-1 69-281 765-203 1180 22 114 97 115 217 129 289 35 664 23 923-81l470-225c119-67 319-194 408-287 63-65 96-120 150-197 74-108 76-106 92-253 98 18 281 61 342 114-7 69-41 36-41 98 39 1 104-48 120-102-41-60-84-50-143-98 47-37 132-54 197-81 140-58 379-234 438-394 47-129 12-344-64-428-80-88-266-133-418-133-181 0-368 130-514 186-56-49-60-105-101-159-47-64-353-224-499-255z" id="ccpath23" fill="#020202"/><path class="ccfil3" d="M2453 1409c102 9 207-181 450-189 358-12 455 176 403 232-42-46-19-69-100-129-192-139-530-21-686 141-53 55-63 65-44 157 21 103 7 382 25 426 215 70 423-35 548-137 123-100 273-220 300-439 13-110-72-194-124-237-128-107-428-92-589-2-43 24-71 50-106 75-61 43-96 39-77 102z" id="ccpath25" fill="#fdf2f2"/><path class="ccfil4" d="M997 840l49-87c13-31 28-67 42-95 70-142 284-378 404-429 179-77 471-26 270 394-33 70-55 140-127 158-14-101 9-92 101-240 69-111 63-171 6-266-206-28-305 58-442 191-80 77-224 225-243 391 506-121 638 172 796-338 20-64 27-93 22-172-6-82-3-99-62-132-238-135-400-22-555 98-128 100-256 388-261 527z" id="ccpath27" fill="#fef3f3"/><path class="ccfil5" d="M427 1768c19-14 19-3 40-51l80-223c-157 13-157 172-120 274z" id="ccpath29" fill="#efefef"/><path class="ccfil6" d="M591 2938c-14-56-186-106-252-100 9 103 145 113 252 100z" id="ccpath31" fill="#faf1f1"/></g></g></symbol><symbol viewBox="0 0 24 24" id="elixir" xmlns="http://www.w3.org/2000/svg"><path d="M12.431 22.383c-3.86 0-6.99-3.64-6.99-8.13 0-3.678 2.774-8.172 4.916-10.91 1.014-1.295 2.931-2.321 2.931-2.321s-.982 5.238 1.683 7.318c2.365 1.847 4.105 4.25 4.105 6.363 0 4.232-2.784 7.68-6.645 7.68z" fill="#9575cd" stroke-width="1.256"/></symbol><symbol viewBox="0 0 323.00001 322.99999" id="elm" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.8053 0 0 .8053 30.106 31.524)"><path fill="#f0ad00" d="M160.8 153.865l68.028-68.03H92.77z"/><path fill="#7fd13b" d="M160.983 5.098H12.033l68.524 68.525H229.51z"/><path fill="#7fd13b" stroke-width=".974" d="M243.906 88.021l74.136 74.137-74.474 74.475-74.137-74.137z"/><path fill="#60b5cc" d="M318.2 145.045V5.098H178.252z"/><path fill="#5a6378" d="M152.164 162.499L3.4 13.733v297.533z"/><path fill="#f0ad00" d="M252.205 245.27l65.995 65.996v-131.99z"/><path fill="#60b5cc" d="M160.8 171.134L12.034 319.899h297.53z"/></g></symbol><symbol viewBox="0 0 24 24" id="email" xmlns="http://www.w3.org/2000/svg"><path d="M20 8l-8 5-8-5V6l8 5 8-5m0-2H4c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 30 30" id="erlang" xmlns="http://www.w3.org/2000/svg"><path style="line-height:1.25;-inkscape-font-specification:'Wide Latin'" d="M5.217 4.367c-.048.052-.097.1-.144.153C2.697 7.182 1.51 10.798 1.51 15.366c0 4.418 1.156 7.862 3.46 10.34h19.414c2.553-1.152 4.127-3.43 4.127-3.43l-3.147-2.52-1.454 1.381c-.866.773-.845.931-2.314 1.78-1.496.674-3.04.966-4.634.966-2.516 0-4.423-.909-5.723-2.059-1.286-1.15-1.985-4.511-2.097-6.68l17.458.067-.182-1.472s-.847-7.129-2.542-9.372zm8.76.846c1.565 0 3.22.535 3.96 1.471.742.937.932 1.667.974 3.524H9.12c.111-1.955.436-2.81 1.372-3.697.937-.888 2.03-1.298 3.484-1.298z" font-weight="400" font-size="48" font-family="Wide Latin" letter-spacing="0" word-spacing="0" fill="#f44336" stroke-width=".97"/></symbol><symbol viewBox="0 0 299.99999 300.00001" id="eslint" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-2.88 18.438) scale(1.0344)"><path d="M97.021 99.016l48.432-27.962c1.212-.7 2.706-.7 3.918 0l48.433 27.962a3.92 3.92 0 0 1 1.959 3.393v55.924a3.924 3.924 0 0 1-1.959 3.394l-48.433 27.962c-1.212.7-2.706.7-3.918 0l-48.432-27.962a3.92 3.92 0 0 1-1.959-3.394v-55.924a3.922 3.922 0 0 1 1.959-3.393" fill="#7986cb"/><path d="M273.34 124.49L215.473 23.82c-2.102-3.64-5.985-6.325-10.188-6.325H89.545c-4.204 0-8.088 2.685-10.19 6.325L21.488 124.27c-2.102 3.641-2.102 8.236 0 11.877l57.867 99.847c2.102 3.64 5.986 5.501 10.19 5.501h115.74c4.203 0 8.087-1.805 10.188-5.446l57.867-100.01c2.104-3.639 2.104-7.907.001-11.547m-47.917 48.41c0 1.48-.891 2.849-2.174 3.59l-73.71 42.527a4.194 4.194 0 0 1-4.17 0l-73.767-42.527c-1.282-.741-2.179-2.109-2.179-3.59V87.847c0-1.481.884-2.849 2.167-3.59l73.707-42.527a4.185 4.185 0 0 1 4.168 0l73.772 42.527c1.283.741 2.186 2.109 2.186 3.59z" fill="#3f51b5"/></g></symbol><symbol viewBox="0 0 24 24" id="exe" xmlns="http://www.w3.org/2000/svg"><path d="M19 4a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h14m0 14V8H5v10h14z" fill="#e64a19"/></symbol><symbol viewBox="0 0 24 24" id="favicon" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.62L12 2 9.19 8.62 2 9.24l5.45 4.73L5.82 21 12 17.27z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="file" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m5 2H6v16h12v-9h-7V4z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 400 400" id="firebase" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 103)"><path d="M72.55 208.77l44.456-292.29 56.209 90.445L195.49-37.57 330.6 209.28z" fill="#ffa712"/><path d="M195.7 276.73l134.9-67.45-46.5-224.83L72.55 208.77z" fill="#fcca3f"/><path d="M173.22 6.932L72.56 208.772l136.35-144.58z" fill="#f6820c"/></g></symbol><symbol viewBox="0 0 24 24" id="flash" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cma"><stop offset="0" stop-color="#d92f3c"/><stop offset="1" stop-color="#791223"/></linearGradient><linearGradient xlink:href="#cma" id="cmb" x1="2.373" y1="12.027" x2="21.86" y2="12.027" gradientUnits="userSpaceOnUse" gradientTransform="translate(-.09 -24.144)"/></defs><rect width="19.487" height="19.487" x="2.283" y="-21.86" transform="rotate(90)" ry="0" fill="url(#cmb)"/><path style="line-height:125%" d="M16.802 5.768l-.013.002a6.43 6.43 0 0 0-1.182.192 5.062 5.062 0 0 0-1.494.718c-.428.323-.817.72-1.17 1.191-.34.48-.682 1.032-1.022 1.66-.12.228-.233.424-.35.636v.002h-.004l-1.34 2.394-.005-.002c-.238.443-.461.847-.665 1.198a4.358 4.358 0 0 1-.716.94 2.79 2.79 0 0 1-.907.594c-.072.027-.161.042-.242.063h-.989v2.414h.989v-.002a6.427 6.427 0 0 0 1.185-.192 5.062 5.062 0 0 0 1.494-.718 5.94 5.94 0 0 0 1.171-1.191c.34-.48.681-1.033 1.021-1.66.12-.228.235-.425.353-.637l.006.002.003-.005.037-.066h2.53v.002h1.124v-2.408h-.33v-.001h-1.98c.22-.407.432-.789.621-1.115.214-.37.452-.682.717-.94a2.79 2.79 0 0 1 .906-.594c.07-.027.16-.041.239-.061h.992V8.18h-.002V5.77h-.977v-.002z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol class="cnflow-logo" viewBox="0 0 299.99999 300" id="flow" xmlns="http://www.w3.org/2000/svg"><title>Flow logo</title><path d="M38.75 33.427l77.461 77.47H54.436l61.145 61.16H38.437l93.462 93.478v-77.158l.01-.01v-77.47h-.01V66.982h46.691l20.394 20.393H153.57v76.531h22.05l24.474 24.473h-15.806l-.01-.01v.01h-31.665l-.01-.01v.01h-.313l.313.313v77.148h109.149l-39.2-39.2v-15.806l8.465 8.466v-77.37h-15.682l.017-38.191 30.09 30.086V56.362h-64.874l-22.94-22.934H113.71z" fill="#fbc02d" fill-opacity=".976" stroke-width=".955" class="cnflow-logo-mark"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="coa" x1="-388.15%" x2="237.68%" y1="-144.18%" y2="430.41%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cob" x1="72.945%" x2="-97.052%" y1="84.424%" y2="-147.7%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="coc" x1="-283.88%" x2="287.54%" y1="-693.6%" y2="101.71%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cod" x1="-821.19%" x2="101.99%" y1="-469.05%" y2="288.24%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coe" x1="-140.36%" x2="419.01%" y1="-230.93%" y2="261.98%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cof" x1="191.08%" x2="20.358%" y1="253.95%" y2="20.403%"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cog" x1="-388.09%" x2="237.67%" y1="-173.85%" y2="518.99%"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="coi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#coa"/><linearGradient id="coj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cob"/><linearGradient id="cok" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#coc"/><linearGradient id="col" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cod"/><linearGradient id="com" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#coe"/><linearGradient id="con" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cof"/><linearGradient id="coo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cog"/><linearGradient id="cop" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#coh"/><linearGradient id="coh" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#coi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#coj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cok)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#col)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#com)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#con)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#coo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cop)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-aurelia-open" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cpi" x1="-31.824" x2="19.682" y1="-11.741" y2="35.548" gradientTransform="scale(.95818 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpa"/><linearGradient id="cpa" x1="-3.881" x2="2.377" y1="-1.442" y2="4.304"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpj" x1="12.022" x2="-15.716" y1="13.922" y2="-23.952" gradientTransform="scale(.96226 1.0392)" gradientUnits="userSpaceOnUse" xlink:href="#cpb"/><linearGradient id="cpb" x1=".729" x2="-.971" y1=".844" y2="-1.477"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpk" x1="-23.39" x2="23.931" y1="-57.289" y2="8.573" gradientTransform="scale(1.0429 .95884)" gradientUnits="userSpaceOnUse" xlink:href="#cpc"/><linearGradient id="cpc" x1="-2.839" x2="2.875" y1="-6.936" y2="1.017"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpl" x1="-53.331" x2="6.771" y1="-30.517" y2="18.785" gradientTransform="scale(.99898 1.001)" gradientUnits="userSpaceOnUse" xlink:href="#cpd"/><linearGradient id="cpd" x1="-8.212" x2="1.02" y1="-4.691" y2="2.882"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpm" x1="-14.029" x2="41.998" y1="-23.111" y2="26.259" gradientTransform="scale(1.0003 .99965)" gradientUnits="userSpaceOnUse" xlink:href="#cpe"/><linearGradient id="cpe" x1="-1.404" x2="4.19" y1="-2.309" y2="2.62"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpn" x1="31.177" x2="3.37" y1="41.442" y2="3.402" gradientTransform="scale(.96254 1.0389)" gradientUnits="userSpaceOnUse" xlink:href="#cpf"/><linearGradient id="cpf" x1="1.911" x2=".204" y1="2.539" y2=".204"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".29"/><stop stop-color="#CD0F7E" offset=".84"/><stop stop-color="#ED2C89" offset="1"/></linearGradient><linearGradient id="cpo" x1="-31.905" x2="19.599" y1="-14.258" y2="42.767" gradientTransform="scale(.95823 1.0436)" gradientUnits="userSpaceOnUse" xlink:href="#cpg"/><linearGradient id="cpg" x1="-3.881" x2="2.377" y1="-1.738" y2="5.19"><stop stop-color="#C06FBB" offset="0"/><stop stop-color="#6E4D9B" offset="1"/></linearGradient><linearGradient id="cpp" x1="4.301" x2="34.534" y1="34.41" y2="4.514" gradientTransform="scale(1.002 .99796)" gradientUnits="userSpaceOnUse" xlink:href="#cph"/><linearGradient id="cph" x1=".112" x2=".901" y1=".897" y2=".116"><stop stop-color="#6E4D9B" offset="0"/><stop stop-color="#77327A" offset=".14"/><stop stop-color="#B31777" offset=".53"/><stop stop-color="#CD0F7E" offset=".79"/><stop stop-color="#ED2C89" offset="1"/></linearGradient></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f06292" fill-rule="nonzero"/><g transform="matrix(.31022 .0619 -.0619 .31022 11.807 7.546)" fill="none"><path d="M8.002 6.127L4.117 8.719.116 2.723 4 .13z" transform="rotate(-11.284 17.839 -78.732)" fill="url(#cpi)"/><path d="M9.179 1.887l6.637 9.946-7.906 5.276-6.637-9.946L.115 5.43 8.02.153z" transform="rotate(-11.284 129.49 -99.884)" fill="url(#cpj)"/><path d="M7.3 1.88l1.462 2.189-6.018 4.015L.124 4.16l1.315-.877L6.143.144z" transform="rotate(-11.284 167.2 -62.32)" fill="url(#cpk)"/><path d="M2.328 1.146L4.016.02l2.619 3.925L2.75 6.537 1.29 4.347l2.197-1.466zm-1.04 3.201L.132 2.612l2.197-1.466 1.158 1.735z" transform="rotate(-11.284 104.37 -149.22)" fill="url(#cpl)"/><path d="M5.346 9.155l-1.315.877L.03 4.035 6.047.019l2.805 4.204L4.15 7.36l4.703-3.138 1.197 1.793z" transform="rotate(-11.284 81.819 7.645)" fill="url(#cpm)"/><path d="M14.533 9.934l1.197 1.793-7.907 5.276-1.196-1.793L.052 5.358 7.958.082z" transform="rotate(-11.284 17.141 -7.825)" fill="url(#cpn)"/><path d="M6.235 7.177L4.038 8.643 2.84 6.849.036 2.646 3.92.053 7.923 6.05z" transform="rotate(-11.284 18.188 -79.174)" fill="url(#cpo)"/><path d="M18.955 35.925L17.48 34.45l3.998-3.998 1.475 1.475z" fill="#714896"/><path d="M33.33 21.55l-1.475-1.474 1.867-1.868 1.475 1.475z" fill="#6f4795"/><path d="M7.12 24.09l-1.525-1.525 3.998-3.998 1.525 1.525z" fill="#88519f"/><path d="M21.495 9.714L19.97 8.19l1.868-1.868 1.524 1.525z" fill="#85509e"/><path d="M31.418 23.462l-6.72 6.72-1.475-1.474 6.72-6.721z" fill="#8d166a"/><path d="M18.058 10.101l1.525 1.525-6.721 6.72-1.525-1.524z" fill="#a70d6f"/><path d="M2.375 11.769l1.9 1.9-1.9 1.901-1.901-1.9z" fill="#9e61ad"/><path d="M15.523 36.482l1.9 1.9-1.9 1.901-1.9-1.9z" fill="#8053a3"/><path d="M8.372 38.294L.017 29.876 29.749.08l8.636 8.201z" transform="translate(1.823 1.548)" fill="url(#cpp)"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#cddc39" fill-rule="nonzero"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#cddc39"/><path d="M11.185 9.613h5.346v2.9l3.782-3.775 3.775 3.775-3.775 3.782h2.9v5.346h-5.346v-5.346h2.446l-3.782-3.782v2.446h-5.346V9.613m0 6.682h5.346v5.346h-5.346z" fill="#f0f4c3" stroke-width=".668"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00acc1" fill-rule="nonzero"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-config-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00acc1"/><path d="M17.293 17.786a2.308 2.308 0 0 1-2.308-2.308 2.308 2.308 0 0 1 2.308-2.307 2.308 2.308 0 0 1 2.308 2.307 2.308 2.308 0 0 1-2.308 2.308m4.899-1.668c.026-.211.046-.422.046-.64 0-.217-.02-.435-.046-.659l1.391-1.075a.333.333 0 0 0 .08-.422l-1.32-2.28c-.079-.146-.257-.205-.402-.146l-1.641.66a4.779 4.779 0 0 0-1.115-.647l-.244-1.747a.333.333 0 0 0-.33-.277h-2.637a.333.333 0 0 0-.33.277l-.243 1.747a4.78 4.78 0 0 0-1.114.646l-1.642-.659a.324.324 0 0 0-.402.145l-1.319 2.281a.325.325 0 0 0 .08.422l1.39 1.075c-.026.224-.046.442-.046.66s.02.428.046.639l-1.39 1.094a.325.325 0 0 0-.08.422l1.319 2.282c.079.145.257.197.402.145l1.642-.666c.342.264.698.488 1.114.653l.244 1.747a.333.333 0 0 0 .33.277h2.637a.333.333 0 0 0 .33-.277l.243-1.747a4.802 4.802 0 0 0 1.115-.653l1.641.666c.145.052.323 0 .403-.145l1.318-2.282a.333.333 0 0 0-.079-.422z" fill="#80deea" stroke-width=".659"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-css-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M12.488 9.415l-.44 2.259h9.188l-.298 1.46h-9.18l-.447 2.251H20.5l-.514 2.576-3.705 1.224-3.211-1.224.223-1.109h-2.258l-.534 2.704 5.307 2.029 6.118-2.029.812-4.076.162-.818 1.041-5.247H12.488z" fill-rule="nonzero" fill="#bbdefb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#e57373" fill-rule="nonzero"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" stroke-width=".644"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-dist-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#e57373"/><path d="M18.575 11.113h-2.576V9.825h2.576m3.864 1.288h-2.576V9.825l-1.288-1.288h-2.576L14.71 9.825v1.288h-2.577c-.715 0-1.288.573-1.288 1.288v7.085a1.288 1.288 0 0 0 1.288 1.288H22.44a1.288 1.288 0 0 0 1.288-1.288V12.4c0-.715-.58-1.288-1.288-1.288z" fill="#ffcdd2" fill-rule="evenodd" stroke-width=".644"/></symbol><symbol id="folder-docker" clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><defs id="cydefs10"><path id="cySVGID_2_" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></defs><path id="cypath2" d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><style id="cystyle2">.cyst0{fill:#fff}.cyst1{clip-path:url(#cySVGID_4_)}</style><g id="cyg34" transform="translate(8.319 9.626) scale(.39491)" fill="#b3e5fc"><g id="cyg32"><g id="cyg30"><title id="cytitle4">Group 3</title><g id="cyg28"><g id="cyg26"><g id="cyg9"><path id="cySVGID_1_" class="cyst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/></g><g id="cyg24"><clipPath id="cySVGID_4_"><use id="cyuse14" width="100%" height="100%" xlink:href="#cySVGID_2_"/></clipPath><g id="cyg22" class="cyst1" clip-path="url(#cySVGID_4_)"><g id="cyg20"><g id="cyg18"><path id="cySVGID_3_" class="cyst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></g></g></g></g></g></g></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docker-open" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="cza"><use width="100%" height="100%" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#SVGID_2_"/></clipPath></defs><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><g transform="matrix(.3949 0 0 .39489 8.319 9.626)" fill="#b3e5fc"><title>Group 3</title><path class="czst0" d="M8.7 24c-1.1 0-2.1-.9-2.1-2s.9-2 2.1-2 2.1.9 2.1 2-1 2-2.1 2zm25.8-10.9c-.2-1.6-1.2-2.9-2.5-3.9l-.5-.4-.4.5c-.8.9-1.1 2.5-1 3.7.1.9.4 1.8.9 2.5-.4.2-.9.4-1.3.6-.9.3-1.8.4-2.7.4H1.1l-.1.6c-.2 1.9.1 3.9.9 5.7l.4.7v.1c2.4 4 6.7 5.8 11.4 5.8 9 0 16.4-3.9 19.9-12.3 2.3.1 4.6-.5 5.7-2.7l.3-.5-.5-.3c-1.3-.8-3.1-.9-4.6-.5zm-12.9-1.6h-3.9v3.9h3.9zm0-4.9h-3.9v3.9h3.9zm0-5h-3.9v3.9h3.9zm4.8 9.9h-3.9v3.9h3.9zm-14.5 0H8v3.9h3.9zm4.9 0h-3.9v3.9h3.9zm-9.7 0H3.2v3.9h3.9zm9.7-4.9h-3.9v3.9h3.9zm-4.9 0H8v3.9h3.9z"/><g class="czst1" clip-path="url(#cza)"><path class="czst0" d="M-48.8-21H1226v151.4H-48.8z"/></g></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-docs-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#0277bd" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m6.075 10.8v-1.35H13.85v1.35h6.075m2.025-2.7v-1.35h-8.1v1.35h8.1z" fill-rule="nonzero" fill="#b3e5fc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#01579b" fill-rule="nonzero"/><style>.dcst0{fill:#1173b6}.st1{fill:#585d67}</style><path class="dcst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-expo-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#01579b"/><path class="ddst0" d="M18.575 9.82c-.489-.745-.605-.844-1.6-.844h-.024c-.996 0-1.106.099-1.601.844-.46.699-5.024 9.058-5.024 9.291 0 .338.087.658.402 1.112.32.46.873.716 1.275.309.273-.274 3.201-5.321 4.616-7.23a.425.425 0 0 1 .693 0c1.414 1.909 4.343 6.956 4.616 7.23.402.407.955.15 1.275-.309.314-.454.402-.774.402-1.112-.006-.233-4.57-8.598-5.03-9.291z" fill="#1173b6" stroke-width=".058" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-font-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef9a9a" fill-rule="nonzero"/><path d="M14.62 17.403l2.38-6.33 2.37 6.33m-3.37-9l-5.5 14h2.25l1.12-3h6.25l1.13 3h2.25l-5.5-14h-2z" fill="#f44336" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-git-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M10.43 14.14l4.044-4.052 1.183 1.19a1.387 1.387 0 0 0 .65 1.56v3.877c-.42.238-.699.693-.699 1.21 0 .768.632 1.4 1.4 1.4.767 0 1.4-.632 1.4-1.4 0-.517-.28-.972-.7-1.21v-3.4l1.448 1.462c-.05.105-.05.224-.05.35 0 .767.633 1.399 1.4 1.399.768 0 1.4-.632 1.4-1.4 0-.767-.632-1.4-1.4-1.4-.126 0-.245 0-.35.05l-1.798-1.799a1.385 1.385 0 0 0-.805-1.637c-.3-.112-.615-.14-.895-.063l-1.19-1.183.553-.545a1.381 1.381 0 0 1 1.973 0l5.591 5.59a1.381 1.381 0 0 1 0 1.974l-5.59 5.591a1.381 1.381 0 0 1-1.974 0l-5.591-5.59a1.381 1.381 0 0 1 0-1.974z" fill="#e64a19" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M21.132 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.217 1.217m.608-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-global-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M21.133 18.585a1.22 1.22 0 0 0-1.156-.846h-.609v-1.825a.608.608 0 0 0-.608-.609h-3.65v-1.217h1.216a.608.608 0 0 0 .609-.608v-1.217h1.217a1.217 1.217 0 0 0 1.216-1.217v-.25a4.858 4.858 0 0 1 1.765 7.79m-4.198 1.545a4.86 4.86 0 0 1-4.26-4.826c0-.377.049-.742.128-1.089l2.915 2.915v.608a1.217 1.217 0 0 0 1.216 1.217m.609-9.735a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.085-6.085 6.085 6.085 0 0 0-6.085-6.084z" fill="#c5cae9" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#5c6bc0" fill-rule="nonzero"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-i18n-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#5c6bc0"/><path d="M17.293 17.786l-1.53-1.512.018-.018a10.555 10.555 0 0 0 2.235-3.934h1.765v-1.205h-4.217V9.912h-1.205v1.205h-4.217v1.205h6.73a9.5 9.5 0 0 1-1.91 3.223 9.424 9.424 0 0 1-1.392-2.018h-1.205c.44.982 1.042 1.91 1.795 2.747l-3.067 3.024.856.856 3.012-3.013 1.874 1.874.458-1.229m3.392-3.054H19.48l-2.711 7.23h1.205l.674-1.808h2.862l.68 1.807h1.206l-2.711-7.23m-1.579 4.218l.976-2.609.976 2.609z" fill="#c5cae9" stroke-width=".602"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-images-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688" fill-rule="nonzero"/><path d="M18.575 12.859h3.713l-3.713-3.713v3.713M13.85 8.134h5.4l4.05 4.05v8.1c0 .74-.61 1.35-1.35 1.35h-8.1a1.35 1.35 0 0 1-1.35-1.35v-10.8c0-.75.6-1.35 1.35-1.35m0 12.15h8.1v-5.4l-2.7 2.7-1.35-1.35-4.05 4.05m1.35-7.425c-.74 0-1.35.61-1.35 1.35s.61 1.35 1.35 1.35 1.35-.61 1.35-1.35-.61-1.35-1.35-1.35z" fill-rule="nonzero" fill="#b2dfdb"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-include-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.788 15.981h-2.434v2.434h-1.217V15.98h-2.434v-1.217h2.434V12.33h1.217v2.434h2.434m-3.042-5.476a6.085 6.085 0 0 0-6.085 6.084 6.085 6.085 0 0 0 6.085 6.085 6.085 6.085 0 0 0 6.084-6.085 6.085 6.085 0 0 0-6.084-6.084z" fill="#b3e5fc" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ffca28" fill-rule="nonzero"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.411 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill-rule="nonzero" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-javascript-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ffca28"/><path d="M17.935 18.374a2.18 2.18 0 0 0 1.972 1.213c.829 0 1.354-.415 1.354-.987 0-.682-.542-.927-1.452-1.324l-.502-.216c-1.435-.613-2.404-1.378-2.404-3.005 0-1.5 1.167-2.638 2.917-2.638a2.957 2.957 0 0 1 2.842 1.599l-1.552.999a1.362 1.362 0 0 0-1.29-.858.873.873 0 0 0-.957.858c0 .583.374.84 1.226 1.213l.502.216c1.697.733 2.654 1.47 2.654 3.139 0 1.798-1.412 2.783-3.308 2.783a3.839 3.839 0 0 1-3.618-2.046zm-7.048.175c.315.583.583 1.027 1.283 1.027s1.066-.256 1.066-1.255v-6.774h1.998v6.804c0 2.064-1.214 3.01-2.982 3.01a3.104 3.104 0 0 1-2.993-1.826z" fill="#ffecb3"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#c0ca33" fill-rule="nonzero"/><path d="M17.39 12.544a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-lib-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#c0ca33"/><path d="M17.391 12.543a2.05 2.05 0 0 0 2.05-2.05 2.05 2.05 0 0 0-2.05-2.052 2.05 2.05 0 0 0-2.05 2.051 2.05 2.05 0 0 0 2.05 2.051m0 2.42a8.992 8.992 0 0 0-6.152-2.42v7.52c2.392 0 4.539.923 6.152 2.42a8.992 8.992 0 0 1 6.152-2.42v-7.52c-2.392 0-4.539.923-6.152 2.42z" fill="#f0f4c3" stroke-width=".684"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#e1bee7" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-effects-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#b2ebf2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#ffcdd2" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.655 8.39l-6.152 2.193.933 8.142 5.219 2.888 5.219-2.888.932-8.142zm-1.278 2.067c.234-.004.487.07.768.223.124.066.498.16.83.21 1.183.17 2.586 1.073 3.03 1.95.306.602.243.927-.225 1.169-.404.209-1.23.108-2.43-.297l-1.012-.342-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.518 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.004-.363-.046 0-.04.164-.243.363-.451.748-.781 1.004-1.365 1.083-2.474l.055-.767.176.365c.194.401.23.98.091 1.478-.115.416-.038.462.173.104.261-.443.345-.373.299.251-.05.678-.283 1.187-.808 1.762-.429.468-.377.552.141.233.5-.308.567-.26.31.224-.487.914-1.516 1.69-2.585 1.948-.647.158-1.106.187-1.7.11-1.55-.204-3.018-1.249-3.718-2.648a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.141.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.108.282-.199.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-ngrx-state-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><path d="M17.655 8.39l-6.152 2.192.933 8.143 5.219 2.888 5.219-2.888.932-8.143zm-1.278 2.067c.234-.004.487.07.768.222.124.067.498.162.83.21 1.183.171 2.586 1.074 3.03 1.95.306.603.243.928-.225 1.17-.404.208-1.23.107-2.43-.298l-1.012-.341-.36.137c-.522.2-1.044.694-1.258 1.19-.154.359-.177.527-.149 1.116.028.59.071.761.28 1.132.239.422.786.96.88.866.026-.026-.03-.197-.124-.38-.093-.183-.148-.368-.122-.41.026-.042.273.114.548.347.611.517 1.326.848 1.981.917.538.056.661-.044.258-.211a1.238 1.238 0 0 1-.374-.25c-.157-.173-.166-.168.504-.318.417-.094 1.24-.531 1.29-.685.016-.05-.118-.07-.338-.053-.2.016-.363-.005-.363-.046 0-.04.164-.243.363-.452.748-.78 1.004-1.364 1.083-2.474l.055-.766.176.364c.194.402.23.981.091 1.479-.115.416-.038.462.173.103.261-.442.345-.372.299.252-.05.678-.283 1.186-.808 1.761-.429.47-.377.553.141.234.5-.308.567-.26.31.224-.487.914-1.516 1.689-2.585 1.948-.647.158-1.106.187-1.7.109-1.55-.203-3.018-1.248-3.718-2.647a8.736 8.736 0 0 0-.572-.989c-.275-.373-.298-.54-.113-.823.093-.142.114-.286.076-.502-.176-.999-.17-1.03.23-1.437.35-.353.371-.4.371-.813 0-.358.036-.475.198-.637.109-.109.282-.2.384-.2.296-.003.807-.277 1.11-.595.252-.265.52-.4.822-.404z" fill="#dcedc8" stroke-width=".696"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-node-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a" fill-rule="nonzero"/><path d="M17.25 8.403c-.188 0-.382.048-.542.139l-5.166 2.986a1.096 1.096 0 0 0-.542.944v5.959c0 .388.208.75.542.944l1.354.778c.66.32.882.326 1.187.326.973 0 1.535-.59 1.535-1.618V12.98a.154.154 0 0 0-.153-.153h-.646c-.09 0-.16.07-.16.153v5.882c0 .458-.472.91-1.228.528l-1.424-.813a.181.181 0 0 1-.076-.145v-5.959c0-.062.027-.118.076-.146l5.167-2.979a.15.15 0 0 1 .152 0l5.167 2.98a.164.164 0 0 1 .076.145v5.959a.181.181 0 0 1-.076.145l-5.167 2.98c-.041.027-.11.027-.16 0l-1.305-.792c-.055-.021-.111-.028-.146-.007-.368.208-.437.25-.778.354-.083.028-.215.076.05.222l1.721 1.021c.167.097.348.146.542.146s.375-.049.542-.146l5.166-2.979c.334-.194.542-.556.542-.944v-5.959c0-.389-.208-.75-.542-.944l-5.166-2.986a1.103 1.103 0 0 0-.542-.14m1.389 4.272c-1.472 0-2.354.618-2.354 1.66 0 1.117.875 1.444 2.291 1.583 1.688.166 1.82.416 1.82.75 0 .576-.465.82-1.549.82-1.375 0-1.666-.341-1.77-1.022a.157.157 0 0 0-.153-.125h-.667c-.083 0-.146.063-.146.153 0 .861.472 1.903 2.736 1.903 1.632 0 2.57-.646 2.57-1.771 0-1.118-.75-1.41-2.34-1.625-1.605-.208-1.765-.32-1.765-.694 0-.313.14-.73 1.327-.73 1.042 0 1.451.23 1.611.945.014.07.076.118.146.118h.673a.134.134 0 0 0 .105-.049c.027-.028.048-.07.034-.11-.097-1.237-.916-1.806-2.57-1.806z" fill-rule="nonzero" fill="#f1f8e9"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#039be5" fill-rule="nonzero"/><path d="M20.036 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.968 4.968 0 0 1-2.679 2.203m-.155-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.1-1.238h2.894c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399h-1.806a4.902 4.902 0 0 1 2.672-2.202c-.37.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.049.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.94 4.94 0 0 1 2.679 2.202m-4.281-3.712a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-public-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#039be5"/><path d="M20.037 16.746c.05-.408.087-.817.087-1.237s-.037-.83-.087-1.238h2.091c.099.396.16.81.16 1.238a5.1 5.1 0 0 1-.16 1.237m-3.186 3.44c.371-.687.656-1.43.854-2.203h1.825a4.967 4.967 0 0 1-2.68 2.203m-.154-3.44h-2.895c-.062-.408-.099-.817-.099-1.237s.037-.835.099-1.238h2.895c.056.403.1.817.1 1.238s-.044.829-.1 1.237m-1.447 3.687a8.39 8.39 0 0 1-1.182-2.45h2.363a8.39 8.39 0 0 1-1.181 2.45m-2.475-7.399H13.06a4.902 4.902 0 0 1 2.672-2.202c-.371.686-.65 1.429-.866 2.202m-1.806 4.95h1.806c.217.773.495 1.515.866 2.202a4.954 4.954 0 0 1-2.672-2.203m-.508-1.237a5.099 5.099 0 0 1-.16-1.237 5.1 5.1 0 0 1 .16-1.238h2.091c-.05.409-.086.817-.086 1.238s.037.829.086 1.237m2.698-6.168a8.425 8.425 0 0 1 1.181 2.456h-2.363a8.426 8.426 0 0 1 1.182-2.456m4.28 2.456h-1.824a9.682 9.682 0 0 0-.854-2.202 4.941 4.941 0 0 1 2.679 2.202M17.34 9.322a6.193 6.193 0 0 0-6.187 6.187 6.186 6.186 0 0 0 6.187 6.186 6.186 6.186 0 0 0 6.186-6.186 6.186 6.186 0 0 0-6.186-6.187z" fill="#b3e5fc" stroke-width=".619"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#00bcd4" fill-rule="nonzero"/><path d="M16.473 13.927c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a17.015 17.015 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359a5.558 5.558 0 0 0-.203.604c.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.569-1.053c-.21-.372-.435-.702-.639-1.032-.38-.022-.78-.022-1.2-.022-.422 0-.823 0-1.202.022-.204.33-.428.66-.639 1.032l-.569 1.053.57 1.054c.21.372.434.702.638 1.032.38.021.78.021 1.201.021.421 0 .822 0 1.201-.02.204-.331.428-.661.639-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.245-9.284c-.436-.267-1.405.14-2.522 1.194.366.414.724.864 1.06 1.334.577.057 1.146.14 1.686.253.359-1.503.225-2.535-.224-2.78m-.492 4.03l.204.358c.077-.203.154-.407.203-.604-.19-.042-.4-.077-.618-.112l.211.358m1.018-4.95c1.033.589 1.145 2.141.71 3.953 1.784.527 3.069 1.398 3.069 2.584 0 1.187-1.285 2.058-3.07 2.585.436 1.812.324 3.364-.709 3.954-1.025.59-2.423-.085-3.77-1.37-1.35 1.285-2.747 1.96-3.78 1.37-1.025-.59-1.137-2.142-.702-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.203 6.538c.239.527.45 1.054.625 1.588 1.475-.443 2.303-1.075 2.303-1.588 0-.512-.828-1.144-2.303-1.587a15.81 15.81 0 0 1-.625 1.587m-7.136 0a15.806 15.806 0 0 1-.625-1.587c-1.474.443-2.303 1.075-2.303 1.587 0 .513.829 1.145 2.303 1.588.176-.534.387-1.06.625-1.588m6.321 1.588l-.21.358c.217-.035.428-.07.617-.113-.049-.196-.126-.4-.203-.604l-.204.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.522 1.194.45-.246.583-1.278.224-2.781-.54.112-1.11.196-1.685.253-.337.47-.695.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.113.049.196.126.4.203.604l.204-.359m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-react-components-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#00bcd4"/><path d="M16.473 13.928c.723 0 1.313.59 1.313 1.327 0 .703-.59 1.3-1.313 1.3a1.318 1.318 0 0 1-1.313-1.3c0-.737.59-1.327 1.313-1.327m-3.252 6.946c.443.267 1.412-.14 2.529-1.194a16.997 16.997 0 0 1-1.06-1.335 15.945 15.945 0 0 1-1.686-.252c-.358 1.502-.225 2.535.217 2.78m.499-4.03l-.204-.359c-.077.204-.154.408-.203.604.19.042.4.078.618.113l-.211-.359m4.593-.533l.569-1.054-.57-1.053c-.21-.372-.434-.702-.638-1.032-.38-.022-.78-.022-1.201-.022-.421 0-.822 0-1.2.022-.205.33-.43.66-.64 1.032l-.569 1.053.569 1.054c.21.372.435.702.64 1.032.378.021.779.021 1.2.021.421 0 .822 0 1.2-.02.205-.33.43-.661.64-1.033m-1.84-4.72c-.133.155-.274.316-.414.506h.828c-.14-.19-.28-.351-.414-.506m0 7.332c.133-.154.274-.316.414-.505h-.828c.14.19.28.35.414.505m3.244-9.284c-.435-.267-1.404.14-2.52 1.194.364.414.723.864 1.06 1.334.575.057 1.144.14 1.685.253.358-1.503.225-2.535-.225-2.78m-.491 4.03l.203.358c.078-.203.155-.407.204-.604-.19-.042-.4-.077-.618-.112l.21.358m1.02-4.95c1.032.589 1.144 2.141.708 3.953 1.784.527 3.07 1.398 3.07 2.584 0 1.187-1.286 2.058-3.07 2.585.436 1.812.323 3.364-.709 3.954-1.025.59-2.423-.085-3.771-1.37-1.348 1.285-2.746 1.96-3.778 1.37-1.026-.59-1.138-2.142-.703-3.954-1.783-.527-3.069-1.398-3.069-2.585s1.286-2.057 3.07-2.584c-.436-1.812-.324-3.364.702-3.954 1.032-.59 2.43.084 3.778 1.37 1.348-1.286 2.746-1.96 3.771-1.37m-.204 6.538c.24.527.45 1.054.625 1.588 1.475-.443 2.304-1.075 2.304-1.588 0-.512-.829-1.144-2.304-1.587a15.81 15.81 0 0 1-.625 1.587m-7.135 0a15.808 15.808 0 0 1-.625-1.587c-1.475.443-2.303 1.075-2.303 1.587 0 .513.828 1.145 2.303 1.588.176-.534.386-1.06.625-1.588m6.32 1.588l-.21.358c.218-.035.428-.07.618-.113a5.56 5.56 0 0 0-.204-.604l-.203.359m-2.03 2.837c1.117 1.053 2.086 1.46 2.521 1.194.45-.246.583-1.278.225-2.781-.54.112-1.11.196-1.685.253-.338.47-.696.92-1.06 1.334m-3.477-6.012l.21-.358c-.217.035-.428.07-.617.112.049.197.126.4.203.604l.204-.358m2.03-2.837c-1.117-1.053-2.086-1.46-2.529-1.194-.442.246-.576 1.278-.217 2.781.54-.112 1.11-.196 1.685-.253.337-.47.695-.92 1.06-1.334z" fill="#b2ebf2" stroke-width=".702"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ab47bc" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-actions-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ab47bc"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#e1bee7" stroke="#e1bee7" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ef5350" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-reducer-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ef5350"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#ffcdd2" stroke="#ffcdd2" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#8bc34a" fill-rule="nonzero"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-redux-store-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#8bc34a"/><g transform="translate(8.378 6.436) scale(.17228)" fill="#dcedc8" stroke="#dcedc8" stroke-miterlimit="4" stroke-width="1.702"><path d="M65.6 65.4c2.9-.3 5.1-2.8 5-5.8S68 54.2 65 54.2h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 1.5.7 2.8 1.6 3.7-3.4 6.7-8.6 11.6-16.4 15.7-5.3 2.8-10.8 3.8-16.3 3.1-4.5-.6-8-2.6-10.2-5.9-3.2-4.9-3.5-10.2-.8-15.5 1.9-3.8 4.9-6.6 6.8-8-.4-1.3-1-3.5-1.3-5.1-14.5 10.5-13 24.7-8.6 31.4 3.3 5 10 8.1 17.4 8.1 2 0 4-.2 6-.7 12.8-2.5 22.5-10.1 28-21.4z"/><path d="M83.2 53c-7.6-8.9-18.8-13.8-31.6-13.8H50c-.9-1.8-2.8-3-4.9-3h-.2c-3.1.1-5.5 2.7-5.4 5.8.1 3 2.6 5.4 5.6 5.4h.2c2.2-.1 4.1-1.5 4.9-3.4H52c7.6 0 14.8 2.2 21.3 6.5 5 3.3 8.6 7.6 10.6 12.8 1.7 4.2 1.6 8.3-.2 11.8-2.8 5.3-7.5 8.2-13.7 8.2-4 0-7.8-1.2-9.8-2.1-1.1 1-3.1 2.6-4.5 3.6 4.3 2 8.7 3.1 12.9 3.1 9.6 0 16.7-5.3 19.4-10.6 2.9-5.8 2.7-15.8-4.8-24.3z"/><path d="M32.4 67.1c.1 3 2.6 5.4 5.6 5.4h.2c3.1-.1 5.5-2.7 5.4-5.8-.1-3-2.6-5.4-5.6-5.4h-.2c-.2 0-.5 0-.7.1-4.1-6.8-5.8-14.2-5.2-22.2.4-6 2.4-11.2 5.9-15.5 2.9-3.7 8.5-5.5 12.3-5.6 10.6-.2 15.1 13 15.4 18.3 1.3.3 3.5 1 5 1.5-1.2-16.2-11.2-24.6-20.8-24.6-9 0-17.3 6.5-20.6 16.1-4.6 12.8-1.6 25.1 4 34.8-.5.7-.8 1.8-.7 2.9z"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#fbc02d" fill-rule="nonzero"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.434h-1.217v8.518a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-resource-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#fbc02d"/><path d="M21.598 12.059h-6.085v-1.217h6.085m-2.434 6.085h-3.65V15.71h3.65m2.434-1.217h-6.085v-1.217h6.085m.608-4.26h-7.301a1.217 1.217 0 0 0-1.217 1.218v7.301a1.217 1.217 0 0 0 1.217 1.217h7.301a1.217 1.217 0 0 0 1.217-1.217v-7.301a1.217 1.217 0 0 0-1.217-1.217m-9.735 2.433h-1.217v8.519a1.217 1.217 0 0 0 1.217 1.217h8.519v-1.217H12.47z" fill="#fff9c4" stroke-width=".608"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" id="folder-sass-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#f8bbd0" fill-rule="nonzero"/><path d="M23.36 10.506c-.39-1.527-2.922-2.03-5.319-1.178-1.426.507-2.97 1.302-4.08 2.34-1.32 1.235-1.53 2.31-1.444 2.759.306 1.584 2.477 2.62 3.37 3.388v.005c-.264.13-2.19 1.104-2.64 2.1-.476 1.052.075 1.806.44 1.908 1.131.315 2.292-.251 2.916-1.182.602-.897.551-2.056.29-2.633.36-.095.781-.138 1.316-.076 1.508.177 1.804 1.118 1.748 1.513-.057.394-.373.61-.48.676-.105.065-.137.088-.129.137.013.07.062.068.152.053.125-.021.792-.321.821-1.048.037-.924-.849-1.958-2.416-1.93-.646.01-1.052.072-1.345.181-.022-.024-.044-.05-.067-.073-.969-1.034-2.76-1.765-2.684-3.156.027-.505.203-1.835 3.442-3.45 2.653-1.322 4.777-.957 5.145-.151.524 1.152-1.136 3.293-3.891 3.601-1.05.118-1.603-.289-1.74-.44-.145-.16-.166-.167-.22-.137-.088.049-.033.19 0 .274.082.214.42.594.995.782.506.166 1.739.258 3.23-.319 1.669-.646 2.972-2.443 2.59-3.944zm-7.103 7.783a2.2 2.2 0 0 1-.065 1.413 2.405 2.405 0 0 1-.453.704c-.5.546-1.198.752-1.497.579-.323-.188-.161-.956.418-1.568.623-.66 1.52-1.083 1.52-1.083l-.002-.002.079-.043z" fill="#ec407a" fill-rule="nonzero" stroke="#ec407a" stroke-width=".5199012000000001"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-scripts-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#546e7a" fill-rule="nonzero"/><path d="M18.466 20.241c.69 0 1.259-.568 1.259-1.258v-8.18H15.32a.632.632 0 0 0-.63.63v6.292h-1.887v-6.922c0-1.036.852-1.888 1.888-1.888h6.921c1.036 0 1.888.852 1.888 1.888v.63h-2.517v8.18a1.896 1.896 0 0 1-1.888 1.887h-6.292a1.896 1.896 0 0 1-1.888-1.888v-.629h6.293c0 .69.568 1.258 1.258 1.258z" fill-rule="nonzero" fill="#cfd8dc"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#4caf50" fill-rule="nonzero"/><g fill="#c8e6c9" transform="translate(2.065 -.225) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-src-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#4caf50"/><g fill="#c8e6c9" fill-rule="evenodd" transform="translate(2.064 -.224) scale(.70678)"><path d="M19.146 30.989a.902.902 0 0 1-.207-.025 1.045 1.045 0 0 1-.726-1.213l2.709-14.431c.049-.279.209-.525.444-.683a.891.891 0 0 1 .7-.122c.519.152.837.684.727 1.213L20.077 30.16a1.032 1.032 0 0 1-.442.681.895.895 0 0 1-.489.148zM24.578 28.944h-.068a.932.932 0 0 1-.668-.377 1.104 1.104 0 0 1 .1-1.419l4.658-4.553-4.638-4.239a1.105 1.105 0 0 1-.141-1.416.938.938 0 0 1 .661-.4.9.9 0 0 1 .709.237l5.47 5c.386.372.448.974.144 1.416a1.05 1.05 0 0 1-.142.163l-5.447 5.324a.913.913 0 0 1-.638.264zM16.423 28.947a.917.917 0 0 1-.639-.267l-5.452-5.327a.874.874 0 0 1-.132-.153 1.097 1.097 0 0 1 .141-1.414l5.471-5a.882.882 0 0 1 .7-.238.939.939 0 0 1 .665.4 1.104 1.104 0 0 1-.14 1.417L12.4 22.6l4.659 4.551c.377.382.42.988.1 1.419a.928.928 0 0 1-.669.377z" fill-rule="nonzero"/></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-test-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1de9b6" fill-rule="nonzero"/><path d="M14 8.097v1.39h.695v9.732A2.794 2.794 0 0 0 17.475 22a2.794 2.794 0 0 0 2.781-2.78V9.486h.695v-1.39H14m2.78 9.732c-.417 0-.695-.278-.695-.695 0-.417.278-.695.696-.695.417 0 .695.278.695.695 0 .417-.278.695-.695.695m1.39-2.78c-.417 0-.695-.278-.695-.696 0-.417.278-.695.695-.695.417 0 .695.278.695.695 0 .418-.278.696-.695.696m.695-3.476h-2.78V9.487h2.78v2.086z" fill="#00897b" fill-rule="nonzero"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#1e88e5" fill-rule="nonzero"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-tools-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#1e88e5"/><path d="M21.043 15.266a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141-2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-3.569 0a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m-2.141 2.855a1.07 1.07 0 0 1-1.07-1.07 1.07 1.07 0 0 1 1.07-1.071 1.07 1.07 0 0 1 1.07 1.07 1.07 1.07 0 0 1-1.07 1.071m3.925-6.424a6.424 6.424 0 0 0-6.423 6.424 6.424 6.424 0 0 0 6.423 6.424 1.07 1.07 0 0 0 1.071-1.07c0-.28-.107-.53-.278-.715a1.105 1.105 0 0 1-.271-.713 1.07 1.07 0 0 1 1.07-1.071h1.263a3.569 3.569 0 0 0 3.57-3.569c0-3.154-2.877-5.71-6.425-5.71z" fill="#bbdefb" stroke-width=".714"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="folder-views-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#ff8a65" fill-rule="nonzero"/><path d="M12.487 21.868L11.384 9.5H23.5l-1.104 12.366-4.961 1.375-4.948-1.373zm4.464-3.2l-3.926-2.36v-.855l3.926-2.361v1.323l-2.504 1.465 2.504 1.465v1.323zm.982-.001v-1.323l2.522-1.464-2.522-1.464v-1.323l3.926 2.35v.874l-3.926 2.35z" fill="#e44d26"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.811 8.52l-5.988 5.506-3.346-2.522-1.383.805 3.298 3.03-3.298 3.032 1.383.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.622v6.396l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vscode-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#42a5f5" fill-rule="nonzero"/><path d="M20.81 8.52l-5.988 5.506-3.346-2.522-1.384.805 3.3 3.03-3.3 3.032 1.384.807 3.346-2.522 5.988 5.503 2.921-1.419V9.94zm0 3.621v6.397l-4.245-3.198z" fill="#bbdefb" stroke-width=".974"/></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#009688" fill-rule="nonzero"/><g transform="translate(8.459 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-vue-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#009688"/><g transform="translate(8.458 6.362) scale(.69572)"><path d="M1.821 4.15l10.21 17.618L22.239 4.235v-.084h-7.692l-2.434 4.178-2.422-4.178z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179z" fill="#35495e"/></g></symbol><symbol clip-rule="evenodd" fill-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack" xmlns="http://www.w3.org/2000/svg"><path d="M10 4H4c-1.11 0-2 .89-2 2v12c0 1.097.903 2 2 2h16c1.097 0 2-.903 2-2V8a2 2 0 0 0-2-2h-8l-2-2z" fill="#03a9f4" fill-rule="nonzero"/><g transform="translate(9.192 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" viewBox="0 0 24 24" id="folder-webpack-open" xmlns="http://www.w3.org/2000/svg"><path d="M19 20H4a2 2 0 0 1-2-2V6c0-1.11.89-2 2-2h6l2 2h7c1.097 0 2 .903 2 2H4v10l2.14-8h17.07l-2.28 8.5c-.23.87-1.01 1.5-1.93 1.5z" fill="#03a9f4"/><g transform="translate(9.193 7.48) scale(.66328)"><path d="M19.376 15.988l-7.708 4.45-7.709-4.45v-8.9l7.709-4.451 7.708 4.45z" fill="#fff" fill-opacity=".785"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18s.41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.94v2.103h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83zm-5 5.08v3.58l4 2.309v-3.58zm10 0l-4 2.308v3.58l4-2.308z" fill="#1c78c0"/></g></symbol><symbol viewBox="0 0 24 24" id="font" xmlns="http://www.w3.org/2000/svg"><path d="M9.62 12L12 5.67 14.37 12M11 3L5.5 17h2.25l1.12-3h6.25l1.13 3h2.25L13 3h-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 500 500" id="fsharp" xmlns="http://www.w3.org/2000/svg"><path d="M235.906 36.66L21.963 250.601l213.943 213.943v-84.36L106.209 250.487l129.697-129.696z" fill="#378bba" stroke-width="14.706"/><path d="M235.906 156.614l-93.622 93.62 93.622 93.622z" fill="#378bba" stroke-width="15.006"/><path d="M263.417 36.64L477.36 250.583 263.417 464.526v-84.36l129.696-129.697-129.696-129.696z" fill="#30b9db" stroke-width="14.706"/></symbol><symbol viewBox="0 0 152.99 160.01" id="fusebox" xmlns="http://www.w3.org/2000/svg"><defs id="fkdefs4"><style id="fkstyle2">.fkcls-1{fill:#fff}.fkcls-2{fill:#515151}.fkcls-3{fill:#1d79bf}.fkcls-4{fill:#383838}</style></defs><title id="fktitle6">Asset 3</title><g id="fkLayer_2" data-name="Layer 2" transform="matrix(.87285 0 0 .87285 10.17 10.175)"><g id="fkFuse_Box" data-name="Fuse Box"><g id="fkLOGO"><path class="fkcls-1" id="fkpolygon8" fill="#fff" d="M76.56 2.19l74.22 24.93-7.7 87.77-65.41 42.66-69.79-43.93-5.7-86.13z"/><path class="fkcls-2" d="M77.69 160L5.87 114.81 0 26 76.55 0 153 25.67l-7.94 90.4zM9.88 112.43l67.77 42.66 63.45-41.39 7.47-85.13-72-24.18L4.36 28.95z" id="fkpath10" fill="#515151"/><path class="fkcls-3" id="fkpolygon12" fill="#1d79bf" d="M76.4 148.8V61.68l66.93-29.82-5.99 78.77z"/><path id="fkF" class="fkcls-4" fill="#383838" d="M76.4 148.8l-60.35-37.39L9.63 31.8 76.4 61.68z"/><path class="fkcls-1" d="M25.58 52.73l.54 15.93 37.35 18.18.12 14.69-37-18.21 1.64 37.1-14.56-9-5.05-80.55 67.79 30.82v15.46z" id="fkpath15" fill="#fff"/><path class="fkcls-1" d="M135.91 90.77c-.08 13.12-6.33 26.59-16.77 33.12l-42.8 27.93V61.71l42.27-18.84c5.16-2.41 9.51-1.43 12.4 3.11 1.9 3 2.89 7.23 2.86 12.21A35.69 35.69 0 0 1 129.34 76c4.29 2 6.66 6.55 6.57 14.77zM123 63.76c0-4.64-2-6.93-4.92-5.45l-29 14.48L89 90l29.44-15.59c2.5-1.32 4.56-5.91 4.56-10.65zM125.15 96c0-5.71-2.42-8.24-6.55-5.93L89 106.64v19.58l29.34-17.46c4.43-2.64 6.79-7.27 6.81-12.76z" id="fkpath17" fill="#fff"/><path id="fkTOP" class="fkcls-4" fill="#383838" d="M76.4 8.82L9.71 31.77l109.77 2.38-84.02 9.21L76.4 61.68l20.76-9.25-27.73-1.37 49.78-8.46 24.12-10.74z"/></g></g></g></symbol><symbol viewBox="0 0 24 24" id="git" xmlns="http://www.w3.org/2000/svg"><path d="M2.6 10.59L8.38 4.8l1.69 1.7c-.24.85.15 1.78.93 2.23v5.54c-.6.34-1 .99-1 1.73a2 2 0 0 0 2 2 2 2 0 0 0 2-2c0-.74-.4-1.39-1-1.73V9.41l2.07 2.09c-.07.15-.07.32-.07.5a2 2 0 0 0 2 2 2 2 0 0 0 2-2 2 2 0 0 0-2-2c-.18 0-.35 0-.5.07L13.93 7.5a1.98 1.98 0 0 0-1.15-2.34c-.43-.16-.88-.2-1.28-.09L9.8 3.38l.79-.78c.78-.79 2.04-.79 2.82 0l7.99 7.99c.79.78.79 2.04 0 2.82l-7.99 7.99c-.78.79-2.04.79-2.82 0L2.6 13.41c-.79-.78-.79-2.04 0-2.82z" fill="#e64a19"/></symbol><symbol viewBox="0 0 494 455" id="gitlab" xmlns="http://www.w3.org/2000/svg"><title>logo</title><defs><path id="fma" d="M0 1173.3h2000V0H0v1173.3z"/></defs><g transform="matrix(.88256 0 0 -.88256 -286.767 742.766)" fill="none" fill-rule="evenodd"><mask id="fmb" fill="#fff"><use width="100%" height="100%" xlink:href="#fma"/></mask><g><g transform="translate(358.67 358.67)"><path d="M492.532 195.445l-27.559 84.815-54.617 168.1c-2.81 8.648-15.045 8.648-17.856 0l-54.619-168.1h-181.37l-54.62 168.1c-2.81 8.648-15.045 8.648-17.856 0l-54.617-168.1-27.557-84.815a18.775 18.775 0 0 1 6.82-20.992l238.51-173.29 238.51 173.29a18.777 18.777 0 0 1 6.82 20.992" fill="#fc6d26"/><path d="M247.2 1.16l90.684 279.1h-181.37z" fill="#e24329"/><path d="M247.201 1.16l-90.684 279.09H29.427z" fill="#fc6d26"/><path d="M29.422 280.256L1.862 195.44a18.774 18.774 0 0 1 6.822-20.991L247.194 1.16z" fill="#fca326"/><path d="M29.422 280.26h127.09l-54.619 168.1c-2.81 8.65-15.047 8.65-17.856 0z" fill="#e24329"/><path d="M247.2 1.16l90.684 279.09h127.09z" fill="#fc6d26"/><path d="M464.98 280.256l27.559-84.815a18.774 18.774 0 0 0-6.821-20.991L247.208 1.16z" fill="#fca326"/><path d="M464.97 280.26H337.88l54.619 168.1c2.81 8.65 15.047 8.65 17.856 0z" fill="#e24329"/></g></g></g></symbol><symbol viewBox="0 0 24 24" id="go" xmlns="http://www.w3.org/2000/svg"><path d="M10.575 1.695c-2.634 0-4.756 2.453-4.756 5.502v4.6l-.027-.003v4.71c0 3.05 2.123 5.502 4.757 5.502h2.286c2.634 0 4.757-2.453 4.757-5.502v-4.6a5.1 5.1 0 0 0 .026.003v-4.71c0-3.049-2.122-5.502-4.756-5.502h-2.287z" fill="#73cddc"/><rect width="2.289" height="3.335" x="-1.178" y="6.092" ry="1.125" transform="matrix(.4849 -.87457 .85979 .51065 0 0)" fill="#73cddc"/><rect width="2.297" height="3.39" x="10.261" y="-15.076" ry="1.143" transform="matrix(.44646 .8948 -.89204 .45195 0 0)" fill="#73cddc"/><circle cx="9.267" cy="5.13" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><circle cx="14.214" cy="5.116" r="2.054" fill="#fff" stroke="#5e5d5b" stroke-width=".1"/><ellipse cx="8.039" cy="5.051" rx=".792" ry=".901" fill="#030d18"/><path d="M11.792 9.556l.763.138a.403.689 0 0 1 .008.138.403.689 0 0 1-.402.69.403.689 0 0 1-.404-.69.403.689 0 0 1 .035-.276z" fill="#fff" stroke="#fff" stroke-width=".155"/><ellipse cx="8.51" cy="5.365" rx=".138" ry=".166" fill="#fff"/><ellipse cx="12.945" cy="5.189" rx=".792" ry=".901" fill="#030d18"/><ellipse cx="13.414" cy="5.446" rx=".138" ry=".166" fill="#fff"/><ellipse cx="-12.982" cy="-3.409" rx=".708" ry="1.026" transform="rotate(-129.403)" fill="#f6d2a1" stroke-width=".4"/><path d="M11.772 9.553l-.757.135a.4.672 0 0 0-.008.135.4.672 0 0 0 .4.672.4.672 0 0 0 .4-.672.4.672 0 0 0-.035-.27z" fill="#fff" stroke="#fff" stroke-width=".153"/><ellipse cx="1.841" cy="-21.563" rx=".707" ry="1.026" transform="scale(1 -1) rotate(50.597)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="-17.281" cy="-21.784" rx=".864" ry="1.27" transform="matrix(.3054 -.95222 -.97065 -.2405 0 0)" fill="#f6d2a1" stroke-width=".4"/><ellipse cx="22.885" cy="2.587" rx=".864" ry="1.27" transform="matrix(.22652 .974 .95652 -.29167 0 0)" fill="#f6d2a1" stroke-width=".4"/><path d="M10.708 8.392a.594.594 0 0 0-.594.597v.115c0 .331.264.598.594.598h.386a.973.772 0 0 1 .697-.235.973.772 0 0 1 .698.235h.334c.33 0 .594-.267.594-.598V8.99a.595.595 0 0 0-.594-.597h-2.115z" fill="#f6d2a1" stroke="#657075" stroke-width=".1"/><ellipse cx="11.734" cy="8.203" rx="1.208" ry=".68" fill="#030d18" stroke="#fff" stroke-width=".162"/></symbol><symbol viewBox="0 0 24 24" id="gradle" xmlns="http://www.w3.org/2000/svg"><path d="M21.718 5.503c-.731-1.315-2.04-1.708-2.963-1.727-1.133-.023-2.065.605-1.888 1.017.037.088.25.55.38.741.19.275.527.064.646 0 .353-.187.73-.248 1.16-.198.409.048.954.3 1.319 1.001.859 1.652-1.794 5.05-5.114 2.697-3.32-2.353-6.548-1.574-8.01-1.1-1.462.475-2.135.952-1.556 2.055.785 1.498.524 1.038 1.285 2.28 1.21 1.97 3.856-.908 3.856-.908-1.972 2.906-3.662 2.204-4.31 1.188a15.864 15.864 0 0 1-1.038-1.97c-4.993 1.76-3.642 9.534-3.642 9.534h2.48c.632-2.862 2.892-2.757 3.28 0h1.892c1.673-5.59 5.914 0 5.914 0h2.466c-.69-3.812 1.388-5.01 2.697-7.246 1.31-2.235 2.551-4.969 1.146-7.364zm-6.362 7.362c-1.304-.426-.837-1.723-.837-1.723s1.139.368 2.68.87c-.09.403-.856 1.175-1.843.853z" fill="#0097a7" stroke-width=".47"/></symbol><symbol preserveAspectRatio="xMidYMid" viewBox="0 0 300 300" id="graphcool" xmlns="http://www.w3.org/2000/svg"><path d="M246.886 107.727c-12.237-6.892-27.616 2.1-30.081 3.646l-52.834 29.965c-7.8-6.196-18.914-5.933-26.412.625-7.499 6.558-9.24 17.537-4.14 26.094 5.102 8.556 15.588 12.246 24.923 8.768 9.335-3.478 14.852-13.129 13.111-22.937l52.688-29.9.321-.196c3.464-2.188 11.5-5.462 15.256-3.34 2.706 1.524 4.252 6.629 4.376 14.148h-.066v66.092a17.313 17.313 0 0 1-8.635 14.95l-75.739 43.755a17.312 17.312 0 0 1-17.261 0l-75.74-43.756a17.312 17.312 0 0 1-8.634-14.95V113.22c.01-6.165 3.3-11.86 8.634-14.95l68.549-39.562c6.522 7.482 17.451 9.25 26 4.206s12.283-15.468 8.886-24.794c-3.397-9.327-12.962-14.904-22.751-13.27-9.79 1.636-17.022 10.02-17.204 19.944L59.397 85.632a31.932 31.932 0 0 0-15.978 27.588v87.454a31.933 31.933 0 0 0 15.927 27.602l75.74 43.755a31.934 31.934 0 0 0 31.846 0l75.74-43.755a31.933 31.933 0 0 0 15.927-27.58V137.12h.05c.373-14.913-3.616-24.794-11.762-29.389z" fill="#27ae60" stroke="#27ae60" stroke-width="7.883622079999999"/></symbol><symbol viewBox="0 0 400 400" id="graphql" xmlns="http://www.w3.org/2000/svg"><path d="M67.008 293.022l-13.143-7.588L200.282 31.839l13.143 7.588z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M50.855 265.174H343.69v15.177H50.855z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M203.122 358.269L56.649 273.7l7.589-13.143 146.472 84.568zm127.24-220.407L183.889 53.293l7.589-13.143 146.472 84.568z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M64.278 137.803l-7.588-13.142 146.472-84.568 7.588 13.143z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M327.661 293.025L181.244 39.43l13.143-7.589 146.417 253.596zM62.466 114.597h15.176v169.136H62.466zm254.528 0h15.176v169.136h-15.176z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M200.538 351.845l-6.628-11.481L321.3 266.812l6.629 11.48z" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/><path d="M352.284 288.67c-8.777 15.268-28.342 20.48-43.61 11.703-15.268-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.36 8.869 20.57 28.342 11.703 43.61M97.574 141.567c-8.778 15.268-28.343 20.48-43.61 11.703-15.269-8.777-20.48-28.342-11.703-43.61 8.777-15.268 28.342-20.48 43.61-11.703 15.268 8.869 20.479 28.342 11.702 43.61M42.353 288.67c-8.777-15.268-3.566-34.741 11.702-43.61 15.268-8.776 34.741-3.565 43.61 11.703 8.776 15.268 3.565 34.741-11.703 43.61-15.36 8.776-34.833 3.565-43.61-11.703m254.71-147.103c-8.776-15.268-3.565-34.741 11.703-43.61 15.268-8.776 34.742-3.565 43.61 11.703 8.777 15.268 3.566 34.741-11.702 43.61-15.268 8.776-34.833 3.565-43.61-11.703m-99.745 236.608c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907c0 17.554-14.262 31.907-31.907 31.907m0-294.206c-17.645 0-31.907-14.262-31.907-31.907s14.262-31.907 31.907-31.907 31.907 14.262 31.907 31.907-14.262 31.907-31.907 31.907" fill="#ec407a" stroke-width="6.803" stroke="#ec407a"/></symbol><symbol viewBox="0 0 24 24" id="groovy" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.982a10.119 10.119 0 0 0-10.12 10.12A10.119 10.119 0 0 0 12 22.22 10.119 10.119 0 0 0 22.12 12.1 10.119 10.119 0 0 0 12 1.983zm1.254 2.422c.91 0 1.647.261 2.213.78.571.518.857 1.188.857 2.013 0 .889-.319 1.673-.959 2.35-.64.677-1.376 1.015-2.207 1.015-.486 0-.89-.119-1.213-.357-.317-.238-.476-.532-.476-.88 0-.212.06-.4.181-.563.127-.164.274-.246.438-.246.159 0 .238.092.238.277 0 .164.06.29.182.38.121.09.261.136.42.136.423 0 .828-.29 1.215-.866.391-.582.587-1.202.587-1.863 0-.465-.151-.844-.453-1.135-.301-.296-.69-.445-1.166-.445-.714 0-1.406.318-2.078.953-.666.635-1.211 1.47-1.635 2.506-.417 1.031-.627 2.014-.627 2.945 0 .857.185 1.54.555 2.047.37.503.863.754 1.477.754 1.037 0 2.027-.734 2.974-2.2l1.493-.212c.185-.026.277.018.277.135 0 .053-.072.28-.215.681-.143.402-.337 1.074-.586 2.016.82-.476 1.455-1.003 1.904-1.58v.914c-.36.418-1.046.888-2.062 1.412-.212 1.407-.682 2.493-1.406 3.26-.725.772-1.54 1.16-2.444 1.16-.433 0-.775-.102-1.023-.303-.243-.2-.365-.477-.365-.832 0-.984.955-1.94 2.865-2.865.2-.714.395-1.356.586-1.928-.333.482-.817.907-1.451 1.278-.635.37-1.225.554-1.77.554-.889 0-1.628-.383-2.22-1.15-.588-.772-.881-1.748-.881-2.928 0-1.243.333-2.42 1-3.531a7.747 7.747 0 0 1 2.625-2.674c1.084-.672 2.134-1.008 3.15-1.008zM12.03 16.592c-1.375.687-2.062 1.365-2.062 2.031 0 .354.169.533.508.533.666 0 1.184-.856 1.554-2.564z" fill="#26c6da"/></symbol><symbol viewBox="0 0 24 24" id="gulp" xmlns="http://www.w3.org/2000/svg"><path d="M8.37 15.94a596.238 596.238 0 0 1-.482-4.982c.002-.042-.225-.077-.505-.077h-.508V8.95h3.966V5.198l1.871-1.124c1.14-.685 1.978-1.125 2.144-1.125.4 0 .866.506.866.939 0 .19-.057.422-.127.517-.07.095-.722.53-1.45.966l-1.321.792-.029 1.393-.028 1.393h3.972v1.932h-.98l-.495 4.983-.495 4.983H8.854l-.485-4.906z" fill="#e53935"/></symbol><symbol viewBox="0 0 24 24" id="h" xmlns="http://www.w3.org/2000/svg"><path d="M16.745 19.818h-3.007v-5.882q0-2.381-1.736-2.381-.869 0-1.438.663-.56.662-.56 1.718v5.882H6.988V4.533h3.016v6.508h.037q1.186-1.802 3.193-1.802 3.511 0 3.511 4.239z" stroke-width=".478" fill="#0277bd"/></symbol><symbol viewBox="0 0 253.6 253.6" id="hack" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-29.243 -29.515) scale(1.2301)"><path fill="#607d8b" d="M69.496 159.551v52.576l51.77-52.576zM123.507 41.523l-54.01 52.755v55.084l54.01-54.009z"/><path fill="#eceff1" d="M130.023 95.663v51.501l52.128-51.5z"/><path fill="#607d8b" d="M185.465 101.867l-55.442 55.174v55.083l55.442-55.262z"/><path fill="#ffa000" d="M73.068 154.283l50.427.09v-50.248z"/></g></symbol><symbol viewBox="0 0 300 300.00001" id="haml" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 165.6)"><path d="M78.42-132.307c-12.047-.302-26.924 5.998-26.924 5.998l49.195 99.791L74.605 85.005c23.81 20.134 50.07 10.504 50.07 10.504L136.76 9.212c1.526 1.446 3.146 2.77 4.777 3.995 5.244 3.714 10.925 6.553 16.606 8.738 5.68 2.185 11.583 3.933 17.482 5.244 3.933.874 7.645 1.53 11.578 1.967-1.748 3.933-2.84 8.083-2.621 12.672 0 .437.22.873.656 1.092h.217c4.152 2.185 8.521 3.934 13.328 5.027 4.589.874 9.615 1.312 14.422.656 5.026-.655 10.051-2.623 13.984-5.9 3.933-3.278 6.774-7.648 8.522-12.237l.219-.218v-.217l.656-5.899v-.22c2.185-1.311 4.37-2.621 6.555-4.37 2.622-2.184 5.025-4.589 6.773-7.648 1.748-3.059 2.84-6.774 2.621-10.488-.218-3.496-1.53-6.99-3.06-10.049-1.53-3.059-3.495-5.901-5.68-8.523-4.37-5.026-9.614-9.176-15.295-12.454-5.462-3.496-11.581-6.338-17.7-8.304l-2.404-.656-1.962-.655c-1.311-.437-2.406-1.092-3.498-1.53-2.185-1.31-3.717-2.622-4.809-4.37-2.185-3.278-2.403-8.301-1.31-13.545.218-1.311.656-2.623 1.093-3.934a96.064 96.064 0 0 0 1.31-4.152c.314-1.412.51-2.829.598-4.402l29.203-25.553c-2.275-8.404-27.488-17.158-27.488-17.158l-74.931 63.726-43.243-81.584c-1.553-.35-3.218-.527-4.94-.57zm107.682 73.14c-.449 2.336-.647 4.795-.647 7.258.219 3.715 1.311 7.87 3.715 11.366 2.403 3.496 5.68 6.117 8.957 7.646a29.663 29.663 0 0 0 5.027 1.967l2.623.654 2.184.438c5.68 1.53 11.142 3.714 16.168 6.554 5.025 2.84 9.833 6.337 13.766 10.27s6.992 8.959 7.43 13.984c.218 3.496-.22 6.118-1.313 8.303-1.093 2.404-2.84 4.588-4.807 6.555-.874.874-1.966 1.747-2.84 2.402a27.11 27.11 0 0 0-.654-5.898c-.219-1.093-.438-1.966-.875-3.059-.437-.874-.872-1.966-1.965-2.621-.218 0-.44-.001-.44.217-1.31 3.277-3.494 6.12-5.898 8.086-2.403 1.966-5.462 2.84-8.521 3.058-3.06.219-6.338-.436-9.616-1.31-3.277-.874-6.552-1.968-9.83-3.06l-.439-.22c-.656-.218-1.526.002-1.963.44-1.748 2.185-3.06 4.149-4.59 6.334a58.435 58.435 0 0 0-2.84 5.027c-3.933-1.53-7.649-2.841-11.582-4.37-5.462-2.186-10.925-4.37-15.95-6.991-5.245-2.404-10.268-5.246-14.638-8.524-3.15-2.363-6.062-4.845-8.185-7.681l2.404-17.172z" fill="#f4511e" stroke-width="0" stroke-linejoin="round"/></g></symbol><symbol viewBox="0 0 24 24" id="handlebars" xmlns="http://www.w3.org/2000/svg"><path d="M8.55 10.32c-2.753 0-4.202 3.48-5.793 3.48-.98 0-1.126-.677-1.126-.915 0-.332.236-.706.564-.706.59 0 .414.77.414.77s.798-.555.272-1.298c-.42-.595-1.31-.623-1.92-.17-.617.458-1.057 1.146-.853 2.287.1.551.468 1.35 1.233 1.805.764.455 1.925.566 2.335.566 2.194 0 4.342-1.633 6.639-2.322a5.513 5.513 0 0 1 1.497-.222 6.19 6.19 0 0 1 1.92.226c2.296.689 4.444 2.323 6.638 2.323.41 0 1.57-.11 2.335-.566.765-.455 1.132-1.256 1.231-1.807.204-1.14-.235-1.829-.853-2.287-.61-.453-1.497-.423-1.918.172-.526.743.27 1.297.27 1.297s-.176-.77.414-.77c.329 0 .565.373.565.705 0 .238-.147.914-1.126.914-1.592 0-3.04-3.478-5.794-3.478-2.565 0-3.076 1.177-3.462 1.718-.004.005-.005.011-.008.016-.005-.006-.007-.013-.012-.02-.386-.54-.896-1.717-3.461-1.717z" fill="#ff7043" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 300.00001 300" id="haskell" xmlns="http://www.w3.org/2000/svg"><g stroke-width="2.422"><path d="M23.928 240.5l59.94-89.852-59.94-89.855h44.955l59.94 89.855-59.94 89.852z" fill="#ef5350"/><path d="M83.869 240.5l59.94-89.852-59.94-89.855h44.955l119.88 179.71h-44.95l-37.46-56.156-37.468 56.156z" fill="#ffa726"/><path d="M228.72 188.08l-19.98-29.953h69.93v29.956h-49.95zm-29.97-44.924l-19.98-29.953h99.901v29.953z" fill="#ffee58"/></g></symbol><symbol viewBox="0 0 210 210" id="haxe" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -87)"><path fill="#f68712" stroke-width=".221" d="M42.78 191.545l63.431-63.43 63.431 63.43-63.431 63.431z"/><path d="M42.8 191.592L31.193 148.28 19.59 104.97 62.9 116.575l43.311 11.605-31.706 31.706z" fill="#fab20b" stroke-width=".266"/><path d="M105.956 128.111l-43.19-11.544-43.177-11.597 22.927.185 23.228.294 20.264 11.36z" fill="#fbc707" stroke-width=".265"/><path d="M19.59 104.97l11.596 43.176 11.545 43.19-11.303-19.948-11.36-20.263-.294-23.228z" fill="#fff200" stroke-width=".265"/><path d="M106.23 128.133l43.312-11.605 43.311-11.605-11.605 43.31-11.605 43.312-31.706-31.706z" fill="#f47216" stroke-width=".266"/><path d="M169.711 191.289l11.545-43.19 11.597-43.176-.185 22.927-.294 23.228-11.36 20.263z" fill="#f1471d" stroke-width=".265"/><path d="M192.853 104.923l-43.176 11.597-43.19 11.544 19.947-11.303 20.264-11.36 23.228-.293z" fill="#fbc707" stroke-width=".265"/><path d="M169.643 191.545l11.605 43.31 11.605 43.312-43.311-11.605-43.311-11.606 31.706-31.705z" fill="#f25c19" stroke-width=".266"/><path d="M106.487 255.025l43.19 11.544 43.176 11.598-22.927-.185-23.228-.294-20.264-11.36z" fill="#f68712" stroke-width=".265"/><path d="M192.853 278.167l-11.597-43.176-11.545-43.19 11.303 19.947 11.36 20.264.294 23.228z" fill="#f1471d" stroke-width=".265"/><path d="M106.211 254.976l-43.31 11.605-43.312 11.605 11.605-43.31L42.8 191.563l31.706 31.706z" fill="#f89c0e" stroke-width=".266"/><path d="M42.731 191.82l-11.545 43.19-11.597 43.176.185-22.927.294-23.228 11.36-20.263z" fill="#fff200" stroke-width=".265"/><path d="M19.59 278.186l43.175-11.597 43.19-11.544-19.947 11.303-20.264 11.36-23.228.293z" fill="#f25c19" stroke-width=".265"/></g></symbol><symbol viewBox="0 0 144 152" id="heroku" xmlns="http://www.w3.org/2000/svg"><path d="M118.68 13.279H26.865c-6.337 0-11.476 5.139-11.476 11.476V129.32c0 6.338 5.139 11.477 11.476 11.477h91.813c6.338 0 11.477-5.14 11.477-11.477V24.755c0-6.337-5.139-11.476-11.477-11.476zM44.08 121.669V96.165l14.346 12.752zm44.632 0v-38.08c-.063-2.976-1.496-6.551-7.97-6.551-12.966 0-27.51 6.52-27.654 6.586l-9.008 4.08V32.407h12.752v36.201c6.366-2.072 15.266-4.321 23.91-4.321 7.882 0 12.6 3.099 15.17 5.698 5.484 5.547 5.56 12.613 5.551 13.43v38.255zm3.188-68.54H79.149c5.011-6.576 8.158-13.496 9.564-20.723h12.751c-.86 7.243-3.796 14.187-9.563 20.722z" fill="#6963b9"/></symbol><symbol viewBox="0 0 24 24" id="hpp" xmlns="http://www.w3.org/2000/svg"><path d="M9.757 19.818H6.751v-5.882q0-2.381-1.737-2.381-.868 0-1.438.663-.56.662-.56 1.718v5.882H0V4.533h3.016v6.508h.037Q4.24 9.239 6.247 9.239q3.51 0 3.51 4.239z" stroke-width=".478" fill="#0277bd"/><path d="M13.073 11.448v2h-2v2h2v2h2v-2h2v-2h-2v-2zm7 0v2h-2v2h2v2h2v-2h2v-2h-2v-2z" fill="#0277bd"/></symbol><symbol viewBox="0 0 24 24" id="html" xmlns="http://www.w3.org/2000/svg"><path d="M12 17.56l4.07-1.13.55-6.1H9.38L9.2 8.3h7.6l.2-1.99H7l.56 6.01h6.89l-.23 2.58-2.22.6-2.22-.6-.14-1.66h-2l.29 3.19L12 17.56M4.07 3h15.86L18.5 19.2 12 21l-6.5-1.8L4.07 3z" fill="#e44d26"/></symbol><symbol viewBox="0 0 24 24" id="http" xmlns="http://www.w3.org/2000/svg"><path d="M16.046 13.784c.074-.613.13-1.225.13-1.856s-.056-1.244-.13-1.856h3.137c.148.594.241 1.215.241 1.856a7.65 7.65 0 0 1-.241 1.856m-4.78 5.16c.557-1.03.984-2.144 1.281-3.304h2.738a7.452 7.452 0 0 1-4.019 3.304m-.232-5.16H9.828a12.314 12.314 0 0 1-.149-1.856c0-.631.056-1.253.149-1.856h4.343c.084.603.149 1.225.149 1.856 0 .63-.065 1.243-.149 1.856M12 19.315c-.77-1.113-1.393-2.348-1.773-3.675h3.545c-.38 1.327-1.002 2.562-1.773 3.675m-3.712-11.1h-2.71a7.353 7.353 0 0 1 4.01-3.304c-.557 1.03-.975 2.144-1.3 3.304m-2.71 7.425h2.71c.325 1.16.743 2.274 1.3 3.304a7.433 7.433 0 0 1-4.01-3.304m-.761-1.856a7.65 7.65 0 0 1-.241-1.856c0-.64.093-1.262.241-1.856h3.137c-.074.612-.13 1.225-.13 1.856 0 .63.056 1.243.13 1.856m4.046-9.253c.77 1.114 1.393 2.357 1.773 3.684h-3.545c.38-1.327 1.002-2.57 1.773-3.684m6.422 3.684h-2.738a14.523 14.523 0 0 0-1.28-3.304 7.412 7.412 0 0 1 4.018 3.304m-6.423-5.568c-5.132 0-9.28 4.176-9.28 9.28a9.28 9.28 0 0 0 9.28 9.282 9.28 9.28 0 0 0 9.281-9.281A9.28 9.28 0 0 0 12 2.647z" fill="#e53935" stroke-width=".928"/></symbol><symbol viewBox="0 0 24 24" id="image" xmlns="http://www.w3.org/2000/svg"><path d="M13.009 9.202h5.368l-5.368-5.368v5.368M6.177 2.37h7.808l5.856 5.856v11.711a1.952 1.952 0 0 1-1.952 1.952H6.178a1.951 1.951 0 0 1-1.952-1.952V4.322c0-1.083.868-1.952 1.952-1.952m0 17.567h11.71V12.13l-3.903 3.903-1.952-1.951-5.856 5.855M8.13 9.202a1.952 1.952 0 0 0-1.952 1.952 1.952 1.952 0 0 0 1.952 1.952 1.952 1.952 0 0 0 1.952-1.952A1.952 1.952 0 0 0 8.13 9.202z" fill="#26a69a" stroke-width=".976"/></symbol><symbol viewBox="0 0 512 512" id="ionic" xmlns="http://www.w3.org/2000/svg"><g fill="#4f8ff7"><path d="M423.592 132.804A31.855 31.855 0 0 0 429 115c0-17.675-14.33-32-32-32a31.853 31.853 0 0 0-17.805 5.409C344.709 63.015 302.11 48 256 48 141.125 48 48 141.125 48 256c0 114.877 93.125 208 208 208 114.873 0 208-93.123 208-208 0-46.111-15.016-88.71-40.408-123.196zM391.83 391.832c-17.646 17.646-38.191 31.499-61.064 41.174-23.672 10.012-48.826 15.089-74.766 15.089-25.94 0-51.095-5.077-74.767-15.089-22.873-9.675-43.417-23.527-61.064-41.174s-31.5-38.191-41.174-61.064C68.982 307.096 63.905 281.94 63.905 256c0-25.94 5.077-51.095 15.089-74.767 9.674-22.873 23.527-43.417 41.174-61.064s38.191-31.5 61.064-41.174c23.673-10.013 48.828-15.09 74.768-15.09 25.939 0 51.094 5.077 74.766 15.089a191.221 191.221 0 0 1 37.802 21.327A31.853 31.853 0 0 0 365 115c0 17.675 14.327 32 32 32 5.293 0 10.28-1.293 14.678-3.568a191.085 191.085 0 0 1 21.327 37.801c10.013 23.672 15.09 48.827 15.09 74.767 0 25.939-5.077 51.096-15.09 74.768-9.675 22.873-23.527 43.418-41.175 61.064z"/><circle cx="256.003" cy="256" r="96"/></g></symbol><symbol viewBox="0 0 24 24" id="java" xmlns="http://www.w3.org/2000/svg"><path d="M2 21h18v-2H2M20 8h-2V5h2m0-2H4v10a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4v-3h2a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="javascript" xmlns="http://www.w3.org/2000/svg"><path d="M3 3h18v18H3V3m4.73 15.04c.4.85 1.19 1.55 2.54 1.55 1.5 0 2.53-.8 2.53-2.55v-5.78h-1.7V17c0 .86-.35 1.08-.9 1.08-.58 0-.82-.4-1.09-.87l-1.38.83m5.98-.18c.5.98 1.51 1.73 3.09 1.73 1.6 0 2.8-.83 2.8-2.36 0-1.41-.81-2.04-2.25-2.66l-.42-.18c-.73-.31-1.04-.52-1.04-1.02 0-.41.31-.73.81-.73.48 0 .8.21 1.09.73l1.31-.87c-.55-.96-1.33-1.33-2.4-1.33-1.51 0-2.48.96-2.48 2.23 0 1.38.81 2.03 2.03 2.55l.42.18c.78.34 1.24.55 1.24 1.13 0 .48-.45.83-1.15.83-.83 0-1.31-.43-1.67-1.03l-1.38.8z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="javascript-map" xmlns="http://www.w3.org/2000/svg"><path d="M18 8v2h2v10H10v-2H8v4h14V8h-4z" fill="#ffca28"/><path d="M2.444 2.506h14.135v14.136H2.444V2.506m3.714 11.811c.315.668.935 1.218 1.995 1.218 1.178 0 1.987-.629 1.987-2.003V8.993H8.805v4.508c0 .675-.275.848-.707.848-.455 0-.644-.314-.856-.683l-1.084.651m4.697-.14c.392.769 1.185 1.358 2.426 1.358 1.257 0 2.199-.652 2.199-1.854 0-1.107-.636-1.602-1.767-2.089l-.33-.141c-.573-.243-.816-.408-.816-.801 0-.322.243-.573.636-.573.377 0 .628.165.856.573l1.028-.683c-.432-.754-1.044-1.045-1.884-1.045-1.186 0-1.948.754-1.948 1.752 0 1.083.636 1.594 1.594 2.002l.33.141c.613.267.974.432.974.888 0 .377-.354.652-.903.652-.652 0-1.029-.338-1.312-.81l-1.083.63z" fill="#ffca28"/></symbol><symbol viewBox="0 0 180 180" id="jenkins" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="gia"><path transform="scale(1 -1)" fill="#37474f" d="M.899-144.42h144.42V0H.899z"/></clipPath></defs><g transform="matrix(1.0691 0 0 -1.0691 9.4 166.143)" clip-path="url(#gia)"><g fill-rule="evenodd"><path d="M107.96 30.661l-12.506-1.876-16.883-1.876-10.943-.312-10.629.312-8.13 2.502-7.19 7.815-5.628 15.945-1.25 3.44-7.504 2.5-4.377 7.191-3.126 10.317 3.44 9.067 8.128 2.814 6.565-3.127 3.127-6.878 3.752.626 1.25 1.563-1.25 7.19-.313 9.068 1.876 12.505-.074 7.143 5.701 9.114 10.005 7.19 17.508 7.504 19.383-2.814 16.883-12.193 7.817-12.505 5.002-9.067 1.25-22.51-3.752-19.384-6.877-17.195-6.566-9.066" fill="#f0d6b7"/><path d="M97.334-23.425l-44.709-1.876v-7.503l3.752-26.262-1.876-2.19-31.264 10.63-2.19 3.752-3.126 35.328-7.19 21.26-1.563 5.002 25.01 17.195 7.818 3.127 6.877-8.441 5.94-5.315 6.88-2.188 3.125-.938L68.57 1.899l2.814-3.44 7.19 2.502-5.002-9.693 27.2-12.818-3.439-1.876" fill="#335061"/><path d="M23.238 85.687l8.128 2.814 6.566-3.127 3.127-6.878 3.751.626.938 3.751-1.876 7.19 1.876 17.197-1.563 9.379 5.627 6.565 12.193 9.692-3.44 4.69-17.194-8.442-7.191-5.627-4.064-8.754-6.253-8.442-1.876-10.005 1.251-10.63" fill="#6d6b6d"/><path d="M36.055 115.07s4.69 11.567 23.448 17.195c18.759 5.628.938 4.065.938 4.065l-20.321-7.817-7.817-7.816-3.438-6.253 7.19.626M26.676 87.875s-6.566 21.886 18.446 25.012l-.938 3.752-17.195-4.065-5.003-16.257 1.251-10.63 3.439 2.188" fill="#dcd9d8"/></g><g fill="#f7e4cd"><path d="M36.681 58.799l4.094 3.966s1.847-.214 2.16-2.402c.312-2.19 1.25-21.886 14.693-32.516 1.227-.97-10.004 1.564-10.004 1.564L37.62 45.042M94.209 64.739s.729 9.477 3.28 8.748c2.553-.729 2.553-3.28 2.553-3.28s-6.198-4.01-5.833-5.468" fill-rule="evenodd"/><path d="M120.16 99.442s-5.153-1.088-5.628-5.628c-.474-4.54 5.628-.938 6.566-.625M82.327 99.129s-6.879-.938-6.879-5.314c0-4.378 7.817-4.065 10.005-2.19"/><g fill-rule="evenodd"><path d="M39.807 78.808s-11.881 7.191-13.131.312c-1.25-6.877-4.065-11.88 1.876-19.07l-4.064 1.25-3.752 9.691-1.25 9.38 7.19 7.504 8.129-.626 4.69-3.751.312-4.69M45.435 98.504s5.315 27.512 32.203 32.827c22.136 4.375 33.765-.938 38.142-5.94 0 0-19.696 23.447-38.455 16.257-18.759-7.191-32.514-20.322-32.202-28.762.532-14.377.313-14.382.313-14.382M117.97 122.27s-9.066.312-9.38-7.817c0 0 0-1.25.625-2.5 0 0 7.192 8.129 11.568 3.751"/><path d="M78.268 111.1s-1.56 12.477-12.199 5.223c-6.878-4.69-6.252-11.255-5.002-12.505s.91-3.77 1.862-2.04c.952 1.728.638 7.356 4.078 8.918 3.439 1.564 9.077 3.31 11.26.404"/></g></g><g fill="#49728b" fill-rule="evenodd"><path d="M48.874 26.597L19.486 13.466s12.193-48.46 5.94-63.467l-4.377 1.563-.313 18.446-8.128 35.015-3.44 9.692 30.639 20.633 9.067-8.753M51.896-.206l4.17-5.087v-18.76h-5.003s-.625 13.132-.625 14.696c0 1.563.624 7.19.624 7.19M52-26.866l-14.069-.625 4.065-2.813L52-31.868"/></g><g fill-rule="evenodd"><path d="M100.15-23.739l11.567.313 2.814-28.764-11.881-1.563-2.5 30.014" fill="#335061"/><path d="M103.27-23.739l17.508.938s7.19 18.133 7.19 19.07c0 .939 6.253 26.263 6.253 26.263l-14.069 14.694-2.813 2.501-7.504-7.503V3.148l-6.565-26.887" fill="#335061"/><path d="M111.09-21.55l-10.942-2.188 1.563-8.755c4.064-1.876 10.943 3.127 10.943 3.127M111.4 33.162l21.885-16.257.626 7.503-16.57 15.32-5.94-6.566" fill="#49728b"/><path d="M62.85-85.332l-6.473 26.266-3.22 19.38-.531 14.385 29.296 1.56 18.226.003-1.658-32.83 2.814-25.324-.312-4.69-23.76-1.876-14.382 3.126" fill="#fff"/><path d="M96.083-23.426s-1.563-32.515 3.127-55.65c0 0-9.38-5.94-23.136-7.503l26.262.938 3.126 1.875-3.752 51.273-.938 10.944" fill="#dcd9d8"/><path d="M115.06-49.691l12.193 3.44 23.135 1.25 3.44 10.629-6.254 18.446-7.19.938-10.005-3.127-9.599-4.686-5.095.935-3.972-1.56" fill="#fff"/><path d="M114.84-43.435s8.128 3.751 9.38 3.438L120.78-22.8l4.065 1.563s2.814-16.257 2.814-18.133c0 0 17.507-.938 19.07-.938 0 0 3.752 7.191 2.814 14.694l3.44-10.005.312-5.628-5.002-7.503-5.627-1.25-9.38.312-3.126 4.064-10.943-1.563-3.44-1.25" fill="#dcd9d8"/></g><path d="M102.56-21.241L95.682-3.733l-7.19 10.317s1.562 4.377 3.75 4.377h7.192l6.878-2.501-.625-11.568-3.127-18.134" fill="#fff"/><path d="M103.9-15.297S95.145 1.585 95.145 4.086c0 0 1.563 3.752 3.752 2.814 2.19-.938 6.879-3.439 6.879-3.439v5.94l-10.63 2.19-7.19-.939 12.193-28.763 2.5-.313" fill="#dcd9d8" fill-rule="evenodd"/><path d="M65.664 25.968l-8.661.942-8.13 2.501v-2.814l3.972-4.38 12.506-5.627" fill="#fff"/><path d="M51.689 25.031s9.693-4.065 12.819-3.127l.311-3.748-8.752 1.872-5.316 3.752.938 1.251" fill="#dcd9d8" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43" fill="#d33833" fill-rule="evenodd"/><path d="M115.03 9.897c-5.305.156-10.098.786-14.294 1.97.285 1.72-.249 3.408.18 4.647 1.17.843 3.13.83 4.898 1.027-1.529.752-3.677 1.049-5.44.615-.042 1.194-.578 1.934-.902 2.868 2.982 1.064 10.024 8.044 13.984 5.732 1.887-1.099 2.689-7.377 2.835-10.43.122-2.533-.23-5.088-1.261-6.43z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669" fill="#d33833" fill-rule="evenodd"/><path d="M89.66 18.569c-.014-.401-.03-.806-.047-1.21-1.656-1.089-4.33-1.076-6.148-1.99 2.68-.117 4.79-.763 6.614-1.672l-.118-3.033c-3.036-2.078-5.81-5.173-9.384-7.122-1.69-.922-7.622-3.294-9.42-2.875-1.017.236-1.109 1.499-1.516 2.689-.866 2.548-2.861 6.605-3.035 10.44-.222 4.846-.71 12.967 4.51 11.969 4.213-.804 9.113-2.745 12.375-4.527 1.993-1.09 3.146-2.436 6.17-2.669z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695" fill="#d33833" fill-rule="evenodd"/><path d="M92.675 12.788c-.463 2.64-.999 3.393-.792 5.695 7.04 4.693 8.361-8.061.792-5.695z" fill="none" stroke="#d33833" stroke-width="2"/><path d="M102.87 10.649s-2.19 3.127-.626 4.065c1.564.938 3.127 0 4.065 1.563s0 2.501.313 4.377 1.877 2.189 3.44 2.501c1.562.313 5.94.938 6.565-.625l-1.876 5.627-3.752 1.25-11.88-6.877-.626-3.44v-6.877M70.041.331c-.376 4.88-.773 9.752-1.215 14.626-.662 7.279 1.748 6.009 8.057 6.009.964 0 5.933-1.15 6.289-1.876 1.705-3.483-2.851-2.709 1.964-5.335 4.065-2.216 11.246 1.346 9.603 6.273-.919 1.095-4.789.341-6.176 1.06l-7.327 3.8c-3.108 1.612-10.29 3.962-13.603 1.709-8.395-5.71.53-19.974 3.524-25.93" fill="#ef3d3a" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M78.268 111.1c-8.521 1.985-12.755-3.566-15.338-9.323-2.306.559-1.389 3.695-.806 5.294 1.525 4.194 7.672 9.778 12.694 9.02 2.161-.325 5.086-2.301 3.45-4.99M119.79 101.4l.404-.016c1.926-4 3.593-8.238 6.022-11.769-1.628-3.79-12.322-7.144-12.157-.338 2.313 1.01 6.305.206 8.356 1.497-1.186 3.254-2.897 6.024-2.625 10.626M82.63 101.29c1.827-3.35 2.422-6.868 5.019-9.4 1.17-1.14 3.444-2.529 2.316-5.698-.263-.747-2.189-2.414-3.3-2.741-4.06-1.2-13.521-.248-10.317 4.814 3.358-.157 7.871-2.18 10.38.257-1.927 3.081-5.363 9.177-4.098 12.768M118.26 67.253c-6.113-3.927-12.93-8.197-22.947-7.207-2.14 1.86-2.956 6.002-.877 8.737 1.082-1.861.402-5.284 3.419-5.799 5.684-.972 12.299 3.477 16.387 5.032 2.535 4.275-.219 5.847-2.503 8.597-4.675 5.636-10.947 12.622-10.72 21.06 1.89 1.37 2.053-2.092 2.325-2.722 2.44-5.714 8.585-13.021 13.07-17.912 1.1-1.205 2.914-2.36 3.115-3.157.582-2.315-1.513-5.09-1.27-6.63M37.668 71.387c-1.916 1.094-2.372 5.91-4.622 6.048-3.215.195-2.629-6.25-2.616-10.018-2.213 2.009-2.602 8.194-.976 11.37-1.853.91-2.68-1.003-3.708-1.677 1.32 9.595 14.036 4.45 11.922-5.723M122.15 63.257c-2.846-5.417-6.871-11.382-15.222-11.555-.17 1.75-.3 4.411.009 5.464 6.384.614 10.325 3.863 15.212 6.091M82.149 59.745c5.326-2.8 15.114-3.102 22.353-2.89.388-1.586.379-3.545.394-5.48-9.305-.463-20.307 1.84-22.747 8.37M81.136 54.523c3.683-9.247 16.341-8.182 27.016-7.927-.47-1.2-1.489-2.62-2.755-3.132-3.42-1.392-12.855-2.448-17.604.074-3.011 1.601-4.946 5.219-6.596 7.34-.797 1.024-4.765 3.64-.06 3.645"/></g><path d="M117.82 3.516c-4.322-7.402-8.457-15.005-13.585-21.534 2.15 6.32 3.07 16.9 3.394 24.965 4.498 2.105 8.349-.474 10.191-3.43" fill="#81b0c4" fill-rule="evenodd"/><g fill="#231f20" fill-rule="evenodd"><path d="M141.07-23.089c-4.839-.969-8.239-5.671-12.959-5.37 2.594 3.658 7.14 5.2 12.959 5.37M143.21-30.661c-3.944-.417-8.576-1.055-12.577-.726 1.894 2.892 9.19 1.894 12.577.726M144.58-37.19c-4.433-.096-9.942-.008-14.155.346 2.492 2.677 11.28.993 14.155-.346"/></g><g fill-rule="evenodd"><path d="M109.48-55.057c.636-5.567 2.843-11.207 2.566-17.304-2.45-.827-3.858-1.55-7.142-1.545-.232 5.181-.925 13.102-.718 18.041 1.615-.107 3.997 1.154 5.294.808" fill="#dcd9d8"/><path d="M102.33 26.985c-2.226-1.453-4.121-3.267-6.259-4.818-4.74-.235-7.327.328-10.81 3.05.057.219.407.121.42.39 5.075-2.262 11.524.92 16.648 1.378" fill="#f0d6b7"/><path d="M75.694-7.603c1.394 6.04 6.857 9.17 11.817 12.497 5.12-6.498 8.234-14.855 11.663-22.92-8.102 2.443-16.38 6.406-23.481 10.423" fill="#81b0c4"/><path d="M104.18-55.865c-.207-4.94.486-12.86.718-18.041 3.283-.004 4.691.718 7.142 1.545.276 6.096-1.93 11.737-2.566 17.304-1.298.346-3.679-.914-5.294-.808zm-51.13 28.09c2.165-19.906 5.301-36.639 11.054-54.266 12.766-3.876 28.157-4.214 39.441-.716-2.072 9.948-1.167 22.06-2.378 32.677-.912 7.98-.447 16.009-1.698 24.15-13.673 2.844-33 .665-46.418-1.845zm49.651 1.72c-.115-8.549.383-16.982 1.036-25.542 3.282.493 5.51.822 8.56 1.49-.99 8.241-.869 17.514-2.886 24.804-2.332-.023-4.385.027-6.71-.752zm16.653 1.378c-1.558.357-3.372.014-4.86-.015.7-6.969 2.397-14.659 2.995-21.974 2.342-.073 3.593 1.032 5.52 1.403.102 6.421-.562 15.268-3.655 20.586zm25.215-23.038c4.882 1.186 7.952 7.165 6.586 13.305-.916 4.127-2.548 11.898-4.295 14.538-1.29 1.953-4.79 4.51-7.584 2.72-4.545-2.91-12.552-3.755-15.867-7.278 1.662-5.534 2.178-13.135 2.864-20.146 5.678-.354 12.665 1.562 17.387-.471-3.297-1.068-7.575-1.077-10.423-2.633 2.328-1.125 7.778-.897 11.332-.035zM99.17-18.025c-3.43 8.063-6.543 16.42-11.663 22.918-4.96-3.327-10.423-6.456-11.817-12.497 7.1-4.017 15.379-7.98 23.481-10.422zm8.453 24.971c-.325-8.065-1.245-18.644-3.395-24.965 5.128 6.53 9.263 14.132 13.585 21.534-1.842 2.957-5.693 5.536-10.19 3.431zm-9.582 3.405c-1.943.21-3.592-2.233-6.117-1.177-.58-.64-1.105-1.333-1.695-1.958 5.579-6.723 8.114-16.262 12.423-24.163 2.312 7.59 2.045 15.904 2.555 24.188-3.177-.201-4.94 2.873-7.166 3.11zm-6.161 8.132c-.208-2.303.328-3.056.791-5.695 7.57-2.367 6.248 10.388-.791 5.695zm-8.394 2.755c-3.261 1.782-8.161 3.723-12.374 4.527-5.222.999-4.732-7.123-4.51-11.968.173-3.836 2.168-7.893 3.035-10.441.406-1.19.498-2.453 1.515-2.69 1.798-.418 7.73 1.954 9.42 2.875 3.575 1.95 6.348 5.045 9.384 7.123.04 1.011.078 2.021.119 3.032-1.826.91-3.935 1.555-6.615 1.673 1.818.914 4.492.901 6.148 1.989.016.405.033.81.047 1.21-3.024.234-4.176 1.58-6.17 2.67zm-31.152 5.659c-2.707-2.748 7.592-6.494 10.871-6.696-.018 1.739.991 3.378.788 4.626-3.895.684-9.013.232-11.66 2.07zm33.345-1.29c-.013-.27-.363-.172-.42-.39 3.482-2.722 6.07-3.285 10.81-3.05 2.137 1.551 4.033 3.365 6.259 4.818-5.124-.458-11.574-3.64-16.648-1.379zm30.606-9.282c-.146 3.053-.948 9.332-2.835 10.431-3.961 2.312-11.002-4.668-13.984-5.732.324-.934.86-1.674.901-2.868 1.764.434 3.912.137 5.44-.615-1.767-.198-3.727-.185-4.897-1.027-.429-1.239.105-2.927-.18-4.647 4.196-1.184 8.989-1.814 14.294-1.97 1.032 1.341 1.383 3.896 1.261 6.429zM47.777 24.24c-.85.606-6.6 8.087-7.388 7.777-10.405-4.103-20.134-11.199-28.828-17.91 8.29-17.787 11.635-39.579 12.227-60.582 9.496-4.441 17.836-10.844 30.722-11.512-1.491 10.55-2.852 19.962-3.699 29.895-3.237 1.365-7.882-.062-10.913.423-.025 3.651 4.628 1.6 5.015 4.054.292 1.858-2.56 1.998-1.631 4.923 2.368-.861 3.612-2.763 6.138-3.477 2.309 5.05-.032 13.985.3 18.205.064.792.397 4.39 2.172 3.759 1.57-.559-.09-9.569.082-13.563.157-3.68-.444-7.242 1.046-9.552a355.817 355.817 0 0 0 38.576 3.16c-2.964 1.272-6.485 2.475-10.345 4.651-2.093 1.18-8.69 3.635-9.293 5.622-.964 3.167 2.528 4.855 3.125 7.57-6.285-3.428-7.511 3.286-8.998 8.042-1.347 4.308-2.114 7.526-2.445 10.01-5.414 2.581-11.203 5.195-15.863 8.505zm63.009 6.872c8.67 4.204 10.232-15.711 6.834-22.127.525-1.914 2.331-2.646 3.069-4.366-4.838-8.667-10.211-16.756-15.148-25.32 3.672 2.286 8.917.409 13.238 2.12 1.58.624 2.722 4.24 3.918 7.133 3.29 7.958 6.743 17.99 8.28 25.586.346 1.73 1.292 5.5 1.08 7.04-.378 2.758-4.12 4.803-6.022 6.508-3.506 3.15-5.714 5.921-9.371 8.866-1.483-2.189-4.666-3.66-5.878-5.44zM27.95 107.99c-4.13-4.545-3.266-13.062-2.766-19.121 7.467 4.697 17.377-.372 17.284-8.36 3.565.094 1.332 4.452.687 7.259-2.107 9.169 3.55 19.13.256 27.516-6.395-.485-11.649-3.097-15.46-7.294zm29.558 26.38c-9.352-2.65-21.337-9.446-25.18-17.847 2.976.432 5.041 1.933 7.977 2.119 1.11.072 2.563-.466 3.838-.148 2.54.63 4.685 6.327 6.602 8.447 1.868 2.07 4.114 2.954 5.651 4.841.988.477 2.448.444 2.504 1.927-.428.457-.879.806-1.392.66zm48.681-2.493c-9.707 5.477-26.136 9.596-36.462 4.449-8.331-4.155-19.593-11.027-23.433-19.737 3.587-8.405-1.062-16.106-1.36-24.64-.157-4.54 2.139-8.504 2.315-13.446-1.228-2.025-4.978-2.275-7.574-2.136-.873 4.372-2.403 9.287-6.906 9.78-6.371.697-11.03-4.576-11.319-10.085-.342-6.48 4.978-17.22 12.517-16.475 2.913.287 3.629 3.207 6.802 3.177 1.72-3.432-2.653-4.51-3.103-6.964-.117-.634.363-3.112.642-4.274 1.37-5.658 4.422-12.982 7.427-17.29 3.814-5.464 11.307-6.288 19.37-6.823 1.44 3.101 6.743 2.846 10.2 2.035-4.143 1.64-7.993 5.617-11.185 9.137-3.665 4.039-7.378 8.371-7.566 13.65 6.927-9.61 12.65-18.003 25.246-22.23 9.53-3.196 20.662 1.465 27.986 6.608 3.039 2.137 4.853 5.529 7.013 8.634 8.082 11.626 11.854 28.219 11.024 44.303-.342 6.633-.327 13.244-2.552 17.706-2.326 4.666-10.193 8.84-14.8 4.62-.853 4.537 3.83 7.344 9.331 5.71-3.922 5.063-8.039 11.145-13.614 14.29zm18.084-149.66c7.585 3.77 21.757 10.149 26.512-.014 1.755-3.746 3.814-10.079 4.723-13.946 1.284-5.456-1.392-16.923-7-18.754-4.953-1.617-10.733-1.518-16.7-.32-.702.585-1.484 1.603-2.03 2.665-4.261.165-8.25-.229-11.615-1.98.319-3.15-1.812-3.656-3.81-4.305-1.48-5.872 2.963-13.541 1.9-18.896-.76-3.815-5.453-4.405-8.902-5.118-.113-2.12.15-3.89.386-5.683-.789-2.907-4.327-4.561-7.679-4.967-11.029-1.326-27.775-1.922-38.384 1.893-2.96 7.261-5.292 16.093-7.758 24.384-10.346-1.105-18.715 4.464-26.603 8.113-2.731 1.266-6.51 1.964-7.53 4.138-.99 2.105-.584 6.14-.83 9.95-.625 9.733-1.16 19.12-3.73 29.086-1.154 4.472-3.165 8.418-4.568 12.727C9.358 5.184 7.092 10.12 6.5 14.1c-.877 5.903 4.681 6.232 8.235 8.79 5.494 3.954 9.806 6.142 15.756 9.711 1.762 1.057 7.077 3.733 7.681 4.966 1.202 2.443-2.062 5.888-2.935 7.803-1.38 3.03-2.1 5.602-2.298 8.59-4.992.789-8.775 3.76-11.06 7.109-3.781 5.543-6.403 15.798-3.132 23.599.257.614 1.536 1.822 1.725 2.765.372 1.858-.7 4.329-.768 6.305-.343 10.14 1.716 18.875 8.541 21.932 2.771 11.038 12.688 14.71 22.032 20.195 3.493 2.05 7.343 3.36 11.32 4.824 14.263 5.25 36.15 4.261 47.987-4.692 5.02-3.797 13.044-11.813 15.914-17.617 7.58-15.323 7.042-40.931 1.74-59.571-.712-2.503-1.746-6.181-3.19-9.187-1.006-2.1-4.134-6.3-3.754-8.153.391-1.916 7.132-7.034 8.577-8.428 2.603-2.51 7.548-5.843 7.948-9.012.43-3.372-1.485-7.984-2.456-11.238-3.245-10.858-6.412-20.895-10.091-30.576" fill="#231f20"/><path d="M73.674 57.38c.411.548 2.674 1.38 5.84-.144 0 0-3.752-.626-3.44-6.881l-1.564.313s-1.615 5.672-.836 6.712" fill="#f7e4cd"/><path d="M101.09 3.617a1.72 1.72 0 1 0-3.44.001 1.72 1.72 0 0 0 3.44-.001M102.81-4.355a1.72 1.72 0 1 0-3.44 0 1.72 1.72 0 0 0 3.44 0" fill="#1d1919"/></g><g><rect transform="matrix(.8 0 0 -.8 0 144)" x="16.854" y="177.38" width="70.412" height="4.12" rx=".983" ry=".983"/><rect transform="scale(1 -1)" x="78.502" y="-2.097" width="50.037" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="13.483" y="-3.697" width="54.831" height="3.296" rx=".786" ry=".786"/><rect transform="scale(1 -1)" x="83.296" y="-3.697" width="45.243" height="3.296" rx=".786" ry=".786"/></g></g></symbol><symbol viewBox="0 0 24 24" id="json" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#fbc02d"/></symbol><symbol viewBox="0 0 50 50" id="julia" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" stroke-width="5.673"><circle cx="13.497" cy="281.632" r="9.555" fill="#bc342d"/><circle cx="36.081" cy="281.632" r="9.555" fill="#864e9f"/><circle cx="24.722" cy="262.389" r="9.555" fill="#328a22"/></g></symbol><symbol viewBox="0 0 64 64" id="karma" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -233)"><path d="M38.556 288.413l-20.29-26.687 9.532-7.246 20.29 26.686h-.001.002l5.527 7.247z" fill="#359b8b" stroke-width=".173"/><path d="M35.681 241.172L24.92 255.327v-14.13H12.947v13.817l7.84 33.235h4.132v-13.147l.003.003 20.29-26.686-.008-.006 5.504-7.24H35.84v.12z" fill="#3cbeae" stroke-width=".206"/></g></symbol><symbol viewBox="0 0 24 24" id="key" xmlns="http://www.w3.org/2000/svg"><path d="M7 14a2 2 0 0 1-2-2 2 2 0 0 1 2-2 2 2 0 0 1 2 2 2 2 0 0 1-2 2m5.65-4A5.99 5.99 0 0 0 7 6a6 6 0 0 0-6 6 6 6 0 0 0 6 6 5.99 5.99 0 0 0 5.65-4H17v4h4v-4h2v-4H12.65z" fill="#26a69a"/></symbol><symbol viewBox="0 0 24 24" id="kivy" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.89 0 0 1.89 -12.157 -11.429)" fill="#90a4ae"><path d="M7.026 8.63v4.474l1.928-1.928a.437.437 0 0 0 0-.619zM9.38 16.072v-4.474l-1.927 1.927a.437.437 0 0 0 0 .62zM18.576 10.412l-5.346.564-.017.018 2.39 2.39zM9.922 8.502s.023 3.304-.003 4.452c-.02.856.371 1.114.746 1.507.538.564 1.599 1.57 1.599 1.57a.53.53 0 0 0 .75 0l1.843-1.844a.53.53 0 0 0 0-.75z"/></g></symbol><symbol viewBox="0 0 24 24" id="kl" xmlns="http://www.w3.org/2000/svg"><defs><style>.a{fill:#3aaae1}.b{fill:#fdfeff}</style></defs><title>kl</title><path d="M12.033 1.737c-.25-.003-.5.11-.729.337C8.225 5.15 5.15 8.227 2.078 11.31c-.144.144-.229.346-.341.521v.41c.16.223.294.474.485.666a3259.51 3259.51 0 0 0 8.936 8.937c.193.192.443.325.666.486h.41c.205-.142.436-.256.609-.428 3.046-3.041 6.09-6.083 9.133-9.127.47-.47.472-1.005.006-1.472l-9.218-9.217c-.23-.23-.48-.347-.731-.35zm-1.062 4.545l1.386.832c.702.422 1.403.846 2.109 1.262a.544.544 0 0 1 .04.026l.016.013.017.013c.061.056.089.123.088.224a510.281 510.281 0 0 0 0 3.794.463.463 0 0 1-.007.094c-.015.069-.054.103-.142.109a.464.464 0 0 1-.044.002c-.045-.002-.09-.002-.136-.003-.323-.006-.648-.001-.998-.001v-.527-1.34-.671-.003l.004-.668c0-.147-.039-.231-.17-.308-.893-.528-1.78-1.066-2.67-1.6-.051-.03-.101-.065-.173-.111l.001-.003h-.001zm.362 3.39c.068-.003.119.043.173.138.085.148.174.293.264.44l.015.025c.096.154.194.31.292.47l-1.915 1.176c-.337.207-.673.417-1.014.617-.113.067-.154.143-.154.277.01.977.01 1.955.014 2.932V16H7.7V16h-.002c-.004-.053-.014-.112-.014-.17-.005-1.25-.006-2.501-.015-3.751 0-.142.045-.222.164-.294a467.13 467.13 0 0 0 3.353-2.054l.016-.01a.606.606 0 0 1 .032-.017l.016-.008a.308.308 0 0 1 .033-.013l.012-.004a.157.157 0 0 1 .028-.005l.01-.001zm5.677 3.126l.314.54.346.594v.001c-.158.094-.298.178-.438.259l-3.097 1.798c-.106.062-.189.071-.3.01l-.893-.496-1.524-.843-.895-.493c-.035-.02-.068-.044-.129-.085h.001l.137-.25.495-.902 1.446.795c.442.243.886.483 1.323.734.121.07.212.072.334 0 .894-.525 1.792-1.043 2.689-1.563.057-.034.118-.061.191-.1z" fill="#29b6f6" stroke-width=".041"/></symbol><symbol viewBox="0 0 500 500" id="kotlin" xmlns="http://www.w3.org/2000/svg"><path d="M500 500H0V0h500L250 250z" fill="#7F52FF"></path></symbol><symbol viewBox="0 0 240 240" id="laravel" xmlns="http://www.w3.org/2000/svg"><path d="M216.05 119.036c-1.433.343-24.945 6.673-24.945 6.673l-19.227-28.622c-.537-.828-.99-1.656.359-1.849 1.345-.196 23.195-4.477 24.182-4.723.99-.245 1.837-.536 3.053 1.267 1.21 1.8 17.836 24.626 18.464 25.506.627.877-.447 1.41-1.883 1.748m-4.101 49.326c.588 1.003 1.176 1.64-.67 2.367-1.843.73-62.243 22.847-63.418 23.39-1.173.546-2.092.73-3.607-1.637-1.51-2.362-21.16-39.264-21.16-39.264l64.03-18.075c1.876-.644 2.317-.405 3.103.822 1.074 1.68 21.143 31.403 21.726 32.4m-103.7-21.087c-.78.202-37.566 9.733-39.525 10.22-1.965.485-1.965.246-2.188-.49-.226-.727-43.728-98.053-44.333-99.271-.605-1.214-.574-2.177 0-2.177.571 0 34.734-3.313 35.944-3.383 1.207-.07 1.08.205 1.526 1.033l49.025 91.818c.84 1.58 1.239 1.81-.452 2.248m94.588-59.77c-3.5-4.58-5.2-3.751-7.357-3.41-2.154.336-27.277 4.915-30.194 5.449-2.918.536-4.758 1.803-2.963 4.53 1.597 2.422 18.113 27.824 21.751 33.42l-65.663 17.066L66.18 49.832c-2.075-3.342-2.507-4.514-7.236-4.28-4.735.23-40.969 3.495-43.55 3.731-2.58.233-5.416 1.479-2.835 8.09 2.583 6.612 43.734 102.82 44.88 105.62 1.149 2.803 4.128 7.345 11.11 5.527 7.157-1.871 31.969-8.894 45.52-12.742 7.163 14.07 21.77 42.619 24.473 46.707 3.607 5.459 6.089 4.56 11.626 2.738 4.325-1.42 67.65-26.129 70.502-27.4 2.855-1.273 4.613-2.184 2.685-5.275-1.419-2.28-18.124-26.558-26.876-39.26 5.993-1.733 27.305-7.888 29.575-8.557 2.646-.779 3.008-2.19 1.572-3.94-1.436-1.755-21.293-28.72-24.79-33.296z" fill="#ff5722" stroke="#ff5722" stroke-width="8.852" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="less" xmlns="http://www.w3.org/2000/svg"><path d="M13.696 2.999V5h2.002v5a2 2 0 0 0 1.999 2 2 2 0 0 0-2 2v5h-2v2h2a2 2 0 0 0 2-2v-4a2 2 0 0 1 2-2h1V11h-1a2 2 0 0 1-2-2V5a2 2 0 0 0-2-2.001zm-.03 12.766v.47a1 1 0 0 0 .03-.236 1 1 0 0 0-.03-.234zM10.566 21v-2.001H8.565v-5a2 2 0 0 0-2-2 2 2 0 0 0 2-2V5h2.001v-2H8.565a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-.999V13h1a2 2 0 0 1 2 2v3.999A2 2 0 0 0 8.564 21zm.03-12.766v-.47a1 1 0 0 0-.03.236 1 1 0 0 0 .03.234z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="lib" xmlns="http://www.w3.org/2000/svg"><path d="M19 7H9V5h10m-4 10H9v-2h6m4-2H9V9h10m1-7H8a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2M4 6H2v14a2 2 0 0 0 2 2h14v-2H4V6z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 40 40" id="livescript" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -257)" fill="#317eac"><path stroke-width="3.299" d="M5.419 260.18h3.685v34.207H5.419z"/><path stroke-width="3.299" d="M37.074 288.197v3.685H2.867v-3.685z"/><path stroke-width="2.894" d="M29.612 265.658l2.004 2.005L7.428 291.85l-2.004-2.005z"/><path stroke-width="2.325" d="M10.73 262.471h2.835v22.08H10.73z"/><path stroke-width="2.063" d="M15.36 262.519h2.835v17.382H15.36z"/><path stroke-width="1.77" d="M19.99 262.471h2.835v12.802H19.99z"/><path stroke-width="1.422" d="M24.526 262.491h2.835v8.254h-2.835z"/><path stroke-width="1.128" d="M28.783 262.463h2.835v5.197h-2.835z"/><path stroke-width="2.325" d="M34.801 286.545v-2.835h-22.08v2.835z"/><path stroke-width="2.063" d="M34.753 281.914v-2.835H17.371v2.835z"/><path stroke-width="1.77" d="M34.801 277.284v-2.835H21.999v2.835z"/><path stroke-width="1.422" d="M34.781 272.749v-2.835h-8.254v2.835z"/><path stroke-width="1.128" d="M34.809 268.492v-2.835h-5.197v2.835z"/></g></symbol><symbol viewBox="0 0 24 24" id="lock" xmlns="http://www.w3.org/2000/svg"><path d="M12 17a2 2 0 0 0 2-2 2 2 0 0 0-2-2 2 2 0 0 0-2 2 2 2 0 0 0 2 2m6-9a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V10a2 2 0 0 1 2-2h1V6a5 5 0 0 1 5-5 5 5 0 0 1 5 5v2h1m-6-5a3 3 0 0 0-3 3v2h6V6a3 3 0 0 0-3-3z" fill="#ffd54f"/></symbol><symbol viewBox="0 0 24 24" id="lua" xmlns="http://www.w3.org/2000/svg"><circle cx="12.203" cy="12.102" r="10.322" fill="none" stroke="#42a5f5"/><path d="M12.33 5.746a6.483 6.381 0 0 0-6.482 6.381 6.483 6.381 0 0 0 6.482 6.38 6.483 6.381 0 0 0 6.484-6.38 6.483 6.381 0 0 0-6.484-6.38zm1.86 1.916a2.329 2.292 0 0 1 2.33 2.293 2.329 2.292 0 0 1-2.33 2.291 2.329 2.292 0 0 1-2.329-2.29 2.329 2.292 0 0 1 2.328-2.294z" fill="#42a5f5" fill-rule="evenodd"/><ellipse cy="4.615" cx="19.631" rx="2.329" ry="2.292" fill="#42a5f5" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 24 24" id="markdown" xmlns="http://www.w3.org/2000/svg"><path d="M2 16V8h2l3 3 3-3h2v8h-2v-5.17l-3 3-3-3V16H2m14-8h3v4h2.5l-4 4.5-4-4.5H16V8z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" preserveAspectRatio="xMidYMid" id="markojs" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -120.96)" stroke-width=".984"><path d="M4.002 126.482c-.655 1.07-1.32 2.14-1.976 3.21-.655 1.06-1.308 2.142-1.963 3.212l.002.002-.002.002c.655 1.07 1.308 2.15 1.963 3.211.655 1.07 1.32 2.141 1.976 3.211h3.33c-.664-1.07-1.318-2.14-1.974-3.21-.653-1.069-1.307-2.145-1.961-3.214.654-1.068 1.308-2.146 1.961-3.215a601.93 601.93 0 0 1 1.974-3.209z" fill="#2196f3"/><path d="M3.999 126.482l-.002.002c.655 1.07 1.31 2.15 1.964 3.212.655 1.07 1.32 2.14 1.974 3.21h3.331c-.664-1.07-1.319-2.14-1.974-3.21-.653-1.068-1.306-2.146-1.96-3.214z" fill="#26a69a"/><path d="M15.203 126.482l.002.002c-.655 1.07-1.31 2.15-1.965 3.212-.655 1.07-1.319 2.14-1.974 3.21h-3.33c.664-1.07 1.318-2.14 1.973-3.21.654-1.069 1.307-2.146 1.961-3.214z" fill="#8bc34a"/><path d="M11.874 126.484c.664 1.07 1.318 2.14 1.974 3.21.653 1.068 1.307 2.146 1.961 3.214-.654 1.069-1.308 2.145-1.961 3.213-.656 1.07-1.31 2.14-1.974 3.21h3.33c.655-1.07 1.319-2.14 1.974-3.21.655-1.06 1.31-2.14 1.966-3.21l-.002-.003.002-.002c-.656-1.07-1.311-2.152-1.966-3.213-.655-1.07-1.319-2.138-1.974-3.209z" fill="#ffc107"/><path d="M16.74 126.482c.665 1.07 1.319 2.14 1.974 3.21.654 1.068 1.306 2.146 1.96 3.214-.654 1.069-1.306 2.145-1.96 3.213-.655 1.07-1.31 2.141-1.974 3.211h3.33c.656-1.07 1.32-2.14 1.974-3.21.655-1.062 1.31-2.141 1.966-3.212l-.002-.002.002-.002c-.655-1.07-1.31-2.152-1.966-3.213-.655-1.07-1.318-2.138-1.973-3.209z" fill="#f44336"/></g></symbol><symbol viewBox="0 0 23 24" id="mathematica" xmlns="http://www.w3.org/2000/svg"><path d="M11.512 1.523l-.073.025-.46.794-.454.763-1.217 2.09H9.29L5.435 3.5l-.1-.047h-.018v.092l.025.163v.086l.132 1.226v.082l.032.252v.082l.22 2.137v.075l.018.082v.06l-2.348.507-.04.015-.457.1-.025.01h-.042l-1.096.244-.04.007-.17.036v.082l.018.01 1.859 2.086.053.052.114.132.804.909v.005l-.053.05-.22.257-2.564 2.875-.01.007v.082l.071.006.295.075 1.697.366v.006l2.139.472h.015v.047l-.036.252v.08l-.046.412v.082l-.036.244v.082l-.045.412v.08l-.05.41v.08l-.036.244v.082l-.046.412v.082l-.05.407v.082l-.032.248V20l-.05.407v.104h.037l3.642-1.6.294-.134h.018l.177.312.539.911.015.032.854 1.465.16.262.404.695.007.022h.092l.005-.022.017-.025.56-.947.014-.042.6-1.033.316-.539.644-1.091.05.013 3.906 1.721h.035v-.085l-.138-1.32v-.082l-.032-.244v-.082l-.035-.245v-.085l-.033-.244v-.081l-.032-.245v-.082l-.032-.244v-.085l-.035-.245v-.082l-.032-.245v-.082l-.033-.244v-.085l-.025-.17v-.053l1.632-.354.043-.008.458-.107h.028v-.01l.23-.05.03-.01h.042l.382-.09.025-.01h.043l.194-.05h.033l1.015-.23.07-.007v-.064l-.015-.013-1.19-1.342-.028-.028-.197-.22-1.428-1.604v-.006l.295-.323.4-.457 2.148-2.408.015-.01v-.065l-.035-.008-1.288-.28-.372-.084-.047-.01-2.481-.544v-.045l.432-4.265v-.02h-.042l-.302.135-.01.014h-.025l-3.307 1.45-.297.135h-.015l-2.028-3.483-.099-.145-.014-.045zm-.001 1.114l1.365 2.323.34.592-.008.025-1.18 1.511-.517.66-.012-.01-.258-.335-.04-.05-1.397-1.787.03-.063 1.378-2.365.287-.491zm4.908 2.039l-.007.025-.168.225-.538.066zm-9.817.004l.053.02.677.3h-.499l-.224-.3zM16.947 5l-.123 1.248-.113-.928.226-.307zm-9.26.156l.053.024.705.309-.757-.175zm7.388.116l.02.168-1.318.403.003-.003.16-.071 1.015-.444zM9.669 6.388l.944 1.204v.01L9.483 7.2zm3.55.172l.21.682-.234.084-.089.022-.702.255.008-.022.776-.982zm-5 .836l.986.356.898.312.048.02 1.054.373.011 3.086-.362-.117-.67-.224-.081-.038-.735-.245-.77-.256-.29-.1-.011-.255-.032-1.195-.01-.287-.015-.894-.013-.297zm6.583 0l-.011.227-.028.9-.008.303-.032 1.475-.01.262-.337.117-.734.245-.77.256-.712.245-.355.117.01-3.086 1.632-.578zm.585.437l.09.735.79-.097-.915 1.302-.018.006.01-.183.018-.877zm-9.451.536l.152.22 1.447 2.049-2.607.968-.05.015-1.972-2.214-.28-.312.003-.01.115-.018.424-.1.14-.021.337-.078.042-.01zm11.146.003l3.284.713.029.01-.022.025-1.954 2.192-.277.312-.092-.036-2.564-.95.475-.681.152-.216zM6.787 8.52h.86l.036 1.258-.013-.006-.763-1.078zm1.358 2.625l.152.06.77.252.712.245.746.247.49.167-.065.092-1.723 2.334-1.015-.302-.082-.017-.035-.015-1.902-.56.938-1.22.981-1.277zm6.73 0l.033.006 1.787 2.327.132.17-.128.036-.032.014-2.196.642-.105.032-.564.17-.018-.003-1.053-1.44-.174-.239-.547-.726-.007-.018.469-.16.769-.254.713-.245.77-.252zm-7.766.305l-.007.02-.405.523-.291-.291.657-.245zm8.802 0l.043.007.578.212.714.27-.661.394-.375-.479-.03-.042-.262-.342zm-10.843.75l-.67.668.355-.397.207-.23zm12.911.016l.068.025.045.042.554.627.042.043.204.228-.255.135zm-6.473.265l.022.015 1.38 1.872.032.05.343.465.008.031-.088.117-.422.629-.047.074-.245.343-.97 1.43-.013.007-1.18-1.72-.096-.16-.493-.708-.008-.037 1.618-2.191.007-.01zm7.827 1.194l.565.633.063.082-.272-.093-.037-.013zm-15.785.148l.297.299-.637.218-.152.05.038-.058zm13.224.47l-.855.448.346.66-.185-.058-.27-.088-1.092-.348.012-.01zm-9.687.255l1.222.356-.006.007-.458.145-.443.135-.032.01-.49.157zm-2.765.048l.318.32 2.007.517-.567.18-.055.004-2.103-.469-.744-.156.007-.006zm14.966.205l.548.188v.003l-.457.1-.043.014-1.069.23zm-10.23.507l.007.227.01.347.025 1.363.025.691-.007.255-.24.107-2.863 1.255.032-.372.033-.255.017-.227.031-.256.037-.407.045-.42.018-.23.032-.251.032-.412.05-.414.013-.14 1.455-.457.003-.014.301-.098zm4.908 0l1.245.39v.014l.312.1 1.146.362.022.23.03.255.043.408.04.42.017.23.033.251.032.412.042.325.078.848-.078-.04-3.025-1.322-.004-.305.06-2.368zm-4.295.617l.015.007.067.107.6.875-.64.531-.034-1.438zm3.671 0h.008l-.005.06-.02.678-.005.214-.479-.223zm-2.888 3.605l.763.915.001.37-.017-.006-.025-.05-.464-.791-.012-.018zm1.53.61l.184.083-.343.586-.018.007.002-.532z" fill="#f44336" fill-rule="evenodd" stroke="#f44336" stroke-width=".7747499999999999" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 720 720" id="matlab" xmlns="http://www.w3.org/2000/svg"><title>Layer 1</title><path d="M209.247 329.98L52.368 387.638l121.325 85.822 96.752-95.804-61.198-47.674z" fill="#4db6ac" fill-rule="evenodd" stroke-width=".3"/><path d="M480.193 71.446c-13.123 1.784-9.565 1.013-28.4 16.09-18.008 14.418-69.925 100.347-97.673 129.256-24.688 25.722-34.46 12.199-60.102 33.661-25.68 21.494-65.273 64.464-65.273 64.464l63.978 47.32L394.15 222.754c23.948-32.932 23.694-37.266 36.744-71.82 6.384-16.907 17.76-29.9 27.756-45.809 12.488-19.874 30.186-34.855 21.543-33.68z" fill="#00897b" fill-rule="evenodd" stroke-width=".3"/><path d="M478.206 69.796c-31.268-.189-62.068 137.245-115.56 242.691-54.543 107.519-162.235 176.82-162.235 176.82 18.156 8.243 34.681 4.91 54.236 23.394 13.375 16.164 52.09 95.976 75.174 146.117 0 0 18.964-10.297 42.994-27.695 24.03-17.397 53.124-41.896 73.384-70.3 26.883-37.692 47.897-61.043 65.703-75.271 17.806-14.23 32.404-19.336 46.458-20.54 50.238-4.305 124.582 85.792 124.582 85.792S527.267 70.09 478.206 69.796z" fill="#ffb74d" fill-rule="evenodd" stroke-width=".3"/></symbol><symbol viewBox="0 0 24 24" id="merlin" xmlns="http://www.w3.org/2000/svg"><text style="line-height:1.25;-inkscape-font-specification:'Century Gothic Bold'" x="1.953" y="21.178" transform="scale(.99582 1.0042)" font-weight="700" font-size="30.255" font-family="Century Gothic" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-width=".756"><tspan x="1.953" y="21.178" style="-inkscape-font-specification:'Century Gothic Bold'" font-size="22.745">M</tspan></text></symbol><symbol viewBox="0 0 192 191.99999" id="mocha" xmlns="http://www.w3.org/2000/svg"><title>Mocha Logo</title><g transform="translate(-354.75 -262.42) scale(4.835)" fill="#a1887f"><path d="M103.6 69.6c0-.5-.4-1-1-1H83.8c-.5 0-1 .4-1 1 0 3.4.5 15.1 5.5 20.8.2.2.4.3.7.3h8.4c.3 0 .5-.1.7-.3 5-5.6 5.5-17.3 5.5-20.8zm-7.4 18.2h-5.9c-.3 0-.5-.1-.7-.3-3.4-4-3.8-12-3.9-14.8 0-.5.4-1 1-1h13.2c.5 0 1 .4 1 1 0 2.8-.5 10.7-3.9 14.8-.3.2-.5.3-.8.3zM95.1 66.6s3.6-2.1 1.4-5.9c-1.3-2-1.9-3.7-1.4-4.4-1.3 1.6-3.5 3.3-1.1 6.9.8.9 1.2 2.8 1.1 3.4zM91.1 66.9s2.4-1.4.9-4c-.9-1.3-1.3-2.5-.9-2.9-.9 1.1-2.3 2.2-.7 4.7.5.5.7 1.8.7 2.2z"/><path d="M99.3 78.5c-.4 2.7-1.2 5.8-2.9 7.8-.2.2-.4.3-.6.3h-5c-.2 0-.5-.1-.6-.3-1.2-1.5-2-3.5-2.5-5.6 0 0 5.8.8 9.1-.4 2.4-.9 2.5-1.8 2.5-1.8z"/></g></symbol><symbol viewBox="0 0 24 24" id="movie" xmlns="http://www.w3.org/2000/svg"><path d="M18 4l2 4h-3l-2-4h-2l2 4h-3l-2-4H8l2 4H7L5 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V4h-4z" fill="#ff9800"/></symbol><symbol viewBox="0 0 24 24" id="music" xmlns="http://www.w3.org/2000/svg"><path d="M16 9V7h-4v5.5c-.42-.31-.93-.5-1.5-.5A2.5 2.5 0 0 0 8 14.5a2.5 2.5 0 0 0 2.5 2.5 2.5 2.5 0 0 0 2.5-2.5V9h3m-4-7a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2z" fill="#ef5350"/></symbol><symbol viewBox="0 0 24 24" id="mxml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#ffa726"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-actions" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#ab47bc" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-effects" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#26c6da" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-reducer" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#e53935" stroke-width="12.914"/></symbol><symbol viewBox="0 0 300 300" id="ngrx-state" xmlns="http://www.w3.org/2000/svg"><path d="M150 27.324L35.85 68.006l17.303 151.09 96.843 53.586 96.843-53.586 17.303-151.09zm-23.719 38.349c4.346-.075 9.04 1.316 14.265 4.131 2.3 1.24 9.235 2.994 15.407 3.889 21.936 3.18 47.975 19.934 56.21 36.186 5.667 11.183 4.508 17.209-4.18 21.702-7.492 3.874-22.822 2-45.08-5.517l-18.785-6.343-6.683 2.552c-9.683 3.698-19.366 12.877-23.33 22.09-2.858 6.645-3.293 9.768-2.77 20.705.523 10.955 1.315 14.12 5.2 20.997 4.423 7.829 14.576 17.818 16.331 16.064.473-.473-.574-3.648-2.308-7.048-1.735-3.4-2.744-6.825-2.26-7.606.482-.781 5.054 2.123 10.157 6.44 11.35 9.6 24.608 15.74 36.77 17.01 9.985 1.045 12.266-.814 4.787-3.912-2.41-.998-5.544-3.088-6.95-4.641-2.907-3.212-3.072-3.12 9.356-5.906 7.736-1.733 23.026-9.849 23.937-12.71.29-.91-2.195-1.296-6.27-.972-3.706.295-6.732-.087-6.732-.85 0-.76 3.032-4.523 6.732-8.385 13.883-14.489 18.62-25.32 20.098-45.906l1.02-14.217 3.257 6.756c3.601 7.452 4.265 18.202 1.701 27.437-2.141 7.711-.712 8.564 3.208 1.92 4.845-8.212 6.39-6.905 5.54 4.666-.924 12.587-5.243 22.017-14.993 32.686-7.95 8.699-7.001 10.254 2.624 4.326 9.273-5.711 10.511-4.815 5.736 4.155-9.031 16.964-28.122 31.35-47.948 36.161-12.016 2.917-20.537 3.461-31.544 2.018-28.78-3.775-56.001-23.157-68.993-49.114-3.378-6.748-8.154-14.994-10.62-18.348-5.092-6.924-5.529-10.038-2.09-15.286 1.715-2.618 2.116-5.307 1.41-9.308-3.273-18.531-3.167-19.11 4.276-26.659 6.468-6.56 6.878-7.44 6.878-15.092 0-6.637.671-8.813 3.67-11.811 2.02-2.02 5.23-3.7 7.12-3.718 5.49-.05 14.97-5.135 20.584-11.033 4.687-4.927 9.674-7.417 15.262-7.51z" fill="#9ccc65" stroke-width="12.914"/></symbol><symbol viewBox="0 0 24 24" id="nim" xmlns="http://www.w3.org/2000/svg"><path d="M4.464 15.75L2.288 3.78l5.985 7.617L12.08 3.78l3.809 7.617 5.985-7.617-2.177 11.97H4.464m15.234 3.264a1.088 1.088 0 0 1-1.088 1.088H5.553a1.088 1.088 0 0 1-1.089-1.088v-1.089h15.234z" stroke-width="1.088" fill="#ffca28"/></symbol><symbol viewBox="0 0 500 500" id="nix" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-1.965 36.302)" stroke-width=".395"><path d="M135.59 415.7c0-.295-2.752-5.283-6.116-11.084-3.364-5.801-6.116-10.776-6.116-11.055s9.514-16.889 21.143-36.912c11.629-20.022 21.323-36.798 21.542-37.279.346-.76-1.608-4.363-14.896-27.466-8.412-14.625-15.294-26.785-15.294-27.023 0-.5 24.46-43.501 25.206-44.31.414-.45.592-.384 1.078.395.32.513 16.876 29.256 36.791 63.87 62.62 108.85 74.852 130.01 75.41 130.46.3.242.544.554.544.694 0 .14-11.836.21-26.302.154-23.023-.09-26.313-.175-26.393-.694-.11-.714-27.662-48.825-28.86-50.392-.746-.978-.906-1.035-1.426-.51-.688.696-28.954 49.323-29.49 50.733l-.365.96h-13.229c-10.896 0-13.229-.095-13.229-.538zm167.58-125.61c-.134-.216 1.188-2.863 2.938-5.882 6.924-11.944 84.291-145.75 96.491-166.88 7.143-12.371 13.142-22.465 13.333-22.433.363.062 25.861 43.105 25.861 43.655 0 .174-6.761 11.952-15.026 26.173-8.46 14.557-14.932 26.104-14.81 26.421.185.483 4.564.564 30.213.564h29.996l.958 1.48c.526.814 3.296 5.547 6.155 10.518 2.859 4.971 5.45 9.29 5.756 9.597.706.705.704.724-.16 1.572-.395.388-3.36 5.323-6.587 10.965-3.228 5.643-6.056 10.387-6.285 10.543-.23.156-19.695.171-43.256.034l-42.84-.249-.804 1.15c-.441.632-7.504 12.736-15.696 26.897l-14.892 25.747H339.03c-8.517 0-20.015.116-25.55.259-6.55.168-10.15.121-10.309-.135zM169.42 132.23c-56.373-.055-102.5-.182-102.5-.282 0-.1 5.617-10.132 12.481-22.294l12.481-22.112h30.332c27.113 0 30.332-.065 30.332-.611 0-.336-6.659-12.228-14.797-26.427-8.139-14.199-14.797-25.917-14.797-26.04 0-.123 2.682-4.853 5.96-10.51s6.003-10.578 6.055-10.934c.086-.586 1.376-.648 13.572-.648 7.413 0 13.463.143 13.446.317-.017.174.222.707.531 1.184.31.476 9.763 16.937 21.007 36.578 11.244 19.64 20.71 36.022 21.036 36.4.554.647 2.549.691 31.428.691h30.837l12.896 22.145c7.093 12.18 12.8 22.301 12.682 22.492-.118.19-4.776.303-10.352.249-5.575-.054-56.26-.143-112.63-.198z" fill="#5075c1"/><path d="M25.289 203.14c-6.098 10.563-6.69 11.711-6.225 12.078.283.224 3.18 5.044 6.44 10.712 3.261 5.668 6.017 10.355 6.124 10.417.106.061 13.585.153 29.95.204 16.367.052 29.994.23 30.285.399.472.273-1.08 3.094-14.637 26.574L62.06 289.793l12.907 21.865c7.1 12.026 12.982 21.906 13.068 21.956.086.05 23.257-39.831 51.492-88.624 11.352-19.617 21.214-36.64 30.37-52.442 23.308-40.452 30.68-53.468 30.73-54.132-1.097-.11-6.141-.187-13.006-.216-3.945-.01-7.82-.02-12.75-.002l-25.341.092-15.42 26.706c-14.256 24.693-15.445 26.663-16.278 26.86l-.024.037c-.011.003-1.62-.001-1.825 0-4.29.062-20.453.063-40.226-.01-22.632-.082-41.615-.125-42.183-.096-.568.03-1.147-.03-1.29-.132-.142-.102-3.29 5.066-6.996 11.485zm205.16-190.3c-.123.149 5.62 10.392 12.761 22.763 12.199 21.131 89.393 155.03 96.276 167 1.502 2.613 2.92 4.803 3.443 5.348.9-1.249 3.531-5.63 7.954-13.219a1342.88 1342.88 0 0 1 10.049-17.76l6.606-11.443c.692-1.403.754-1.818.653-2.117-.162-.48-6.904-12.332-14.982-26.337-8.078-14.005-14.824-25.849-14.991-26.32a.73.73 0 0 1-.009-.366l-.426-.913L359.42 72.5c3.69-6.307 6.425-11.042 9.47-16.29 9.159-15.948 12.037-21.189 11.896-21.55-.126-.324-2.7-4.83-5.72-10.017-3.021-5.185-5.845-10.148-6.275-11.026-.483-.987-.734-1.364-1.1-1.456-.054.014-.083.018-.145.035-.42.112-5.454.195-11.189.185-5.734-.01-11.22.024-12.188.073l-1.76.089-14.997 25.978c-12.824 22.212-15.084 25.964-15.595 25.883-.024-.004-.15-.189-.235-.301-.109.066-.2.09-.272.05-.255-.148-7.143-11.902-15.306-26.119l-14.36-25.016c-.115-.186-.444-.744-.457-.752-.477-.275-50.502.287-50.737.57zm-18.646 283.09c-.047.109-.026.262.042.48.329 1.05 25.338 43.735 25.772 43.985.207.119 14.178.239 31.05.266 26.651.044 30.75.152 31.234.832.308.43 9.988 17.214 21.513 37.296s21.152 36.627 21.394 36.767c.242.14 5.927.243 12.633.23 6.706-.013 12.401.099 12.657.246.132.076.382-.141.852-.795l6.008-10.406c5.234-9.065 6.62-11.684 6.294-11.888-.575-.36-15.597-26.643-23.859-41.482-3.09-5.45-5.37-9.516-5.441-9.774-.195-.712-.065-.822 1.156-.98 1.956-.252 57.397-.057 58.07.205.238.092.79-.569 2.594-3.497 1.866-3.067 5.03-8.524 11-18.866 7.22-12.505 13.044-22.784 12.942-22.843-.102-.059-.771-.051-1.489.016l-.046.001c-4.452.204-33.918.203-149.74.025-38.96-.06-69.786-.09-71.912-.072-1.121.01-2.095.076-2.66.172a.25.25 0 0 0-.062.083z" fill="#7db7e1"/></g></symbol><symbol viewBox="0 0 24 24" id="nodejs" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.85c-.27 0-.55.07-.78.2l-7.44 4.3c-.48.28-.78.8-.78 1.36v8.58c0 .56.3 1.08.78 1.36l1.95 1.12c.95.46 1.27.47 1.71.47 1.4 0 2.21-.85 2.21-2.33V8.44c0-.12-.1-.22-.22-.22H8.5c-.13 0-.23.1-.23.22v8.47c0 .66-.68 1.31-1.77.76L4.45 16.5a.26.26 0 0 1-.11-.21V7.71c0-.09.04-.17.11-.21l7.44-4.29c.06-.04.16-.04.22 0l7.44 4.29c.07.04.11.12.11.21v8.58c0 .08-.04.16-.11.21l-7.44 4.29c-.06.04-.16.04-.23 0L10 19.65c-.08-.03-.16-.04-.21-.01-.53.3-.63.36-1.12.51-.12.04-.31.11.07.32l2.48 1.47c.24.14.5.21.78.21s.54-.07.78-.21l7.44-4.29c.48-.28.78-.8.78-1.36V7.71c0-.56-.3-1.08-.78-1.36l-7.44-4.3c-.23-.13-.5-.2-.78-.2M14 8c-2.12 0-3.39.89-3.39 2.39 0 1.61 1.26 2.08 3.3 2.28 2.43.24 2.62.6 2.62 1.08 0 .83-.67 1.18-2.23 1.18-1.98 0-2.4-.49-2.55-1.47a.226.226 0 0 0-.22-.18h-.96c-.12 0-.21.09-.21.22 0 1.24.68 2.74 3.94 2.74 2.35 0 3.7-.93 3.7-2.55 0-1.61-1.08-2.03-3.37-2.34-2.31-.3-2.54-.46-2.54-1 0-.45.2-1.05 1.91-1.05 1.5 0 2.09.33 2.32 1.36.02.1.11.17.21.17h.97c.05 0 .11-.02.15-.07.04-.04.07-.1.05-.16C17.56 8.82 16.38 8 14 8z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 300 300" id="nodemon" xmlns="http://www.w3.org/2000/svg"><title>nodemon</title><path d="M149.868 20.62c-2.124 0-4.25.55-6.154 1.648L41.899 81.083a12.306 12.306 0 0 0-6.15 10.652v117.633a12.29 12.29 0 0 0 6.152 10.646l101.815 58.766h.001a12.282 12.282 0 0 0 12.291 0l101.84-58.766a12.29 12.29 0 0 0 6.153-10.652V91.738a12.31 12.31 0 0 0-6.146-10.652L156.015 22.27a12.302 12.302 0 0 0-6.153-1.648zM83.303 70.93s11.789 33.031 35.477 31.934l27.74-15.961a7.348 7.348 0 0 1 3.414-.99h.641a7.233 7.233 0 0 1 3.404.99l27.738 15.961c23.69 1.094 35.475-31.934 35.475-31.934 5.233 23.154 1.06 38.641-5.924 48.942l4.541 2.614h.002c2.321 1.327 3.734 3.795 3.737 6.49l-.12 95.811a3.724 3.724 0 0 1-1.855 3.227 3.624 3.624 0 0 1-3.735 0L177.1 206.971c-2.311-1.363-3.742-3.818-3.742-6.48v-44.763a7.44 7.44 0 0 0-3.737-6.465l-15.642-9.01a7.28 7.28 0 0 0-3.715-1.01 7.378 7.378 0 0 0-3.742 1.01l-15.648 9.01c-2.316 1.323-3.729 3.798-3.729 6.467v44.762c0 2.663-1.413 5.1-3.738 6.48l-36.748 21.041a3.571 3.571 0 0 1-3.71 0c-1.173-.65-1.864-1.887-1.864-3.224l-.137-95.812a7.483 7.483 0 0 1 3.74-6.49l4.541-2.615c-6.982-10.302-11.16-25.79-5.925-48.942z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 990 990" id="npm" xmlns="http://www.w3.org/2000/svg"><defs><style>.hncls-1{fill:#cb3837}.cls-2{fill:#fff}</style></defs><title>n</title><path class="hncls-1" d="M113.26 876.74V113.27h763.47v763.47zm143.59-620.4v476.18h240.61V355.63h140.21v376.96h95.457V256.34z" fill="#e53935" stroke-width=".771"/></symbol><symbol id="nunjucks" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.host0{fill:#388e3c}</style><path class="host0" d="M11.2 21.1H8.1l-2.3-7.9v7.9H2.7V2.9h3.1l2.3 7.4V2.9h3.1zM21.3 19.2c0 1-.8 1.9-1.9 1.9h-4.8c-1 0-1.9-.8-1.9-1.9v-3.8l3.2-.7V18h2.3V7.2h3.1v12z"/></symbol><symbol viewBox="0 0 150 150.00001" id="ocaml" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.76136 0 0 .76136 11.616 19.98)"><path d="M83.02 101.645l.023-.062c-.035-.159-.047-.195-.024.062z" fill="none" stroke-width="1.028"/><linearGradient id="hpa" gradientUnits="userSpaceOnUse" x1="-696.735" y1="97.7" x2="-696.735" y2="142.997" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M82.313 138.79c-.471-1.004-1.904-3.621-2.624-4.46-1.562-1.828-1.927-1.966-2.386-4.275-.799-4.02-2.913-11.31-5.405-16.341-1.286-2.596-3.426-4.777-5.385-6.66-1.71-1.652-5.565-4.431-6.237-4.294-6.296 1.257-8.249 7.432-11.21 12.323-1.638 2.705-3.374 5.007-4.665 7.885-1.192 2.646-1.087 5.577-3.128 7.849-2.093 2.333-3.454 4.814-4.48 7.829-.194.574-.747 6.596-1.348 8.015l9.357-.659c8.719.594 6.2 3.936 19.81 3.208l21.487-.665c-.666-1.97-1.584-4.25-1.938-4.991-.599-1.248-1.352-3.69-1.848-4.763z" fill="url(#hpa)" stroke-width="1.028"/><linearGradient id="hpb" gradientUnits="userSpaceOnUse" x1="-666.972" y1="142.12" x2="-666.972" y2="142.12" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><linearGradient id="hpc" gradientUnits="userSpaceOnUse" x1="-675.228" y1="-1.28" x2="-675.228" y2="142.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M109.553 94.296c-1.652 1.193-4.88 4.06-11.902 5.145-3.152.487-6.1.527-9.335.365-1.584-.076-3.077-.157-4.665-.177-.936-.008-4.074-.107-3.919.193l-.349.871c.054.287.169 1.004.2 1.177.129.704.165 1.265.192 1.912.048 1.331-.11 2.719-.043 4.062.141 2.787 1.175 5.326 1.306 8.137.143 3.13 1.69 6.442 3.188 8.998.569.973 1.434 1.084 1.811 2.283.442 1.373.024 2.83.239 4.293.842 5.675 2.477 11.606 5.032 16.728.018.043.038.09.06.128 3.156-.53 6.318-1.665 10.418-2.271 7.517-1.115 17.972-.54 24.688-1.17 16.993-1.597 26.216 6.97 41.478 3.459V22.459c0-11.84-9.594-21.438-21.435-21.438H19.239C7.4 1.021-2.197 10.62-2.197 22.458v46.774c3.067-1.11 7.479-7.635 8.861-9.222 2.419-2.775 2.858-6.315 4.062-8.544 2.743-5.078 3.215-8.57 9.451-8.57 2.907 0 4.061.67 6.027 3.31 1.368 1.834 3.731 5.224 4.837 7.49 1.277 2.615 3.357 6.153 4.272 6.867.677.53 1.35.928 1.976 1.163 1.012.38 1.848-.316 2.525-.855.863-.687 1.235-2.088 2.035-3.957 1.152-2.696 2.408-5.926 3.122-7.054 1.237-1.949 1.658-4.261 2.993-5.381 1.97-1.652 4.54-1.768 5.246-1.908 3.957-.781 5.755 1.906 7.704 3.645 1.276 1.138 3.019 3.432 4.256 6.507.967 2.4 2.199 4.622 2.714 6.008.497 1.339 1.725 3.484 2.453 6.055.661 2.336 2.43 4.125 3.102 5.235 0 0 1.029 2.882 7.285 5.516 1.357.572 4.1 1.501 5.736 2.096 2.718.988 5.351.86 8.704.458 2.391 0 3.686-3.462 4.772-6.234.643-1.639 1.259-6.334 1.678-7.667.406-1.297-.544-2.3.265-3.437.946-1.327 1.508-1.399 2.054-3.129 1.172-3.704 7.95-3.89 11.761-3.89 3.176 0 2.772 3.083 8.16 2.028 3.086-.605 6.059.398 9.335 1.265 2.758.732 5.352 1.566 6.906 3.385 1.005 1.178 3.5 7.08.958 7.331.244.3.423.84.88 1.135-.566 2.226-3.03.64-4.4.355-1.845-.383-3.147.057-4.952.856-3.085 1.374-7.598 1.214-10.286 3.452-2.281 1.898-2.277 6.133-3.34 8.507-.002-.001-2.955 7.6-9.402 12.248z" fill="url(#hpc)" stroke-width="1.028"/><linearGradient id="hpd" gradientUnits="userSpaceOnUse" x1="-735.137" y1="90.833" x2="-735.137" y2="141.967" gradientTransform="matrix(1.02783 0 0 1.02783 776.895 2.337)"><stop offset="0" stop-color="#f29100"/><stop offset="1" stop-color="#ec670f"/></linearGradient><path d="M38.247 105.09c-1.467-.15-2.83-.317-4.256-.605-2.662-.536-5.57-1.06-8.193-1.688-1.592-.385-6.895-2.263-8.048-2.792-2.702-1.246-4.496-4.63-6.609-4.282-1.348.22-2.662.682-3.5 2.042-.685 1.11-.917 3.016-1.391 4.294-.55 1.485-1.5 2.87-2.331 4.284-1.53 2.595-4.282 4.941-5.468 7.469-.239.52-.45 1.101-.649 1.708V144.415a48.57 48.57 0 0 1 4.45.96c11.955 3.19 14.872 3.46 26.598 2.119l1.1-.146c.897-1.867 1.59-8.227 2.171-10.195.454-1.51 1.077-2.712 1.313-4.253.223-1.463-.02-2.858-.146-4.188-.329-3.332 2.427-4.522 3.742-7.384 1.186-2.589 1.871-5.535 2.853-8.181.941-2.54 2.41-6.13 4.918-7.408-.305-.355-5.237-.518-6.554-.65z" fill="url(#hpd)" stroke-width="1.028"/></g></symbol><symbol viewBox="0 0 24 24" id="pdf" xmlns="http://www.w3.org/2000/svg"><path d="M14 9h5.5L14 3.5V9M7 2h8l6 6v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m4.93 10.44c.41.9.93 1.64 1.53 2.15l.41.32c-.87.16-2.07.44-3.34.93l-.11.04.5-1.04c.45-.87.78-1.66 1.01-2.4m6.48 3.81c.18-.18.27-.41.28-.66.03-.2-.02-.39-.12-.55-.29-.47-1.04-.69-2.28-.69l-1.29.07-.87-.58c-.63-.52-1.2-1.43-1.6-2.56l.04-.14c.33-1.33.64-2.94-.02-3.6a.853.853 0 0 0-.61-.24h-.24c-.37 0-.7.39-.79.77-.37 1.33-.15 2.06.22 3.27v.01c-.25.88-.57 1.9-1.08 2.93l-.96 1.8-.89.49c-1.2.75-1.77 1.59-1.88 2.12-.04.19-.02.36.05.54l.03.05.48.31.44.11c.81 0 1.73-.95 2.97-3.07l.18-.07c1.03-.33 2.31-.56 4.03-.75 1.03.51 2.24.74 3 .74.44 0 .74-.11.91-.3m-.41-.71l.09.11c-.01.1-.04.11-.09.13h-.04l-.19.02c-.46 0-1.17-.19-1.9-.51.09-.1.13-.1.23-.1 1.4 0 1.8.25 1.9.35M8.83 17c-.65 1.19-1.24 1.85-1.69 2 .05-.38.5-1.04 1.21-1.69l.48-.31m3.02-6.91c-.23-.9-.24-1.63-.07-2.05l.07-.12.15.05c.17.24.19.56.09 1.1l-.03.16-.16.82-.05.04z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="perl" xmlns="http://www.w3.org/2000/svg"><path d="M12 14c-1 0-3 1-3 2 0 2 3 2 3 2v-1a1 1 0 0 1-1-1 1 1 0 0 1 1-1v-1m0 5s-4-.5-4-2.5c0-3 3-3.75 4-3.75V11.5c-1 0-5 1.5-5 4.5 0 4 5 4 5 4v-1M10.07 7.03l1.19.53c.43-2.44 1.58-4.06 1.58-4.06-.43 1.03-.71 1.88-.89 2.55C13.16 3.55 15.61 2 15.61 2a15.916 15.916 0 0 0-2.64 3.53c1.58-1.68 3.77-2.78 3.77-2.78-2.69 1.72-3.9 4.45-4.2 5.21l.55.08c0 .52 0 1 .25 1.38C14.1 11.31 18 11.47 18 16s-4.03 6-6.17 6C9.69 22 5 21.03 5 16s4.95-5.07 5.83-7.08c.12-.38-.76-1.89-.76-1.89z" fill="#9575cd"/></symbol><symbol viewBox="0 0 24 24" id="php" xmlns="http://www.w3.org/2000/svg"><path d="M12 18.08c-6.63 0-12-2.72-12-6.08s5.37-6.08 12-6.08S24 8.64 24 12s-5.37 6.08-12 6.08m-5.19-7.95c.54 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.58 1.09-.28.22-.71.33-1.29.33h-.87l.53-2.76h.99m-3.5 5.55h1.44l.34-1.75h1.23c.54 0 .98-.06 1.33-.17.35-.12.67-.31.96-.58.24-.22.43-.46.58-.73.15-.26.26-.56.31-.88.16-.78.05-1.39-.33-1.82-.39-.44-.99-.65-1.82-.65H4.59l-1.28 6.58m7.25-8.33l-1.28 6.58h1.42l.74-3.77h1.14c.36 0 .6.06.71.18.11.12.13.34.07.66l-.57 2.93h1.45l.59-3.07c.13-.62.03-1.07-.27-1.36-.3-.27-.85-.4-1.65-.4h-1.27L12 7.35h-1.44M18 10.13c.55 0 .91.1 1.09.31.18.2.22.56.13 1.03-.1.53-.29.87-.57 1.09-.29.22-.72.33-1.3.33h-.85l.5-2.76h1m-3.5 5.55h1.44l.34-1.75h1.22c.55 0 1-.06 1.35-.17.35-.12.65-.31.95-.58.24-.22.44-.46.58-.73.15-.26.26-.56.32-.88.15-.78.04-1.39-.34-1.82-.36-.44-.99-.65-1.82-.65h-2.75l-1.29 6.58z" fill="#1E88E5"/></symbol><symbol viewBox="0 0 79 78" id="postcss" xmlns="http://www.w3.org/2000/svg"><title>postcss-logo-symbol</title><g transform="translate(5.48 5.52) scale(.85425)" fill="#e53935" fill-rule="evenodd" stroke="#e53935" stroke-width="1.519"><path d="M15.447 32.623c.106.08.29.132.106.29-.132.184-.29.342-.395.553-.105.185-.184.237-.342.106.21-.343.42-.66.63-.95zM68.342 60.24c0 .078.026.13.026.21.053-.105.053-.158.08-.21zm0 .236v-.026zm-5.368 10.277l-4.58-25.402c-.078-.025-.183-.077-.368-.13.053.105.08.184.106.263.13-.026.184-.026.236-.052 0-.026 0-.052.027-.08l4.58 25.404zm-4.737-31.12c-.026.078-.026.158-.026.237 0-.08 0-.16.028-.238zm.026.526c-.026 0-.026 0-.052-.028v.026c.028.026.028.026.054 0zm-.052.21v-.185c-.077.026-.156.026-.262.053.132.05.264.078.264.13z"/><path d="M78.71 33.967c-.052-1.028-.078-2.056-.184-3.083-.184-1.397-.368-2.82-.684-4.19-.237-1.133-.63-2.214-1.026-3.294-.5-1.265-1-2.556-1.632-3.768-1.026-1.95-2.368-3.69-3.605-5.508-.818-1.16-1.87-2.108-2.66-3.294-.447-.685-1.105-1.264-1.763-1.79-1.053-.845-2.158-1.61-3.263-2.347a32.525 32.525 0 0 0-2.58-1.634c-.71-.397-1.473-.713-2.21-1.056-.842-.395-1.658-.87-2.605-1.054-.238-.05-.448-.13-.685-.21-.605-.21-1.184-.447-1.79-.632-.92-.29-1.815-.632-2.763-.87C50.342 1 49.394.843 48.446.71 47.394.555 46.316.5 45.262.397a26.83 26.83 0 0 0-2.026-.184C42.236.16 41.21.16 40.21.134c-.5-.027-1.026-.08-1.526-.053-.763.026-1.526.105-2.29.21-.736.08-1.473.21-2.183.317-.867.105-1.735.158-2.604.264-.816.106-1.658.264-2.473.396-.29.053-.58.158-.87.21-.63.132-1.288.185-1.92.396-1.13.344-2.263.74-3.368 1.16-1.027.422-2.027.87-3 1.397-1 .552-1.948 1.21-2.895 1.844a45.325 45.325 0 0 0-2.66 1.923c-.84.66-1.63 1.397-2.394 2.135-.42.42-.763.922-1.158 1.396-.657.765-1.315 1.502-1.947 2.293-.524.66-1 1.344-1.5 2.03-.893 1.21-1.656 2.502-2.366 3.794-.29.527-.553 1.054-.816 1.58-.395.79-.816 1.555-1.184 2.372-.264.554-.474 1.16-.632 1.766-.367 1.292-.736 2.61-1.078 3.9-.316 1.16-.395 2.372-.42 3.558-.027 1.054.078 2.082.183 3.136.027.264-.13.58.184.79-.105.29-.026.45.13.5-.182.29.08.476-.024.74-.027.052.08.157.13.236 0 .08-.025.185 0 .264.028.237.133.474.133.738 0 .184.157.395.21.58.026.078 0 .21-.053.263-.158.184-.132.342.105.448.133.342.08.5.054.66.052.236-.027.315 0 .368.21.422.29.896.315 1.37 0 .106.053.212.106.343.026 0 0 .5 0 .5.13-.078.237-.104.368-.157.08.342.158.66.263.95.132.21.132.314.08.34.105.474.157.922.34 1.37 0-.5-.05-1-.13-1.475.368.132.684.263.895.263.027-.08.053-.184.08-.237-.158-.157-.29-.394-.448-.552.053.21 0 .29 0 .37-.105-.054-.237-.107-.368-.16.105-.13.21-.263.368-.42 0-.238-.13-.45-.5-.423.158-.052.316-.13.5-.184.29-.157-.026-.447-.026-.816.026-.447-.237-.895-.316-1.37-.132-.737-.105-1.844-.184-2.582-.158-.132-.29.21-.316.237.08.632.158 1.264.21 1.897-.157-.527-.263-1.107-.394-1.74-.027.185-.053.264-.053.37-.13.13-.026.29.053.474-.184-.08-.395-.052-.395-.052v.738c-.262-.264-.34-.474-.473-.66-.052-.21-.08-.42-.13-.63.05-.133 0-.212 0-.29a15.968 15.968 0 0 1-.08-.634c.026-.026-.026-.42-.026-.42.21.025.343.05.474.05-.263-.34-.08-.552.027-.763.053-.106.237-.13.29-.238.21-.395.553-.71.553-1.212 0-.237.08-.5.105-.738.053-.448.105-.896.13-1.344.054-.58 0-1.16.133-1.713.212-.92.475-1.843.764-2.766.21-.66.448-1.29.71-1.95.395-1.028.764-2.056 1.264-3.03.71-1.424 1.526-2.794 2.316-4.19.5-.87 1.026-1.687 1.58-2.53.525-.817 1.05-1.66 1.657-2.425a21.452 21.452 0 0 1 2.79-2.978c1.053-.948 2.053-1.923 3.184-2.793a32.218 32.218 0 0 1 4.685-3.005c1.343-.71 2.737-1.266 4.132-1.793.895-.342 1.868-.5 2.79-.79 1.052-.343 2.105-.5 3.21-.527.71-.027 1.395-.106 2.105-.185.632-.05 1.263-.104 1.948-.183-.08.105-.106.158-.132.21-.288.422-.604.844-.894 1.265-.237.343-.5.712-.737 1.054-.422.555-.87 1.108-1.264 1.688-.605.87-1.158 1.766-1.79 2.635-.63.843-1.315 1.634-1.973 2.45-.868 1.134-1.684 2.293-2.552 3.426-.79 1.08-1.63 2.11-2.394 3.19-.684.947-1.29 1.95-1.948 2.923-.973 1.45-1.947 2.872-2.92 4.322a271.93 271.93 0 0 1-2.316 3.294c-.053.08-.132.104-.21.157-.21.342-.21.527-.29.685-.21.395-.42.79-.658 1.16-.132.21-.316.394-.474.605-.026-.316.42-.474.21-.87-.13.212-.263.396-.394.607l-.316.63c.105.08.29.133.105.29-.08.133-.158.29-.237.423a.954.954 0 0 0 .29-.264c0 .29-.158.526-.29.763-.105.21-.368.37-.552.527.026.027.21.106.237.132.237-.08.316-.21.343-.132.08-.105.158-.184.184-.263.104-.264.262-.474.525-.58.106-.053.184-.132.263-.21.79-.818 1.606-1.608 2.316-2.478 1.106-1.345 2.106-2.74 3.16-4.11.446-.58.973-1.16 1.446-1.714.078.606.026 1.185 0 1.74-.08.974-.132 1.95-.21 2.95-.027.395 0 .79-.027 1.186 0 .105-.08.184-.08.29 0 .263.08.553.08.817-.08.975-.186 1.923-.265 2.898-.027.21.078.422.13.607-.13 1.422.16 2.925-.078 4.427.184-.29.237-.474.237-.658.025-.158 0-.316 0-.5v-.264c.025-.475.13-.975.078-1.45-.053-.527-.053-1.027.053-1.528.053-.21-.026-.474.106-.738v.395c-.026 1.5.027 3.003-.183 4.505-.027.132.08.37-.21.343-.238.474.052.817-.21 1.08-.054.053.05.29.077.448-.106.317-.106.317.052.343.026.58.08 1.106.105 1.66.42-1 .21-2.03.396-3.058.026.422.053.844.026 1.29 0 .687-.026 1.345-.052 2.03 0 .132-.027.264-.053.396-.08.37-.105.738-.237 1.08-.105.264-.052.66-.052.975v1.003c.105.448-.027.685.052.948-.08.265-.105.344-.08.423l.08.395c.527-.053.29.343.5.553-.158.212-.105.29-.105.397 0 .237-.025.448-.052.685 0 .606-.026 1.212-.026 1.792 0 .08.026.157.026.236 0 .054-.026.74-.026.74.053.078 0 .157-.08.236-.025 0-.104-3.347-.104-3.347h-.395c-.052 1.58.08 3.003-.21 4.48-.316.025-.42.078-.764.078-.816 0-1.632 0-2.448.026-.974 0-1.92.026-2.895.026-.472 0-.972.054-1.446.054-.632 0-1.29-.08-1.92-.08-.975 0-1.922.08-2.896.106-.71.026-1.42.026-2.13.053-.475.025-.95.05-1.422.104-.21.026-.395.105-.658.184-.08 0-.263-.026-.42 0-.265.053-.5.21-.765.264-.395.08-.5.184-.448.58v.263c-.026.052.58-.08.58-.08-.054 0-.08.158-.16.29.212-.08.343-.132.475-.184.395.185.737.08 1.052.16 1.026.262 2.078.37 3.13.473.685.053 1.343.08 2.027.105.973.053 1.947.106 2.92.106.816 0 1.606-.08 2.42-.08 1.13 0 2.264.052 3.395.08.237 0 .5-.028.763-.028h1.92c1.712-.052 3.422-.08 5.133-.13.975-.028 1.975-.08 2.948-.107l3-.08c1.158-.026 2.316-.026 3.448-.05.868 0 1.71-.03 2.58-.055.972-.026 1.972-.105 2.946-.157.527-.027 1.054-.08 1.58-.132.632-.052 1.29-.13 1.92-.157.948-.054 1.922-.08 2.87-.133 1.184-.078 2.368-.183 3.578-.21 1.106-.052 2.237-.026 3.343-.052.974-.027 1.948-.08 2.948-.106l1.66-.08s1.104-.026 1.657-.08c.947-.052 1.894-.157 2.842-.183.604-.027 1.21 0 1.815-.027.973-.026 1.973-.08 2.947-.08.367 0 .762.054 1.236.08-.21.185-.342.29-.5.422.105.026.21.08.316.132a.71.71 0 0 1-.42.13c-.054.133-.107.186-.16.45h.474c-.184 0-.342.237-.526.395-.21-.054-.395 0-.5.29.184.104.158.183.132.29-.316.104-.553.21-.42.552-.107.052-.238.105-.37.184-.13.21-.368.263-.316.553.106.025.21.08.29.104-.132.053-.263.132-.395.184-.473.29-.262.422-.157.554-.08.053-.158.105-.237.132.052.237.13.29.157.29a9.3 9.3 0 0 0-.395.316c-.08.237-.185.342-.29.5s-.158.37-.29.527c-.552.607-.947 1.32-1.657 1.793-.264.185-.5.422-.737.66-.474.447-.895.948-1.395 1.37a29.595 29.595 0 0 1-2.052 1.554 151.56 151.56 0 0 1-2.604 1.792c-.474.315-1 .552-1.5.842s-.974.554-1.474.843c-.316.21-.606.5-.948.66-.868.37-1.79.685-2.684 1.028-.87.37-1.5.685-2.158.922-.605.21-1.237.37-1.868.5-.21.054-.448 0-.685.027-.448.08-.895.186-1.343.238-1.158.158-2.316.264-3.473.422-.685.08-1.343.21-2.027.29-.473.026-.973-.026-1.447-.026-.342 0-.71.08-1.053.027-.552-.08-1.105-.21-1.658-.316-.13-.026-.316-.08-.42-.026-.21.106-.396-.052-.607 0-.13.027-.262-.08-.394-.08-.106-.025-.238.028-.37 0-.29-.078-.552-.183-.87-.157-.313.026-.63-.132-.97-.21-.475-.106-.92-.21-1.396-.317a2.38 2.38 0 0 1-.525-.237c-.685 0-1.133-.026-1.554-.185-.368-.13-.71-.315-1.105-.262-.104.026-.183-.026-.29-.026-.08-.106-.157-.317-.235-.317-.526.027-.842-.42-1.29-.553-.236-.08-.42-.343-.657-.422-.58-.237-1.052-.737-1.71-.816-.21-.027-.42-.132-.658-.21.08.104.13.183.21.262-.763-.37-1.473-.79-2.184-1.186-.104-.026-.183-.13-.262-.184l-.71-.474c-.395.08-.553-.08-.66-.132-.71-.5-1.525-.817-2.21-1.37-.29-.238-.63-.396-.84-.686-.37-.448-.817-.764-1.317-1.027-.394-.21-.762-.448-1.13-.685-.185-.132-.37-.29-.37-.58 0-.185-.078-.37-.315-.264-.105-.158-.21-.342-.342-.395-.316-.13-.526-.37-.763-.58s-.42-.5-.71-.605c-.527-.21-.843-.658-1.158-1.027-.738-.87-1.396-1.82-2.08-2.74-.053-.08-.158-.133-.237-.212.105.29.237.527.368.79-.262-.105-.446-.29-.604-.474-.027.027 1.815 3.057 1.815 3.057.16.237.29.475.448.712a.813.813 0 0 1-.79-.422c-.236-.42-.5-.684-1.026-.63a4.588 4.588 0 0 1-.13-.58c-.107 0-.185 0-.37-.027.37.58.685 1.08 1.027 1.66-.133-.08-.21-.132-.265-.158.473.5.815 1.133 1.42 1.45.132.605.816.895.974 1.475-.13-.027-.238-.053-.37-.08-.21-.263-.447-.526-.683-.816.052.184.13.342.236.474.316.395.606.79.974 1.133.132.134.316.187.316.424.21.105.29.13.368.13.054.16-.025.397.29.344.21.395.42.395.71.264.343.343.528.37.764.16 0 .13.026.262.026.368.105-.053.08-.132.08-.264.13.105.21.158.262.21.263.37.5.712.868 1.002.5.422.948.87 1.42 1.265.922.765 1.95 1.398 2.975 1.977 1.264.712 2.475 1.476 3.764 2.16 1.552.818 3.21 1.372 4.92 1.767.632.132 1.237.263 1.87.42.55.16 1.104.397 1.657.528.842.185 1.71.343 2.552.5.183.027.37.054.58.08.235.053.524-.053.577.027.132.21.237.104.395.078.184-.053.395-.053.605-.053.737.026 1.447.184 2.184.132.16 0 .396-.133.528.13.236-.105.368-.105.473-.13.028.236 0 .236-.05.262-.054.026-.133.053-.238.132.947.184 1.842.21 2.63 0 1.37.105 2.554-.053 3.686-.448.105.132.184.316.342.053.052-.08.184-.107.29-.133.236-.053.526-.158.736-.08.238.08.317-.13.5-.13.317 0 .606-.027.896-.08.158-.026.316-.105.5-.158a1.285 1.285 0 0 0-.58-.133c.317-.158.606-.29.896-.42-.053.078-.106.183-.21.183h.367c-.08 0-.185.237-.316.395.946-.237 1.814-.448 2.657-.66-.29-.552.315-.367.526-.684-.263.08-.526.158-.79.21.895-.447 1.816-.842 2.71-1.237-.13.158-.29.237-.525.37.158.025.263.025.342.05.42.133.316-.262.447-.5.5 0 .71-.078.947-.158.263-.08.526-.158.79-.263.42-.184.815-.42 1.236-.63.08-.028.21 0 .316 0 .29-.186.394-.344.473-.318.37.053.63-.08.736-.42.184-.133.316-.238.447-.318.578-.316 1.13-.632 1.71-.948.21 0 .316 0 .368-.027.344-.16.66-.342.975-.527a2.258 2.258 0 0 1-.263-.13c.262-.054.34-.08.5-.133.63-.74 1.5-1.24 2.157-1.82.29-.026.29-.105.29-.157.104-.132.21-.29.34-.396.58-.527 1.21-.975 1.737-1.528a37.16 37.16 0 0 0 2.184-2.374c.63-.738 1.264-1.475 1.79-2.292.737-1.133 1.368-2.293 2.026-3.48.474-.842.895-1.685 1.37-2.528.05-.08.157-.185.236-.185.71-.08 1.422-.13 2.106-.21.158-.026.342-.13.5-.21-.08-.132-.132-.29-.21-.422-.106-.16-.264-.29-.37-.45-.104-.13-.183-.29-.262-.447-.08-.13-.158-.236-.237-.37a9.7 9.7 0 0 1-.45-.894c-.026-.08-.08-.21-.052-.29.474-1.027.658-2.134 1.105-3.162.447-1.054.58-2.24.79-3.373.184-1.08.29-2.16.42-3.24.08-.764.185-1.502.21-2.266.16-1.212.106-2.346.08-3.48-.026-1-.08-2.028-.13-3.03zM12.685 66.405c-.184-.21-.342-.448-.526-.658l.08-.08c.287.317.577.633.866.976-.158-.08-.342-.132-.42-.238zm.42.238c.08-.027.16-.027.238-.053.08.132.132.29.21.448-.368-.027-.552-.185-.447-.395zm27.37 10.883v-.08c.5-.052.973-.105 1.473-.157v.077c-.5.08-.973.13-1.473.158zm6.63-.685c-.367.08-.762.133-1.13.186-.132.026-.29.158-.342-.08-.053.027-.106.027-.158.054.13.394.447.078.71.236-.58.08-1.13.132-1.684.21v-.052c.16-.026.343-.053.5-.08v-.078a7.743 7.743 0 0 0-.79-.053c-.077 0-.183.106-.262.132-.105.026-.21.053-.342.053-.447.026-.894.026-1.316.052-.027 0-.08-.026-.106-.026v-.08c1.763-.236 3.5-.473 5.263-.71.027.052.027.105.053.157-.158 0-.263.055-.395.08zm.396-.262c.606-.08 1.16-.132 1.738-.21-1.21.342-1.605.394-1.737.21zM24.58 23.374c.84-1.16 1.71-2.32 2.552-3.505.263-.345.473-.714.736-1.056.08-.106.185-.158.316-.264l-.026-.05c.105-.133.21-.24.263-.344.134-.21.213-.448.318-.685a.385.385 0 0 1 .105-.103c.37.184.37-.21.5-.343.237-.264.474-.553.684-.817.158-.21.316-.395.448-.632.026-.08-.053-.21-.08-.317h-.078c.08-.052.158-.13.237-.184.026 0 .026 0 .052-.026.158-.238.316-.475.474-.686.315-.42.657-.842 1.025-1.21-.052.13-.105.263-.158.368.027 0 .027.027.053.027.316-.422.658-.817.974-1.24-.027-.025-.053-.052-.08-.052-.13.132-.236.264-.368.396-.026-.027-.052-.053-.08-.053.265-.343.528-.685.79-1.08.053.08.106.184.21.395.107-.263.212-.447.29-.632-.078.08-.183.158-.262.238l-.08-.08.474-.71c.5-.712 1-1.45 1.5-2.162.185-.263.42-.474.58-.738.5-1 1.29-1.792 1.894-2.714.132-.184.316-.342.474-.5.13-.16.237-.106.342.026.71.896 1.42 1.818 2.13 2.714.528.66 1.054 1.29 1.554 1.976.605.844 1.184 1.687 1.79 2.53.684.975 1.368 1.95 2.026 2.95 1 1.477 1.947 2.953 2.947 4.428.737 1.08 1.474 2.135 2.184 3.215h-1.344c-1.236-.025-2.5-.13-3.736-.078-1.684.08-3.394.264-5.078.396-2.132.185-4.29.21-6.42.21-.765 0-1.528.107-2.29.16-.922.052-1.817.105-2.738.13-1.08.054-2.13.08-3.21.107-.606.026-1.237 0-1.895 0zm30.183 12.12v.238c-.026 0-.052.027-.105.027-.105-.37-.21-.766-.342-1.135-.263-.765-.553-1.53-1.027-2.214-.528-.737-1-1.5-1.528-2.265-.13-.185-.316-.343-.474-.5-.553-.607-1.106-1.24-1.816-1.687a21.485 21.485 0 0 0-3.29-1.688 7.374 7.374 0 0 1-.92-.474h.63l4.5-.08c.974-.025 1.922-.025 2.895-.078.236 0 .368.08.5.29.236.395.473.79.736 1.186.027.052.08.13.08.21 0 .58 0 1.186.026 1.766.025.606.08 1.186.104 1.792 0 .606-.053 1.238-.026 1.87.027.897.053 1.82.053 2.74zM26.447 26.67c1.237-.053 2.42-.132 3.632-.185.945-.053 1.92-.08 2.866-.132.395-.025.764-.05 1.158 0-.42.212-.842.423-1.21.686-.474.316-.92.737-1.395 1.08-.475.342-.896.764-1.29 1.212-.5.605-1.053 1.132-1.58 1.712-.37.422-.79.817-1.105 1.265-.447.58-.842 1.21-1.263 1.87.132-2.504.29-4.98.184-7.51zm17.185 25.35c-.843.21-1.71.448-2.58.553-.736.106-1.5.08-2.263.08a25.42 25.42 0 0 1-2.028-.08c-.763-.078-1.526-.157-2.263-.5-.633-.29-1.29-.553-1.92-.87-.634-.316-1.265-.684-1.74-1.264-.34-.423-.815-.765-1.236-1.134.08.316.263.58.553.764-.132.158-.316.08-.58-.343-.078.053-.157.08-.21.106.08-.185.158-.37.237-.527-.105-.21-.237-.448-.342-.66-.21-.342-.42-.71-.605-1.053-.053-.08-.053-.158-.105-.237a5.893 5.893 0 0 1-.37-.475c-.21-.315-.394-.657-.657-.974 0 .08.027.158.027.264-.027 0-.053.026-.053.026l-.554-1.344c-.026 0-.026 0-.052.026l.473 1.74c-.026 0-.052.025-.08.025-.077-.104-.156-.21-.21-.34-.052-.212-.21-.212-.34-.133-.08.053-.133.237-.106.316.185.448.395.896.606 1.344.052.158.105.29.184.448.027.053.106.105.106.184.106.21.185.42.316.606.237.316.5.632.737.948.235.316.445.66.656.975.026.053.105.053.13.08.133.395.58.684.896.526.08.606.737.817 1 1.397a11.957 11.957 0 0 1-.763-.343c-.027.026-.027.052-.054.105.316.158.632.316.92.5.265.16.528.317.765.5.316.29.685.45 1.13.554a.282.282 0 0 0-.05-.107c.736.343 1.5.712 2.078 1-2.737.054-5.658.107-8.685.16 0-.5-.026-.975-.026-1.476 0-.21.052-.395.025-.606-.08-1.21-.08-2.424-.237-3.61-.157-1.264-.157-2.503-.13-3.77.025-.683-.027-1.394-.054-2.08 0-.922 0-1.82.028-2.74 0-.132.053-.237.106-.37h.08c.025.054 0 .133.05.16.08.08.212.21.265.184.157-.106.394-.21.447-.37.13-.315.184-.658.184-.974 0-.236.106-.394.21-.553.054-.08.08-.158.133-.263-.105-.08-.21-.132-.342-.237.106-.29.08-.633.475-.79.052-.027.052-.16.08-.238.025-.213.05-.45.078-.66.052.08.08.105.13.157a.42.42 0 0 1 .054-.08c0-.104-.026-.315 0-.315.316-.053.184-.395.342-.553.025-.028-.027-.107-.027-.16 0-.052 0-.13.026-.13.367-.08.315-.475.552-.66.08-.053.105-.13.21-.263.21.368-.158.553-.184.816.446-.263.578-.895.315-1.08.105-.08.21-.184.29-.29.29-.316.604-.606.868-.922.185-.236.29-.526.474-.763.106-.132.316-.237.474-.317.474-.262.92-.552 1.21-1 .053-.053.132-.105.21-.158.08-.053.238-.053.264-.132.027-.052-.052-.184-.105-.263.104-.053.21-.158.42-.264-.08.158-.105.264-.158.37l.13.13c.238-.184.606-.394.843-.552 0-.025-.132-.13-.132-.13-.157.08-.394.21-.63.316.05-.08.05-.132.08-.158.367-.237.735-.474 1.13-.66.92-.42 1.842-.842 2.763-1.237.158-.08.37-.026.553-.026.078 0 .13 0 .21-.026.42-.132.842-.264 1.263-.37.183-.052.393-.078.58-.078.787.025 1.577.025 2.366.078.342.026.658.105.974.21a9.88 9.88 0 0 1 1.184.5c.447.24.868.502 1.29.792.763.5 1.473 1.054 2.236 1.502.737.448 1.316 1.054 1.79 1.74.58.816 1.237 1.554 1.5 2.555l.394 1.74c.08.316.264.632.185 1-.133.66-.238 1.345-.343 2.004-.052.265-.105.53-.078.79.05.82-.265 1.53-.58 2.268-.106.237-.264.475-.395.738a.798.798 0 0 0 .21.106l.237-.474c.027 0 .027 0 .053.027-.132.368-.237.764-.37 1.133-.314.817-.63 1.66-1.025 2.45-.21.448-.58.817-.842 1.24-.262.368-.473.763-.736 1.106-.237.29-.473.58-.79.79-.71.527-1.447 1.054-2.21 1.476-.473.29-1.026.448-1.552.58zm-14.027-1.4l-.026.027c-.055-.026-.134-.052-.186-.105l-.632-.95c-.052-.078-.08-.157-.052-.262.29.448.58.87.895 1.29zm16.37 3.61c1.183-.5 2.157-1.21 3.05-2.028.133-.132.264-.263.422-.37 1.106-.684 1.92-1.633 2.658-2.687.842-1.212 1.395-2.582 2.08-3.873a2.73 2.73 0 0 1 .157-.29c-.053 3.004.29 5.955.684 8.933-2.973.105-6 .21-9.052.316zm26.683-.79c-.026.053-.08.106-.105.16-.027-.054-.027-.133-.053-.24-.158.423-.5.212-.737.212-1.42.027-2.868.027-4.29.027-1.368 0-2.762 0-4.13.024-.448 0-.922.105-1.37.132-1.078.052-2.157.08-3.236.105-.08 0-.158-.13-.29-.236a1.81 1.81 0 0 1-.158.237c-.028-.052-.08-.104-.133-.183-.026.08-.053.158-.08.21H58c-.053-.368-.158-.71-.158-1.08 0-.79.08-1.58.105-2.372.027-.368 0-.71 0-1.054.106.08.185.133.29.21.052-.103.105-.182.158-.26 0 0-.053-.028-.106-.08.05-.027.104-.08.104-.106.026-.08.08-.158.08-.21 0-.185-.054-.343-.08-.5.026 0 .052 0 .08-.028l.157.79h.08c-.106-.183.236-.342-.053-.552-.026-.027.026-.185.026-.264-.08-.157-.13-.315-.21-.526.026-.026.105-.053.184-.08-.105-.052-.184-.104-.263-.13.263-.238.263-.37.026-.633.054-.025.106-.025.106-.05 0-.238 0-.475-.052-.71-.053-.266.08-.58-.316-.74a.79.79 0 0 0 .105.21s-.08.027-.158.08c-.342-.317-.13-.74-.21-1.213.184.053.316.106.447.16-.053-.186-.184-.397-.263-.634h-.107v-1.74c0 .027.184.027.29.054 0-.027.025-.053.025-.08-.08-.105-.185-.21-.29-.342l.053-.053c-.21-.262-.105-.63-.105-.71V39.4c.264.264-.13.606.264.764v-.263h-.027c-.026-.395-.026-.79-.052-1.186h-.052c-.027.054-.027.08-.054.133h-.052l.158-6.298c.263.342.552.66.736 1 .606 1.108 1.395 2.057 2.132 3.058.632.87 1.21 1.818 1.79 2.714.71 1.08 1.394 2.16 2.105 3.24a81.41 81.41 0 0 0 1.63 2.426c.5.71 1.028 1.396 1.554 2.082.446.606.92 1.212 1.367 1.818.527.738 1.053 1.475 1.58 2.187.262.368.552.737.84 1.106.16.21.396.37.554.5-.025 0-.052 0-.104-.026.08.105.13.184.184.237.29.158.316.316.158.554zM74 46.854v-.185c0 .052.026.13 0 .184zm.895-11.62c-.027 0-.184-.16-.21-.186-.027.08 0 .158-.053.264-.027-.078-.21-.052-.21-.13-.027.368.157.737.13 1.106.08-.053.395-.08.474-.158.027.026.08.052.106.052-.527.396-.395.79-.158 1.24.052.104.21.315.052.526-.052.053.027.21.053.343h.077v.05l-.237.08c-.052-.08-.367-.236-.367-.37v1.346c.263.08.263.448.368.633a.768.768 0 0 0 .107-.21l.027.024c-.027.158-.053.316-.106.475-.052.236-.105.447-.13.684 0 .026.05.08.05.105-.288.66-.13 1.396-.235 2.08-.08.5 0 1.03-.053 1.556-.054.448-.16.922-.264 1.37-.027.08-.08.105-.21.158.052-.316.026-.527-.027-.817-.028 0-.37-.184-.397-.184 0 .37.21.87.29 1.29-.08-.026-.395-.21-.42-.21-.054.316-.054.738-.08 1.08-.027.264-.263.5-.29.79 0 .16.184.264.158.528h.21c0-.526.238-1 .238-1.554h.078c.027.053.106.106.08.132-.053.29-.16.606-.132.896 0 .158.13.316.08.5-.054.16-.08.317-.107.554-.027-.132-.053-.184-.053-.263-.026 0-.263-.027-.29-.027-.026.158.185.316.158.448-.026.026-.052.026-.105.053l-.868-1.266c-.686-1-1.37-2.003-2.054-3.03a6.312 6.312 0 0 1-.475-.79 37.09 37.09 0 0 0-2.71-4.033c-.762-.974-1.37-2.03-2.08-3.055-.656-.975-1.314-1.924-1.972-2.9-.237-.315-.526-.605-.737-.948-.683-1.08-1.29-2.187-1.972-3.267-.58-.897-1.21-1.767-1.816-2.636-.21-.29-.42-.607-.632-.923a.37.37 0 0 1-.052-.182c-.053-.58-.106-1.16-.132-1.713 0-.527.053-1.054.053-1.608v-.474c0-.132.025-.237.025-.37.025-.025.052-.078.078-.104-.763 0-1.553-.028-2.316 0-.5.025-.763-.186-1.105-.555-1-1.133-1.737-2.424-2.605-3.636a162.42 162.42 0 0 0-2.5-3.427c-.685-.922-1.37-1.818-2.053-2.74-.764-1.054-1.5-2.108-2.29-3.162a381.983 381.983 0 0 0-2.895-3.794c-.45-.58-.95-1.133-1.45-1.74.343.054.66.106.975.133l1.264.08c.947.077 1.894.13 2.84.26.79.107 1.58.265 2.396.396 1.738.29 3.448.765 5.106 1.318.974.316 1.92.738 2.87 1.133 2.13.87 4.157 1.924 6.157 3.03.63.343 1 .896 1.472 1.397.685.712 1.37 1.423 2.027 2.16.762.87 1.472 1.766 2.21 2.662.657.79 1.34 1.58 2 2.372.21.237.37.527.552.79.42.633.895 1.24 1.263 1.924.262.502.42 1.082.604 1.635.262.817.526 1.607.79 2.424.183.606.34 1.24.472 1.87.106.423.08.87.21 1.29.16.556 0 1.16.16 1.715.025.053.05.132.078.185.105.104.184.21.026.368-.025.026-.025.13 0 .21.054-.052.08-.105.133-.184 0 .053.025.08.025.105 0 .104-.027.21 0 .315 0 .052.052.13.078.184.053-.054.105-.08.21-.16.237.897.264 1.793.264 2.715 0 .87.157 1.74-.21 2.583.078-.29-.106-.555-.027-.818z"/><path d="M58.08 45.482c.025 0 .052.027.052.027l-.027-.03c0-.025 0-.025-.026 0zm4.157 26.036c-.29.21-.58.395-.948.474-.028-.026-.028-.053-.054-.08.29-.184.605-.368.895-.553.027.05.08.104.106.157zM12.895 35.81c.29-.367.58-.736.894-1.105.025.026.235.08.262.105-.29.37-.685.87-.974 1.265-.054-.053-.133-.237-.185-.264zM5.42 48.725c-.21-.448-.42-.923-.63-1.37a.91.91 0 0 1 .236-.106c.29.42.42.92.632 1.37 0 0-.21.105-.237.105zm6.712-12.65c-.158.238-.316.502-.474.74-.026-.028-.316.104-.342.078.158-.237.552-.66.71-.896.027.026.053.053.106.08zM59.422 72.6c.025 0 .025-.026.052-.026.184.026.394.052.605.052-.344.237-.555.21-.66-.026zm-47.24-35.418c.028-.08.08-.158.133-.237.052 0 .13-.027.13-.027.107-.184.107-.316.212-.474-.026-.026-.053-.026-.08-.053-.157.108-.315.24-.473.345.053.052.053.08.053.132-.21-.027-.29.08-.395.368-.026.08-.158.106-.29.21-.026.054-.052.186-.105.317l.027.028c-.053.053-.132.08-.132.08-.158.157-.342.29-.5.447-.026.08-.052.158-.052.237.185-.184.5-.527.737-.738l.027.027c.105-.158.184-.316.29-.474.025.026.025.052.052.08-.08.21-.158.446-.237.657-.055.026-.134.08-.134.053-.105.08-.184.184-.29.263l-.473.316c-.263.237-.526.447-.816.685-.184.29-.368.553-.58.896.317-.08.396.053.37.317.368.052.395-.237.5-.448.026-.054.053-.16.105-.186.237-.21.5-.394.763-.605.053-.053.053-.16.053-.238 0-.026-.133-.026-.212-.053.237-.264.58-.71.816-1 .132-.08.263-.186.263-.265-.026-.29.158-.368.37-.474-.106-.08-.133-.157-.133-.183z"/><path d="M12.71 36.892c-.105.184-.21.342-.315.527l-.158-.08c-.105.605-.474 1.132-.842 1.237.105.053.21.106.29.08.078-.027.13-.16.183-.238l.71-1.028.238-.396-.105-.105zM3.948 48.46c.132 0 .264.026.42.026 0-.105.133-.08.133-.184h.08c0 .132.026.237.026.37h-.552c-.027-.027-.132-.186-.106-.212zm-.21-1.212c-.08-.08-.21-.158-.21-.237-.027-.104.052-.235.13-.367.054.184.08.342.132.527-.027.025-.053.052-.053.078zm.658-1.687c.105.266.21.556.316.82a.798.798 0 0 0-.21.105c-.105-.264-.237-.554-.342-.817a.652.652 0 0 1 .237-.106zm58.58 25.194c.13-.052.288-.08.5-.13-.238.183-.422.315-.58.473-.027-.026-.053-.053-.08-.053.053-.105.106-.184.16-.29zM30.63 15.074c.157-.106.29-.185.447-.29l.052.052c-.16.21-.29.42-.475.685-.026-.183-.026-.29-.053-.42-.026 0 0 0 .027-.026zm7.71 13.333c.237-.106.474-.21.763-.343-.026.158-.026.264-.026.37a.927.927 0 0 0-.264-.054c-.158.027-.448.238-.58.264-.025 0 .106-.21.106-.237zm19.74 22.346c.052.263.552.395.052.658.08.055.157.08.236.134a.2.2 0 0 1-.052.106c-.053.025-.158.078-.21.05-.027 0-.08-.104-.08-.157 0-.237.027-.474.053-.79z"/></g></symbol><symbol viewBox="0 0 24 24" id="powerpoint" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M8 11v2h1v6H8v1h4v-1h-1v-2h2a3 3 0 0 0 3-3 3 3 0 0 0-3-3H8m5 2a1 1 0 0 1 1 1 1 1 0 0 1-1 1h-2v-2h2z" fill="#d14524"/></symbol><symbol viewBox="0 0 67.47 70" id="powershell" xmlns="http://www.w3.org/2000/svg"><path d="M18.545 12.4c-3.014 0-6.08 2.34-6.873 5.248L1.91 53.438c-.793 2.908.996 5.248 4.01 5.248h42.887c3.014 0 6.08-2.34 6.873-5.248l9.761-35.79c.794-2.908-.993-5.248-4.007-5.248h-42.89zm4.848 6.243c.652.04 1.29.33 1.76.86l7.96 9.013-3.957 3.246 3.957-3.244 4.832 5.47c.037.042.06.088.094.131.026.034.057.06.082.096.02.028.032.057.05.086.057.087.105.176.15.267.028.06.055.117.08.178a2.546 2.546 0 0 1 .171.764c.005.073.01.146.008.219-.002.09-.01.178-.021.267a2.53 2.53 0 0 1-.036.217 2.56 2.56 0 0 1-.07.252c-.024.076-.048.15-.08.224a2.547 2.547 0 0 1-.111.22 2.503 2.503 0 0 1-.133.218 2.546 2.546 0 0 1-.147.187c-.058.07-.118.137-.185.202-.027.026-.048.057-.076.082-.037.032-.077.054-.116.084-.038.03-.07.065-.11.093L16.8 52.271a2.552 2.552 0 0 1-3.563-.626 2.553 2.553 0 0 1 .63-3.563l18.349-12.853-3.06-3.467-7.839-8.873a2.549 2.549 0 0 1 .225-3.608 2.546 2.546 0 0 1 1.85-.638zm22.441 28.214c1.377 0 2.255 1.083 1.969 2.43-.287 1.347-1.627 2.433-3.004 2.434l-9.957.006c-1.378 0-2.256-1.083-1.969-2.43.287-1.347 1.626-2.433 3.004-2.434l9.957-.006z" fill="#03a9f4" stroke-width="5.342" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 210 210" id="prettier" xmlns="http://www.w3.org/2000/svg"><title>prettier-icon-dark</title><g transform="matrix(.9 0 0 .9 10.5 10.5)" fill="none" fill-rule="evenodd"><rect fill="#56B3B4" x="165" y="40" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="200" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="135" y="120" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="75" y="120" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="120" width="50" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="160" width="60" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="80" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="65" y="20" width="110" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="20" width="40" height="10" rx="5"/><rect fill="#F7BA3E" x="55" y="180" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="55" y="60" width="20" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="180" width="30" height="10" rx="5"/><rect fill="#F7BA3E" x="15" y="60" width="30" height="10" rx="5"/><rect fill="#56B3B4" x="95" y="100" width="90" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="100" width="40" height="10" rx="5"/><rect fill="#EA5E5E" x="15" y="100" width="20" height="10" rx="5"/><rect fill="#BF85BF" x="105" y="40" width="50" height="10" rx="5"/><rect fill="#56B3B4" x="15" y="40" width="80" height="10" rx="5"/><rect fill="#F7BA3E" x="45" y="140" width="100" height="10" rx="5"/><rect fill="#BF85BF" x="15" y="140" width="20" height="10" rx="5"/><rect fill="#EA5E5E" x="135" y="60" width="60" height="10" rx="5"/><rect fill="#F7BA3E" x="135" y="80" width="60" height="10" rx="5"/><rect fill="#56B3B4" x="15" width="130" height="10" rx="5"/></g></symbol><symbol viewBox="0 0 80 80" id="protractor" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="hxa"><path transform="scale(1 -1)" fill="#564b55" stroke-width="27.224" d="M-2.983-69.251h69.412v67.108H-2.983z"/></clipPath></defs><g transform="matrix(1.13039 0 0 -1.13039 5.714 82.137)" clip-path="url(#hxa)"><g transform="scale(.1)"><path d="M1180.54 92.324c-5.53 0-9.93-1.797-13.23-5.39-3.29-3.614-5.22-8.594-5.81-14.97h36.02c0 6.583-1.47 11.622-4.4 15.126-2.93 3.496-7.12 5.234-12.58 5.234zm2.84-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.88 6.21-8.83 14.824-8.83 25.84 0 11.101 2.73 19.922 8.21 26.464 5.45 6.524 12.81 9.805 22.02 9.805 8.63 0 15.46-2.851 20.48-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.45c.21-8.086 2.26-14.22 6.12-18.418 3.89-4.18 9.34-6.29 16.38-6.29 7.42 0 14.76 1.563 22 4.669V34.14c-3.68-1.602-7.18-2.746-10.48-3.438-3.28-.684-7.24-1.035-11.89-1.035M1272.34 30.918v44.57c0 5.606-1.28 9.805-3.82 12.559-2.56 2.773-6.56 4.16-12.02 4.16-7.2 0-12.49-1.953-15.84-5.851-3.34-3.895-5.03-10.32-5.03-19.286V30.918h-10.42v68.887h8.47l1.71-9.422h.5c2.14 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.15 2.804 12.88 2.804 8.29 0 14.54-2.011 18.73-6.015 4.19-3.985 6.28-10.391 6.28-19.192V30.918h-10.43M1328.96 38.406c7.1 0 12.27 1.938 15.48 5.813 3.22 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.44 6.25-15.56 6.25-6.11 0-10.79-2.383-14.04-7.129-3.26-4.746-4.88-11.472-4.88-20.136 0-8.797 1.61-15.45 4.84-19.93 3.23-4.484 7.97-6.723 14.22-6.723zm20.85 1.762h-.56c-4.83-7.004-12.02-10.5-21.62-10.5-9.01 0-16.03 3.066-21.04 9.238-5 6.153-7.5 14.922-7.5 26.27 0 11.355 2.51 20.176 7.54 26.465 5.03 6.289 12.03 9.433 21 9.433 9.34 0 16.5-3.398 21.49-10.195h.81l-.43 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.38 9.25M1434.91 38.27c1.85 0 3.63.136 5.34.421 1.72.274 3.09.547 4.1.84v-7.976c-1.15-.559-2.81-.996-5.01-1.36-2.18-.351-4.17-.527-5.94-.527-13.32 0-19.97 7.012-19.97 21.055V91.71h-9.88v5.027l9.88 4.336 4.38 14.707h6.04V99.805h20V91.71h-20V51.16c0-4.15.98-7.333 2.96-9.56 1.97-2.206 4.67-3.331 8.1-3.331M1463.81 65.43c0-8.809 1.76-15.508 5.27-20.118 3.53-4.609 8.69-6.906 15.53-6.906s12.01 2.297 15.56 6.875c3.53 4.602 5.3 11.301 5.3 20.149 0 8.75-1.77 15.41-5.3 19.953-3.55 4.539-8.77 6.824-15.69 6.824-6.82 0-11.99-2.246-15.47-6.73-3.46-4.48-5.2-11.16-5.2-20.047zm52.47 0c0-11.23-2.83-20-8.48-26.309-5.66-6.309-13.47-9.453-23.44-9.453-6.17 0-11.64 1.445-16.42 4.336-4.78 2.89-8.46 7.031-11.06 12.45-2.59 5.401-3.88 11.73-3.88 18.976 0 11.23 2.8 19.968 8.41 26.242 5.61 6.258 13.4 9.402 23.38 9.402 9.64 0 17.3-3.222 22.97-9.62 5.69-6.415 8.52-15.087 8.52-26.024M1591.71 92.324c-5.54 0-9.94-1.797-13.23-5.39-3.3-3.614-5.24-8.594-5.81-14.97h36c0 6.583-1.46 11.622-4.39 15.126-2.93 3.496-7.13 5.234-12.57 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.11 9.297-5.89 6.21-8.83 14.824-8.83 25.84 0 11.101 2.74 19.922 8.2 26.464 5.46 6.524 12.81 9.805 22.04 9.805 8.62 0 15.45-2.851 20.48-8.523 5.03-5.676 7.54-13.157 7.54-22.461v-6.613h-47.45c.21-8.086 2.25-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.36-6.29 7.43 0 14.77 1.563 22.01 4.669V34.14c-3.69-1.602-7.17-2.746-10.46-3.438-3.3-.684-7.27-1.035-11.91-1.035M1683.5 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12.01 4.16-7.2 0-12.48-1.953-15.83-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M1740.11 38.406c7.12 0 12.28 1.938 15.49 5.813 3.21 3.879 4.81 10.129 4.81 18.758v2.199c0 9.765-1.62 16.726-4.87 20.898-3.25 4.18-8.43 6.25-15.56 6.25-6.12 0-10.8-2.383-14.05-7.129-3.24-4.746-4.88-11.472-4.88-20.136 0-8.797 1.64-15.45 4.85-19.93 3.22-4.484 7.96-6.723 14.21-6.723zm20.87 1.762h-.57c-4.82-7.004-12.03-10.5-21.62-10.5-9.01 0-16.02 3.066-21.03 9.238-5 6.153-7.52 14.922-7.52 26.27 0 11.355 2.52 20.176 7.55 26.465 5.02 6.289 12.02 9.433 21 9.433 9.34 0 16.5-3.398 21.48-10.195h.83l-.44 4.96-.25 4.845v28.039h10.43V30.918h-8.49l-1.37 9.25M1846.07 38.27c1.85 0 3.64.136 5.36.421 1.7.274 3.07.547 4.08.84v-7.976c-1.13-.559-2.8-.996-5-1.36-2.2-.351-4.18-.527-5.94-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.86v5.027l9.86 4.336 4.4 14.707h6.04V99.805H1855V91.71h-19.98V51.16c0-4.15.98-7.333 2.95-9.56 1.97-2.206 4.68-3.331 8.1-3.331M1894.26 92.324c-5.53 0-9.94-1.797-13.22-5.39-3.31-3.614-5.25-8.594-5.83-14.97h36.01c0 6.583-1.45 11.622-4.38 15.126-2.95 3.496-7.13 5.234-12.58 5.234zm2.83-62.656c-10.19 0-18.22 3.086-24.1 9.297-5.9 6.21-8.84 14.824-8.84 25.84 0 11.101 2.73 19.922 8.2 26.464 5.47 6.524 12.81 9.805 22.03 9.805 8.63 0 15.46-2.851 20.49-8.523 5.03-5.676 7.55-13.157 7.55-22.461v-6.613h-47.46c.22-8.086 2.26-14.22 6.13-18.418 3.87-4.18 9.33-6.29 16.37-6.29 7.42 0 14.75 1.563 22 4.669V34.14c-3.7-1.602-7.17-2.746-10.47-3.438-3.28-.684-7.25-1.035-11.9-1.035M1983.36 49.727c0-6.426-2.4-11.368-7.18-14.844-4.77-3.477-11.47-5.215-20.11-5.215-9.13 0-16.26 1.445-21.37 4.336v9.687a51.32 51.32 0 0 1 10.65-3.964c3.79-.977 7.45-1.457 10.97-1.457 5.46 0 9.64.87 12.57 2.609 2.95 1.738 4.41 4.394 4.41 7.95 0 2.694-1.17 4.98-3.5 6.894-2.32 1.914-6.85 4.152-13.6 6.757-6.41 2.383-10.97 4.473-13.67 6.25-2.71 1.778-4.72 3.81-6.04 6.067-1.31 2.254-1.98 4.96-1.98 8.113 0 5.606 2.29 10.04 6.86 13.281 4.57 3.25 10.84 4.883 18.79 4.883 7.42 0 14.66-1.515 21.74-4.531l-3.71-8.496c-6.9 2.851-13.17 4.277-18.79 4.277-4.94 0-8.67-.77-11.18-2.324-2.52-1.543-3.78-3.691-3.78-6.406 0-1.844.48-3.418 1.42-4.707.95-1.309 2.46-2.54 4.56-3.711 2.09-1.184 6.11-2.871 12.07-5.086 8.16-2.98 13.69-5.98 16.55-8.996 2.87-3.02 4.32-6.809 4.32-11.367M2021.28 38.27c1.85 0 3.64.136 5.35.421 1.71.274 3.09.547 4.09.84v-7.976c-1.14-.559-2.81-.996-5.01-1.36-2.18-.351-4.18-.527-5.93-.527-13.33 0-19.99 7.012-19.99 21.055V91.71h-9.87v5.027l9.87 4.336 4.4 14.707h6.02V99.805h20V91.71h-20V51.16c0-4.15 1-7.333 2.97-9.56 1.98-2.206 4.67-3.331 8.1-3.331M2053.61 30.918h-10.42v68.887h10.42zm-11.31 87.559c0 2.39.59 4.14 1.76 5.253 1.18 1.106 2.65 1.661 4.42 1.661 1.67 0 3.1-.567 4.32-1.7 1.22-1.132 1.82-2.871 1.82-5.214 0-2.344-.6-4.09-1.82-5.247-1.22-1.16-2.65-1.726-4.32-1.726-1.77 0-3.24.566-4.42 1.726-1.17 1.157-1.76 2.903-1.76 5.247M2121.59 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.49l1.69-9.422h.5c2.15 3.387 5.14 6.023 8.99 7.887 3.85 1.867 8.16 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2159.29 77.742c0-4.812 1.35-8.465 4.08-10.926 2.72-2.48 6.51-3.71 11.37-3.71 10.19 0 15.28 4.953 15.28 14.831 0 10.344-5.16 15.532-15.47 15.532-4.9 0-8.67-1.32-11.31-3.965-2.63-2.649-3.95-6.555-3.95-11.762zm-5.67-58.387c0-3.73 1.58-6.55 4.72-8.488 3.14-1.922 7.65-2.879 13.52-2.879 8.75 0 15.24 1.309 19.45 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.15 6.32-3.45 7.754-2.31 1.457-6.65 2.168-13.01 2.168h-12.51c-4.74 0-8.43-1.12-11.06-3.386-2.65-2.266-3.97-5.508-3.97-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.23-3.39 3.15-5.754.91-2.371 1.37-5.039 1.37-8.02 0-6.746-2.29-12.128-6.91-16.152-4.61-4.012-10.93-6.023-18.98-6.023-2.05 0-3.98.156-5.78.5-4.45-2.356-6.67-5.305-6.67-8.871 0-1.883.77-3.282 2.34-4.176 1.54-.902 4.21-1.36 7.97-1.36h12.2c7.46 0 13.19-1.574 17.19-4.707 4-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2192.38 2.004 2183.46 0 2171.72 0c-9 0-15.95 1.68-20.82 5.027-4.88 3.352-7.34 8.079-7.34 14.211 0 4.18 1.35 7.813 4.03 10.88 2.68 3.046 6.45 5.116 11.32 6.21-1.77.8-3.24 2.031-4.44 3.711-1.19 1.68-1.78 3.633-1.78 5.84 0 2.52.66 4.707 2.01 6.602 1.34 1.882 3.44 3.71 6.34 5.468-3.56 1.465-6.46 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.26 13.37 6.79 17.452 4.52 4.082 10.93 6.133 19.22 6.133 3.6 0 6.86-.429 9.75-1.27h23.82M2284.61 91.71h-17.54V30.919h-10.43v60.793h-12.31v4.707l12.31 3.766v3.839c0 16.922 7.4 25.391 22.19 25.391 3.65 0 7.93-.73 12.82-2.195l-2.7-8.364c-4.03 1.301-7.46 1.946-10.31 1.946-3.93 0-6.85-1.309-8.73-3.926-1.89-2.617-2.84-6.816-2.84-12.598v-4.472h17.54V91.71M2302.87 65.43c0-8.809 1.76-15.508 5.28-20.118 3.52-4.609 8.7-6.906 15.52-6.906 6.84 0 12.02 2.297 15.57 6.875 3.54 4.602 5.3 11.301 5.3 20.149 0 8.75-1.76 15.41-5.3 19.953-3.55 4.539-8.78 6.824-15.69 6.824-6.83 0-11.99-2.246-15.46-6.73-3.48-4.48-5.22-11.16-5.22-20.047zm52.48 0c0-11.23-2.82-20-8.47-26.309-5.67-6.309-13.48-9.453-23.46-9.453-6.15 0-11.62 1.445-16.4 4.336-4.77 2.89-8.47 7.031-11.06 12.45-2.59 5.401-3.9 11.73-3.9 18.976 0 11.23 2.81 19.968 8.43 26.242 5.6 6.258 13.4 9.402 23.38 9.402 9.63 0 17.28-3.222 22.97-9.62 5.68-6.415 8.51-15.087 8.51-26.024M2403.79 101.074c3.07 0 5.8-.254 8.22-.761l-1.43-9.676c-2.86.633-5.37.933-7.55.933-5.58 0-10.33-2.261-14.3-6.785-3.95-4.531-5.94-10.156-5.94-16.902V30.918h-10.43v68.887h8.62l1.19-12.754h.5c2.56 4.48 5.63 7.949 9.23 10.37 3.61 2.423 7.56 3.653 11.89 3.653M2500.33 69.766l-10.68 28.476c-1.39 3.594-2.81 8.028-4.28 13.262-.93-4.024-2.24-8.438-3.96-13.262l-10.81-28.476zm14.77-38.848l-11.44 29.227h-36.83l-11.32-29.227h-10.81l36.34 92.273h8.98l36.13-92.273h-11.05M2583.07 30.918v44.57c0 5.606-1.27 9.805-3.83 12.559-2.55 2.773-6.55 4.16-12 4.16-7.21 0-12.49-1.953-15.84-5.851-3.35-3.895-5.03-10.32-5.03-19.286V30.918h-10.43v68.887h8.48l1.69-9.422h.51c2.14 3.387 5.14 6.023 8.99 7.887 3.84 1.867 8.15 2.804 12.88 2.804 8.3 0 14.54-2.011 18.74-6.015 4.19-3.985 6.29-10.391 6.29-19.192V30.918h-10.45M2620.76 77.742c0-4.812 1.36-8.465 4.08-10.926 2.73-2.48 6.53-3.71 11.37-3.71 10.2 0 15.28 4.953 15.28 14.831 0 10.344-5.15 15.532-15.45 15.532-4.91 0-8.68-1.32-11.32-3.965-2.64-2.649-3.96-6.555-3.96-11.762zm-5.66-58.387c0-3.73 1.57-6.55 4.71-8.488 3.15-1.922 7.65-2.879 13.53-2.879 8.75 0 15.23 1.309 19.44 3.926 4.21 2.617 6.31 6.172 6.31 10.652 0 3.723-1.14 6.32-3.45 7.754-2.31 1.457-6.64 2.168-13 2.168h-12.51c-4.74 0-8.43-1.12-11.07-3.386-2.63-2.266-3.96-5.508-3.96-9.747zm54.94 80.45v-6.582l-12.76-1.512c1.18-1.477 2.22-3.39 3.14-5.754.92-2.371 1.38-5.039 1.38-8.02 0-6.746-2.3-12.128-6.92-16.152-4.61-4.012-10.92-6.023-18.97-6.023-2.05 0-3.99.156-5.78.5-4.46-2.356-6.67-5.305-6.67-8.871 0-1.883.78-3.282 2.33-4.176 1.55-.902 4.21-1.36 7.98-1.36h12.2c7.46 0 13.18-1.574 17.18-4.707 4.01-3.144 6-7.714 6-13.71 0-7.618-3.06-13.426-9.17-17.43C2653.87 2.004 2644.94 0 2633.2 0c-9 0-15.95 1.68-20.83 5.027-4.88 3.352-7.33 8.079-7.33 14.211 0 4.18 1.35 7.813 4.02 10.88 2.69 3.046 6.47 5.116 11.32 6.21-1.77.8-3.23 2.031-4.43 3.711-1.19 1.68-1.79 3.633-1.79 5.84 0 2.52.66 4.707 2.01 6.602 1.35 1.882 3.45 3.71 6.35 5.468-3.56 1.465-6.47 3.953-8.71 7.48-2.23 3.516-3.35 7.54-3.35 12.06 0 7.55 2.25 13.37 6.79 17.452 4.52 4.082 10.92 6.133 19.21 6.133 3.62 0 6.86-.429 9.75-1.27h23.83M2692.7 99.805V55.117c0-5.605 1.27-9.805 3.83-12.566 2.56-2.766 6.57-4.145 12.01-4.145 7.2 0 12.47 1.965 15.81 5.903 3.33 3.945 4.99 10.379 4.99 19.304v36.192h10.44V30.918h-8.62l-1.5 9.25h-.58c-2.13-3.41-5.1-5.988-8.88-7.793-3.8-1.809-8.13-2.707-12.99-2.707-8.37 0-14.65 1.992-18.81 5.977-4.18 3.964-6.26 10.351-6.26 19.101v45.059h10.56M2760.61 30.918h10.43v97.805h-10.43zM2810.67 38.27c6.5 0 11.6 1.789 15.31 5.343 3.71 3.575 5.56 8.555 5.56 14.961v6.23l-10.44-.448c-8.3-.286-14.27-1.583-17.94-3.868-3.66-2.273-5.5-5.82-5.5-10.644 0-3.781 1.14-6.64 3.42-8.613 2.29-1.973 5.48-2.961 9.59-2.961zm23.57-7.352l-2.07 9.805h-.51c-3.44-4.305-6.86-7.227-10.27-8.77-3.42-1.523-7.68-2.285-12.8-2.285-6.83 0-12.17 1.758-16.05 5.273-3.87 3.528-5.81 8.536-5.81 15.032 0 13.906 11.12 21.199 33.37 21.875l11.7.359v4.277c0 5.418-1.17 9.395-3.5 11.985-2.32 2.566-6.03 3.855-11.15 3.855-5.74 0-12.24-1.758-19.49-5.273l-3.21 7.988c3.4 1.836 7.11 3.281 11.16 4.324a47.81 47.81 0 0 0 12.16 1.575c8.23 0 14.3-1.817 18.27-5.461 3.96-3.66 5.93-9.5 5.93-17.54V30.919h-7.73M2893.6 101.074c3.07 0 5.8-.254 8.25-.761l-1.46-9.676c-2.84.633-5.35.933-7.54.933-5.56 0-10.33-2.261-14.3-6.785-3.96-4.531-5.93-10.156-5.93-16.902V30.918h-10.44v68.887h8.61l1.19-12.754h.5c2.57 4.48 5.65 7.949 9.25 10.37 3.6 2.423 7.56 3.653 11.87 3.653M2901.63 6.727c-3.94 0-7.04.558-9.31 1.691v9.121c2.97-.84 6.08-1.25 9.31-1.25 4.14 0 7.3 1.25 9.45 3.77 2.16 2.507 3.24 6.132 3.24 10.859v91.895h10.69V31.797c0-7.95-2.01-14.121-6.04-18.496-4.02-4.383-9.8-6.574-17.34-6.574M2999.96 55.371c0-8.086-2.93-14.394-8.8-18.918-5.87-4.52-13.83-6.785-23.88-6.785-10.9 0-19.27 1.406-25.14 4.219v10.3c3.77-1.59 7.88-2.847 12.31-3.765 4.45-.93 8.85-1.399 13.21-1.399 7.12 0 12.49 1.36 16.09 4.063 3.59 2.695 5.4 6.465 5.4 11.277 0 3.196-.63 5.805-1.91 7.832-1.29 2.024-3.42 3.907-6.42 5.625-2.99 1.711-7.56 3.664-13.67 5.84-8.55 3.059-14.66 6.692-18.32 10.871-3.66 4.2-5.51 9.668-5.51 16.407 0 7.089 2.68 12.714 7.99 16.914 5.32 4.191 12.36 6.289 21.12 6.289 9.13 0 17.54-1.68 25.2-5.032l-3.32-9.304c-7.59 3.183-14.96 4.785-22.13 4.785-5.66 0-10.07-1.223-13.26-3.652-3.19-2.43-4.78-5.809-4.78-10.118 0-3.191.59-5.8 1.76-7.832 1.17-2.031 3.14-3.886 5.95-5.597 2.78-1.688 7.04-3.563 12.79-5.625 9.63-3.426 16.26-7.118 19.89-11.063 3.62-3.937 5.43-9.043 5.43-15.332M741.648 375.406h30c28.965 0 50.227 5.039 63.774 15.117 13.531 10.079 20.32 25.821 20.32 47.247 0 19.832-6.074 34.628-18.191 44.402-12.141 9.758-31.028 14.641-56.692 14.641h-39.211zm172.192 64.246c0-36.062-11.809-63.691-35.434-82.898-23.621-19.219-57.234-28.82-100.847-28.82h-35.911V198.73h-56.445v345.329h99.438c43.14 0 75.457-8.829 96.961-26.465 21.496-17.637 32.238-43.614 32.238-77.942M1099.26 464.691c11.17 0 20.39-.789 27.63-2.371l-5.43-51.718c-7.88 1.894-16.07 2.832-24.57 2.832-22.2 0-40.19-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.514v261.227h43.464l7.32-46.055h2.83c8.66 15.594 19.96 27.95 33.9 37.09 13.93 9.141 28.93 13.699 45 13.699M1206.88 329.82c0-60.308 22.28-90.465 66.85-90.465 44.08 0 66.13 30.157 66.13 90.465 0 59.688-22.21 89.512-66.61 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.95-75.972-32.83-99.898-21.89-23.945-52.35-35.918-91.41-35.918-24.41 0-45.97 5.508-64.7 16.543-18.75 11.016-33.16 26.836-43.23 47.48-10.08 20.625-15.11 44.551-15.11 71.793 0 42.364 10.86 75.43 32.58 99.2 21.73 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.05-24.328 33.06-57.121 33.06-98.379M1558.11 238.887c13.54 0 27.07 2.129 40.62 6.386v-41.816c-6.13-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.59 0-78.88 27.715-78.88 83.144v140.778h-35.68v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.48-9.57 26.34-9.57M1783.44 464.691c11.17 0 20.38-.789 27.62-2.371l-5.43-51.718c-7.88 1.894-16.06 2.832-24.56 2.832-22.2 0-40.2-7.246-53.97-21.731-13.78-14.48-20.66-33.301-20.66-56.453V198.73h-55.52v261.227h43.46l7.34-46.055h2.82c8.66 15.594 19.95 27.95 33.9 37.09 13.92 9.141 28.93 13.699 45 13.699M1925.05 236.523c20.15 0 36.32 5.625 48.52 16.895 12.21 11.25 18.31 27.051 18.31 47.344v22.676l-33.54-1.407c-26.13-.937-45.16-5.312-57.04-13.105-11.89-7.793-17.82-19.727-17.82-35.781 0-11.661 3.45-20.665 10.39-27.051 6.91-6.387 17.32-9.571 31.18-9.571zm82.66-37.793l-11.11 36.387h-1.87c-12.62-15.918-25.29-26.738-38.04-32.48-12.74-5.742-29.13-8.633-49.13-8.633-25.67 0-45.7 6.934-60.1 20.801-14.41 13.847-21.62 33.457-21.62 58.808 0 26.934 10 47.246 30 60.934 19.99 13.691 50.45 21.172 91.41 22.441l45.09 1.414v13.938c0 16.699-3.88 29.16-11.68 37.441-7.79 8.262-19.88 12.383-36.25 12.383-13.39 0-26.23-1.953-38.5-5.891a294.638 294.638 0 0 1-35.44-13.933l-17.94 39.668c14.17 7.41 29.68 13.035 46.52 16.894 16.85 3.868 32.77 5.789 47.72 5.789 33.22 0 58.31-7.246 75.22-21.726 16.94-14.492 25.4-37.246 25.4-68.262V198.73h-39.68M2220.04 194.004c-39.52 0-69.55 11.543-90.1 34.609-20.55 23.067-30.82 56.172-30.82 99.321 0 43.925 10.74 77.707 32.23 101.339 21.5 23.614 52.56 35.418 93.18 35.418 27.56 0 52.35-5.117 74.41-15.359l-16.78-44.641c-23.46 9.133-42.82 13.704-58.1 13.704-45.19 0-67.79-29.993-67.79-89.981 0-29.293 5.63-51.305 16.89-66.031 11.26-14.707 27.76-22.09 49.48-22.09 24.72 0 48.11 6.152 70.15 18.437v-48.417c-9.92-5.84-20.5-10-31.76-12.52-11.26-2.52-24.93-3.789-40.99-3.789M2451.52 238.887c13.54 0 27.08 2.129 40.63 6.386v-41.816c-6.15-2.676-14.05-4.922-23.73-6.738-9.69-1.797-19.73-2.715-30.12-2.715-52.6 0-78.9 27.715-78.9 83.144v140.778h-35.66v24.558l38.26 20.325 18.9 55.261h34.26v-58.113h74.39v-42.031h-74.39v-139.84c0-13.379 3.34-23.242 10.03-29.629 6.69-6.387 15.47-9.57 26.33-9.57M2585.92 329.82c0-60.308 22.28-90.465 66.84-90.465 44.09 0 66.15 30.157 66.15 90.465 0 59.688-22.22 89.512-66.62 89.512-23.31 0-40.2-7.707-50.67-23.144-10.47-15.43-15.7-37.54-15.7-66.368zm190.13 0c0-42.672-10.94-75.972-32.83-99.898-21.89-23.945-52.36-35.918-91.4-35.918-24.42 0-45.98 5.508-64.72 16.543-18.74 11.016-33.14 26.836-43.22 47.48-10.07 20.625-15.12 44.551-15.12 71.793 0 42.364 10.87 75.43 32.59 99.2 21.74 23.777 52.36 35.671 91.89 35.671 37.79 0 67.7-12.156 89.75-36.492 22.04-24.328 33.06-57.121 33.06-98.379M2972.33 464.691c11.18 0 20.38-.789 27.63-2.371l-5.43-51.718c-7.87 1.894-16.05 2.832-24.57 2.832-22.2 0-40.19-7.246-53.96-21.731-13.78-14.48-20.67-33.301-20.67-56.453V198.73h-55.51v261.227h43.46l7.33-46.055h2.83c8.66 15.594 19.96 27.95 33.89 37.09 13.94 9.141 28.94 13.699 45 13.699" fill="#100f0d"/><path d="M610.11 372.83c0-170.584-138.257-308.862-308.846-308.862-170.602 0-308.846 138.278-308.846 308.863 0 170.576 138.244 308.846 308.846 308.846 170.59 0 308.846-138.27 308.846-308.846" fill="#e53935" stroke-width="1.029"/><path d="M460.694 521.792l-105.04.958-61.415 61.415-72.096-47.883 12.445-12.438-29.207.26-99.129-166.817H67.357l24.39-24.402-24.57-41.363L294.66 64.049c2.192-.04 4.399-.08 6.603-.08 170.416 0 308.585 138.055 308.846 308.408L460.694 521.792" fill="#d51c2f" stroke-width="1.029"/><path d="M149.093 350.258c0 84.048 68.13 152.151 152.171 152.151 84.028 0 152.139-68.103 152.139-152.151zm342.063-7.017v14.046h44.015c-1.75 59.337-25.556 113.104-63.54 153.419L438.75 477.81l-9.925 9.94 32.875 32.887c-40.314 37.983-94.081 61.79-153.41 63.527l-.015-44.003h-14.035v44.003c-59.34-1.737-113.096-25.556-153.41-63.527l32.887-32.887-9.945-9.92-32.883 32.875c-37.975-40.315-61.781-94.082-63.53-153.419h44.002l-.008-14.034H67.176v-51.511h468.176v51.5h-44.196" fill="#f5f5f5" stroke-width="1.029"/></g></g></symbol><symbol id="pug" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><style>.st0{fill:#c1272d}.hyst1{fill:#efcca3}.st2{fill:#ed1c24}.hyst3{fill:#ccac8d}.hyst4{fill:#fff}.st5{fill:#ff931e}.st6{fill:#ffb81e}.hyst7{fill:#56332b}.hyst8{fill:#442823}.hyst9{fill:#7f4a41}.hyst10{fill:#331712}.st11{fill:#fc6}.st12{fill:#ccc}.st13{fill:#b3b3b3}.st14{fill:#989898}.st15{fill:#323232}.st16{fill:#1e1e1e}.st17{fill:#4c4c4c}.st18{fill:#e6e6e6}.st19{fill:#606060}</style><path class="hyst1" d="M107.4 50.9c-.2-4.4.4-8.3-1.6-11.6-4.8-8.2-16.8-13-40.8-13v.7h-.5.5v-.7c-24 0-36.6 4.8-41.4 13.1-1.9 3.4-1.7 7.2-2 11.6-.2 3.5-1.8 7.2-1.1 11.2.8 5.2 1.1 10.4 1.9 15.2.6 3.9 6 7.2 6.5 10.9 1.4 10.2 12 14.9 36 14.9v.8h-.6.7v-.8c24 0 34.2-4.7 35.5-14.9.5-3.8 5.5-7 6.1-10.9.8-4.8 1.1-10 1.9-15.2.7-4-.9-7.8-1.1-11.3z"/><path class="hyst3" d="M64.6 54.5c4.3.1 7.3 2.8 10.1 5.3 3.3 2.9 8.9 4.9 11.2 7.4 2.3 2.5 5.3 5 6.4 8.9 1.1 3.9 1.4 8.9 1.4 10.2 0 1.3.7 1 2.7 0 4.7-2.3 9.9-8.5 9.9-8.5-.6 3.9-5.7 7.4-6.2 11.1C98.9 99.1 89 104 64.5 104h-.1.6"/><path class="hyst3" d="M80.4 46.7c.9 3.1 4.1 13.6-2.1 10.1 0 0 2.6 1.5 4.2 7.2 1.7 5.7 5.8 6.4 5.8 6.4s6.7 1.3 11.7-3c4.2-3.6 4.9-10 3.1-14.9-1.8-4.8-5-6.3-9.7-7.3-4.7-1.1-14.1-2-13 1.5z"/><circle cx="92.3" cy="58.1" r="8.8"/><circle class="hyst4" cx="90" cy="54.2" r="2.3"/><path class="hyst1" d="M78.9 57.7s7.9 5.4 12.2 10.7c4.3 5.3 4.2 6.3 4.2 6.3l-3.1 1.4s-4.4-8.3-9.8-11.4c-5.5-3.1-6.1-5.7-6.1-5.7l2.6-1.3z"/><path class="hyst3" d="M64.9 54.5c-4.3.1-7.5 2.8-10.4 5.3-3.3 2.9-9.1 4.9-11.4 7.4-2.3 2.5-5.4 5-6.5 8.9-1.1 3.9-1.5 8.9-1.5 10.2 0 1.3.2 1.4-2.7 0-4.7-2.2-9.9-8.5-9.9-8.5.6 3.9 5.7 7.4 6.2 11.1C30.1 99.1 40 104 64.5 104h.5"/><path class="hyst7" d="M88.1 71.4C83.3 65.5 75.6 60 64.9 60h-.1c-10.7 0-18.4 5.5-23.2 11.4-5 6.1-4.6 8.5-4.6 14.3 0 21 7.4 15 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.7 12.3-17.3.1-5.8.4-8.4-4.6-14.5z"/><path class="hyst8" d="M64.4 65.2s-.7 9.7-2.1 11.6l2.6-.6-.5-11z"/><path class="hyst8" d="M65.1 65.2s.7 9.7 2.1 11.6l-2.6-.6.5-11z"/><path class="hyst7" d="M56.7 62.9c-1-2.3 2.6-6 8.3-6.1 5.7 0 9.3 3.7 8.3 6.1-1 2.4-4.6 3.1-8.3 3.2-3.6-.1-7.3-.8-8.3-3.2z"/><path d="M65 65.2c0-.4 3.4-.5 5.2-1.7 0 0-3.7 1.2-4.5.7-.8-.4-1-1.6-1-1.6s-.3 1.2-.9 1.6c-.7.4-4.9-.7-4.9-.7s5.6 1.4 5.6 1.7c0 .3-.1 1.3-.1 2 0 2.5 0 8.7.4 9.2.6.9.4-6.7.4-9.2-.1-.8-.1-1.6-.2-2z"/><path class="hyst9" d="M65.2 78.6c1.7 0 4.7 1.2 7.4 3.1-2.6-2.9-5.7-4.9-7.4-4.9-1.8 0-5.6 2.2-8.3 5.4 2.8-2.2 6.4-3.6 8.3-3.6z"/><path class="hyst8" d="M64.5 96.3c-3.8 0-7.5-1.2-10.9-2.1-.7-.2-1.4.3-2.1.1-6.3-2-11.4-5.4-14.5-9.7v1c0 21 7.4 15.1 12.3 17.6 5 2.5 10.2 1.7 15.5 1.7h.1c5.4 0 10.5.7 15.5-1.8 4.9-2.5 12.3 3.6 12.3-17.4 0-.8 0-1.6.1-2.3-2.9 4.7-8.2 8.4-14.8 10.6-.6.2-2-.3-2.6-.2-3.6 1.2-6.8 2.5-10.9 2.5z"/><path class="hyst8" d="M55 85s-2.5 7.5-.8 10.8l-2.3-1s1.7-7.6 3.1-9.8zM74.8 85s2.5 7.5.8 10.8l2.3-1s-1.8-7.6-3.1-9.8z"/><path class="hyst3" d="M48.6 46.7c-.9 3.1-4.1 13.6 2.1 10.1 0 0-2.6 1.5-4.2 7.2s-5.8 6.4-5.8 6.4-6.7 1.3-11.7-3c-4.2-3.6-4.9-10-3.1-14.9s5-6.3 9.7-7.3c4.7-1.1 14-2 13 1.5z"/><path d="M64.9 76.8c2.7 0 11.1 5.8 11.2 12.9v-.4c0-7.4-6.8-13.3-11.2-13.3-4.4 0-11.2 6-11.2 13.3v.4c.1-7.1 8.5-12.9 11.2-12.9z"/><ellipse transform="rotate(-14.465 66.712 61.468)" class="hyst10" cx="66.7" cy="61.5" rx=".8" ry="1.5"/><ellipse transform="rotate(17.235 62.371 61.462)" class="hyst10" cx="62.4" cy="61.5" rx=".8" ry="1.5"/><circle cx="37.2" cy="58.1" r="8.8"/><circle class="hyst4" cx="39.5" cy="54.2" r="2.3"/><path class="hyst9" d="M67.5 58.2c0-.1-2.3 1-2.9 1.1-.6-.1-2.9-1.2-2.9-1.1h5.8z"/><path class="hyst1" d="M50 57.7s-7.9 5.4-12.2 10.7c-4.3 5.3-4.2 6.3-4.2 6.3l3.1 1.4s4.4-8.3 9.8-11.4 6.1-5.7 6.1-5.7L50 57.7z"/><path class="hyst3" d="M32.7 41.7S30 49.1 24 52.2c0 0 9.4-1.1 8.7-10.5zM95.8 41.7s2.7 7.4 8.7 10.5c0 0-9.4-1.1-8.7-10.5zM78.7 55.5s-5.9-6.2-13.8-6.4h.1.1c-8 .2-13.8 6.4-13.8 6.4 6.9-4.8 12.8-4.7 13.8-4.7-.1 0 6.7-.1 13.6 4.7zM71.8 42.5s-3-4.2-7-4.3h.2c-3 .1-6.9 4.3-6.9 4.3 3.4-3.3 6.9-3.2 6.9-3.2s3.3-.1 6.8 3.2zM37.2 73.2s-4.7 2.3-8.1.9H29c-3-1.7-4.5-6.8-4.5-6.8s3 9 12.7 5.9zM92 73.2s4.7 2.3 8.1.9c4-1.7 4.6-6.8 4.6-6.8s-3 9-12.7 5.9z"/><path class="hyst3" d="M42.6 41.2c2.6-.5 6.9-.6 10.3.5 4.3 1.5.8 7 1.7 7.3.9.3 2.1-3.8 10.1-3.4 8.1.4 9 4 10.1 3.4s-1.1-10 11-7.8c0 0-12.7-3.4-12.1 5.8 0 0-7.3-5.6-17.5-.6.1 0 2.7-8.6-13.6-5.2zM86.9 41.2c.2 0 .3.1.4.1.1 0-.1-.1-.4-.1zM86.9 41.2zM39.1 28.9S28.3 42.5 26.7 47.7c-1.6 5.3-2.8 27-4.2 30.1l-5-21.4 9.2-22.3 12.4-5.2zM89.9 28.9s10.8 13.6 12.4 18.8c1.6 5.3 2.8 27 4.2 30.1l5-21.4-9.2-22.3-12.4-5.2z"/><path class="hyst7" d="M89.4 28.9s11.6 9.7 15 20.9c3.4 11.2 2 24.8 4.6 26.5 3.7 2.4 7.9-11.9 9.3-13.4 2.2-2.4 9.5-8.5 10-9.6.5-1.1-14.8-17.8-21.5-21.1-8.1-3.8-18.1-4.1-17.4-3.3z"/><path class="hyst8" d="M99.3 34.9s13.7 17.5 13.5 39.3l5.5-11.2c-.1 0-4.9-14.3-19-28.1z"/><path class="hyst7" d="M39.1 28.9s-11.6 9.7-15 20.9-2 24.8-4.6 26.5c-3.7 2.4-7.9-11.9-9.3-13.4C8 60.5.7 54.4.2 53.3-.3 52.2 15 35.5 21.7 32.2c8.1-3.8 18.1-4.1 17.4-3.3z"/><path class="hyst8" d="M29.2 34.9S15.5 52.4 15.7 74.2L10.3 63s4.8-14.3 18.9-28.1z"/><path class="hyst3" d="M21.8 74.6s1 5.4 2.6 7.1.5-1.3.5-1.3-1.7-.9-1.4-7.8-1.7 2-1.7 2zM107.1 74.6s-1 5.4-2.6 7.1-.5-1.3-.5-1.3 1.7-.9 1.4-7.8 1.7 2 1.7 2z"/><g><circle class="hyst8" cx="54.5" cy="70.5" r=".8"/><circle class="hyst8" cx="49.9" cy="75.3" r=".8"/><circle class="hyst8" cx="48.4" cy="70.5" r=".8"/></g><g><circle class="hyst8" cx="74" cy="70.5" r=".8"/><circle class="hyst8" cx="78.6" cy="75.3" r=".8"/><circle class="hyst8" cx="80.1" cy="70.5" r=".8"/></g></symbol><symbol viewBox="0 0 50 50" id="puppet" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -247)" fill="#fbc02d"><path stroke-width=".283" d="M11.559 249.467h13.587v13.587H11.559zM27.435 265.056h13.587v13.587H27.435zM11.559 281.074h13.587v13.587H11.559z"/><path stroke-width=".256" d="M16.62 251.615l18.305 18.305-3.236 3.236-18.305-18.305z"/><path stroke-width=".256" d="M37.834 271.331L19.53 289.636l-3.237-3.237 18.305-18.304z"/></g></symbol><symbol viewBox="0 0 100 99.999997" id="purescript" xmlns="http://www.w3.org/2000/svg"><path clip-path="url(#SVGID_2_)" d="M98.079 38.548L79.22 19.68l-5.087 5.088L90.447 41.09 74.134 57.41l5.087 5.087 18.858-18.86a3.59 3.59 0 0 0 1.055-2.55 3.578 3.578 0 0 0-1.055-2.54M25.483 42.794l-5.09-5.089L1.53 56.568a3.566 3.566 0 0 0-1.05 2.545c0 .961.373 1.863 1.05 2.542L20.394 80.52l5.089-5.086L9.162 59.113z" fill="#42a5f5" stroke-width="1.192"/><path clip-path="url(#SVGID_2_)" transform="matrix(1.19175 0 0 1.19175 -306.84 -629.047)" fill="#42a5f5" d="M281.841 551.736l6.461 6.037h28.379l-6.461-6.037zM288.302 566.861l-6.463 6.035h28.381l6.463-6.035zM281.838 581.982l6.464 6.035h28.381l-6.463-6.035z"/></symbol><symbol viewBox="0 0 24 24" id="python" xmlns="http://www.w3.org/2000/svg"><path d="M19.14 7.5A2.86 2.86 0 0 1 22 10.36v3.78A2.86 2.86 0 0 1 19.14 17H12c0 .39.32.96.71.96H17v1.68a2.86 2.86 0 0 1-2.86 2.86H9.86A2.86 2.86 0 0 1 7 19.64v-3.75a2.85 2.85 0 0 1 2.86-2.85h5.25a2.85 2.85 0 0 0 2.85-2.86V7.5h1.18m-4.28 11.79c-.4 0-.72.3-.72.89 0 .59.32.71.72.71a.71.71 0 0 0 .71-.71c0-.59-.32-.89-.71-.89m-10-1.79A2.86 2.86 0 0 1 2 14.64v-3.78A2.86 2.86 0 0 1 4.86 8H12c0-.39-.32-.96-.71-.96H7V5.36A2.86 2.86 0 0 1 9.86 2.5h4.28A2.86 2.86 0 0 1 17 5.36v3.75a2.85 2.85 0 0 1-2.86 2.85H8.89a2.85 2.85 0 0 0-2.85 2.86v2.68H4.86M9.14 5.71c.4 0 .72-.3.72-.89 0-.59-.32-.71-.72-.71-.39 0-.71.12-.71.71s.32.89.71.89z"/><path d="M9.264 22.379c-.895-.24-1.581-.799-1.947-1.582-.228-.489-.237-.606-.238-2.957-.001-2.745.057-3.074.666-3.785.193-.226.568-.517.833-.648.47-.23.579-.239 3.839-.288 3.131-.048 3.386-.065 3.814-.264.626-.291 1.07-.687 1.4-1.247.27-.46.278-.522.311-2.29l.034-1.82.932.051c1.075.058 1.504.211 2.098.748.853.77.869.841.869 3.957 0 2.434-.02 2.783-.18 3.075a3.365 3.365 0 0 1-1.337 1.33l-.517.273-3.95.031-3.951.031.068.274c.037.151.164.377.282.503.209.224.262.229 2.433.229h2.22v1.05c0 1.653-.394 2.437-1.54 3.072l-.545.302-2.644.018c-1.455.01-2.782-.018-2.95-.063zm6.12-1.692c.22-.222.253-.325.206-.675-.07-.523-.278-.73-.732-.73-.467 0-.672.217-.735.78-.042.372-.012.496.163.672.3.3.77.28 1.097-.047z" fill="#fc0" stroke="#fc0" stroke-width=".102"/><path d="M9.349 22.38c-.911-.15-1.936-1.074-2.176-1.963-.073-.273-.101-1.279-.079-2.868.033-2.317.047-2.473.27-2.926.13-.263.401-.623.603-.8.674-.592.87-.63 3.484-.675 4.399-.076 4.927-.166 5.705-.967.642-.662.706-.9.774-2.883l.061-1.784.951.055c.523.031 1.11.122 1.304.204.54.225 1.358 1.042 1.472 1.47.153.572.243 3.18.16 4.617-.071 1.23-.093 1.327-.395 1.78-.193.288-.577.647-.966.903l-.647.425-3.922.008c-2.157.004-3.942.028-3.966.052-.115.115.354.82.587.883.14.038 1.181.073 2.314.079l2.06.01v.91c0 1.739-.326 2.446-1.454 3.162l-.631.4-2.543-.011c-1.398-.007-2.733-.043-2.966-.081zm5.98-1.718c.285-.256.313-.328.251-.658-.09-.483-.301-.682-.722-.682-.436 0-.625.193-.715.73-.065.384-.044.453.2.663.358.308.595.295.985-.053z" fill="#fdd835" stroke-width=".102"/><path d="M4.281 17.396c-.88-.215-1.714-.935-2.024-1.747-.149-.389-.168-.804-.142-3.041.027-2.26.054-2.638.215-2.962.259-.519.851-1.092 1.392-1.346.437-.206.632-.217 4.408-.245l3.95-.03-.067-.275a1.367 1.367 0 0 0-.282-.504c-.21-.224-.263-.23-2.433-.23h-2.22l.002-1.143c.003-1.338.157-1.795.84-2.493.746-.763 1.103-.838 4.025-.838 2.961 0 3.28.06 4.067.768.37.333.572.621.728 1.037.201.539.213.735.183 3.072-.035 2.777-.045 2.824-.78 3.598-.787.829-.76.824-4.59.883-3.812.06-3.797.057-4.61.806-.765.706-.917 1.2-.964 3.133l-.04 1.653-.677-.01c-.371-.007-.813-.045-.98-.086zM9.59 5.551c.237-.204.286-.326.286-.72 0-.547-.201-.763-.71-.763-.502 0-.765.248-.765.724 0 .492.141.782.439.902.345.14.444.12.75-.143z" fill="#3c78aa"/></symbol><symbol viewBox="0 0 24 24" id="r" xmlns="http://www.w3.org/2000/svg"><path d="M11.956 4.05c-5.694 0-10.354 3.106-10.354 6.947 0 3.396 3.686 6.212 8.531 6.813v2.205h3.53V17.82c.88-.093 1.699-.259 2.475-.497l1.43 2.692h3.996l-2.402-4.048c1.936-1.263 3.147-3.034 3.147-4.97 0-3.841-4.659-6.947-10.354-6.947m1.584 2.712c4.349 0 7.558 1.45 7.558 4.753 0 1.77-.952 3.013-2.505 3.779a1.081 1.081 0 0 1-.228-.156c-.373-.165-.994-.352-.994-.352s3.085-.227 3.085-3.302-3.23-3.127-3.23-3.127h-7.092v7.413c-2.64-.766-4.462-2.392-4.462-4.255 0-2.63 3.52-4.753 7.868-4.753m.156 4.12h2.143s.983-.05.983.974c0 1.004-.983 1.004-.983 1.004h-2.143v-1.977m-.031 4.566h.952c.186 0 .28.052.445.207.135.103.28.3.404.476-.57.073-1.17.104-1.801.104z" fill="#1976d2" stroke-width="1.035"/></symbol><symbol viewBox="0 0 24 24" id="raml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="razor" xmlns="http://www.w3.org/2000/svg"><path d="M15.45 11.91c-.11-2.21-1.75-3.54-3.73-3.54h-.08c-2.29 0-3.55 1.8-3.55 3.84 0 2.29 1.53 3.74 3.54 3.74 2.25 0 3.72-1.65 3.83-3.59m-3.81-5.97c1.53 0 2.97.68 4.02 1.74 0-.51.33-.89.83-.89h.11c.74 0 .89.7.89.92v7.9c-.04.52.54.78.87.44 1.27-1.29 2.78-6.69-.79-9.81-3.33-2.92-7.8-2.44-10.18-.8-2.52 1.74-4.14 5.61-2.57 9.22 1.71 3.95 6.61 5.13 9.52 3.95 1.48-.59 2.15 1.4.65 2.05-2.34.99-8.77.89-11.78-4.32-2.03-3.52-1.93-9.71 3.46-12.92C10.81 1.42 16.24 2.1 19.5 5.5c3.45 3.6 3.25 10.3-.1 12.91-1.51 1.18-3.76.03-3.74-1.7l-.02-.56a5.611 5.611 0 0 1-3.99 1.66C8.63 17.81 6 15.15 6 12.13c0-3.05 2.63-5.74 5.65-5.74z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="react" xmlns="http://www.w3.org/2000/svg"><path d="M12 10.11c1.03 0 1.87.84 1.87 1.89 0 1-.84 1.85-1.87 1.85-1.03 0-1.87-.85-1.87-1.85 0-1.05.84-1.89 1.87-1.89M7.37 20c.63.38 2.01-.2 3.6-1.7-.52-.59-1.03-1.23-1.51-1.9a22.7 22.7 0 0 1-2.4-.36c-.51 2.14-.32 3.61.31 3.96m.71-5.74l-.29-.51c-.11.29-.22.58-.29.86.27.06.57.11.88.16l-.3-.51m6.54-.76l.81-1.5-.81-1.5c-.3-.53-.62-1-.91-1.47C13.17 9 12.6 9 12 9c-.6 0-1.17 0-1.71.03-.29.47-.61.94-.91 1.47L8.57 12l.81 1.5c.3.53.62 1 .91 1.47.54.03 1.11.03 1.71.03.6 0 1.17 0 1.71-.03.29-.47.61-.94.91-1.47M12 6.78c-.19.22-.39.45-.59.72h1.18c-.2-.27-.4-.5-.59-.72m0 10.44c.19-.22.39-.45.59-.72h-1.18c.2.27.4.5.59.72M16.62 4c-.62-.38-2 .2-3.59 1.7.52.59 1.03 1.23 1.51 1.9.82.08 1.63.2 2.4.36.51-2.14.32-3.61-.32-3.96m-.7 5.74l.29.51c.11-.29.22-.58.29-.86-.27-.06-.57-.11-.88-.16l.3.51m1.45-7.05c1.47.84 1.63 3.05 1.01 5.63 2.54.75 4.37 1.99 4.37 3.68 0 1.69-1.83 2.93-4.37 3.68.62 2.58.46 4.79-1.01 5.63-1.46.84-3.45-.12-5.37-1.95-1.92 1.83-3.91 2.79-5.38 1.95-1.46-.84-1.62-3.05-1-5.63-2.54-.75-4.37-1.99-4.37-3.68 0-1.69 1.83-2.93 4.37-3.68-.62-2.58-.46-4.79 1-5.63 1.47-.84 3.46.12 5.38 1.95 1.92-1.83 3.91-2.79 5.37-1.95M17.08 12c.34.75.64 1.5.89 2.26 2.1-.63 3.28-1.53 3.28-2.26 0-.73-1.18-1.63-3.28-2.26-.25.76-.55 1.51-.89 2.26M6.92 12c-.34-.75-.64-1.5-.89-2.26-2.1.63-3.28 1.53-3.28 2.26 0 .73 1.18 1.63 3.28 2.26.25-.76.55-1.51.89-2.26m9 2.26l-.3.51c.31-.05.61-.1.88-.16-.07-.28-.18-.57-.29-.86l-.29.51m-2.89 4.04c1.59 1.5 2.97 2.08 3.59 1.7.64-.35.83-1.82.32-3.96-.77.16-1.58.28-2.4.36-.48.67-.99 1.31-1.51 1.9M8.08 9.74l.3-.51c-.31.05-.61.1-.88.16.07.28.18.57.29.86l.29-.51m2.89-4.04C9.38 4.2 8 3.62 7.37 4c-.63.35-.82 1.82-.31 3.96a22.7 22.7 0 0 1 2.4-.36c.48-.67.99-1.31 1.51-1.9z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="readme" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h-2V7h2m0 10h-2v-6h2m-1-9A10 10 0 0 0 2 12a10 10 0 0 0 10 10 10 10 0 0 0 10-10A10 10 0 0 0 12 2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="reason" xmlns="http://www.w3.org/2000/svg"><path d="M3 3v18h18V3H3zm5.119 8.993h2.798c.382 0 .71.025.985.075.275.05.534.159.774.326.244.168.435.386.577.654.145.265.218.598.218 1 0 .552-.112 1.001-.335 1.35-.22.348-.536.638-.947.87l2.16 3.203H12.31l-1.763-2.742h-.77v2.742H8.12v-7.478zm6.594 0h4.676v1.447h-3.018v1.29h2.802v1.447h-2.802v1.848h3.018v1.446h-4.676v-7.478zM9.778 13.37v2.014h.513c.266 0 .49-.014.67-.044.18-.03.329-.1.45-.207a.96.96 0 0 0 .253-.34c.055-.128.082-.297.082-.508 0-.187-.034-.35-.1-.483a.698.698 0 0 0-.343-.317 1.086 1.086 0 0 0-.395-.095 6.012 6.012 0 0 0-.526-.02h-.604z" fill="#f44336" stroke-width="1.067"/></symbol><symbol viewBox="0 0 172 193" id="restql" xmlns="http://www.w3.org/2000/svg"><title>Group</title><g transform="translate(14.767 16.713) scale(.82795)" fill="none"><path d="M171.39 55.799c-.975-6.147-4.673-11.642-10.15-14.805L96.381 3.546C93.217 1.72 89.615.756 85.964.756s-7.253.964-10.415 2.788L10.69 40.992A20.896 20.896 0 0 0 .272 59.035v74.89a20.894 20.894 0 0 0 10.416 18.042l64.859 37.446c3.165 1.827 6.767 2.791 10.417 2.791s7.252-.964 10.415-2.79l64.859-37.445c5.479-3.166 9.178-8.66 10.152-14.808zm-16.516 85.147L90.017 178.39a8.104 8.104 0 0 1-8.108 0l-64.857-37.444a8.109 8.109 0 0 1-4.053-7.021v-74.89a8.109 8.109 0 0 1 4.053-7.021l64.857-37.446c1.254-.725 2.654-1.086 4.054-1.086s2.8.361 4.054 1.086l64.857 37.446a8.106 8.106 0 0 1 4.053 7.021v74.89a8.109 8.109 0 0 1-4.053 7.021z" fill="#83e8c2"/><path d="M158.93 59.035a8.109 8.109 0 0 0-4.053-7.021L90.02 14.568c-1.254-.725-2.654-1.086-4.054-1.086s-2.8.361-4.054 1.086L17.055 52.014a8.106 8.106 0 0 0-4.053 7.021v74.89a8.109 8.109 0 0 0 4.053 7.021l64.857 37.444a8.104 8.104 0 0 0 8.108 0l64.857-37.444a8.109 8.109 0 0 0 4.053-7.021zm-46.766 31.681c.119-.069.242-.118.365-.149.044-.012.088-.01.131-.018.076-.012.152-.029.228-.029l.015.001c.02.001.038.005.059.006.093.005.184.019.273.04l.1.03c.077.025.15.057.223.095.028.014.057.027.084.043.094.057.184.122.263.199.007.008.013.017.021.024.07.071.133.15.188.235.018.029.033.059.05.09.04.072.072.148.099.229a1.512 1.512 0 0 1 .081.46v16.209l-3.278 1.893a1.548 1.548 0 0 0-.678.83 1.533 1.533 0 0 0-.098.514v3.785l-14.038 8.104-.01.004a1.55 1.55 0 0 1-.354.146c-.045.012-.09.011-.135.018-.074.012-.15.029-.225.029l-.014-.001c-.02-.001-.039-.005-.059-.006a1.463 1.463 0 0 1-.273-.041c-.034-.008-.066-.019-.1-.03a1.318 1.318 0 0 1-.223-.094c-.029-.015-.057-.027-.084-.044a1.45 1.45 0 0 1-.263-.198c-.009-.008-.015-.019-.023-.027a1.495 1.495 0 0 1-.185-.232c-.019-.029-.034-.06-.051-.09a1.422 1.422 0 0 1-.098-.229 1.702 1.702 0 0 1-.033-.101 1.487 1.487 0 0 1-.048-.358l-.001-.002v-20.053a1.446 1.446 0 0 1 .727-1.255zM85.24 31.369a1.449 1.449 0 0 1 1.452 0l45.741 26.41a1.45 1.45 0 0 1 0 2.512l-17.366 10.027a1.457 1.457 0 0 1-1.452 0l-15.49-8.943 1.727-.996a1.552 1.552 0 0 0 0-2.688l-13.111-7.57c-.239-.139-.508-.207-.775-.207s-.535.068-.775.207l-3.278 1.893-14.038-8.104a1.451 1.451 0 0 1 0-2.513zM57.59 47.558c.251 0 .501.065.726.194l15.489 8.942-1.727.997a1.552 1.552 0 0 0 0 2.688l1.727.996-15.488 8.943a1.457 1.457 0 0 1-1.452 0L39.499 60.291a1.45 1.45 0 0 1 0-2.512l17.366-10.027c.225-.129.475-.194.725-.194zm-9.56 92.328c-.241 0-.489-.062-.724-.196l-17.365-10.026a1.45 1.45 0 0 1-.726-1.256V75.59c0-.847.694-1.453 1.452-1.453.242 0 .49.062.724.197l17.366 10.025c.449.26.726.738.726 1.257v17.886l-1.727-.997a1.552 1.552 0 0 0-2.327 1.344v15.139c0 .555.295 1.067.775 1.344l3.278 1.894v16.209a1.45 1.45 0 0 1-1.452 1.451zm29.828 14.929a1.452 1.452 0 0 1-2.177 1.257l-17.365-10.026a1.452 1.452 0 0 1-.726-1.257v-17.885l1.726.996c.25.145.515.211.773.211.811 0 1.554-.648 1.554-1.555v-1.993l15.489 8.942c.449.26.726.738.726 1.257zm0-32.768c0 .127-.02.246-.049.36-.009.035-.021.067-.032.101-.026.08-.059.157-.099.229-.017.03-.032.061-.05.09a1.48 1.48 0 0 1-.188.235l-.021.025a1.51 1.51 0 0 1-.264.199c-.026.016-.055.028-.082.043a1.597 1.597 0 0 1-.324.124 1.362 1.362 0 0 1-.278.041c-.018.001-.036.006-.055.006l-.015.001c-.077 0-.155-.018-.233-.03-.043-.007-.084-.005-.125-.017a1.484 1.484 0 0 1-.366-.149l-14.035-8.104v-3.784a1.545 1.545 0 0 0-.776-1.343l-3.276-1.892V91.976c0-.127.02-.246.049-.361.009-.034.021-.066.032-.1a1.33 1.33 0 0 1 .099-.229c.017-.03.032-.062.051-.091.054-.084.116-.163.187-.234l.021-.025c.079-.076.168-.142.263-.199.027-.016.056-.029.084-.043a1.476 1.476 0 0 1 .601-.166c.019 0 .036-.005.055-.005l.015-.001c.078 0 .157.018.236.03.04.007.081.005.122.017.124.031.246.08.366.149l17.361 10.023a1.456 1.456 0 0 1 .726 1.259zm-9.984-45.373a1.448 1.448 0 0 1-.544-.55 1.466 1.466 0 0 1 0-1.413c.121-.219.303-.41.544-.55l14.038-8.104 3.277 1.892c.48.276 1.071.276 1.551 0l3.278-1.893 14.038 8.105a1.45 1.45 0 0 1 0 2.513L86.691 86.7a1.447 1.447 0 0 1-1.452 0zm74.842 51.733c0 .518-.276.997-.726 1.256l-45.741 26.409a1.452 1.452 0 0 1-2.177-1.257v-20.053c0-.519.277-.997.727-1.257l15.488-8.941v1.992c0 .906.743 1.555 1.553 1.555.26 0 .523-.066.774-.21l13.11-7.57a1.55 1.55 0 0 0 .776-1.344v-3.784l14.038-8.105a1.452 1.452 0 0 1 2.177 1.257v20.052zm0-32.764c0 .519-.276.997-.726 1.256l-15.489 8.943v-1.993c0-.906-.744-1.554-1.554-1.554a1.519 1.519 0 0 0-.773.21l-1.727.996V85.616c0-.519.277-.997.727-1.257l17.365-10.025c.234-.135.482-.197.724-.197.758 0 1.453.606 1.453 1.453z" fill="#111d5a"/><g fill="#83e8c2"><path d="M59.402 90.568zM94.485 123.06zM94.771 123.29zM77.775 122.51zM77.072 123.33zM77.418 123.09zM77.856 122.05zM76.749 123.45zM94.119 122.41zM77.131 133.51l-15.489-8.942v1.993c0 .906-.743 1.555-1.554 1.555a1.53 1.53 0 0 1-.773-.211l-1.726-.996v17.885c0 .519.276.997.726 1.257l17.365 10.026a1.452 1.452 0 0 0 2.177-1.257v-20.053a1.454 1.454 0 0 0-.726-1.257zM94.25 122.74zM110.28 111.42zM94.494 100.98c.088-.089.189-.168.303-.232l17.365-10.026-17.365 10.026a1.392 1.392 0 0 0-.303.232zM77.627 122.83zM58.027 90.936zM58.374 90.693zM59.044 90.521l-.015.001c.083-.001.167.015.251.029-.079-.012-.158-.03-.236-.03zM57.819 91.195zM58.696 90.568zM57.589 91.977zM76.043 123.46zM57.67 91.516zM75.677 123.31l-14.035-8.11zM76.401 123.5l.015-.001c-.082.001-.166-.016-.248-.029.078.012.156.03.233.03zM112.16 90.716zM77.662 101.27zM113.64 90.734zM96.237 123.31zM113.33 90.597zM112.89 90.52c-.075 0-.151.018-.228.029.081-.014.162-.029.242-.028l-.014-.001zM141.26 74.137c-.241 0-.489.062-.724.197l-17.365 10.025c-.449.26-.727.738-.727 1.257v17.885l1.727-.996c.25-.145.515-.211.773-.21.81 0 1.554.647 1.554 1.554v1.993l15.489-8.943a1.45 1.45 0 0 0 .726-1.256V75.59c0-.847-.695-1.453-1.453-1.453zM112.96 90.526zM95.523 123.5c.074 0 .15-.018.225-.029-.08.013-.159.028-.238.028l.013.001zM95.451 123.5zM85.238 86.7zM95.078 123.43zM141.26 106.9c-.241 0-.489.062-.724.196l-14.038 8.105v3.784c0 .555-.296 1.067-.776 1.344l-13.11 7.57c-.251.144-.515.21-.774.21-.81 0-1.553-.648-1.553-1.555v-1.992l-15.488 8.941c-.449.26-.727.738-.727 1.257v20.053a1.452 1.452 0 0 0 2.177 1.257l45.741-26.409a1.45 1.45 0 0 0 .726-1.256v-20.053a1.454 1.454 0 0 0-1.454-1.452zM67.871 41.396a1.451 1.451 0 0 0 0 2.513l14.038 8.104 3.278-1.893c.24-.139.508-.207.775-.207s.536.068.775.207l13.111 7.57a1.552 1.552 0 0 1 0 2.688l-1.727.996 15.49 8.943a1.457 1.457 0 0 0 1.452 0l17.366-10.027a1.45 1.45 0 0 0 0-2.512l-45.741-26.41a1.449 1.449 0 0 0-1.452 0zM39.497 57.779a1.45 1.45 0 0 0 0 2.512l17.366 10.027a1.457 1.457 0 0 0 1.452 0l15.488-8.943-1.727-.996a1.552 1.552 0 0 1 0-2.688l1.727-.997-15.489-8.942a1.458 1.458 0 0 0-1.451 0zM49.481 138.43v-16.209l-3.278-1.894a1.55 1.55 0 0 1-.775-1.344v-15.139c0-.906.743-1.555 1.554-1.554.259 0 .523.065.773.21l1.727.997V85.611a1.45 1.45 0 0 0-.726-1.257L31.39 74.33a1.436 1.436 0 0 0-.724-.197c-.758 0-1.452.606-1.452 1.453v52.817c0 .518.276.997.726 1.256l17.365 10.026a1.45 1.45 0 0 0 2.176-1.255zM114.34 108.18l-3.278 1.893 3.278-1.893V91.971zM114.11 91.193zM114.16 91.283z"/></g><g fill="#de5941"><path d="M94.494 100.98a1.45 1.45 0 0 0-.424 1.023v20.053l.001.002c0 .126.02.244.048.358.01.034.021.066.033.101.026.08.059.156.098.229.017.03.032.061.051.09.055.084.115.162.185.232.009.009.015.02.023.027.079.077.169.142.263.198.027.017.055.029.084.044a1.46 1.46 0 0 0 .596.165c.02.001.039.005.059.006.079 0 .158-.016.238-.028.045-.007.09-.006.135-.018.119-.031.238-.08.354-.146l.01-.004 14.038-8.104v-3.785c0-.18.04-.35.098-.514.122-.343.353-.643.678-.83l3.278-1.893V91.977c0-.127-.021-.246-.049-.361-.009-.033-.021-.065-.032-.099a1.266 1.266 0 0 0-.099-.229c-.017-.031-.032-.061-.05-.09a1.425 1.425 0 0 0-.188-.235l-.021-.024a1.41 1.41 0 0 0-.263-.199c-.027-.016-.056-.029-.084-.043a1.509 1.509 0 0 0-.323-.125 1.591 1.591 0 0 0-.273-.04c-.021-.001-.039-.005-.059-.006-.08-.001-.161.015-.242.028-.043.008-.087.006-.131.018-.123.031-.246.08-.365.149l-17.365 10.026a1.447 1.447 0 0 0-.302.233zM77.13 100.74L59.769 90.717a1.424 1.424 0 0 0-.366-.149c-.041-.012-.082-.01-.122-.017-.084-.015-.168-.03-.251-.029-.019 0-.036.005-.055.005-.095.005-.188.02-.278.041-.034.009-.065.02-.099.03a1.406 1.406 0 0 0-.224.095c-.028.014-.057.027-.084.043a1.515 1.515 0 0 0-.263.199l-.021.025c-.07.071-.133.15-.187.234-.019.029-.034.061-.051.091-.04.073-.072.149-.099.229a1.463 1.463 0 0 0-.081.461v16.206l3.276 1.892a1.547 1.547 0 0 1 .776 1.343v3.784l14.035 8.104c.119.068.242.117.366.149.041.012.082.01.125.017.082.014.166.03.248.029.019 0 .037-.005.055-.006.095-.004.188-.019.278-.041.034-.008.065-.019.099-.029.077-.025.152-.058.225-.095.027-.015.056-.027.082-.043.095-.058.185-.123.264-.199l.021-.025c.07-.071.133-.15.188-.235.018-.029.033-.06.05-.09.04-.072.072-.149.099-.229a1.448 1.448 0 0 0 .081-.461v-20.047a1.456 1.456 0 0 0-.726-1.259zM86.689 86.7l17.365-10.026a1.45 1.45 0 0 0 0-2.513l-14.038-8.105-3.278 1.893a1.556 1.556 0 0 1-1.551 0l-3.277-1.892-14.038 8.104c-.241.14-.423.331-.544.55a1.466 1.466 0 0 0 0 1.413c.121.218.303.41.544.55L85.238 86.7a1.447 1.447 0 0 0 1.451 0z"/></g></g></symbol><symbol viewBox="0 0 24 24" id="riot" xmlns="http://www.w3.org/2000/svg"><defs><path d="M13.26 3.04l.58.05.54.07.52.09.49.11.46.13.44.14.41.16.39.17.36.19.33.21.32.22.29.23.26.25.22.22.2.22.19.24.17.24.15.25.15.26.12.27.12.28.1.29.08.31.07.31.05.32.04.34.02.35.01.37v.05l-.02.51-.05.49-.09.48-.13.45-.15.43-.19.4-.22.39-.26.37-.28.34-.31.33-.33.3-.37.28-.39.27-.41.24-.44.22L21 21h-7.04l-3.48-5.14H9.17V21H3V3h9.01l.64.01.61.03zm-4.09 8.52h2.66l.99-.11.75-.35.47-.55.16-.74v-.05l-.17-.75-.47-.54-.74-.32-.96-.11H9.17v3.52z" id="ija"/></defs><use xlink:href="#ija" fill="#ff1744"/><use xlink:href="#ija" fill-opacity="0" stroke="#000" stroke-opacity="0"/></symbol><symbol viewBox="0 0 24 24" id="robot" xmlns="http://www.w3.org/2000/svg"><path d="M12.05 2.804a1.787 1.787 0 0 1 1.788 1.788c0 .661-.357 1.242-.893 1.546v1.135h.893a6.256 6.256 0 0 1 6.256 6.256h.894a.894.894 0 0 1 .893.893v2.681a.894.894 0 0 1-.893.894h-.894v.894a1.787 1.787 0 0 1-1.787 1.787H5.795a1.787 1.787 0 0 1-1.787-1.787v-.894h-.894a.894.894 0 0 1-.894-.894v-2.68a.894.894 0 0 1 .894-.894h.894a6.256 6.256 0 0 1 6.255-6.256h.894V6.138a1.773 1.773 0 0 1-.894-1.546 1.787 1.787 0 0 1 1.788-1.788m-4.022 9.83a2.234 2.234 0 0 0-2.234 2.235 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.234-2.234 2.234 2.234 0 0 0-2.234-2.234m8.043 0a2.234 2.234 0 0 0-2.234 2.234 2.234 2.234 0 0 0 2.234 2.234 2.234 2.234 0 0 0 2.235-2.234 2.234 2.234 0 0 0-2.235-2.234z" fill="#ff5722" stroke-width=".894"/></symbol><symbol viewBox="100 100 800 800" id="rollup" xmlns="http://www.w3.org/2000/svg"><style>.ilst0{fill:url(#ilXMLID_4_)}.ilst1{fill:url(#ilXMLID_5_)}.ilst2{fill:url(#ilXMLID_8_)}.ilst3{fill:url(#ilXMLID_9_)}.ilst4{fill:url(#ilXMLID_11_)}.ilst5{opacity:.3;fill:url(#ilXMLID_16_)}</style><g id="ilXMLID_14_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_4_" x1="444.47" x2="598.47" y1="526.05" y2="562.05" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_15_" class="ilst0" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_4_)"/></g><g id="ilXMLID_2_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_5_" x1="420.38" x2="696.38" y1="475" y2="689" gradientUnits="userSpaceOnUse"><stop stop-color="#BF3338" offset="0"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_10_" class="ilst1" d="M721 410c0-33.6-8.8-65.1-24.3-92.4-41.1-42.3-130.5-52.1-152.7-.2-22.8 53.2 38.3 112.4 65 107.7 34-6-6-84-6-84 52 98 40 68-54 158S359 779 345 787c-.6.4-1.2.7-1.9 1h368.7c6.5 0 10.7-6.9 7.8-12.7l-96.4-190.8c-2.1-4.1-.6-9.2 3.4-11.5C683 540.6 721 479.8 721 410z" fill="url(#ilXMLID_5_)"/></g><linearGradient id="ilXMLID_8_" x1="429.39" x2="469.39" y1="517.16" y2="559.16" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_3_" class="ilst2" d="M329.82 813.46c15.58-8.903 122.41-220.34 227.02-320.5s117.96-66.771 60.094-175.83c0 0-221.46 310.49-301.58 464.06" fill="url(#ilXMLID_8_)" stroke-width="1.113"/><g id="ilXMLID_7_" transform="translate(-54.117 -62.353) scale(1.1129)"><linearGradient id="ilXMLID_9_" x1="502.11" x2="490.11" y1="589.46" y2="417.46" gradientUnits="userSpaceOnUse"><stop stop-color="#FF6533" offset="0"/><stop stop-color="#FF5633" offset=".157"/><stop stop-color="#FF4333" offset=".434"/><stop stop-color="#FF3733" offset=".714"/><stop stop-color="#F33" offset="1"/></linearGradient><path id="ilXMLID_12_" class="ilst3" d="M373 537c134.4-247.1 152-272 222-272 36.8 0 73.9 16.6 97.9 46.1-32.7-52.7-90.6-88-156.9-89H307.7c-4.8 0-8.7 3.9-8.7 8.7V691c13.6-35.1 36.7-85.3 74-154z" fill="url(#ilXMLID_9_)"/></g><linearGradient id="ilXMLID_11_" x1="450.12" x2="506.94" y1="514.21" y2="552.85" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FBB040" offset="0"/><stop stop-color="#FB8840" offset="1"/></linearGradient><path id="ilXMLID_6_" class="ilst4" d="M556.84 492.96c-104.61 100.16-211.44 311.6-227.02 320.5s-41.732 10.016-55.643-5.564c-14.801-16.582-37.837-43.401 86.802-272.65 149.57-274.99 169.15-302.7 247.05-302.7 40.953 0 82.24 18.473 108.95 51.302 1.447 2.337 2.893 4.785 4.34 7.233-45.738-47.074-145.23-57.98-169.93-.222-25.373 59.204 42.622 125.08 72.335 119.85 37.837-6.677-6.677-93.48-6.677-93.48 57.757 108.95 44.403 75.563-60.205 175.72z" fill="url(#ilXMLID_11_)" stroke-width="1.113"/><linearGradient id="ilXMLID_16_" x1="508.33" x2="450.33" y1="295.76" y2="933.76" gradientTransform="translate(-54.117 -62.353) scale(1.1129)" gradientUnits="userSpaceOnUse"><stop stop-color="#FFF" offset="0"/><stop stop-color="#FFF" stop-opacity="0" offset="1"/></linearGradient><path id="ilXMLID_13_" class="ilst5" d="M373.22 547.49c149.57-274.99 169.15-302.7 247.05-302.7 33.719 0 67.661 12.575 93.48 35.277-26.708-30.492-66.326-47.519-105.72-47.519-77.9 0-97.486 27.71-247.05 302.7-124.64 229.25-101.6 256.07-86.802 272.65 2.114 2.337 4.563 4.34 7.122 6.01-13.02-18.919-18.807-62.877 91.922-266.42z" fill="url(#ilXMLID_16_)" opacity=".3" stroke-width="1.113"/></symbol><symbol viewBox="0 0 24 24" id="ruby" xmlns="http://www.w3.org/2000/svg"><path d="M16 9h3l-5 7m-4-7h4l-2 8M5 9h3l2 7m5-12h2l2 3h-3m-5-3h2l1 3h-4M7 4h2L8 7H5m1-5L2 8l10 14L22 8l-4-6H6z" fill="#f44336"/></symbol><symbol viewBox="0 0 144 144" id="rust" xmlns="http://www.w3.org/2000/svg"><path d="M68.252 26.206a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0M25.766 58.451a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m84.97.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m-74.661 4.88a3.252 3.252 0 0 0 1.651-4.29l-1.58-3.574h6.214v28.01H29.823a43.847 43.847 0 0 1-1.42-16.738zm25.994.688v-8.256h14.798c.764 0 5.397.883 5.397 4.347 0 2.877-3.553 3.908-6.475 3.908zm-20.203 44.452a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m52.769.166a3.561 3.561 0 0 1 7.123 0 3.561 3.561 0 0 1-7.123 0m1.101-8.076a3.246 3.246 0 0 0-3.856 2.498l-1.787 8.342a43.847 43.847 0 0 1-36.566-.175l-1.787-8.342a3.246 3.246 0 0 0-3.854-2.497l-7.365 1.581a43.847 43.847 0 0 1-3.808-4.488h35.834c.406 0 .676-.074.676-.443V84.527c0-.369-.27-.442-.676-.442h-10.48V76.05h11.335c1.035 0 5.532.296 6.97 6.045.45 1.768 1.44 7.519 2.116 9.36.674 2.065 3.417 6.19 6.34 6.19h18.501a43.847 43.847 0 0 1-4.06 4.7zm19.898-33.468a43.847 43.847 0 0 1 .093 7.612h-4.499c-.45 0-.631.296-.631.737v2.066c0 4.863-2.742 5.92-5.145 6.19-2.288.258-4.825-.958-5.138-2.358-1.35-7.593-3.6-9.214-7.152-12.016 4.409-2.8 8.996-6.93 8.996-12.457 0-5.97-4.092-9.729-6.881-11.572-3.914-2.58-8.246-3.096-9.415-3.096H39.336A43.847 43.847 0 0 1 63.867 28.52l5.484 5.753a3.243 3.243 0 0 0 4.59.105l6.137-5.869a43.847 43.847 0 0 1 30.017 21.38l-4.201 9.487a3.256 3.256 0 0 0 1.652 4.29zm10.477.154l-.143-1.467 4.327-4.036c.88-.82.55-2.472-.574-2.891l-5.532-2.068-.433-1.428 3.45-4.792c.704-.974.058-2.53-1.127-2.724l-5.833-.949-.7-1.31 2.45-5.38c.502-1.095-.43-2.496-1.636-2.45l-5.92.206-.935-1.135 1.36-5.766c.275-1.17-.913-2.36-2.084-2.085l-5.765 1.359-1.136-.935.207-5.92c.046-1.198-1.357-2.135-2.45-1.637l-5.379 2.452-1.31-.703-.95-5.833c-.193-1.183-1.75-1.83-2.723-1.128l-4.796 3.45-1.425-.432-2.068-5.532c-.42-1.127-2.072-1.452-2.89-.576l-4.036 4.33-1.467-.143-3.117-5.036c-.63-1.02-2.318-1.02-2.946 0l-3.117 5.036-1.467.143-4.037-4.33c-.819-.876-2.47-.551-2.89.576l-2.069 5.532-1.426.432-4.795-3.45c-.974-.703-2.53-.055-2.723 1.128l-.951 5.833-1.31.703-5.379-2.452c-1.093-.5-2.496.439-2.45 1.637l.206 5.92-1.136.935-5.765-1.36c-1.171-.272-2.36.915-2.086 2.086l1.358 5.766-.933 1.135-5.92-.206c-1.193-.035-2.134 1.355-1.637 2.45l2.453 5.38-.703 1.31-5.832.949c-1.185.192-1.827 1.75-1.128 2.724l3.45 4.792-.433 1.428-5.532 2.068c-1.123.42-1.452 2.07-.574 2.891l4.328 4.036-.143 1.467-5.035 3.116c-1.02.63-1.02 2.318 0 2.946l5.035 3.117.143 1.467-4.328 4.037c-.878.818-.549 2.468.574 2.89l5.532 2.068.433 1.428-3.45 4.793c-.701.976-.056 2.532 1.129 2.723l5.831.948.703 1.312-2.453 5.378c-.5 1.093.444 2.5 1.638 2.451l5.917-.207.935 1.136-1.358 5.768c-.275 1.168.915 2.355 2.086 2.08l5.765-1.357 1.137.932-.207 5.921c-.046 1.199 1.357 2.136 2.45 1.636l5.379-2.45 1.31.702.95 5.83c.193 1.187 1.75 1.829 2.725 1.13l4.792-3.453 1.427.435 2.069 5.53c.42 1.123 2.072 1.454 2.89.574l4.037-4.328 1.467.146 3.117 5.035c.628 1.016 2.316 1.018 2.946 0l3.117-5.035 1.467-.146 4.036 4.328c.818.88 2.47.549 2.89-.574l2.068-5.53 1.428-.435 4.793 3.453c.974.699 2.53.055 2.722-1.13l.952-5.83 1.31-.703 5.378 2.451c1.093.5 2.493-.435 2.45-1.636l-.206-5.92 1.135-.933 5.765 1.357c1.171.275 2.36-.912 2.085-2.08l-1.358-5.768.932-1.136 5.92.207c1.194.048 2.138-1.358 1.636-2.451l-2.45-5.378.7-1.312 5.833-.948c1.187-.19 1.831-1.747 1.127-2.723l-3.45-4.793.433-1.428 5.532-2.068c1.125-.422 1.454-2.072.574-2.89l-4.327-4.037.143-1.467 5.035-3.117c1.02-.628 1.021-2.315.001-2.946z" fill="#ff7043" stroke-width="1.146"/></symbol><symbol viewBox="0 0 500 500" id="sass" xmlns="http://www.w3.org/2000/svg"><path d="M422.676 96.573c-12.192-47.839-91.508-63.557-166.575-36.892-44.68 15.877-93.029 40.786-127.81 73.311-41.349 38.675-47.943 72.328-45.216 86.395 9.583 49.622 77.585 82.069 105.535 106.126v.144c-8.246 4.05-68.565 34.584-82.684 65.799-14.893 32.932 2.372 56.556 13.804 59.742 35.424 9.859 71.764-7.866 91.311-37.01 18.853-28.12 17.28-64.422 9.086-82.487 11.3-2.976 24.476-4.314 41.218-2.36 47.248 5.52 56.517 35.017 54.747 47.366-1.77 12.35-11.681 19.14-14.998 21.186-3.317 2.045-4.326 2.766-4.05 4.287.405 2.215 1.94 2.137 4.758 1.652 3.894-.656 24.804-10.042 25.709-32.828 1.14-28.933-26.587-61.302-75.684-60.45-20.216.354-32.933 2.268-42.123 5.69-.681-.774-1.363-1.547-2.084-2.307-30.35-32.382-86.46-55.285-84.088-98.824.866-15.823 6.372-57.5 107.817-108.052 83.104-41.415 149.637-30.009 161.135-4.76 16.427 36.08-35.554 103.137-121.858 112.812-32.88 3.684-50.198-9.059-54.498-13.804-4.536-4.995-5.204-5.218-6.909-4.287-2.753 1.533-1.01 5.938 0 8.574 2.583 6.712 13.15 18.603 31.176 24.515 15.863 5.205 54.459 8.063 101.156-9.99 52.283-20.255 93.12-76.523 81.125-123.548zM200.213 340.34c3.92 14.5 3.487 28.016-.564 40.248a65.289 65.289 0 0 1-3.225 7.97c-3.12 6.477-7.316 12.534-12.442 18.132-15.653 17.069-37.507 23.532-46.88 18.092-10.122-5.874-5.048-29.944 13.083-49.11 19.52-20.636 47.602-33.903 47.602-33.903l-.039-.079 2.465-1.35z" fill="#ec407a" stroke="#ec407a" stroke-width="16.286552999999998"/></symbol><symbol viewBox="0 0 300 300" id="sbt" xmlns="http://www.w3.org/2000/svg"><path d="M105.46 209.517c-7.875 0-13.452-7.521-13.452-15.37v-.327c0-7.848 5.578-13.735 13.452-13.735h164.05c1.476-4.905 2.625-11.446 3.281-17.986h-137.81c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h137.31c-.82-6.54-1.969-13.081-3.773-17.986h-104.01c-7.875 0-14.273-6.05-14.273-13.898s6.398-13.898 14.273-13.898h91.87c-21.327-37.607-60.864-61.315-106.14-61.315-67.918 0-123.04 54.448-123.04 122.3 0 67.856 55.122 123.28 123.04 123.28 46.59 0 87.112-25.507 107.95-63.114h-152.73z" fill="#0277bd" stroke-width="1.638"/></symbol><symbol viewBox="0 0 256 256" id="scala" xmlns="http://www.w3.org/2000/svg"><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M59.607 50.647l149.097-21.982v49.488L59.607 100.135zM59.593 114.08L208.69 92.098v49.488L59.593 163.568zM59.587 177.358l149.097-21.982v49.488L59.587 226.846z"/><path fill="#f44336" fill-rule="evenodd" stroke-width=".3" d="M62.425 91.414l95.605 30.923-2.832 8.757-95.605-30.922zM113.084 61.13l95.604 30.922-2.832 8.757-95.605-30.922zM62.425 154.79l95.605 30.922-2.833 8.758-95.604-30.923zM113.097 124.408l95.604 30.923-2.832 8.757-95.605-30.922z"/></symbol><symbol viewBox="0 0 24 24" id="settings" xmlns="http://www.w3.org/2000/svg"><path d="M12 15.5A3.5 3.5 0 0 1 8.5 12 3.5 3.5 0 0 1 12 8.5a3.5 3.5 0 0 1 3.5 3.5 3.5 3.5 0 0 1-3.5 3.5m7.43-2.53c.04-.32.07-.64.07-.97 0-.33-.03-.66-.07-1l2.11-1.63c.19-.15.24-.42.12-.64l-2-3.46c-.12-.22-.39-.31-.61-.22l-2.49 1c-.52-.39-1.06-.73-1.69-.98l-.37-2.65A.506.506 0 0 0 14 2h-4c-.25 0-.46.18-.5.42l-.37 2.65c-.63.25-1.17.59-1.69.98l-2.49-1c-.22-.09-.49 0-.61.22l-2 3.46c-.13.22-.07.49.12.64L4.57 11c-.04.34-.07.67-.07 1 0 .33.03.65.07.97l-2.11 1.66c-.19.15-.25.42-.12.64l2 3.46c.12.22.39.3.61.22l2.49-1.01c.52.4 1.06.74 1.69.99l.37 2.65c.04.24.25.42.5.42h4c.25 0 .46-.18.5-.42l.37-2.65c.63-.26 1.17-.59 1.69-.99l2.49 1.01c.22.08.49 0 .61-.22l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.66z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="shaderlab" xmlns="http://www.w3.org/2000/svg"><path d="M9.11 17H6.5l-4.91-5L6.5 7h2.61l1.31-2.26L17.21 3l1.87 6.74L17.77 12l1.31 2.26L17.21 21l-6.79-1.74L9.11 17m.14-.25l5.13 1.38L11.42 13H5.5l3.75 3.75m6.87.38L17.5 12l-1.38-5.13L13.15 12l2.97 5.13M9.25 7.25L5.5 11h5.92l2.96-5.13-5.13 1.38z" fill="#1976d2"/></symbol><symbol viewBox="0 0 24 24" id="slim" xmlns="http://www.w3.org/2000/svg"><path d="M6.959 2.5a4.605 4.605 0 0 0-4.615 4.615v9.957a4.605 4.605 0 0 0 4.615 4.615h9.957a4.605 4.605 0 0 0 4.615-4.615V7.115A4.605 4.605 0 0 0 16.916 2.5zm4.938 2.691a6.811 6.811 0 0 1 6.81 6.813H13.43L9.938 7.287l.699 4.717H5.086a6.811 6.811 0 0 1 6.81-6.813z" fill="#f57f17"/></symbol><symbol id="smarty" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.iust0{fill:#ffce00}</style><path class="iust0" d="M9.14 20.606c0 .556.398.953.954.953h3.812c.556 0 .953-.397.953-.953v-.953H9.141zM12 2.5c-3.653 0-6.671 3.018-6.671 6.671 0 2.303 1.112 4.289 2.859 5.48v2.144c0 .556.397.953.953.953h5.718c.556 0 .953-.397.953-.953V14.65c1.747-1.191 2.86-3.177 2.86-5.48 0-3.653-3.019-6.671-6.672-6.671zm2.7 10.563l-.794.555v2.224h-3.812v-2.224l-.794-.555A4.712 4.712 0 0 1 7.235 9.17 4.78 4.78 0 0 1 12 4.405a4.78 4.78 0 0 1 4.765 4.765 4.712 4.712 0 0 1-2.065 3.892z"/></symbol><symbol viewBox="0 0 200 200" id="snyk" xmlns="http://www.w3.org/2000/svg"><title>Group 2</title><g transform="translate(15.255 18.22) scale(1.8477)" fill="none" fill-rule="evenodd"><path d="M65.161 24.997c-1.656 5.974-5.255 23.587-5.255 23.587s-6.618-2.464-14.148-2.476h-.055c-.413.002-.822.012-1.23.026v41.649h6.677v.003h5.815v-.003h20.858c.111-8.177-2.036-27.066-2.036-27.066-1.088-2.279.46-7.668.46-7.668-8.869-9.092-11.086-28.051-11.086-28.051zm-3.357 43.958c5.476 0 1.381 4.64.9 5.168H52.35c.944-1.18 4.504-5.168 9.453-5.168z" fill="#607d8b" stroke-width="1.6"/><path d="M26.366 24.995s-2.217 18.961-11.087 28.053c0 0 1.548 5.391.46 7.669 0 0-2.15 18.895-2.038 27.066h19.273v.003h7.079v-.003h5.744V46.107h-.025c-7.532.013-14.151 2.478-14.151 2.478s-3.6-17.615-5.255-23.59zm3.264 43.96c4.95 0 8.51 3.987 9.452 5.168H28.73c-.479-.528-4.573-5.168.9-5.168z" fill="#90a4ae" stroke-width="1.6"/><g transform="translate(23.76 77.45) scale(1.5998)"><g transform="translate(17.526)"><path d="M7.357.06H.177v.075C.177 2.64 2.345 4.67 4.89 4.67 7.431 4.67 9.6 2.64 9.6.135V.059z" fill="#455a64"/><path d="M1.972.06v.075a2.692 2.692 0 1 0 5.386 0V.059z" fill="#fff"/><path d="M5.496.06H4.234c-.012 0-.023.005-.034.007.157.033.243.388.21.624a.721.721 0 0 1-.71.617c.102.471.487.85.997.922a1.188 1.188 0 0 0 1.35-1.007C6.112.743 5.881.06 5.495.06z" fill="#37474f"/></g><path d="M7.552.06H.372v.075c0 2.505 2.17 4.535 4.712 4.535 2.544 0 4.712-2.03 4.712-4.535V.059z" fill="#455a64"/><path d="M2.168.06v.075a2.692 2.692 0 1 0 5.385 0V.059z" fill="#fff"/><path d="M5.692.06H4.428c-.01 0-.022.005-.032.007.156.033.242.388.21.624a.72.72 0 0 1-.712.617c.104.471.488.85.999.922A1.187 1.187 0 0 0 6.24 1.223C6.308.743 6.078.06 5.69.06z" fill="#37474f"/></g><path d="M25.514-.27l-4.202 7.697C19.838 10.17 6.858 34.465 6.858 43.243v.516L12.8 59.573c-.8 7.258-2.203 21.643-1.78 28.21h5.73c-.354-3.787.648-17.008 1.903-28.25l.076-.677-1.075-2.892c3.694-3.868 6.285-9.193 8.073-14.261l.174 1.235 5.869 9.629 2.291-.983c.058-.024 5.935-2.523 11.643-2.523 5.672 0 11.646 2.5 11.702 2.525l2.29.976 5.86-9.626.23-1.608c1.769 5.117 4.358 10.536 8.07 14.49l-1.127 3.035.076.678c1.259 11.286 2.266 24.564 1.916 28.252h5.677c.406-6.567-1.05-20.952-1.848-28.208l5.838-15.817v-.514c0-8.779-12.876-33.074-14.347-35.816L65.923-.27l-5.897 41.229-2.723 4.478c-2.628-.882-7.1-2.11-11.603-2.11-4.498 0-8.94 1.225-11.557 2.108l-2.722-4.476-2.07-14.452a.832.832 0 0 0 .006-.071l-.016-.004zm-3.166 18.39l1.206 8.407c-.46 3.143-2.561 15.47-8.198 23.24l-2.598-6.99c.325-4.554 5.067-15.462 9.59-24.656zm46.763 0c4.523 9.194 9.267 20.104 9.592 24.657L76.166 49.6c-6.09-8.553-8-22.459-8.166-23.73z" fill="#607d8b" stroke-width="1.6"/></g></symbol><symbol viewBox="0 0 24 24" id="solidity" xmlns="http://www.w3.org/2000/svg"><path d="M5.8 14.05l6.253 8.61 6.252-8.61-6.254 3.807z" fill="#0288d1" stroke-width="4.553" stroke-linejoin="round"/><path d="M12.051 1.347L5.8 11.833l6.252 3.807 6.254-3.807z" fill="#0288d1" stroke-width="5.025" stroke-linejoin="round"/></symbol><symbol viewBox="0 0 120 120" id="sonar" xmlns="http://www.w3.org/2000/svg"><style>.a,.b{fill:#fff}.b{stroke:#fff;stroke-miterlimit:10}</style><path d="M115.45 23.033S97.961 33.27 97.534 33.412c-.427.284-.852.57-1.137.854-1.422 1.421-1.848 3.41-1.422 5.26.285.852.711 1.849 1.422 2.56.711.71 1.564 1.137 2.559 1.422 1.848.426 3.84 0 5.262-1.422.426-.427.709-.853.851-1.28l.143-.427 2.56-4.692zm-39.102 9.242c-27.441 0-31.99 13.08-31.99 29.29 0 3.838.569 7.962-1.99 11.942-3.84 5.972-8.957 5.828-10.236 5.828-1.706 0-7.962-.993-8.246-2.841h.994c6.682 0 11.658-5.404 11.658-12.655v-2.56h-5.686c-4.123 0-7.82 1.849-10.238 5.12-2.417-3.271-6.113-5.12-10.236-5.12h-5.83v2.56c0 7.11 5.688 12.795 12.797 12.795h1.848c0 4.124 5.687 20.332 47.63 20.332 16.352 0 40.665-2.843 40.665-33.697 0-5.829-1.848-11.23-4.691-15.78-.996.284-1.992.568-3.13.568a8.92 8.92 0 0 1-8.956-8.957c0-.995.141-1.991.425-2.986-4.265-2.702-8.53-3.838-14.787-3.838z" fill="#1e88e5" stroke-width="1.422"/></symbol><symbol viewBox="0 0 412 395" id="stylelint" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-white</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#cfd8dc" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 412 395" id="stylelint_light" xmlns="http://www.w3.org/2000/svg"><title>stylelint-icon-black</title><g transform="translate(31.478 29.499) scale(.84775)" fill="#546e7a" fill-rule="evenodd"><path d="M208.8 393.05c45.057-161.12 43.75-161.85 76.32-276.73l7.832 4.523c4.255 2.458 7.738.448 7.738-4.455V61.602c8.643-30.27 15.416-53.66 17.4-60.693h35.287l58.618 54.304-38.498 33.27 29.11 31.473-191.86 273.09c-.938 1.542-2.244 1.19-1.947 0zm20.96-347.28c1.733 0 3.148.958 3.148 2.147v28.077c0 1.186-1.415 2.15-3.147 2.15h-47.396c-1.742 0-3.153-.96-3.153-2.15V47.917c0-1.185 1.41-2.147 3.153-2.147h47.396z"/><path d="M288.26 14.688l-52.14 30.1c.605.92.973 1.98.973 3.136v28.078c0 1.457-.565 2.77-1.496 3.83l52.663 30.402c3.59 2.073 6.535.377 6.535-3.764V18.456c0-4.145-2.944-5.836-6.535-3.768zM175.02 76V47.923c0-1.15.368-2.21.966-3.13l-52.14-30.105c-3.588-2.068-6.53-.376-6.53 3.768v88.013c0 4.14 2.938 5.84 6.53 3.76l52.66-30.405c-.926-1.06-1.487-2.37-1.487-3.827z"/><path d="M201.25 393.05h1.947c-45.05-161.12-43.753-161.85-76.32-276.73l-7.833 4.523c-4.253 2.458-7.737.448-7.737-4.455V61.602C102.662 31.332 95.892 7.942 93.902.909H58.619L.002 55.213l38.494 33.27-29.11 31.473z"/><circle cx="204.57" cy="122.54" r="14.231"/><circle cx="204.57" cy="207.16" r="14.231"/><circle cx="204.57" cy="291.78" r="14.23"/></g></symbol><symbol viewBox="0 0 200.00001 200.00001" id="stylus" xmlns="http://www.w3.org/2000/svg"><path d="M126.814 155.9c14.64-17.51 16.362-35.595 5.024-69.18-7.177-21.24-19.09-37.602-10.334-50.807 9.329-14.065 29.135-.43 12.63 18.371l3.301 2.297c19.806 2.296 29.566-24.83 14.783-32.58C113.179 3.621 79.02 42.803 94.09 88.156c6.458 19.232 15.5 39.613 8.18 55.83-6.314 13.923-18.514 22.103-26.695 22.39-17.079.862-5.74-38.32 13.922-48.08 1.722-.861 4.162-2.01 1.866-4.88-24.256-2.727-38.464 8.468-46.645 24.112-23.825 45.497 45.21 62.29 82.095 18.371z" fill="#c0ca33" stroke-width="1.435"/></symbol><symbol viewBox="0 0 24 24" id="swc" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="jba"><stop offset="0" stop-color="#791223"/><stop offset="1" stop-color="#d92f3c"/></linearGradient><linearGradient xlink:href="#jba" id="jbb" x1="12.356" y1="21.559" x2="12.356" y2="2.949" gradientUnits="userSpaceOnUse"/></defs><path d="M6 3c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6 3 6.5V19a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6.5c0-.5-.17-.93-.46-1.27l-1.39-1.68C18.88 3.21 18.47 3 18 3H6zm-.07 1h12l.94 1H5.12l.81-1z" fill="url(#jbb)"/><path style="line-height:125%" d="M11.053 11.918h-.008c-.244.022-.475.054-.676.11a2.9 2.9 0 0 0-.856.412 3.399 3.399 0 0 0-.67.683 9.36 9.36 0 0 0-.586.95c-.07.131-.134.244-.201.365v.001h-.002l-.768 1.372-.003-.001c-.136.253-.264.485-.38.686-.123.212-.26.39-.411.539a1.599 1.599 0 0 1-.52.34c-.04.016-.092.024-.138.036h-.567v1.383H5.834v-.001c.245-.02.477-.053.679-.11a2.9 2.9 0 0 0 .856-.411c.245-.185.469-.413.67-.683.195-.275.39-.591.585-.95.07-.131.135-.244.202-.366l.004.001.002-.002.02-.038H10.948v-1.378h-.19v-.001H9.624c.125-.234.246-.452.355-.64.123-.21.259-.39.41-.538.152-.148.325-.26.52-.34.04-.015.091-.024.136-.035h.57V13.3h-.002v-1.381h-.56v-.001z" font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#fff"/></symbol><symbol viewBox="0 0 24 24" id="swift" xmlns="http://www.w3.org/2000/svg"><path d="M17.09 19.72c-2.36 1.36-5.59 1.5-8.86.1A13.807 13.807 0 0 1 2 14.5c.67.55 1.46 1 2.3 1.4 3.37 1.57 6.73 1.46 9.1 0-3.37-2.59-6.24-5.96-8.37-8.71-.45-.45-.78-1.01-1.12-1.51 8.28 6.05 7.92 7.59 2.41-1.01 4.89 4.94 9.43 7.74 9.43 7.74.16.09.25.16.36.22.1-.25.19-.51.26-.78.79-2.85-.11-6.12-2.08-8.81 4.55 2.75 7.25 7.91 6.12 12.24-.03.11-.06.22-.05.39 2.24 2.83 1.64 5.78 1.35 5.22-1.21-2.39-3.48-1.65-4.62-1.17z" fill="#fe5e2f"/></symbol><symbol viewBox="0 0 24 24" id="table" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5m4 7.5h-4v2h1l-2 1.67L10 13h1v-2H7v2h1l3 2.5L8 18H7v2h4v-2h-1l2-1.67L14 18h-1v2h4v-2h-1l-3-2.5 3-2.5h1v-2z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 200 200" id="terraform" xmlns="http://www.w3.org/2000/svg"><g transform="translate(177.03 -58.705) scale(.92881)" fill="#5c6bc0" stroke="#b0aff5" stroke-linejoin="round"><g stroke-width=".288"><path transform="skewY(26.439) scale(.89541 1)" d="M-203.8 170.95h64.714v51.88H-203.8zM-124.37 171.04h64.714v51.88h-64.714zM-124.37 236.09h64.714v51.88h-64.714z"/></g><path transform="skewY(-22.59) scale(-.92328 1)" stroke-width=".284" d="M-19.172 128.27h62.76v51.88h-62.76z"/></g></symbol><symbol viewBox="0 0 24 24" id="test-js" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#ffca28"/></symbol><symbol viewBox="0 0 24 24" id="test-jsx" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#00bcd4"/></symbol><symbol viewBox="0 0 24 24" id="test-ts" xmlns="http://www.w3.org/2000/svg"><path d="M5 19a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1c0-.21-.07-.41-.18-.57L13 8.35V4h-2v4.35L5.18 18.43c-.11.16-.18.36-.18.57m1 3a3 3 0 0 1-3-3c0-.6.18-1.16.5-1.63L9 7.81V6a1 1 0 0 1-1-1V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v1a1 1 0 0 1-1 1v1.81l5.5 9.56c.32.47.5 1.03.5 1.63a3 3 0 0 1-3 3H6m7-6l1.34-1.34L16.27 18H7.73l2.66-4.61L13 16m-.5-4a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tex" xmlns="http://www.w3.org/2000/svg"><g font-weight="400" font-size="40" font-family="sans-serif" letter-spacing="0" word-spacing="0" fill="#42a5f5" stroke-linejoin="miter"><text style="line-height:125%" x="9.914" y="364.919"><tspan x="9.914" y="364.919" font-size="287.5">T</tspan></text><text style="line-height:125%" x="136.374" y="435.558"><tspan x="136.374" y="435.558" font-size="287.5">E</tspan></text><text style="line-height:125%" x="307.819" y="361.201"><tspan x="307.819" y="361.201" font-size="287.5">X</tspan></text></g></symbol><symbol viewBox="0 0 24 24" id="todo" xmlns="http://www.w3.org/2000/svg"><path d="M3 5h6v6H3V5m2 2v2h2V7H5m6 0h10v2H11V7m0 8h10v2H11v-2m-6 5l-3.5-3.5 1.41-1.41L5 17.17l4.59-4.58L11 14l-6 6z" fill="#42a5f5"/></symbol><symbol id="travis" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg"><style id="jkstyle2">.jkst0{fill:#cb3349}.jkst1{fill:#f4edae}.jkst2{fill:#e6ccad}.jkst3{fill:#656c67}.jkst4{fill:#e5caa3}.jkst5{fill:#c7b39a}.jkst6{fill:#ebd599}.jkst7{fill:#2d3136}.jkst8{fill:#edf6fa}.jkst9{opacity:.8}.jkst10{opacity:.75;fill:#ebd599}</style><g id="jkg99" transform="translate(11.017 12.484) scale(.8858)"><g id="jkg10"><path class="jkst0" d="M47.781 86.572s-31.118 21.903-32.335 30.247l2.335-.48S55.045 91.64 84.584 88.628l.669-3.749z" id="jkpath4" fill="#cb3349"/><path class="jkst0" d="M96.629 83.442l-24.511 17.385 1.325 1.063c.999-.806 43.539-13.798 43.539-13.798l8.969-5.623c-6.018.749-29.322.973-29.322.973z" id="jkpath6" fill="#cb3349"/><path class="jkst0" d="M117.932 104.469c17.405 0 43.495-17.046 43.495-17.046l-8.434-1.605c-.417.417-13.6-.462-13.6-.462l-6.258-1.738-14.951 17.036-1.217 2.956c1.075-.437.965.859.965.859z" id="jkpath8" fill="#cb3349"/></g><path class="jkst0" d="M174.728 158.832l-5.377 1.514-24.843-.537-15.541-12.085-18.784 4.7-21.726-1.88-12.166 13.294-22.828 6.819-11.398-3.534-.574-.494 5.116 12.527s11.588 12.424 18.061 13.885c6.472 1.461 18.165-.105 26.935-1.463 8.769-1.357 15.764-4.489 18.582-9.603 2.818-5.117 3.236-6.578 3.236-6.578s8.353 11.797 15.556 13.155c7.203 1.357 28.605-5.952 28.605-5.952s13.051-3.549 15.346-8.038c2.297-4.489 8.353-19.209 8.353-19.209zM44.456 169.038l-.361-.166-2.013-1.736z" id="jkpath12" fill="#cb3349"/><g id="jkg97"><path class="jkst1" d="M195.832 70.085a48.125 48.125 0 0 0-.21-2.009 26.472 26.472 0 0 0-.215-1.424c-1.793-1.509-3.831-2.851-5.952-4.071-2.299-1.343-4.704-2.546-7.159-3.663-2.438-1.15-4.942-2.191-7.461-3.207a134.313 134.313 0 0 0-3.798-1.477c-1.269-.495-2.55-.956-3.835-1.424 2.697.447 5.366 1.059 8.015 1.741 1.723.446 3.437.945 5.14 1.477-12.112-31.655-41.07-52.27-72.687-52.27-31.622 0-60.577 20.615-72.686 52.27a109.044 109.044 0 0 1 5.137-1.477c2.653-.682 5.323-1.294 8.018-1.741-1.289.468-2.567.929-3.84 1.424-1.267.472-2.536.967-3.798 1.477-2.519 1.016-5.016 2.057-7.46 3.207-2.45 1.117-4.857 2.32-7.156 3.663-2.121 1.219-4.157 2.562-5.957 4.071-.075.457-.151.951-.21 1.424a51.768 51.768 0 0 0-.21 2.009 51.354 51.354 0 0 0-.177 4.061 59.216 59.216 0 0 0 .5 8.11c.37 2.692.864 5.366 1.595 7.951.36 1.295.768 2.572 1.24 3.808.237.617.495 1.225.764 1.816.134.294.274.585.413.864l.172.328c.199.101.408.204.607.3l1.204.575c.671.305 1.6.746 2.368 1.09.043-.037.086-.075.123-.114l-2.235-8.513c.474-.13 4.718-1.225 12.032-2.617a38.816 38.816 0 0 1-1.772-.381c-1.665-.414-3.309-.919-4.899-1.564a22.415 22.415 0 0 1-2.309-1.115c-.742-.426-1.472-.908-2.037-1.548 8.036 2.622 24.64 1.434 39.399-.091 13.499-1.391 27.029-2.293 40.63-2.32 13.602.027 27.137.929 40.63 2.32 14.766 1.525 31.37 2.713 39.405.091-.564.64-1.293 1.123-2.035 1.548a22.5 22.5 0 0 1-2.308 1.115c-1.592.645-3.234 1.15-4.899 1.564-.247.059-.496.113-.743.166 8.02 1.488 12.689 2.697 13.188 2.831l-2.138 8.11c.43-.194.864-.381 1.29-.574l1.202-.575c.2-.097.403-.199.607-.3l.166-.328c.146-.279.286-.57.419-.864.27-.591.528-1.199.764-1.816a42.235 42.235 0 0 0 1.241-3.808c.731-2.585 1.225-5.259 1.595-7.951.345-2.685.526-5.398.501-8.11a50.874 50.874 0 0 0-.179-4.059z" id="jkpath14" fill="#f4edae"/><path class="jkst2" d="M116.787 182.661c-1.064.16-2.128.295-3.186.375-.682.033-1.404.102-2.059.102l-.242.005c.822-1.837 1.446-3.26 1.919-4.339.963 1.08 2.188 2.417 3.568 3.857z" id="jkpath16" fill="#e6ccad"/><path class="jkst2" d="M119.101 185.018c3.304 3.272 7.398 5.146 11.904 5.479-7.569 3.074-14.702 4.26-20.197 4.63-5.478.367-11.032-.279-16.474-1.771.456-.082.79-.14 1.193-.189.447-.054 10.206-1.327 14.605-7.868l.413.009 1.08-.009c.731 0 1.395-.06 2.094-.087a43.69 43.69 0 0 0 4.878-.703c.167.171.333.338.504.509z" id="jkpath18" fill="#e6ccad"/><path class="jkst3" d="M128.464 87.071a98.82 98.82 0 0 1-1.048 1.343c-1.933 2.444-4.614 5.57-7.794 8.627a369.585 369.585 0 0 0-11.404-.177c-6.46 0-12.655.171-18.537.457 8.311-3.449 18.296-6.818 29.109-8.842a113.323 113.323 0 0 1 9.674-1.408z" id="jkpath20" fill="#656c67"/><path class="jkst3" d="M79.821 90.792c-2.966 2.084-6.317 4.744-9.566 7.971a360.155 360.155 0 0 0-21.567 2.81c9.207-4.232 19.713-8.127 31.133-10.781z" id="jkpath22" fill="#656c67"/><path class="jkst3" d="M181.48 107.969l-3.384 23.679-16.212 11.355-42.283-4.807-6.365-20.961a1.383 1.383 0 0 0-1.108-.971c-1.567-.253-2.953-.382-4.108-.382-1.16 0-2.541.129-4.115.382-.522.086-.95.461-1.106.971l-6.209 20.45-42.047 9.357-16.662-11.672-3.283-26.572c.715-.404 1.441-.806 2.176-1.209 1.031-.222 2.191-.457 3.475-.704l3.094 25.073c.048.392.264.741.586.967l11.462 8.032a1.425 1.425 0 0 0 1.101.213l34.57-7.692c.119-.027.237-.069.344-.124a1.39 1.39 0 0 0 .682-.827l6.225-20.498c1.67-.43 5.947-1.429 9.706-1.429 3.749 0 8.03.999 9.701 1.429l6.225 20.498c.161.532.624.912 1.176.977l34.57 3.927c.335.037.677-.05.952-.242l11.469-8.025c.31-.22.52-.566.573-.946l3.062-21.421c2.301.444 4.224.846 5.733 1.172z" id="jkpath24" fill="#656c67"/><path class="jkst3" d="M185.751 93.119l-2.976 11.29c-6.086-1.342-19.456-3.975-37.654-5.747 5.946-2.535 12-5.715 17.531-9.69 10.829 1.53 18.78 3.169 23.099 4.147z" id="jkpath26" fill="#656c67"/><g id="jkg32"><path class="jkst4" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath28" fill="#e5caa3"/><path class="jkst4" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath30" fill="#e5caa3"/></g><g id="jkg38"><path class="jkst5" d="M63.841 128.441c2.357-1.274 5.021-1.085 9.19-1.079.447.011.908.005 1.39-.005.41-.005.822-.011 1.258-.022 4.296-.042 7.869.366 7.806-6.381-.065-6.746-3.062-12.198-7.354-12.155-4.297.037-8.454 5.564-8.197 12.306.07 1.756.328 3.023.742 3.937-3.745.938-4.777 3.254-4.835 3.399zm51.657-27.749a46.634 46.634 0 0 1-5.249 3.712l-6.097 3.68a52.065 52.065 0 0 0-7.331 1.467 1.216 1.216 0 0 0-.317.14 1.406 1.406 0 0 0-.629.794l-6.209 20.46-33.185 7.38-10.452-7.321-3.041-24.634c5.936-1.09 13.874-2.352 23.41-3.42a56.802 56.802 0 0 0-2.955 3.855l-5.677 8.149 8.266-5.511c.123-.086 5.387-3.549 13.998-7.761a377.407 377.407 0 0 1 35.468-.99z" id="jkpath34" fill="#c7b39a"/><path class="jkst5" d="M151.835 125.675c-.042-.16-.945-2.873-4.942-2.397.461-1.003.666-2.356.521-4.21-.528-6.731-4.443-12.08-8.735-11.931-4.292.152-7.042 5.731-6.805 12.478.236 6.741 3.84 6.694 8.132 6.543 5.77-.107 8.939-1.88 11.829-.483zm21.18-19.385l-2.992 20.944-10.539 7.379-33.141-3.766-6.183-20.363a1.41 1.41 0 0 0-.945-.934c-.205-.06-4.308-1.23-8.659-1.607l.795-.053c.687-.049 12.118-1.451 25.767-6.157 15.115 1.161 27.458 3.02 35.897 4.557z" id="jkpath36" fill="#c7b39a"/></g><path class="jkst2" d="M187.481 115.502c.508.419.911 1.504.456 6.558-.559 6.188-3.16 17.049-4.771 18.8-1.778.344-5.505-.064-7.778-.595.393-1.559.505-2.306.822-3.9l3.975-2.781c.317-.22.526-.566.58-.941l2.778-19.466c1.686.912 3.421 1.899 3.938 2.325z" id="jkpath40" fill="#e6ccad"/><path class="jkst2" d="M40.937 140.908c.199.704.408 1.407.624 2.1-2.139.628-6.495 1.23-8.465.886-1.633-1.645-4.679-12.966-5.345-18.978-.543-4.871-.162-5.924.333-6.334.575-.483 2.728-1.708 4.593-2.707l2.519 20.449c.048.393.257.741.586.967z" id="jkpath42" fill="#e6ccad"/><path class="jkst2" d="M121.347 141.194l-.151 1.305s-4.581 4.248-11.956 5.199c-7.375.95-13.171-3.582-13.171-3.582.242.788.586 2.567 2.256 4.086a53.184 53.184 0 0 0-6.313-.393c-.804 0-1.616.023-2.401.061-4.539.237-10.924 7.1-15.414 14.014-2.203.697-9.089 2.883-17.06 5.237-7.44-10.309-11.098-20.842-11.469-21.932l.005-.006c-.15-.419-.301-.839-.441-1.268l1.913 1.338v.005l4.726 3.309 1.58 1.101c.236.167.515.253.794.253.102 0 .204-.011.305-.031l43.435-9.67a1.385 1.385 0 0 0 1.025-.95l6.194-20.39c1.069-.145 2.008-.22 2.814-.22.801 0 1.746.075 2.815.22l6.374 20.997c.162.532.624.919 1.171.977z" id="jkpath44" fill="#e6ccad"/><path class="jkst2" d="M170.926 140.066l1.402-.984c-.232.973-.484 1.94-.747 2.896-1.949 6.248-4.25 11.774-6.805 16.656-.565.039-1.161.061-1.8.061-1.972 0-3.986-.167-6.215-.371-3.868-.355-10.007-1.058-11.946-1.283-1.67-1.332-7.385-5.873-12.14-9.615-.187-.151-.348-.291-.505-.42-.837-.708-1.789-1.513-3.717-1.513-1.751 0-4.308.638-10.489 2.508 3.212-2.401 3.233-5.5 3.233-5.5l.151-1.305 40.748 4.629a1.41 1.41 0 0 0 .955-.241l4.094-2.868z" id="jkpath46" fill="#e6ccad"/><path class="jkst6" d="M140.937 54.337c.124 3.625.033 10.194-1.655 16.345a1.335 1.335 0 0 0 0 .704 259.298 259.298 0 0 0-6.446-.591c2.412-5.054 2.938-10.436 3.052-12.332 1.852-1.317 3.696-2.896 5.049-4.126z" id="jkpath48" fill="#ebd599"/><path class="jkst6" d="M79.456 58.462c.112 1.896.638 7.267 3.046 12.317-2.149.171-4.297.37-6.441.596a1.328 1.328 0 0 0 0-.694c-1.686-6.139-1.772-12.714-1.654-16.345 1.353 1.231 3.19 2.81 5.049 4.126z" id="jkpath50" fill="#ebd599"/><path class="jkst7" d="M151.835 125.675c-2.89-1.396-6.059.377-11.828.484-4.292.151-7.896.198-8.132-6.543-.237-6.747 2.513-12.326 6.805-12.478 4.292-.15 8.207 5.2 8.735 11.931.145 1.854-.06 3.207-.521 4.21 3.996-.477 4.899 2.235 4.941 2.396zm-13.488-9.878a2.203 2.203 0 0 0 2.154-2.235 2.186 2.186 0 0 0-2.235-2.153 2.194 2.194 0 0 0 .081 4.388z" id="jkpath52" fill="#2d3136"/><circle transform="rotate(-1.049 138.093 113.428)" class="jkst8" cx="138.307" cy="113.602" id="jkellipse54" r="2.194" fill="#edf6fa"/><path class="jkst7" d="M83.484 120.953c.063 6.747-3.509 6.339-7.806 6.381-.435.011-.848.016-1.258.022-.482.011-.944.016-1.39.005-4.168-.005-6.833-.194-9.19 1.079.058-.145 1.09-2.461 4.835-3.4-.414-.914-.673-2.181-.742-3.937-.257-6.741 3.9-12.269 8.197-12.306 4.292-.042 7.289 5.411 7.354 12.156zm-6.634-3.529a2.195 2.195 0 1 0-.122-4.388 2.195 2.195 0 0 0 .122 4.388z" id="jkpath56" fill="#2d3136"/><circle transform="rotate(-1.473 76.78 115.216)" class="jkst8" cx="76.79" cy="115.23" id="jkellipse58" r="2.195" fill="#edf6fa"/><g class="jkst9" id="jkg64" opacity=".8"><path class="jkst6" d="M50.691 75.155s.667-8.692 2.03-12.023c.702-1.717 4.996-2.81 8.276-3.591 3.278-.78 8.508-2.342 9.524 2.264 1.015 4.606 2.653 7.963 3.746 9.446l-1.404-18.97-22.562 5.464-1.484 16.786.703 1.327 1.171-.703" id="jkpath60" fill="#ebd599"/><path class="jkst6" d="M164.855 75.155s-.666-8.692-2.029-12.023c-.703-1.717-4.997-2.81-8.275-3.591-3.28-.78-8.51-2.342-9.526 2.264-1.013 4.606-2.654 7.963-3.748 9.446l1.407-18.97 22.562 5.464 1.483 16.786-.703 1.327-1.171-.703" id="jkpath62" fill="#ebd599"/></g><path class="jkst10" d="M132.965 18.378s-.598 45.49-11.224 45.49h-14.875-12.752c-10.626 0-11.484-45.47-11.484-45.47l-5.22 15.438.085 21.183 3.707 2.947 1.685 9.096 2.357 5.307 45.482.084 2.105-3.791 1.769-6.4.254-4.043 5.023-14.341z" id="jkpath66" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M166.429 60.794s2.187 15.692 7.974 18.522c5.788 2.829 0 0 0 0l-8.103-2.444z" id="jkpath68" opacity=".75" fill="#ebd599"/><path class="jkst10" d="M48.908 60.794s-2.187 15.692-7.975 18.522c-5.788 2.829 0 0 0 0l8.104-2.444z" id="jkpath70" opacity=".75" fill="#ebd599"/><path class="jkst7" d="M167.987 76.8c2.755.902 5.526 1.858 8.036 3.325-1.343-.532-2.729-.913-4.126-1.257a70.385 70.385 0 0 0-4.201-.924c-2.82-.531-5.65-.982-8.498-1.327-2.841-.37-5.687-.682-8.546-.924-2.858-.241-5.709-.483-8.573-.65-11.446-.704-22.924-.88-34.41-.892-11.483.006-22.962.221-34.409.897-2.862.166-5.715.409-8.572.651-2.857.241-5.71.548-8.546.923-2.847.345-5.678.796-8.498 1.327-1.407.264-2.81.57-4.206.919-1.391.344-2.783.725-4.126 1.257 2.509-1.466 5.28-2.427 8.041-3.331.232-.075.467-.139.703-.214-.015-.059-.032-.113-.043-.177-.048-.317-1.069-7.859.709-18.645.086-.516.456-.935.962-1.075l2.917-.831c.634-22.625 9.952-33.266 10.243-33.594-8.326 13.397-8.25 29.286-8.106 32.986l18.128-5.152c.016-.005.026-.005.042-.01.076-.016.151-.027.226-.032.021 0 .049-.006.075-.006a1.19 1.19 0 0 1 .297.027c.015 0 .031.011.053.016.075.016.145.042.224.075.033.016.054.033.086.049.058.033.119.07.177.112.016.011.034.016.049.033l.032.032c.016.016.037.027.054.044.012.016.494.493 1.262 1.209-.182-5.973.102-23.108 8.262-37.31-.172.498-6.646 19.428-4.415 40.645.724.58 1.486 1.149 2.229 1.649.359.247.58.655.585 1.09.006.07.161 6.833 3.148 12.586.042.086.074.177.102.268 7.429-.505 14.878-.709 22.312-.714 7.436.005 14.88.22 22.307.731.027-.097.06-.193.109-.285 2.986-5.753 3.142-12.516 3.142-12.586.01-.436.231-.843.591-1.09.741-.5 1.493-1.069 2.224-1.649 2.234-21.217-4.24-40.147-4.411-40.645 8.153 14.201 8.444 31.336 8.262 37.31a62.536 62.536 0 0 0 1.261-1.209c.016-.016.039-.027.053-.044.012-.01.018-.021.033-.032.016-.016.033-.022.049-.033.06-.042.119-.079.177-.118.028-.01.054-.027.081-.043.081-.033.155-.059.236-.08.016 0 .033-.011.049-.011.096-.021.2-.032.296-.027.027 0 .049.006.07.006.075.005.156.016.231.032.012.006.028.006.042.01l18.129 5.152c.146-3.7.221-19.59-8.104-32.986.289.328 9.609 10.969 10.237 33.594l2.922.831c.499.14.875.559.962 1.075 1.777 10.786.752 18.328.708 18.645-.01.065-.026.124-.042.182.239.07.47.139.707.215zm-3.297-.968c.14-1.207.789-7.809-.591-16.801l-20.52-5.833c.184 3.475.265 11.012-1.707 18.199a1.619 1.619 0 0 1-.101.258c.203.021.408.037.606.064 5.769.661 11.511 1.584 17.189 2.83 1.712.398 3.426.823 5.124 1.283zm-25.409-5.151c1.688-6.15 1.779-12.72 1.655-16.345-1.353 1.23-3.197 2.809-5.049 4.125-.114 1.896-.64 7.278-3.052 12.332 2.149.173 4.298.366 6.446.591a1.33 1.33 0 0 1 0-.703zm-56.78.098c-2.408-5.05-2.934-10.422-3.046-12.317-1.858-1.316-3.696-2.895-5.049-4.125-.119 3.631-.032 10.206 1.654 16.345.065.237.058.473 0 .694 2.145-.227 4.292-.425 6.441-.597zm-8.933.864a1.65 1.65 0 0 1-.098-.247c-1.975-7.187-1.889-14.723-1.712-18.199L51.244 59.03c-1.38 8.982-.736 15.583-.597 16.797 1.703-.462 3.411-.887 5.131-1.284 2.835-.628 5.693-1.154 8.556-1.638 2.869-.478 5.747-.843 8.626-1.192.205-.027.404-.042.608-.07z" id="jkpath72" fill="#2d3136"/><g id="jkXMLID_1_"><g id="jkg78"><path class="jkst7" d="M129.293 18.973v17.025h-12.068v-4.974h-2.72v22.981h4.109v12.85H97.505v-12.85h4.092v-22.98h-2.711v4.974h-12.06V18.973zm-3.626 13.408v-9.789H90.443v9.789h4.816v-4.974h9.964v30.225h-4.1v5.606h13.865v-5.606h-4.1V27.407h9.964v4.974z" id="jkpath74" fill="#2d3136"/><path class="jkst0" id="jkpolygon76" fill="#cb3349" d="M101.123 57.632h4.1V27.407h-9.964v4.974h-4.816v-9.79h35.224v9.79h-4.816v-4.974h-9.964v30.225h4.1v5.606h-13.864z"/></g></g><path class="jkst3" d="M30.694 93.119c1.759-.399 4.136-.907 7.051-1.47a104.37 104.37 0 0 0-6.222 4.597z" id="jkpath83" fill="#656c67"/><path class="jkst5" d="M95.111 139.78s.492 3.165-3.938 4.519c-4.428 1.355-32.482 9.716-35.682 9.263-3.199-.451-11.319-5.874-11.319-5.874l-1.969-7.004 12.016 7.492z" id="jkpath85" fill="#c7b39a"/><path class="jkst5" d="M120.242 139.167s-.354 3.182 4.131 4.345c4.484 1.161 32.875 8.295 36.05 7.704 3.176-.591 11.053-6.361 11.053-6.361l1.663-7.084-11.045 6.588z" id="jkpath87" fill="#c7b39a"/><path class="jkst5" d="M28.412 133.956s3.887 7.775 10.166 5.083l4.485 1.645-.448 3.29-9.419 1.195-2.541-1.494z" id="jkpath89" fill="#c7b39a"/><path class="jkst5" d="M187.551 131.822s-6.353 8.115-12.632 5.424l-2.019 1.302.448 3.289 9.419 1.196 2.54-1.495z" id="jkpath91" fill="#c7b39a"/><path class="jkst5" d="M89.279 192.904s23.03 11.611 49.106-4.188l-8.374-.571s-18.272 7.232-32.738 3.235z" id="jkpath93" fill="#c7b39a"/><path class="jkst7" d="M112.626 171.509l1.594 1.899c.036.046 3.577 4.26 7.906 8.552 2.879 2.853 6.357 4.297 10.343 4.297 1.361 0 2.791-.175 4.235-.523 1.34-.326 2.796-.673 4.287-1.03 5.384-1.287 11.482-2.749 14.438-3.577.585-.166 1.238-.315 1.925-.472 3.935-.909 9.329-2.163 12.187-7.889 2.149-4.297 5.047-9.874 7.197-13.961-1.863.859-3.816 1.79-5.203 2.52-2.138 1.123-4.938 1.667-8.558 1.667-2.152 0-4.266-.181-6.605-.389-4.675-.43-12.586-1.361-12.667-1.372l-.606-.067-.478-.383c-.071-.052-7.003-5.575-12.606-9.981-.227-.186-.434-.358-.621-.513-.59-.503-.59-.503-.942-.503-1.797 0-7.02 1.62-18.462 5.167l-.703.223-.689-.26c-.078-.026-7.585-2.81-16.581-2.81-.736 0-1.47.019-2.185.056-.901.046-5.958 2.448-12.425 12.68l-.419.657-.741.238c-.107.037-11.238 3.63-23.042 7.005l-.766.218-.725-.337c-.077-.031-4.696-2.174-9.091-4.194 2.397 3.541 5.462 7.958 8.159 11.422 4.711 6.067 10.649 11.674 22.034 11.674 1.428 0 2.945-.088 4.503-.265 11.581-1.309 14.563-1.837 16.168-2.117.543-.092.973-.171 1.522-.238.088-.011 9.571-1.237 12.232-7.206 2.744-6.134 3.298-7.595 3.319-7.651l.968-2.583s.12-.669.317-.877c0 .005 0 .005.005.005l.019.016c.305.219.757.902.757.902zM40.499 55.71c-2.516 1.014-5.016 2.06-7.46 3.209-2.449 1.119-4.856 2.32-7.155 3.66-2.121 1.222-4.157 2.563-5.954 4.076-.077.455-.149.952-.211 1.423a51.357 51.357 0 0 0-.388 6.068c-.026 2.713.16 5.426.502 8.112.372 2.692.864 5.369 1.594 7.952a41.963 41.963 0 0 0 1.243 3.804c.233.623.492 1.228.762 1.818.134.294.274.585.413.864l.172.326c.201.104.409.207.605.3l1.206.574c.673.311 1.6.751 2.366 1.093.046-.037.088-.078.124-.114l-2.231-8.511c.471-.129 4.717-1.227 12.032-2.619a33.744 33.744 0 0 1-1.775-.379 36.704 36.704 0 0 1-4.898-1.563 22.857 22.857 0 0 1-2.309-1.119c-.741-.425-1.471-.905-2.035-1.547 8.035 2.624 24.637 1.433 39.398-.088 13.501-1.393 27.028-2.293 40.628-2.325 13.6.031 27.138.931 40.63 2.325 14.77 1.522 31.374 2.713 39.406.088-.564.642-1.293 1.122-2.034 1.547-.739.42-1.522.782-2.309 1.119a36.965 36.965 0 0 1-4.903 1.563c-.244.056-.492.114-.741.166 8.02 1.486 12.689 2.697 13.186 2.832l-2.138 8.107c.43-.192.864-.377 1.288-.574l1.207-.574c.196-.094.404-.196.606-.3l.166-.326c.144-.279.284-.57.419-.864.27-.591.528-1.196.767-1.818.471-1.231.879-2.51 1.236-3.804.731-2.583 1.228-5.26 1.595-7.952.346-2.686.528-5.4.502-8.112a52.755 52.755 0 0 0-.176-4.059 51.573 51.573 0 0 0-.213-2.009 29.83 29.83 0 0 0-.213-1.423c-1.797-1.513-3.831-2.853-5.954-4.076-2.299-1.34-4.704-2.541-7.159-3.66-2.438-1.149-4.943-2.195-7.46-3.209a140.105 140.105 0 0 0-3.801-1.476c-1.267-.491-2.552-.956-3.835-1.423 2.696.445 5.369 1.06 8.013 1.739 1.724.446 3.444.948 5.141 1.481-12.11-31.658-41.07-52.272-72.685-52.272-31.622 0-60.576 20.614-72.684 52.272a107.832 107.832 0 0 1 5.135-1.481c2.651-.678 5.322-1.294 8.02-1.739-1.29.466-2.568.931-3.842 1.423-1.268.47-2.535.967-3.799 1.475zm159.43 18.316a53.972 53.972 0 0 1-.258 8.733 55.462 55.462 0 0 1-1.619 8.605c-.4 1.414-.86 2.811-1.404 4.198a38.295 38.295 0 0 1-.89 2.071c-.161.341-.331.678-.523 1.025l-.284.512a8.975 8.975 0 0 1-.348.574l-.294.457-.461.237c-.492.254-.895.445-1.342.653l-1.298.585a88.22 88.22 0 0 1-2.62 1.065c-.611.239-1.15.457-1.662.674l-1.444 5.487c-.036-.009-.471-.12-1.283-.315l-.078.574c1.594.833 4.726 2.522 5.793 3.403 2.148 1.775 2.299 4.587 1.823 9.841-.244 2.697-1.139 7.946-2.381 12.767-2.144 8.298-3.283 9.273-4.753 9.649-.746.192-1.894.383-3.008.383-2.266 0-5.353.063-7.429-.439-.533 1.888-2.055 6.812-5.068 12.962.151-.073.3-.135.435-.207 3.717-1.952 10.861-5.064 11.162-5.199l5.643-2.452-2.89 5.435c-.067.118-6.264 11.773-10.059 19.383-3.769 7.538-10.835 9.179-15.065 10.151-.637.151-1.241.291-1.733.425-3.035.854-9.18 2.319-14.599 3.623-.064.016-.13.033-.197.042a64.057 64.057 0 0 1-10.955 5.411c-14.568 5.518-29.923 5.208-43.844.092a647.05 647.05 0 0 1-9.193 1.097 45.12 45.12 0 0 1-4.985.291c-13.264 0-20.294-6.736-25.425-13.331-5.493-7.062-12.212-17.546-12.497-17.985L31 158.426l6.585 2.961c3.152 1.419 12.524 5.757 15.205 7 .217-.061.43-.124.642-.186-4.457-6.357-8.112-13.605-10.695-21.634-2.195.662-5.576 1.175-8.206 1.175-.961 0-1.822-.072-2.484-.228-1.471-.336-3.148-1.754-5.431-9.795-1.325-4.668-2.314-9.764-2.603-12.387-.57-5.121-.466-7.864 1.662-9.636 1.283-1.071 5.611-3.344 6.507-3.809l-.192-1.58c-13.75 8.08-21.991 15.22-22.157 15.366L0 134.302l7.005-11.047c5.544-8.755 11.948-15.832 17.84-21.284-.244-.098-.471-.196-.71-.294l-1.299-.585a34.907 34.907 0 0 1-1.34-.653l-.461-.237-.295-.457c-.166-.249-.238-.388-.347-.574l-.29-.512c-.181-.347-.358-.684-.518-1.025a30.878 30.878 0 0 1-.89-2.071 44.74 44.74 0 0 1-1.404-4.198 54.745 54.745 0 0 1-1.62-8.605 54.664 54.664 0 0 1-.259-8.733c.078-1.455.218-2.909.419-4.354.104-.725.213-1.45.358-2.17.15-.734.296-1.418.518-2.221l.155-.564.404-.317c2.294-1.802 4.768-3.163 7.284-4.369a78.87 78.87 0 0 1 6.311-2.616c5.943-16.493 16.162-31.118 29.591-41.311C74.337 5.57 90.664 0 107.671 0s33.334 5.57 47.218 16.106c13.43 10.193 23.649 24.819 29.588 41.307a78.282 78.282 0 0 1 6.316 2.62c2.515 1.206 4.99 2.567 7.283 4.369l.404.317.156.564c.227.803.372 1.487.517 2.221.146.72.26 1.445.357 2.17.203 1.443.348 2.897.419 4.352zm-11.995 48.031c.456-5.052.058-6.139-.455-6.554-.513-.43-2.247-1.412-3.935-2.329l-2.779 19.464a1.39 1.39 0 0 1-.58.942l-3.977 2.781c-.315 1.593-.429 2.345-.817 3.903 2.273.528 5.999.938 7.775.595 1.612-1.748 4.214-12.61 4.768-18.802zm-5.161-17.648l2.977-11.29c-4.318-.978-12.27-2.615-23.1-4.148-5.53 3.976-11.582 7.155-17.53 9.691 18.199 1.771 31.57 4.406 37.653 5.747zm-4.68 27.237l3.385-23.676a240.127 240.127 0 0 0-5.731-1.169l-3.059 21.422a1.415 1.415 0 0 1-.575.943l-11.472 8.023c-.27.192-.616.28-.947.243l-34.572-3.929a1.391 1.391 0 0 1-1.176-.973l-6.227-20.5c-1.668-.431-5.949-1.43-9.696-1.43-3.764 0-8.041.999-9.708 1.43l-6.228 20.5a1.388 1.388 0 0 1-1.025.947l-34.572 7.692a1.483 1.483 0 0 1-.306.033 1.36 1.36 0 0 1-.792-.25l-11.467-8.029a1.396 1.396 0 0 1-.585-.968l-3.091-25.072c-1.284.249-2.443.487-3.479.703-.734.405-1.46.809-2.174 1.213l3.281 26.568 16.666 11.675 42.047-9.354 6.207-20.449a1.389 1.389 0 0 1 1.108-.975c1.574-.253 2.95-.382 4.116-.382 1.153 0 2.536.129 4.105.382.528.083.957.461 1.108.975l6.366 20.956 42.282 4.808zm-8.07-4.411l2.992-20.948c-8.439-1.536-20.78-3.394-35.897-4.554-13.647 4.707-25.077 6.108-25.766 6.155l-.797.057c4.353.374 8.454 1.544 8.66 1.605.452.135.804.481.944.933l6.186 20.366 33.138 3.764zm2.303 11.845l-1.404.983-3.779 2.651-4.095 2.868c-.279.192-.621.28-.954.243l-40.746-4.633-2.966-.337a1.39 1.39 0 0 1-1.171-.977l-6.377-20.998c-1.066-.145-2.014-.219-2.81-.219-.809 0-1.751.073-2.817.219l-6.192 20.392a1.383 1.383 0 0 1-1.025.946l-43.435 9.672c-.103.02-.206.03-.305.03-.279 0-.559-.083-.798-.253l-1.578-1.098-4.726-3.307v-.011l-1.91-1.335c.135.43.289.85.441 1.268l-.006.006c.368 1.092 4.028 11.622 11.467 21.929a873.96 873.96 0 0 0 17.057-5.234c4.488-6.917 10.877-13.777 15.418-14.014a51.12 51.12 0 0 1 2.402-.061c2.221 0 4.344.16 6.31.393-1.671-1.517-2.013-3.298-2.256-4.085 0 0 5.793 4.53 13.17 3.584 7.378-.953 11.959-5.204 11.959-5.204s-.021 3.102-3.236 5.503c6.182-1.869 8.739-2.511 10.489-2.511 1.931 0 2.883.808 3.717 1.519.161.129.322.268.507.419a3519.302 3519.302 0 0 1 12.141 9.614c1.936.227 8.075.926 11.943 1.283 2.23.201 4.245.372 6.217.372.637 0 1.233-.026 1.797-.063 2.558-4.88 4.857-10.411 6.808-16.653.261-.96.516-1.928.743-2.901zm-15.034-51.593c-.01-.006-.02-.012-.031-.012a551.624 551.624 0 0 0-9.826-.651 905.6 905.6 0 0 0-13.667-.668 72.95 72.95 0 0 1-1.574 2.225c-2.479 3.355-7.398 9.51-13.704 14.729 8.926-1.6 24.409-5.56 37.803-14.905.336-.238.668-.486.999-.718zm-29.876.926c.377-.471.729-.926 1.044-1.34-3.281.331-6.512.808-9.67 1.408-10.814 2.024-20.801 5.389-29.11 8.837a383.259 383.259 0 0 1 18.54-.455c3.908 0 7.708.067 11.404.176 3.179-3.056 5.861-6.182 7.792-8.626zm3.587 102.085c-4.503-.332-8.598-2.205-11.903-5.477a271.86 271.86 0 0 0-.502-.512 44.25 44.25 0 0 1-4.881.704c-.698.026-1.361.087-2.091.087l-1.083.011-.413-.011c-4.396 6.539-14.159 7.813-14.605 7.87-.403.046-.734.103-1.191.186 5.442 1.491 10.996 2.138 16.474 1.77 5.492-.367 12.627-1.558 20.195-4.628zm-17.4-7.461a45.604 45.604 0 0 0 3.184-.378 138.958 138.958 0 0 1-3.568-3.857 398.441 398.441 0 0 1-1.92 4.339h.243c.658.001 1.378-.071 2.061-.104zm-3.354-78.632c1.827-1.103 3.582-2.366 5.249-3.712a422.33 422.33 0 0 0-7.278-.072c-10.137 0-19.606.415-28.189 1.061-8.61 4.209-13.875 7.672-13.998 7.76l-8.268 5.514 5.679-8.149a52.452 52.452 0 0 1 2.956-3.857c-9.536 1.066-17.477 2.329-23.41 3.422l3.038 24.632 10.453 7.321 33.184-7.378 6.212-20.464c.104-.337.331-.621.627-.793.098-.063.202-.109.315-.14.192-.052 3.51-.999 7.336-1.465zm3.816-18.788c-2.31-.036-4.623-.057-6.933-.062h-.005c-3.39.005-6.787.041-10.189.109l-6.269 2.971c-.005.005-.041.021-.088.048-.942.46-9.174 4.613-16.919 12.021 6.943-3.65 17.146-8.418 29.153-12.115a144.186 144.186 0 0 1 11.25-2.972zM70.251 98.761c3.251-3.225 6.605-5.886 9.567-7.967-11.415 2.651-21.923 6.543-31.128 10.778a360.846 360.846 0 0 1 21.561-2.811zm2.159-9.949a150.122 150.122 0 0 1 11.813-2.796c-5.798.212-11.6.481-17.393.808-3.366.186-6.715.414-10.065.667-1.678.129-3.345.263-5.007.445-.476.046-.942.098-1.418.16-4.369 2.614-21.127 13.134-32.631 26.889 11.179-7.769 30.654-19.443 54.701-26.173zm-30.85 54.197a68.861 68.861 0 0 1-.621-2.102l-5.162-3.612a1.391 1.391 0 0 1-.586-.969l-2.516-20.449c-1.864.999-4.017 2.225-4.592 2.707-.497.409-.875 1.46-.336 6.332.668 6.01 3.712 17.333 5.348 18.979 1.968.347 6.327-.258 8.465-.886zm-3.815-51.36a229.005 229.005 0 0 0-7.051 1.47l.829 3.127a103.93 103.93 0 0 1 6.222-4.597z" id="jkpath95" fill="#2d3136"/></g></g></symbol><symbol viewBox="0 0 24 24" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="tune" xmlns="http://www.w3.org/2000/svg"><path d="M6.85 2.852h-2v6h2v-6m12 0h-2v10h2v-10m-16 10h2v8h2v-8h2v-2h-6v2m12-6h-2v-4h-2v4h-2v2h6v-2m-4 14h2v-10h-2v10m4-6v2h2v4h2v-4h2v-2h-6z" fill="#fbc02d" fill-rule="nonzero"/></symbol><symbol viewBox="0 0 50 50" id="twig" xmlns="http://www.w3.org/2000/svg"><path d="M9.727 47.556c-.125-.223-.297-2.168-.183-2.087.034.025.171.267.304.537.132.27.282.487.332.482.123-.011.075-1.196-.1-2.454-.331-2.398-1.176-4.435-2.358-5.69-.2-.212-.344-.4-.319-.419.093-.067 1.327.843 1.842 1.359.293.293.735.825.981 1.181.328.474.465.618.51.534.078-.147-.21-9.903-.376-12.701-.074-1.255.063-1.023.61 1.035 1.064 4.006 1.858 7.922 2.342 11.55.086.637.173 1.172.195 1.19.022.016.092.001.157-.034.888-.483 1.524-.667 2.55-.736.727-.048.945.062.35.178-1.15.222-1.99 1.013-2.344 2.201-.315 1.061-.327 2.707-.024 3.434.152.366.037.426-1.067.56-.716.088-.977.096-1.202.037-.356-.092-1.118-.098-1.195-.008-.031.036-.243.066-.47.066-.38 0-.423-.017-.535-.215zm1.974-3.233c.152-.205.072-.41-.204-.522-.225-.09-.263-.088-.437.025-.21.137-.252.43-.08.554.18.13.607.096.72-.057zm1.248.086a.763.763 0 0 0 .214-.203c.241-.33-.352-.622-.745-.366-.406.265.08.785.531.569zm2.288 3.094c-.033-.039.117-.387.334-.775.216-.387.411-.665.433-.618.07.152-.201 1.28-.33 1.372-.15.108-.354.117-.437.02zM8.2 47.092c-.29-.343-.221-.434.14-.182.176.123.321.263.321.31 0 .165-.279.087-.46-.128zm8.649-.145c0-.053.102-.18.227-.282.25-.204.312-.113.143.207-.095.18-.37.236-.37.075zm8.065-.827c-.243-.025-.48-.088-.527-.141-.11-.125-.114-3.043-.004-3.043.045 0 .132.149.193.331.127.38.228.42.31.124.094-.337.065-3.472-.039-4.297-.449-3.55-1.865-6.124-4.342-7.89-1.086-.774-2.653-1.436-4.047-1.711-.764-.15-.522-.224.598-.182 2.364.089 4.167.706 5.847 2.001a11.046 11.046 0 0 1 2.32 2.502c.453.682.64.854.64.584 0-.07.063-.882.139-1.805.679-8.26 2.396-15.1 4.984-19.86 1.86-3.422 5.108-6.817 7.885-8.244 1.397-.718 2.539-.988 4.02-.952.933.023 1.01.036 1.77.307a6.822 6.822 0 0 1 1.363.662c.612.407 1.309 1.004 1.235 1.058-.026.018-.343-.165-.705-.407-2.657-1.771-5.062-1.52-7.12.742-1.108 1.22-2.651 3.53-3.634 5.443-2.828 5.503-4.541 11.464-5.291 18.413-.163 1.509-.282 3.76-.195 3.703.032-.022.266-.52.518-1.108 1.597-3.723 3.578-6.428 5.79-7.908.672-.449 1.612-.904 1.715-.83.022.016-.172.22-.432.454-1.957 1.754-3.248 3.76-4.232 6.572-.938 2.68-1.366 5.588-1.368 9.3-.002 1.741.188 4.385.366 5.101.125.505.08.546-.585.546-.55 0-2.306.138-3.416.27-.414.05-.817.04-1.609-.036-.58-.056-1.129-.119-1.218-.14-.165-.037-.18-.014-.2.302-.01.186-.098.203-.728.139zm2.507-6.725c.294-.11.375-.22.375-.517 0-.63-1.309-.706-1.524-.088-.074.211.13.51.42.616.297.108.413.106.73-.011zm2.369-.052c.277-.222.318-.364.174-.611-.4-.691-1.755-.307-1.428.404.121.266.299.35.738.354.227 0 .387-.045.516-.147zm3.011 6.681c-.027-.05.088-.268.256-.484.879-1.135 1.22-1.544 1.284-1.544.04 0 .056.037.036.082l-.423.964c-.212.485-.445.924-.519.977-.169.122-.57.125-.634.005zm2.446-.596c0-.121.853-.683.896-.59.018.04-.056.209-.166.376-.168.259-.238.305-.464.305-.164 0-.266-.035-.266-.091zm-13.04-.124c-.177-.159-.493-.656-.462-.725.018-.038.248.1.512.309.264.207.457.405.428.438-.075.088-.371.074-.478-.022z" fill="#9bb92f" stroke-width=".078"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript" xmlns="http://www.w3.org/2000/svg"><path d="M49 51h408v408H49V51zm246.669 314.879l19.463-1.702c.922 7.8 3.067 14.199 6.435 19.198 3.368 4.998 8.597 9.04 15.688 12.124 7.09 3.085 15.067 4.627 23.93 4.627 7.87 0 14.819-1.17 20.845-3.51 6.027-2.34 10.512-5.548 13.455-9.625 2.942-4.077 4.413-8.526 4.413-13.348 0-4.892-1.418-9.164-4.254-12.816-2.836-3.651-7.516-6.718-14.039-9.2-4.183-1.63-13.436-4.165-27.759-7.604s-24.355-6.683-30.099-9.732c-7.445-3.899-12.993-8.739-16.644-14.517-3.652-5.779-5.478-12.249-5.478-19.41 0-7.871 2.234-15.227 6.701-22.069 4.467-6.842 10.99-12.036 19.569-15.581 8.58-3.546 18.116-5.318 28.61-5.318 11.557 0 21.75 1.861 30.577 5.584 8.828 3.722 15.617 9.199 20.368 16.432 4.75 7.232 7.303 15.421 7.657 24.568l-19.782 1.489c-1.064-9.856-4.662-17.301-10.795-22.335-6.133-5.034-15.191-7.551-27.174-7.551-12.479 0-21.573 2.286-27.281 6.86-5.707 4.573-8.561 10.086-8.561 16.538 0 5.602 2.021 10.21 6.062 13.826 3.971 3.617 14.34 7.321 31.109 11.115 16.769 3.793 28.273 7.108 34.513 9.944 9.076 4.183 15.776 9.483 20.101 15.9 4.325 6.417 6.488 13.809 6.488 22.175 0 8.296-2.375 16.113-7.126 23.452-4.751 7.338-11.575 13.046-20.474 17.123-8.898 4.077-18.913 6.116-30.045 6.116-14.11 0-25.933-2.056-35.47-6.169-9.537-4.112-17.017-10.299-22.441-18.559-5.424-8.26-8.278-17.602-8.562-28.025zm-65.728 50.094V278.454h51.583v-18.399H157.938v18.399h51.37v137.519h20.633z" fill="#0288d1"/></symbol><symbol viewBox="0 0 500 500" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="1.414" id="typescript-def" xmlns="http://www.w3.org/2000/svg"><path d="M457 459H49V51h408v408zM69 71v368h368V71H69z" fill="#0288d1"/><text x="342.219" y="344.544" font-family="ArialMT" font-size="12" fill="#0288d1" transform="translate(-6058.94 -5838) scale(18.1514)"><tspan style="-inkscape-font-specification:sans-serif" font-family="sans-serif" font-weight="400">TS</tspan></text></symbol><symbol viewBox="0 0 24 24" id="url" xmlns="http://www.w3.org/2000/svg"><path d="M16 6h-3v1.9h3a4.1 4.1 0 0 1 4.1 4.1 4.1 4.1 0 0 1-4.1 4.1h-3V18h3a6 6 0 0 0 6-6c0-3.32-2.69-6-6-6M3.9 12A4.1 4.1 0 0 1 8 7.9h3V6H8a6 6 0 0 0-6 6 6 6 0 0 0 6 6h3v-1.9H8c-2.26 0-4.1-1.84-4.1-4.1M8 13h8v-2H8v2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="verilog" xmlns="http://www.w3.org/2000/svg"><path d="M17.282 17.08H6.718V6.513h10.564m4.226 4.226V8.627h-2.113V6.514c0-1.173-.95-2.113-2.113-2.113H15.17V2.288h-2.113v2.113h-2.112V2.288H8.83v2.113H6.718c-1.173 0-2.113.94-2.113 2.113v2.113H2.492v2.113h2.113v2.113H2.492v2.113h2.113v2.113a2.113 2.113 0 0 0 2.113 2.113H8.83v2.113h2.113v-2.113h2.112v2.113h2.113v-2.113h2.113a2.113 2.113 0 0 0 2.113-2.113v-2.113h2.113v-2.113h-2.113V10.74m-6.339 2.113h-2.112V10.74h2.112m2.113-2.113H8.831v6.34h6.338z" fill="#ff7043" stroke-width="1.056"/></symbol><symbol viewBox="0 0 24 23.999999" id="vfl" xmlns="http://www.w3.org/2000/svg"><defs><style>.jra{fill:#f05223}.jrb{fill:url(#jra)}</style><radialGradient id="jra" cx="205.45" cy="208.29" r="225.35" gradientTransform="matrix(.04556 0 0 .0456 2.888 2.88)" gradientUnits="userSpaceOnUse"><stop stop-color="#ffd104" offset="0"/><stop stop-color="#faa60e" offset=".35"/><stop stop-color="#f05023" offset="1"/></radialGradient></defs><title>houdinibadge</title><g stroke-width=".046"><path class="jra" d="M19.97 3H4.03A1.03 1.031 0 0 0 3 4.031v4.135C4.548 6.977 6.563 6.21 8.948 6.21c5.107.003 8.35 3.574 8.348 8.081 0 3.13-1.46 5.485-3.746 6.71h6.42A1.03 1.031 0 0 0 21 19.968V4.031a1.03 1.031 0 0 0-1.03-1.03z" fill="#f4511e"/><path class="jrb" d="M3 17.722v2.247A1.03 1.031 0 0 0 4.03 21h1.837C4.474 20.21 3.49 19 3 17.722z" fill="url(#jra)"/><path class="jra" d="M8.948 8.231c-2.586-.09-4.598.86-5.948 2.264v3.163c.918-2.654 3.447-3.87 5.565-3.85 2.647.027 4.689 2.025 4.7 4.284.012 2.159-.892 3.748-3.33 4.14-1.33.213-3.411-.567-3.318-2.578.046-1.037.854-1.622 1.777-1.58-.905 1.213.293 2.102 1.139 1.921 1.048-.224 1.475-1.156 1.475-1.878 0-.762-.718-1.994-2.498-1.951-2.204.052-3.591 1.639-3.638 3.602-.056 2.468 2.253 4.091 4.622 4.121 3.48.046 5.543-2.24 5.539-5.586-.005-3.029-2.434-5.946-6.085-6.072z" fill="#f05223"/></g></symbol><symbol viewBox="0 0 24 24" id="virtual" xmlns="http://www.w3.org/2000/svg"><path d="M21 14H3V4h18m0-2H3c-1.11 0-2 .89-2 2v12a2 2 0 0 0 2 2h7l-2 3v1h8v-1l-2-3h7a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 281.25 281.25" id="visualstudio" xmlns="http://www.w3.org/2000/svg"><path d="M196.18 101.74l-52.778 42.444 52.778 40.889V101.74m-136.67 110l-30-18.889v-100L62.843 81.74l47.778 37 96.666-89.222 44.444 27.778v172.22l-55.555 22.222-85.111-81.555-51.555 41.555m3.333-48.889l20.667-19.111-20.667-19.778z" fill="#ab47bc" stroke-width="11.111"/></symbol><symbol viewBox="0 0 300 300" id="vscode" xmlns="http://www.w3.org/2000/svg"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0}.icon-white{fill:#fff}</style></defs><title>BrandVisualStudioCode</title><path d="M218.62 29.953l-105.41 96.92L54.301 82.47 29.955 96.64l58.068 53.359-58.068 53.359 24.346 14.212 58.909-44.402 105.41 96.878 51.424-24.976V54.93zm0 63.744v112.6l-74.719-56.302z" fill="#2196f3" stroke-width="17.15"/></symbol><symbol viewBox="0 0 24 24" id="vue" xmlns="http://www.w3.org/2000/svg"><path d="M1.821 4.15l10.21 17.618L22.24 4.235V4.15h-7.692L12.113 8.33 9.691 4.15H1.82z" fill="#41b883"/><path d="M5.937 4.15l6.152 10.616 6.18-10.617h-3.722l-2.434 4.179-2.422-4.179H5.937z" fill="#35495e"/></symbol><symbol viewBox="0 0 420 419" id="watchman" xmlns="http://www.w3.org/2000/svg"><g stroke="#fff" stroke-linecap="round" stroke-linejoin="bevel"><path d="M166.95 145.32a93.935 123.23 0 0 1 92.934 3.263" fill="none" stroke-width="18.467"/><path d="M162.92 137.96L44.63 256.25a174.07 173.93 0 0 0 5.705 16.486l123.68-123.68-11.096-11.096zM266.54 144.04l-11.096 11.096 117.16 117.16a174.07 173.93 0 0 0 5.691-16.5l-111.76-111.76zm170.65 170.65v22.193l17.1 17.1 11.096-11.098-28.195-28.195z" fill="#fff" stroke-width="1.963"/><path d="M167.52 273.36a93.935 123.23 0 0 1 92.934-3.263" fill="none" stroke-width="18.467"/><path d="M49.516 144.56a174.07 173.93 0 0 0-.809 2.213 174.07 173.93 0 0 0-4.757 14.344 174.07 173.93 0 0 0-.016.055l119.56 119.56 11.098-11.096-125.07-125.07zM454.87 64.703l-17.668 17.668v22.191l28.764-28.764-11.096-11.096zm-80.984 80.984l-117.86 117.86 11.098 11.096 112.18-112.18a174.07 173.93 0 0 0-5.416-16.777z" fill="#fff" stroke-width="1.963"/></g><image x="21.229" y="20.262" width="378" height="377.1" preserveAspectRatio="none" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAaQAAAGjCAYAAABjSWGNAAAgAElEQVR4AeydB3hUVdrH/+fOpNMF JAFUmivFXtZuIBRRQUUTil1RV3et6Lr6rSu6rg3B1dXVtXeBCCioCASIimLDhkFsgAIJSAkhPZmZ 8z3vjReGMJlMueeWmfc88MzMLaf8zp3855zznvcV4MQEkoxA7sVr01PrUrv4pdZNBNA1AHQRkJ0g ZAcJrYOA7Aipv8/Q6JgUKdBkG0iZISDSDVwSaAfAY3z+/bVSAD56L+mfwA5INAqgSkrUCKA2IOQO IcQOIUU5pNwhNZQLia2Q2OyH3OJBxqaiwk4VzfLlj0wg4QmIhG8hNzDJCEgxJH9DjgfevgGBAyCx vxSyh5DoAWA/CHQH0MEFUOoBbJbAeiHEBiED6wMSv0DgF02In1J2Vq+ZP78fXcOJCSQMARakhOnK 5GrICaO3tE3P8B0kERgkgP6Qop8E+gqgL4Bdo5gEphKAwHpIrIHAjxL4DpCrAo1yVfHsHhsSuN3c tAQmwIKUwJ2bGE2TYsjY33qLQOBwCHk4JA6HhgE08kmM9ilpRQUgvwPE1xD4MiC0r2R67dfFz/eq U1IaZ8oETCLAgmQSSM7GHAIj8td38sN7HAROkpDHATgMTWs15hSQvLn4JLBaQHwqEPgQQiwvmpHz XfLi4JY7kQALkhN7JYnqlDd2w4GQnuMk5AkCOBHAQQD4ubTgGZDAFg1ieQCBj6Dhg7SKmhW8LmUB eC6iRQL8xW8RDZ8wn4AUQ8eVHib9YjCELj4nAOhqfjmcY4wE6iGxAkIsCwDvSel5v7iwa1WMefFt TCBqAixIUSPjG6IhkDtmQw/N4xkKTQ4TEkNZgKKhZ/u1DZD4CAKLIMSik/p3WzF5sgjYXiuuQMIS YEFK2K61p2GTJ0tt2Xebj5EyMArAab+vAdlTGS7VVAI0xSeAd6QQb2uBtIW8V8pUvJwZz9XzM2AG AdpoKqpTh3uEdlYA8gwBdDEjX87D0QQaALlMCLzha5Rz2NTc0X3lmsrxCMk1XeWsig6/YFOWr0Ge JoBzICWNhNo6q4ZcGwsJkFOKT4TUZiOgzSqate8aC8vmohKIAAtSAnWm6qaMHPljWl2bzBGapk2A lKMBZKguk/N3JYHPpMCrHuGbuWj6fqWubAFX2hYCLEi2YHdToVIMKSg7SQAXABgDoJObas91tZWA H5DvCeCV2rrUwg/ndqm0tTZcuOMJsCA5vovsqeDwszd19af4LwbEZQAOtKcWXGoCEaiCwAwB7emi Gd0+TqB2cVNMJMCCZCJMt2dFFnIflJQNh8BEgKbkRIrb28T1dySBlULIpzyBwCsLCntud2QNuVK2 EGBBsgW7swqlvUIer+cSQE7UPWI7q3pcm8QlUAtgtibFU4sKu70PCJm4TeWWRUKABSkSSgl5DTkt 3XSqJuVfJDAiRFyfhGw1N8qxBH4AxJP+hrpnit/otcOxteSKKSXAgqQUr/My18216wMXCOB6AH9w Xg25RklOoEoI8bzw+R5ZNKvnj0nOIumaz4KUJF0+YsyWbL/Xdx0gr5BAxyRpNjfTvQQCkPIdDdqD iwqz33NvM7jm0RBgQYqGlguvbfKmrd2MJrPtNBc2gauc5ASElJ8EIO4/eWD2m+xLL7EfBhakBO3f wfmlR2oCtwI4G4CWoM3kZiURASmwGpAPVLTPeXnFk6IxiZqeNE1lQUqwrs4bW3Y0AoF/QIjT2Vdh gnUuN8cgsFYK3LNPoPzFwsKBDcZBfnU/ARYk9/eh3oIh+WXHaELeIZs8bCdIq7gZTCAsgXUQuK9T oPw5FqawnFxzkgXJNV0VuqJD8zcdDCHvlvpG1tDX8FEmkOAE1gohJp/Yv9vLvMbk7p5mQXJp/w0Z u7mPkP7JACbwGpFLO5GrbS4BiRII/H3xzOw3eZOtuWityo0FySrSJpWTO760s8cv7wQEeVVINSlb zoYJJBAB+bGQ4uaiwpxlCdSopGgKC5JLuvm4/PUZmcJ7NSD/DqCDS6rN1WQCdhKY45f+vxYX9vzJ zkpw2ZETYEGKnJVNV1L4h9JxAuI+9jNnUxdwsW4mQFZ4//E31N/NLomc340sSA7uo2H5Gw7zC+3f AjjFwdXkqjEBNxDYKgVuPbl/9rNs+ODc7mJBcmDf5J61toMnNfVfgLgCgNeBVeQqMQF3EpD4XGja NRyTyZndx4LkqH6RYkj+pouEkFMAdHZU1bgyTCBxCEgIPFvvabx52av7lydOs9zfEhYkh/Th0HM2 95Ye//8ADHVIlbgaTCDRCWwWENcWzcyemegNdUv7WJBs7qncXOnVum66XoBMuZFpc3W4eCaQdAQE xFyfz//n4tk9NiRd4x3WYBYkGzuEjBak0J6WwJE2VoOLZgJMANgJIW49qX+3J9jowb7HgQXJBva0 pyhLeO+QkJPYaMGGDuAimUDLBJYJgSuKZuR81/IlfEYVARYkVWRbyHfouLJcBPCUhOzbwiV8mAkw AXsJ1EOKe3Z07HYvh7mwtiNYkCzinXvx2nRvTdq9EriOw0JYBJ2LYQLxEfgCUlywuDB7VXzZ8N2R EmBBipRUHNcNGVt6hJB4CcCAOLLhW5kAE7CeQB1tqF0yI/thdtiqHj4LkkLG+fnSUy7KbpWQ/wBE isKiOGsmwASUEpBLPBouXji9+3qlxSR55ixIih6A3DEbemhe7WV2+6MIMGfLBKwnsA3AxMUzc96w vujkKJEFSUE/D87fOFoT4lkA+yjInrNkAkzAVgLyv/7MhknFz/eqs7UaCVg4C5KJnTpy5I9p9W3b TBGQf2HDBRPBclZMwHkEVnr8KFg4K2e186rm3hqxIJnUd0PGlO4vPHgdAkeZlCVnwwSYgLMJVEHI yxfP6D7d2dV0T+1YkEzoq6H5ZadLIV/gKToTYHIWTMBlBIQQj6bsrLpp/vx+9S6ruuOqy4IUR5fo VnRa2Z1S4jaeoosDJN/KBNxP4DMhcW5RYc6v7m+KfS1gQYqRvR6zKCXtVQiMjDELvo0JMIEEIiCB LR4p8hcVZr+XQM2ytCmapaUlSGF5+WUDvKnpn7EYJUiHcjOYgAkEBNAlIAKLho7deI0J2SVlFjxC irLbh+SXni0EXgTQJspb+XImwASShICAeC6lsuoqXleKrsNZkKLglVdQeiuAf/F6URTQ+FImkLwE lnkatXMWzun2W/IiiK7lLEgR8KL9RQ1tsyia60URXM6XMAEmwAQMAmsDHjFq6WvZJcYBfm2ZAAtS y2z0M7njSzt7/JgD4MRWLuXTTIAJMIFQBCqkkAVLZnRfGOokH9tNgI0adrPY611u/vq+Xr9YzmK0 Fxo+wASYQOQE2gsp3h4ytnRi5Lck55UsSC30+5D8smM8wvMhB9JrARAfZgJMIBoCXiHxVN7YjXcC kmemWiDHYEKAyRu76QzIALkDyQpxmg8xASbABGImQBZ4vt+6XVFcLHwxZ5KgN7IgNevYvPyNl0EI MmDwNDvFH5kAE2AC5hCQmJ9Zh3PnzcupMSfDxMiFp+yC+nFIQdlNEOIpFqMgKPyWCTAB8wkIjKxJ xyLy+GJ+5u7NkUdIet9JkVdQRvuLaJ8RJybABJiAVQS+9kvvqcWFXTdZVaCTy0l6QZo8WWrLVpU9 JoE/ObmjuG5MgAkkJgEB8ZNPw7Di6dnrErOFkbcqqQWJvHVvF2UU2fXCyJHxlUyACTAB0wn86pf+ vOLCnj+ZnrOLMkzaNaTcXOndLja9zGLkoqeVq8oEEpfAfh7hfW/4OaUHJW4TW29ZUo6Q8vNLUreh 42tCYEzriPgKJsAEmIBlBDZDiiGLC7NXWVaigwpKOkEiMSoXnQol5GgH9QNXhQkwASagE6C4SprU 8ooKu61MNiRJNWVH03Q0MmIxSrbHnNvLBNxDgOIqSREoGjq2tL97am1OTZNmhEQGDNtE6asCosAc dJwLE2ACTEAlAVkGIXMXz+jxg8pSnJR3UoyQfreme57FyEmPHteFCTCB8ARENqS2mJw8h78ucc4m gSBJUS7KHgVwfuJ0G7eECTCBJCHQwyM8i3LHbOiRDO1NeEEaMrbsHt70mgyPMreRCSQsgQM8Hu3d kfllXRK2hb83LKEFaUjBxluExN8SvRO5fUyACSQ4AYGBjQjMG5q/vX0itzRhBSlvbOmVAuLeRO48 bhsTYALJQ0AK8UcpamfT1pVEbXVCCtLg/I2jIfEYgKSxIkzUB5TbxQSYQDABMWS76Phsogb5SzhB Gjxu0x81IV7jEBLBDzG/ZwJMIIEInJc3tuyhBGrPrqYklCCReaQIBOYByNzVQn7DBJgAE0g0AhLX Dc0vTbj18YSZ0iILlEaBjyRk0tjsJ9p3jNvDBJhAVAQkpBy/uLD7jKjucvDFCTFCokW+Bsg3WIwc /KRx1ZgAEzCbgIAQz9IyhdkZ25VfAgiSFPoin8DxdkHkcpkAE2ACNhHI1AKBuYmycdb1gjS0oOz/ AJxn08PAxTIBJsAE7CbQ1ePV3kmEPUquFqS8/LJzJXCX3U8Dl88EmAATsJnAwVLUv+R2c3CPzRBj Ln5o/qaDIeRbABJ2k1jMcPhGJsAEkpHAH3oPrNTWlkxb6tbGu9LKLu/sDfsgRfsEQB+3gud6MwEm wAQUEJBC4NyiGTmzFeStPEvXTdlRKAmkaLTxlcVI+ePBBTABJuAyAkJKvKDPILms4lRd1wlSudj0 LwDDXMiaq8wEmAATsIJAG6kFZrnRyMFVgkQ+6iTkX63oUS6DCTABJuBaAhL9Aqhznc871wjS0HM2 99aEeIEdprr2K8IVZwJMwEICQmBMXsGmGy0sMu6iXGHUkHvx2nRPddpHEDg87hZzBkyACTCBpCEg G4UUQ4oKc5a5ocmuGCF5a9IeYjFyw+PEdWQCTMBZBESKFGKGbpnsrIqFrI3jBWno2NIxHII8ZN/x QSbABJhABARkDlI8z7lh06yjBWnImNL9IfF0BMT5EibABJgAE2iRgByVl192bYunHXLCsWtItN9o O8reBztNdcijwtVgAkzA5QTqhSaPK5re/UuntsOxI6RyUXo7i5FTHxuuFxNgAi4kkBaQ4tVRo0od G8DUkYI0JL/sGAlBXrw5MQEmwASYgEkEhMRBtZnifpOyMz0bx03ZDb9gU5a/PvAFgANNby1nyASY ABNgAlIKeeqSGd0XOg2F40ZI/nr/AyxGTntMuD5MgAkkEAEhpHh2RP76Tk5rk6MEacjYjcMBcZXT IHF9mAATYAIJRqC7X3gedVqbHDNld8LoLW3T0xu/BbCf0yBxfZgAE2ACiUhASoxZUpgzxyltc8wI KT29cQqLkVMeC64HE2ACyUBACPnYiRN+6eiUtjpCkIbll50C4AqnQOF6MAEmwASSg4DITvN7pzml rbZP2ZFNfE0GvuGAe055JLgeTIAJJB0BiZGLC3Petbvdto+QajPEP1iM7H4MuHwmwASSmoDA407Y MGurIOWNX3+IRMBV8TqS+qHlxjMBJpCoBA6oyRB32N042wRp8mSpwe99AhApdkPg8pkAE2ACTEDe OHjshkPt5GCbIC0r2XQlII+zs/FcNhNgAkyACewi4NWkeEIfLOw6ZO0bWwRpZH5ZFynkPdY2lUtj AkyACTCB8ATEse9/V3Zp+GvUnbVFkBqaxKiDumZxzkyACTABJhATAYl7cs9aa8vfZ8sFadi40qMA 2KbAMXUQ38QEmAATSBICAuiipabfZUdzLRYkKQIB+R8AFpdrB1oukwkwASbgTgIC8uph+RsOs7r2 lgrDkPxNFwHiWKsbyeUxASbABJhAVAQ8AWgPRXWHCRdb5qnhd48MPwDobkK9OQsmwASYABNQTEAI eVbRjO5vKi5mV/aWjZBq0sVNLEa7uPMbJsAEmIDjCUgpHjjyCmnZXlFLBGnEmC3ZEPJmx9PnCjIB JsAEmEAwgQM7lJddHXxA5XtLBMmX0vhPAG1UNoTzZgJMgAkwAQUEBG63KkSFckEafk7pQZC4SAEm zpIJMAEmwATUE9gn1Z/yV/XFWGB+7feARkdeKxrDZTABJsAEmID5BITENXnjN+9rfs575qh0hPT7 Jthz9iySPzEBJsAEmIDLCGSJQODvquusVJACAZC/OstMy1XD4vyZABNgAslKQEp5xfD8Tb1Utl+Z IA0pKD0ZwDCVlee8mQATYAJMwDICqX7NTwFVlSVlgiQgbQ/2pIwaZ8wEmAATSEYCUpw/ZOzmPqqa rkSQ8saVngSIIaoqzfkyASbABJiALQS8Aj5la0lKBAkBOdkWVFwoE2ACTIAJqCUgxflDz9ncW0Uh pgvS0PzSE3l0pKKrOE8mwASYgCMIeOFRY3FnuiBB4FZHIONKMAEmwASYgBICEvK84eM29jQ7c1MF KW/8+kMkMNLsSnJ+TIAJMAEm4CgCqb6AuMHsGpkqSPB7yL0E7zsyu5c4PybABJiAwwgI4PIR+es7 mVkt0wQpd1zZAQDGmlk5zosJMAEmwAQcS6CNT3j/bGbtTBMkLYAb2WedmV3DeTEBJsAEnE5AXntc /voMs2ppitPT3LPWdhCQl5hVKc6HCdhNwOsVaN9WQ1amQFaGQEaGhox0gdQUAaEBaali19x0Q6PU j1OdG30S/gDQ0CDh9wPVtQFUVUlUVgdQXRNATa20u2lcPhMwk0DnLOE5H8BTZmRqiiBpqekTAcnx jszoEc7DUgIkLL3396JvrxT03T8FPXK8yNnXgy77eJTUo7pG4tdSH9Zv9OGXDT78vK4Rq39uRFV1 QEl5nCkTUE1AAtcD8mlAxP1rK24DhNxc6fV0LfsZwH6qG875M4F4CXTqoOHQAWk4dGAqBv0hFQf0 NOU3WbzV0gXqu58asXJ1A75YWY/NW/xx58kZMAGrCAjg1KKZOQviLS/ub6O3a9mZksUo3n7g+xUR SEkROKR/Kv54eJr+v3t23I+8kpr27O4F/R9+StN0/MZNPnzxTQM++7oeK76pR31D3D8+ldSbM2UC REBKXAUgbkGKe4SUl1+6FAK53C1MwCkEaK3n2CPSMfiEdBx/VLpTqhVXPZZ9WocPP6vDxyvq9fWo uDLjm5mA+QQCwu/pVzRr3zXxZB2XIOXllw2AkCXxVIDvZQJmEKCR0PFHpWHw8Rk48ZjEEKGWuJAw LXq/Fp98WY/GRh45tcSJj1tLQEDcXzQz+2/xlBqfIBVsfBQQptqhx9MYvjf5CPQ5IAUjB2fgrFOz kq/xAN4qqsE7i2vww5rGpGw/N9pRBLZ2kuXdCwsHNsRaq5gFadSo0syaDGwE0CHWwvk+JhALAbKM yzsxA6OGZ6Jfr5RYski4e0iQ5i2qwdIPa1FXz6OmhOtglzRISHleUWH3V2OtbsyCNLRg46US4plY C+b7mEC0BPbt4sGoYZkYdybvMAjHbtbb1Zi7qAYby3zhLuNzTMB8AhIfLC7MoWjhMaWYBElKeeCM udULXp1TeQDtq+DEBFQSGHRQKkYPy8SQE03bEK6yuo7J+73ldZi7sBpfr4p5BsUxbeGKuIOApkE+ PLnTjQMOSv93LDWOWpBIjAB8T4XNW1iDp1/bCRalWNDzPa0ROO7IdH1a7pjD0lq7lM+HIfDNqgZ9 xFT8UW2Yq/gUE4iPAE2fP35fZyOTvwsh/mV8iPQ1FkG6F8AuSwoWpUhR83WREsg9PkMfER0yIDXS W/i6CAjQxtt5C6t1Cz3JExsREONLIiXQe78UPDlllxgZt3mFEFHt8I5KkKSU5E9lr4lpFiWDP7/G Q+CUY2lElIXDBrIQxcMxknsfeqoCbxfVRHIpX8MEwhJoQYzontOEEPPD3tzsZLSCdCqAkAWQKP3v 5Z1s4dMMMH9snQBNyZHFHE3RcbKOwKofGnTLPNrTxIkJxEIgjBhRdjOFEFGFJIpWkF4EcEFLFSdR eviZipZO83EmsAeBA3unYPTwTJw6OHOP4/zBWgLkmohMxskbBCcmECkB8gP59INdWru8vRBiZ2sX GecjFiQpJf3VqDZubOmVRaklMnzcINCxvaabbp9zuns3s1IYCWMdJjNDoLau6XNKCnaFojDa65ZX GimRVd53P/ImW7f0mV31pC0Y40a30Wc2WqnDJUKI51u5ZtfpaASJhl7Td90Z5g2LUhg4SXxKCGD8 WW1w6bi2jqZAISHWl/qwcZMf5OR0yzY/tu8IoKIygIqdgV1CFK4RXg/QsYMHnTtq6NTRg25dPdi/ hxf75XhBZuxOTrSPqfCtamzdHtV6tJObxHUzkUAUYkSlLhJCDI+0+GgE6U0AoyPNmEUpUlLJcR15 Vrj1Guc59aA/urRP59vVDbr7nTW/+pT7hyNh7pHtxYADU3BQ31TdiKNnjvO8kD8/sxKvzK6KSICT 4ynmVkYpRgawbCHEJuNDuNeIBElK2RHA9nAZhTrHohSKSnIdoz+8Z52a6Shfcx9/UY9PvqjDFysb 9BGQE3qka2cPjjg4DUcflgayNnRSuu/RHSj6gA0fnNQndtQlRjGiql4jhHg0kjpHKkgUnvzZSDJs fg2LUnMiyfP5vDFtcMlYZ0zP0R/UpR/V4qtvGxwfW4g8lx91SBpOOCYNp+Y6w+CDnLjSd/nnX3h9 KXm+wbtb2r6thosL2kayZrT7pt3vioUQg3d/bPldpIL0NtmUt5xN+DMsSuH5JNpZp3hYIKuxhe/V 6kHu3BqmgcSJggtS4D4nxHZ66fUqvPh6JU/jJdqXNkx7sjIFJo5vF6sYGTnvK4T4zfjQ0murgiSl bA9gR0sZRHqcRSlSUu69zuMBLhvXDgWj7bOeW7fBh/lLavQpJjJASKTUrq2GYSdl4KqL2tnerH8+ VI73PmYzcds7QnEFTBIjquWVQognW6tuJII0HkDM7sSDK8CiFEwjsd4POzkDt/zZPqMFGg3NXViD L1bWJxbYEK0ho4hDB6TqXi3sXG+a/U41XplTpVsehqgmH3I5ARPFiEgsFEKMaA1JJII0E0B+axlF ep5FKVJS7riOwoVfPLYtzjnNnlHRzHnVmLeoGmWbk9NEOWdfjy5M+WfYw5+e0keeqdB/DLjjieVa RkKA9tZdPiHuabrmRXUQQoT1nNCqIAUCcqYQ5gkS1ZBFqXk/ufOznaOiFwsrMXt+DaqqE2taLtYn gf6AnD0yyzYjkjcX1GD2/GqOwRRrBzrsvusuax/vmlGoFh0phPgi1AnjWKuCNOutquvHnJ71kHGD Wa8sSmaRtD4fWiuiX0/n2vCr/IXCSsycW+14Sznre6WpRK9XYMxpmbjiPHvWmZ54aSdef6tVhy52 4eFyIyCgSIyoZE0IEdbPPHnvDpu2+f5y7uqfGo/PO8nc4Gh/6JOCju09+OTLxJ/zDwvYZSfJ0uvZ aV0w4EBrvQ2QEP3ffeX63iF/cs7ORfSkBAJAyfeN+tpOba3EkYdYG0vqqEPT0KG9B6Wb/dhZyaPX iDrNQRepEqM/3bK14sWnMqesWnVnfILUa9Ckh0s3+Tuv/qkRLEoOenJsqArtKbpuIhldWpdmzK3C P6aU47Ov6sFCFDl38rNX8kMjXn2jGo0+icMHWSdM9GPzrFOzUF0r2S9e5F1m+5WqxOiav28lLyjp dahatGbV1F/DNTTsCGnI2M19hJR3UQbk14tFKRzKxD3X94AUfVOclc5Q58yvxgOPV2Dph3U8PRfH o0UjppXfNWDGvGpoAjjYQj96Rx+ahvbtPPh5nU93PhtHM/hWxQRUidH1d2zDqh9+30wtsGltydQl 4ZoSVpB6DbxxrADOMDIgUfplgw+nHMfTdwaTRH+lX7p33dwR/XqnWNLU5Svq8PgLO/HGuzU85WMi cRpdfvltAxYva9o71L+fNVOuB/VNAVkA/rYtgJ/WsZcHE7vUtKyuubQ9Ro8w3yMIiRH5iAxKaWtL pj4d9Hmvt2GNGobkl84SAmOa30V7H26/gdzbmZvY0MFcnvHmRhswrTTnfvQ5EiJnLoiTFdthA9NA 01H7dfcip5sHbTI1tMnSdMwNjVIfBZBn8A2lPtAG3a9K6rH2170CLMfbLabcT/uYRg/PwinHWec3 j4wdyOiBk3MIkBidaY0YUaP9XunvuqCwZ4t+UVsUpPx86dkuyrYCCLnbUZUokfnof54Na6runN5M 0JrQegMFzjvpj9b8saJ1ohdmVoH+qDsppaUKDD4hA8NPzsAhA2IbUWwvD+CDT+vwzmJn+oEbNSzT 0nXBjz6v04MB0pogJ3sJqBKjG+7YhpV7jox2N1SK/MWF2a/vPrDnuxYFKS9/w3EQ2kd7Xr7nJxal PXkkwieaXrnyAmtMht9bTt4VqvXwD05iRzvUzz29DS44t42p1fr863p9Ayn9UXZS0jToa4QTzja3 veHa+NyMptAW4a7hc+oIqBKj2+7djk/D/dgQeHLxjJwrW2pZi2tIvQbedJEQGNLSjXSc1pNUrCnR vDMthn7KJuHh8Jt+jqboLjjXGu/cT76yU18r2rzFOTbcNCKiP8r33baP7prHbMA53bz6iOsPfVJR XRvAxjJntJ0s8mh9icJy0B6zvr3UrxfSKJy+4zSlSdF3OVlH4M8Xt1MSDqZVMWpqYoe1JVMfaam1 LY6QhuaXLpACEUX645FSS3jdcZz2FNHUDXleUJ30Hf3vVDsmDhG1lzaTjh1tvZcDGimR/z0aOTkp 0ZoC/YK2Kt37nx1YvIzjLVnBm8SIPHqYnSIUI71YKf09lhT23BiqDiFHSLm50ivaVP0XQEQT5zxS CoXWHcdGDc/EnZM6os/+6n8VP/x0BV6aVYXKKmdsmKTRwLgz22DaHftYuk/HeDIoSuzQkzJ0I4kd lQE4ZbT4/c+NmPVONVJThCUboGmt0uMR+KpkD4ssAxO/mkTACWJETdEgVqxZNW1lqGaFFKRex151 tJDy6lA3tHSMRaklMs49ftn4tpg4Qf16EVnO0eZWChXuhETesmmt7N93ddajtNpdpwN6pmBEbia6 dfVie7kfW7fbL9iNjdBHbr+W+nGyBRFsDx63vWsAACAASURBVOmfqntuIevE6hqewjP7mVQlRpOn lmP5iihH+EJsXVsy9a1QbQwpSH37T5oAgWGhbgh3jEUpHB3nnOvYXsOV57eDFRtdyWKSgrrV1tn/ R4aEiPZVPfqvziAXN05LfQ5IwWl5mejU0YMt2wIor7BfmNat9+mjWlpfG/SHiCZMYsZKJvW0zWB9 qR9ULidzCKgSI4qJ9cEnMRnoZK0tmfpYqNaFXEPKKyibC8hRoW6I5BivKUVCyZ5rjjksDffc2kl5 4bQ2MnNuFTY5wGiBhIj+0N9wuXXrImYApvW2eQur9T1NZuQXbx4nHJ2OO28yf/9hqHqRN/cXX68K dYqPRUHgTxeocYIcb4DGVCm6zi/M3tK8KSFHSL0GTnpYADGvfPFIqTlmZ3ymxcxbrwm5rczUCj79 WiWeea0SVQ6YeqHQ3/+7vwsorLrbElmbjh6RpW++JWG321np+tKm0VJ6qoaBikdLhw5MA0XI/ea7 BvicYYzotscHThUjAtkoRPG6kqk/Noe6lyANz9/USwp5W/MLo/3MohQtMbXXTxzfFpeOU2vSvWRZ LR5+Zifo1e405MQMkH8uFRZFVreN3PzQVGN6uoYNZfavsaxYWY9fNvpwyrFqrTIP6puqm+GvXN2I Tb+xKkXz3CkTo3+bE7peCPnz2pJpxc3btJcgHXDwDacC4tzmF8bymUUpFmrm3pOWJnD1Re2Vxy4i bwsPP70Tv2219w8HucK59rL2utFC1857Pd7mwrU4NxqV0BoLWb+tXe9DXb1963L03SaHrRlpAqr9 4tEot6IyALL+49Q6AVViNOVxMs+Pac0oRKVFw9qSqS81P7HXN7b3oJsmAji2+YWxfmZRipVc/PfR lM+F57bF6UPNd5wYXLsHn6jQg+YFH7P6PcVposXb8We1Qbcuez3WVldHaXmDDkpFwag2ujB9v6YR ZBFnRyKHrZ99XY/ynQEce4TaKdE/Hp6u7xejDbycWiYwcUJbFIw23+MGidGCYlNnPjpflP/gA8XF e8ZH2suoIa9g43JAmCZIBjo2dDBIWPOad2KG8vUicoY7Y16VrdMpZC1HfvdIkJI1vTK7Cq+9UWXr iClnXw/yz2ijIuz1Ht1Khh5Pv7rTEVabe1TMAR9IjGhfndlJgRjpVfT40X/hrJzVwfXd46ckOVSt FVX/BmD6LkkeKQVjV/ueHkrVgfTojyB5bq6qtmfaiJydXj6haR8VbTBN5kR7eMjlEfXEdz82gmIg WZ0qq6Ue/ZnqQF7RVSUa9Xfr6sGOioDt08Oq2hhLvm4TI72NQi5vvkF2D0HKOfjKgwBcHwuQSO5h UYqEUnzXkPHChflqjRfuf2yHvpM/vprGdnf/vim4dFw7XH1RO9CGUjsTTR9R8Luff/FhQ5kfASn1 zZ121YmE4PwxbdDogx5M0w5h+mZVA35c68OQE9QZPPTaLwWnDs7E9h0BikRqF27HlKtKjMizyjtL TJ2m24OZEFi7pmTaouCDe0zZ5Y0tPQ8SLwdfoOL9iNwM3HyV+ebHyR66QtUGOOMZoCm619+2xw9d v14p+nTQaUPUrocZbW3p1fDYTYEEySlp89Q2S9MNSM4bY/7USfOyWvtMDmxnvV1tS+j3lBShj2DH nBbz7pHWmqeff3lWFZ6fWRnRtYl4kSoxeuSZCt3PokpmUorFSwqzhwaXsccIqffASRcBOD74AhXv KaTx5q1+0EY7M1Oyegnvso8Hl09ohzNPVfflnzm3Gv99YaflfugO6OnVR3yTrmwPEiW7Erk9euqV nfr+KtqP01KimE7kk418wdEIhabT7EpHHpKGC85pq6+30FSelYnaTgYPZAlI9VCVaOqW9iuFDXmg qnCb8724oK0+VWt2NawQI6qzEGi/tmTqA8H133OEVFBKw6c9FCv4YrPf80gpfqL0B2/a5H3izyhM DnZEcqV1IfJArvoXdphm66dKvm/QfynG6o2a3DSRqfa4s+wfMVE/vrmgOuTIrjUO8Zy3IuAjjd4L 36pC6WZ7tx3Ewymae0mMzj/H/GfKKjEy2iok9i8qzPl112fjDb3mFWwsBUR28DHV71WJ0pz51Xjs +cQOl2yFJd3N/9ymx8pR/RwY+dOUF5luF4xWN9ozymrt9YH/7sDC98yZQ9+3iwdnjshCwSj72/XQ UxV4u6imteabep6CAF51oZrQB8EVjSYMQvB9bnqfKGKkM5cYubgw512D/64puxMn/NLRG/DcbZyw 6lXV9B1t1mvbRkOihkomx6g3XKHONxv5orvlX9vx68aWp6fMfkbox8kjd3dW7pamtXrTH2zyTk7P plmJPFiv+KYeRR/U6lN5FIPKrkRulAb8IVWfygs3/Whm/Wi9jb6LZHBxxMHqpvDyTsrAth0B/Jig xg6qxOh/L+3EnPnW/kjRny8hvlpbMnW58aztEqQDD/rrURC41Dhh5SuLUnS06aGk0BGqEnldePyF naD1EKsSmS3/5RJ1AhtJO2hK6//u367UcovMo8kwYulHTUYRqr0ctNTunH29GHx8Bsj4wMrNpt+u brJKpLJVJRJcenLJ4i+REhnKXKTAgpbEqPCtaptQiQ1rS6bONQrfJUi9Dp40EsAZxgmrX1mUIiNO bkFUrkfQw0lB9KxMNNq74jz1cZlaahO1+dZ7t+um0i1dY/ZxcpRKI4YPP6unxV0c2Nseg42DD0rV nbeSAYJViUZlbxXVIC1NA4WcUJHIBD4zQ8Pn31jXLhXtMPIkMbpkrPk/Qu0VI2qdrF9bMu0Zo527 BKn3oEkXmOkyyCggmlcWpfC0/nJJO6WL/HdOK8f8peasmYRvye6zfQ9IsSykwe5Sm96RR3ISom+/ t9YCLbgeFPPo4y/qdXGiUOoUE8nqRKO01FSBL1ZaN6Kg+FiffFkP8rWoKs4STYt2bO9ByQ+Nlo72 ze6/xBUjIiXarS2Zep/BbJcg9Rk46RoA/YwTdr2yKO1Nnhb6aUGYFsVVJLJQuu/RHVi52ro/SEY7 aPqxn8Wjg+dmkBCV61M6ofYSGXWz8pWixH74WR2+WtUACoZn9aZf8o9nbFy3st0kghSm5OjD1Kwr 0QisXRtND3hIG2ndllSJ0bPTKzFjrl3TdHv0QlrvQ/76xNpvH9Qrs0uQeg2c9A8Anfe41KYPLEq7 we/X3YsJZ6nzETb7nWrQ2okdsXbItFvFBund9PZ890JhJf7vvnJ9zcQOLwZ71ib0p81b/Hj/kzpQ yIWMdIH9e1jnFql9O49pVoWhWxf6KO2RIiexZDWqItEPnjOGZeplbCxzj1m4KjGiH2SvzrF2Wj5c v2oy8Maakmnr6RpdkJp82FVOBcQugQqXgRXnWJSAwwel4rF7OiubZ6fQ4k+/at8ud/rCDein3tqM /O7d/sB2fP51gy1eC2L5vlD8n/eW12HVj43IzBCwwl8f+Yhb9UOjLXt5SCjeXlyL9FSh7Hknwftt WwA/rbNvijbSZyH/jCxMnGD+uiqJEX0fHJWE9t6akqlfU510Aeox8KoDpMCNjqokoJvdqvDo4AaT 8NzjM3D3X9WFGievC9PftPfBJFdH7dtqyh676W9UYfK0cny8ot62EA3xNo42epJF3k/rfPo2hpxu akdM6ekCxcvNinkTXeuNdSUKRKgqIi15hSfvEbSu5NREYnTlBUkiRmTWAJSsLZm6lPpDf7r98PcF 9nDa4Ji+MmJwmD21Y0QSdeLmWfJQoNJb9z8p6qNNf3SMB8vrgbJf/TPnVYOmIrdud8/0jMGlpdeP Pq8D/acfKqOHZYJc5qhIJx6Trlv92bm29uTLO1G+w6/kjzIxu+L8droFnhN94CWbGFF/aEAf41nW BUlqYn9h3ZYTo+yIX5NJlFQ9kAbsa2/fhlU/WG+8YJRvvHbsYP7sMDkSJQ8dm7YkjhAZvIzX4o9q Qf8piuqo4Vkg7+dmp306emwXc9oXQ/14x40dzW6enh+53UlPE3oIFSUFxJCpqu8+OaB13DRdEB8p sb/xUf+r0HvQjWcC4hTjoBNfk2FNidZUVMwbU3+SJR25VdlQZp73gXieE/pjkD/KHF9cNBp64L8V IH9zZLGVDIlCXsxfUqOviZgdnHDRB7Uod4BFGnkJ+fDzemiK9mmRWTgZcnz6pf17lc4ckYmrLzZ/ Y7grvKELYG3J1Ifoe6uPkITUekp9b7Ozv8qJPFJS5RKEetSJfv0aTJjCp82VJLQ//2JCZs5+9Fus 3btLa7CguAan5WXihsvN/4PWYsEWnfh5XaPuZd7nB+iPttmJ8iTBe+H1Sj3on9n5R5IfRTy+5lLz +84VYtQEKCc3V3qLi4VPHyEdMOCma4RA70jg2X1NIo6UKKYJuc5RkWio/uQr9lnStdQm2ogZT7hl cm1EFkO0sZQTdN9t5GFjZ6XEMYfHt6eHhN4JIySjX/1+6KMYVcYOtFcpI03Tpwgrdlr7PJEYXXtZ UosRdbOW0rby6Z+/nbZTN3ESQvY0Ot8NrzRSomiGZicydCDLLyuT7groTDVi9NQrlfofbSvbw2XZ R4AMEd54txpTHt9hXyUUlkzGDuRdQ0UaNTwTz0ztAnKlZFVSJUZkPetEg41wXGUA+9F5w+a2R7iL nXhu3qIaUOwOs5OVokTid+4Zarwv/PupCpCTVE5MIJEIvPZGFR58wvzvvcHooTv3gdlrckbewa8q xcjOvYXBbYzmfUBqetgjbdSoUpqYNX9yNpraxHgthUhwqyjRnLFheh5j81u87Z8PlevOK1u8gE8w ARcToHUzitOlKt11c0cMO1mN1wiqM4VZUTFNRyMjN4oRMZEIdKNXrTrTY2lAPrMfIpWiRNNpZidy B3PdZe2VLNBSXW+6axve+9iejY1ms+L8mEBLBChkxgXX/qYbtbR0TTzHb/lzB9AoxuykKiCpm8WI GAsNXejVK4Xs7OQ9SJE8ECRKlMz+1WFMpz3xkjmRZ7MyBSaObwearzY7kbXZ7Hersd7CgHpmt4Hz YwLRECjb7Mdjz1cgINVY4NHfE/JcMdMkJ6QsRmF6V4ocOuvVAgFXj5CMJjpdlLp29mD8mWqcpJIY Pf3aTlBUUk5MIF4CNbXWWprFU18yB//PsxXw+SQorpbZieJ0kfd18vsYT1IlRq+/Ve3aabpmPHXH 3l4BdEmUP2NOFaVuXTwYO1qNGJFVFXnr5sQEzCIg3aNHu5r8+Is7dR91tLnc7ERRWj2aiNlyjdw9 me36jNpIYmTW7I3ZzKLPT+xL93ghRQe4fc4uqPVOE6Xe+6XgySlqonok1gMZ1In8lgnEQID2pdXU Slx+nvmRVcnVkNeLqEcjpxybjr9f1yGG1oS/JfG++1KHpEnR9CZ88911lkRJhfUdrSlFY+jQr5c6 MaJFzMT5deSu54tr61wCtNVh2pNqzMJpI3c0338So9tvMN8XX+KJkf486aA0IPEEiZpntyhRBM7H 71MzMiKXIG4173TunzKuWaIQeGdxDf4xpVxJcyL9UapKjMgNWIL+EG0aIQkI8yVcyaMQfaZ2iRIF 1vv3nftEX+EI7qBpCbftwo6gWXwJEzCVAIXquO4favYqkSiF8+hysqKRkRN9UprYaem5F69NJ08N 5jtSMrGW8WZltSgdc1gaptyuRoz+99JOR7uRj7ev+H4mYCaBku8bcOmNW5TsVaJN7aEcotL3/x8K pukSXIyaur06q4NXAubv/jTzqTIhLxIlSqr3KdHDeM+taqK8PvxMhZIvlgl4OQsm4FgCFMLi+cIm /3dm7/8zPIXTd5OSqu9/UogRgFQEMryQSHdosFhTH3LVovTFynplYnT/Yzuw6P1aU3lwZkwgWQiQ B+9HnlWzgdYQueUr6pR8/5NFjOhZDABtvBCIz1e9i55qlaJkeHUwG8fkqeVY9im7AjKbK+eXXATI EzptoA0EpOk+JEmUDGEyk+qbC2rw2PPJs8fQ70EmBehL6DWk5g+IKlFqXo4ZnynC66df2R/N0oy2 cB5MwAkE6A88xVdS9QPSrDaSGJGAJlMSgUAaCVLSjJCMznWDKN04eRu++a7BqDK/MgEmYBIBMpuu b5BQ4dXBjComoxgRN02KtmRll24GRLflQaLk1OHw9XewGLnteeL6uosAbZ+g/XxOS8kqRkY/0Agp aRMtGHo9wJUKwkzECvWKm7diza+Nsd7O9zEBJhAhAdrP1+iTuGSs+a6GIqzCHpeRk+Rkm6YLBiAR aJPUgkQwCt+q1pnYLUr0ML65sBrr1vuC+4jfMwEmoJDAK7Or0NAgbf9RSt9/w3xcYXMdnbUAPCRI zvh5YCMqu0WJHsbpc6uweYvfRgpcNBNITgL0/acwFuG8L6gkw2K0my4JEq0jJX2yS5ToYXx5dhW2 lbMYJf1DyABsI0DT936/NH3zfGsNYjHak1DST9kF47BalOhh5MB6wT3A75mAfQTI0Ims71TELgrV Khajvanw6KgZExIl8hmnOrEYqSbM+TOB6AksKK7FPx9S4yk8uDb0/f/fy+r/zgSX6Yb3JEgujA+p Fq1qUWIxUtt/nDsTiIfAex/XKRUl4/tfV58osbrjob3nvSRITZ4H9zye1J+yMgVy9lU3m0luRkYO yUxqxtx4JuBkAocMUOcvgL7/+WeYH2rdyTwjrZu6v7qR1sBh15EYTRzfTolvquCmUuRJEWR2Hnwu Gd5npFPrOTEB5xG47rL2yr//FBKdEsc2293/EvCzIO3mAavEyCiS9j4JAcyc17QXyjieDK8eXr1M hm52VRszMwQun6D+x6gBhUXJINH0KqBV0Z8FdiUNWC5GRldccX47x/rUMurIr0wg0Ql0bK9ZKkYG TxKliwuSfiuogQM0Qkp6d9JWj4x20f/9DbkuSfEKHr43BxPmcy0vCIehw6eiIdCtiwdjR7dRPk3X Up14pNREJiBkJQlScvk4b/ZU0DDdijWjZsXu9ZEeSq8XePpVtjHZC06IA7SJkRMTiJdA7/1S8OSU zvFmE/f99P0nv3rkyihZk9S0eg0yuUdIVs4Zt/agjTuzDSZO4OF7a5z4PBMwg0D/fs4QI6MtNFPi 1JAYRh1Vvnr8qNEgkncNyQprmmg7kESJLPA4MQG7CGRkJL4F5CEDUvGfu+0fGTXv42QWJQ2o0gSQ lNuFnShGxsNJ0SztcvRo1IFf3UugPs64jhr9VUjgdPxR6Zh2xz6ObWGyilIDtNqkXENyshgZ35Kz R2aB/jAkc3wUgwW/RkegsZHX11oiNuzkDNzy5w4tnXbMcRIlSkm1ppRVvUOTkOodNzmmmwFVYvT6 73GVzGzqmSMy9fqmpyX2L1YzmXFeTKAlAqOHZyoTI4pAa3YiUco/I8vsbJ2aX13x873qvIDY4dQa ml0vVWJ0273b8elXTdbzNN1mZiI3I5QK36pC6WYOUWEmW84reQgUjMoC7flTkcgZK/m/o2SMbMwq xwgcakQiMCtfB+aj65AmZHIIkhVi9MRLO6FipESi9OIjXXFQ3xQHPkdcJSbgbAIXntvGEjGi6bWX Z5lvtk2ilAQjJX2mToOQCT9CskKMjK8kiZIqV0CP/qszjj5MndNHow38ygQShQBto7gwX81Witsf 2D0yMniRbzoWJYNGNK9NAyNNAluiuc1t11opRgabJ1/eielvmv9LifK/99ZOGHJChlEUvzIBJtAC Ado+QdsoVKS/3bMdy1eE9rqmUpTMXhJQwSa2POVmuk8LaFpZbBk4/y47xMigQh4XVPxSovxvu7YD Rg3j8BUGa35lAs0JXHNpe6j64339Hdvw+dfhPa6pEiUSWTLOSMC0ldrkFVLobxKtgXaKkcGSHkpy B2L2Qiflf93E9qANjDPnJp+ncIMvvzKB5gQ6ddBwwTltlfmlu/KWrfh5XWPzYkN+pu8/JcNXXciL Yjh47WXt9bso5HrCJCFLqS3erBp/WU2CzQA5QYyMB4UWOn0+4PLzzJ/HvuK8dsjK0KDC5NSoP78y AbcQ+EOfFDx2jxrvCxTldfa71Vi/0RcVDhIl8lFp9tRhoomSDDQtHWnz5uWQzCaM1DpJjIwnd8bc KvzvJTUOMcj31VUXqjFnNerPr0zA6QTI+4JKMZo+typqMTKY0fS9ijVlEqVEmb4T0DYRLyNM2gYD nptfad7Y2LdjZjuC9xnFmi/tI3jkGTWO1c85PQvU9pQU3kAba//wfe4lcFpeJu66uaOSBtDI6OnX dmLzlvj2AKoUpTOGun9NSRMB3ZZBjxgrpVgvhDxQSY9alCn9QSbPBmanux4q37XpNd68ac63vkHi 5qvMd11CbScXZLPnV2N9aXTTCvG2i+9nAnYRoDhGKqbDqT1vLqjBo89VQJrkickILWP29N31l7fX 16oXFNfa1Q1xlys0/EqZ6CMkoQXWx52jjRmoEiPagf3+7zuwzWoePTQkcioSjQ6fe6gLDh/Ee5VU 8OU8nUWAjIVUidGc+dW6H0mzxMggp2qkRD9yR+S61hjA37ApRx8hNU3ZSbhWkFSKkeEOxHiYzHol kbv5n9vNym6vfKbc3glDT3Ltw7lXe/gAE2hO4OqL2imLHUTeVh57Xs2aL7WDREmFRxcXi1JpcbHQ p3V0QZJC6MOl5p3u9M9uFCOD6Zff1uNPt2zFO4vV2JP87S8dcM5p5vrVM+ruhFdaxD7qEB4JOqEv rKxD504e3eHwGEXP9qtzqkDeVlQnVW7GXClKQQMifQ0JkL8C7loQd7MYGQ/7T+sa8eLrVfD7ocQY 46qL2qFDew3PvGa+J2KjDXa9nnB0Ouh/8Ue1mLuoBt+sijMIkF0NMbFcjwf6jxBVTkRNrGpMWR06 IBVTFcYxou0TVoZ7MITP7A28JEqNPmDJMpesKQUNiHRB8gY8P/pFIKaHxI6bEkGMDG5bt/vx3xeb fpGpsBAcf1YbZKQLPPqc+l99RpusfM09PgP0f+F7tZi3sBrf/RTZpkUr66i6LCEAGjEksvk/rY/Q H1pVibZl2OFRW5Uo3XZNB/h9cpcXclXczMlXrjHy8dCbIwZOqawVlbcCQv9snHTiayKJkcE3EAA+ +bIebdto6N8v1Ths2utBfVPRsb0Hm7b4UbHTGT88AhIgsTQr9TkgBWT+26mjB1u2BVBe4Yx2mtW+ UPmQEJ11ahZ0p7uHmjd9+eaCauxwyHNC7R53Vhtcc0mTd4JQHOI99u+nKvDGu2qmziOp2+ff1CMz Q8OAA8397p9yXAZ+2eDT/0dSD7uuEZDPrymZ9iWVrwvQqlV3yt4DbzofgHPj+gL6XhsVpt3B8Uzs 6hQq97Ov1DyYlDftYj9zRBbW/OrDr1HuNlfBhKYUzj2jjel7pw7snaL7+WvXVmsS4MrEFKbTh2bi v/d2xjGHmydERj/TNHJdvUm2zkamMb6SN5ILzjXvh0vzakyeWo6iD+yf2iJRUvGD1BWipImpa7+d qtsx7BoR9Rk4aSSAfs07zCmfE3FkFIotPZgej8Ah/c39tWSURdNbldUSqx0wtdW/Xwp65vy+jGlU 0KRXGhWSAGdladj0mx87qxJDmIafkoHrr1DnXLeqOoDnZ6rxVB9N12ZmCPzpwvagTd+q0rW3b8OK b8I7SVVVdqh86QdpMoqSP+C9bd2qKfpDt0uQeg2adAyAY0OBsvtYsoiRwfmrkgb9F+qRiqzIjjks TRc9KsfOJCFw8rHpSqswoF+qPq2Vnq5hQ5kP1TXO+OUfbaPph8S1l7bX14q6dNr1tY02m1avX/5F vel771ottNkFBx+UiosK2mLkYPM3ulNR5H2BRkY0neW0pFKUflzr078DDmvzzqWF3f5u1GnXk917 0KT9AZxhnHDKa7KJkcG95IdG/LY1gOOPVvMHm0ZgNK21YmW9aTvRjbpH+kpThxeca77T2VDlD/xD qm6BlpoisHa9zzFTUqHqGnzsxGPS8eeLm+L67Ntl19c1+BJT39Pifumm+NzkxFMhcoMzeVJH9NpP TXRk8r7wyLMVqKl17g8TVaJEcdRoZmSjjf0b4tn4Zm3J1KeM47ue8D4DJmVA4FLjhBNek1WMDPZk Fk4PUJ6iTa40rUW/trduD2BbufVTWrQLnkYsVkbBHXRQKgpGNa1d/bCmEY0ONcr74xFpuPrC9nro gpx91UxrGs+Z8frdj436pk3js9Wv5HlBpck6bUb97wvusDZVJUr0t8RhorR4bcnUN4xnbZcg9Rt4 fVVAaLcYJ+x+/csl7fSpFrPr4RQDhkjbRb9mPvi0DgJCN0yI9L5Ir+vbKwW0QL55qx8/r7N+CoP+ CLZv58FBfdX8Im6JA00LkZUfrddRHWgvmBPSUYem4U8XtsPFBW3RI9saITLa/ez0Sqz5xfpngJwC k+cF+qGgKlGwzKddth+PREnFd8NJoiQhX1tbMu1Do993CdLPqx6q7T3wxisBYc0cilGDEK80RXH2 SPMXM+9/bAeWfBg67HCIajjm0I6KAFaubkCbTE2JKFFDaZMppa9t2GBK0Tc7tPMoa1u4jqSpSwrh QRM4q35oBJng25EOGZCKyye0xcQJ7ZQZeoRr18x51SicZ32wx4EHpuLisW11k/1w9Yvn3JMv78Qr c+w31IilDZ9+mdiipEnt32tWTf3RYLNLkOhArwE3jxQCvY2TdryqEqMpj+/QN0/a0SYzyiQzaZV7 laiOhw5M09eVSJSsHDHQ1N2n9GuwrfUjJaNvDhuYpk+PEWea0rBKmPr3TcGl49rpI4QDelo7SjTa To5En/h9c7ZxzIrXkUOawkb03l9du+/9zw68VWTfHiMzOKoUpa9WNcQdWiOeNgrg1jWrpu6Ky7OH IPUeNOkQAMfHU0A896oUIze7Zg9mSsN4r1fgYEVm4bSuRCOGb75r0PfxBJet+j198eoaJFRZF0ZS /yMObhKm2jqJ739uVGbw0Wf/FN2SjEIH0KZeuxK5ynnyFetdS106ri2uPF9tYMnrbt+m/4izi62Z 5aoSpRG5mfji2wb8ttWWOettiwtzCjVkfQAAIABJREFU/i+Y056CNHBSVwBjgi+w6j2LUeSkv/y2 AVWKjQGGn5Jpy36lku8b8frb1boQqBLdSEjTWs4F57RFTZ3U15giuSeSa2jf1QXntMHNV3cAbeK1 Ky1fUYfHX9iJtxU5922pXZ06aLj8vHYw239bcHlk1v23e7Y70cQ5uJpRv1clSqcOtkeUpBTL166a +mIwiD0Eqc+gSWRz9OfgC6x4z2IUPWVaiCevC7Q/RVWi/UoZGZrlmwdp2oxEd96iGgT8AFnG2ZVI mC48ty12VjWNmGKtR86+Hpx/Tlvcek0HJe6hIq0X+fp7bnqlbk1ntfkvmbBTmHHyGqIqGaEjGhqc a9YdT9tJlMgNmNkM7RAlTcjZa0qmLQrmsYcgHTngwfJaUXUTAHVPTHDppH6KDBhozShRpumaIdv1 kfbxqLTAo4Jo0blrZ3tMw8l9DU0nLHivVl/Tor1EdiVy0UPCtG1HAD+uidxWvMs+TUL09+s7mu6r LFoW0/5XgUeeqcDPNljSnX9OG9D0pMr0YmElnplu/fSjyjaFypvWkhNBlITEY2tWTVsZ3Ma9Yk7k FWxcDghLPDawGAV3RezvyckmsSRHmyoT/TGjMOx2pe7ZXowelqnUnUwkbaNRBnkWX/R+bYtrTB3b a/pGXHIManci56E0NWd29NNI2kWbry/KbwsVPiiDy3/46Qp9RB18LNHfX3dZeyVha668ZSt+Xhf5 j65YOQuBAUUzcr4Lvn9vQRpb+m9IXBd8kYr3LEbmU504vq3uGdn8nHfnSFMihsv83Uetfdeze5Mw qdgaEG1LSJRopEpGEB4N6NhBQ98DUkBTfXYnCjlCnrvtECJqO7mF+scNHZVj+L/7tieM8UK0sFSJ 0hU3b8WaX5WKUsVJA7I7TZ68Z9yjEIK0cRykeC1aMNFcz2IUDa3orqXF4j9doNZ6iUK70wjBbl94 ZKlGMaTI3Qyn3QTI/Y9hGLL7qLXvaGMvTdOpTGS88PLsKmwrt8VCTGXTosrbpaK0aPHMnOHNG7rH GhKd7HPQLTXQpLIREotR8y4w9zNt7ly3wQdyO68qHdDDC7LCa2gEvv3ePgetFPPo4y/q9bAdZApv p/m0KtbR5EuRgW+9dzu+/V7pL9uwVdqvu1f3MpE/Su30MW3kJTdANDJN9kTfQRWb5unH3rJP69XE FhPylbUl04qb991eIyS6YEhB6W8C6NL84ng/sxjFSzDy+7t38+Lc07OUzDEH14KcVU5/swpbttn/ K5W8HdAak0rLw+C2O+X9C4WVeO2Navh89v5xpj9g9GtddbIruqvqdsWTf1amwMTx7ZR835VM3wlt 1OIZ3d5q3ua9Rkh0Qe+BN51EMd2aXxzPZxajeOhFf29lVUCfV8/KND8SZXBtyAcdCd9v2wIgZ7B2 ps1b/Hj/kzqQp3QK206/1hM5kX82GhF9sbLBMs8SoXimpQndKSo5R1Wd/jGlPOGtZ2NhSE6CVY6U Fr5Xq+99jKVuoe5Jlbj+p1VT97KQakGQbuwJiGGhMorlGDlOHHOa+UP4ZDDtjoV38D3kJ67BB5AH ApXp+KPS9TDMX5XUg8KT25nKNvtRvLwOZA1Hgd5UBQG0q43kXeH2B7brU5VWungK1V4ajf7v/s6g uFMqE60X3fVQue7WSWU5bs5bpSileIW+FGBSPLHvFhbmTAnFOqQg9Rn0Vz8gJ4a6IdpjtMCuIuoj mbLOX2J/6OFoedhx/berG/S9J4MVbqKldg04MFXf/PnLRp8jgp9RXJ+lH9Xhp3U+PRJnTjd3j5im v1GFO6eVY/mKekeEzZg4oa3ug0/1M134VjUee36na4MrquYTnD+JEq0jZ2WY64iZNuKmmidKs9eW TN1ruo7aEVKQ9usyZZOWVTUJQFw/e0iMVLgIof0wtIufU+QE1pf68M6SWqSlqgljEVyTU47NAE3j kLcFJyRqe9EHtfi11I8O7TRYEejOzHbTAv49/9mBDz6pc0RgwWOPSMNVF7XHqbnqrRtpi8FLr7vT U7eZz0A0eTU0Sn00QwJipkcHs0RJSPlQ8w2xRvtCCtK6dXcG+gyYdAoE+hgXRvuqUozs3JwZLQcn XU9RMmmXN8WfoXhAKtOgP6Tqng0oTLRTQkWvW+/TvT5s2uJHp44ePTihSgbx5j3r7Wrc/98KFH9U 65jRAe11u3Zie0tiNd3yr+1Y/AHPgsTyHNHUGlnbOlGU/H55/brvpoWMlBhSkAjAAQMn7S8EhsQC g8UoFmrW3UMjF4oSe9xRasKjB7eEzM8zMzR8/Z29C+/BdSLXOfOX1FjGILjsSN6/8W41pjxeoY/q yDjFCYnWCGktmLxDq07U/kl3bQeNbDnFTkC1KJERRdQRlwXWLH29+90ttapFQeo98AYfhLispRtb Os5i1BIZZx3/cW2jbpGmKhJtcGv1taUxbWyLShtcl+D3xOClWbSxMoDjjlQvzsFlh3pPcXumPlGh W5FV7HSGENH+LgoaSBGcaSuB6vTcjErQfiq7jTVUt9Oq/FWKEu19ilqUBApbWj8iJi0K0lEDp5bV iqprAUT8TWUxsuoxM6ecHTubTMPT0zRY4biUotKSb7MNZT49tIU5rYg/F3KWSsK0s1KCnKhancik lox0SJBos69T0ojcDDx+b2dLng1qM5l0v2NxOAynsFZZDxKlX0t9GGNyFG5aU4pWlATEA2tKppa0 1N6QG2ONi/MKSmcDONv4HO6VxSgcHeefOy0vEzdeoX5To0Hi6dcqQVZjTkvkqJb2VV2p2P0StZvW huYuqsE3NoSND8edfAWeOTxTubNeow40RUcjI/rDyUkdgd77peDJKZ1NL4BM8p9+LSIryIBX+rss KOy5vaVKtDhCohv6DLipEwROb+lm4ziLkUHCva80fbXkwzp4NHMtc1oiQvuiaCqPDC2ctlZAZrOv zKlCQ4Oa/VsffV6nmzFPf7Pa1vDRofrmgnPb4I4bO4IiB1uR6IfJs9Mro1+LsKJyCVYGjb7JFRB5 1DAzGSOllasbQLHMwqTPFxX2eCTMeYQdIeXmr+/rEZ4fw2XAYhSOjjvP0Y57CmNuVZozv1p3Bkqe FpyWPB6A9m+dPjQzbstEmpKjX5M//2KvR4tQjIeckKH/oVJtfWmU7RQHvUZ9kulV5Ujp4WcqwqG8 Z/HMPUOWN784rCDRxXkFpd8DOLD5jfSZxSgUlcQ4RtE9J09SHzogmBYFWCPvzQHnLKMEVw8d2ms4 8uA0HH5wKg7okYIe2R60ydL2uMb4QKbltFb2/U+NesTdVT80wOc8vdVDZYwengmasrUq0b6qJ18O afVrVRWSvhw7RElInFRUmLMsHPxIBOlBALRJdo/EYrQHjoT8QPuVaDf+OQrcPoUDZncgwHB1a36O XBNlpGtITxfw+yRq6qTugbqx0dnrISSk487MwrgzrRsJE7up/yMPK7ypvflzZMfnfr1S8Ph9ataU QoyUtneS2V0LC0XYn2Vh15AIUu+BN9H8wkXBwFiMgmkk7nsaqZAvvPKdARx7RMTGlnED+eMR6Ti4 f6rulYCC3zk50Zw5hUCg/UJVNRL1DdKxIzziqGnAhLPb4P7/64RBijdHB/cbGS7c8eAOfPOdM7x3 BNctWd9v3xHAF9824NTB5o6OaU2JQqzTJvygNOutwnazgj6HfNuqIHU64cGN6XW7zb9ZjEJyTOiD P/zciIXv18LjESDv3lak7K5e5B6Xgb69UnQT8dLNYX9YWVElV5dB1oMU4v4/d3fG4YOsNW0n9z/P z6zi2EUOfIJ+2+q3RJSkEHevLZm6qjUErU7ZUQZDC0pnSiCfpm9UDPEp0Nbsd6pbqyufdwABKyLS hmqmU02kQ9XVScdIiGh96IbLrTPpN9pPBhxvLqwGuWzi5GwCNFr+9537mF5Jipf2n2d3NAqZ0aWo sFNYiwcqPCJByhu7cdzE8e1eUyFGHGzL9GdAeYb79/DirBHqg/+FasiyT+t0x7orvtljOiDUpUl9 jIRoxCmZuOkq64WIwD/1SiVmzHXePrOkfihaabwqUVr6Ue36ISdk7tdK8frpiASp9LfGU7O7eOdH kmE017AYRUPLeddSWJGrLmxnS8U+/6ZeN6H+8LM6W8p3aqFkpj5qWJbu6seOOvKoyA7q5pV5+KBU TLnd/JESgH5CiJ9aq2lEgiSlfA3AuNYyi+Y8i1E0tJx7bbcuHj3ECK1P2JHW/NqoC9O7xbVwumWb Sj5tszR9H9Gl49RHbW2pHbRW9PpbPPXeEh+3HD/msDTcc2sns6vbTQixubVMIxWkCwG80FpmkZ5n MYqUlHuus9r1UCgy09+swluLakB7gJIl0X4S2nk/api5llLR8CMLOgqi58SNzdG0g6/dTcBkUSoW QgzenXvL7yIVpA4AylvOJvIzLEaRs3LblakpAhcXtEXBaHtGSwavT76ox4L3an6PrOrs/UBGnaN5 peCHucel47QhmZY5Pm2pfryvqCUy7j9uoihdI4R4NBIiEQkSZSSlfBPA6EgybekaFqOWyCTW8aMO SdN/tZN3b7sTWfkUfVCD1T81QrpYm8hI4ZD+qcg7McNSrwot9R+NRskHnVO9arRUbz4eHQGTRClb CLEpkpKjEaSxAKZHkmmoa1iMQlFJ7GMFo7Jwxfn2GD2EIkvrGxQGfNWPDa4QJxKhAf1SccIx6SCW TkhLljV5KP92NW9wdUJ/WFEH+mF5500xuxFbJIQYHmk9oxEkmqSOacWSxSjS7ki869q20XDe2W10 wwcnta7og1p8+mU9Vqysh1OC4RGfrEyBwwam4Y9HpOlTck5i9uhzO0HrRZySj8Apx6bj9htiEqVL hBDPR0osYkGiDKWULwK4INLM6ToWo2hoJe61hw5IxejhWTjlOPun8ZpTXrfBh5XfNej/v1/TiNJN PstGUB3bayBXKxQgkTwoWOUJozmDcJ9fnVOFFwo5ims4RslwLkZRai+EiNiTbrSCdCqAiPcjsRgl w2MaXRuHnZyBW/5MNjLOTuRzjTwMbNzkQ+kmP0o3+0ARdmMZTZGT2k4dNHTu6EGPHA96ZnuxXw+v 7mm7a+dWvXfZBmreoqZwGWRaz4kJEIEoRalQCFEQDbloBYm+PRH5AWExiqYbkutaWhspGNUGl59n 356ZWIn7/UBFZUDf80SOVBt9TZYSXo8Atcv4nJmu6Y5MMzMFaI+QmxJZKc5dVA165cQEmhOIQpRO F0K80/z+cJ+jEiTKSEp5L4C/hcuUxSgcHT5nEKCRw4Sz2oCilHJyBoEHn6jAu0s5PIQzesO5tYhw psMrRPhwE81bGIsg/QHA6uYZGZ/Zh5VBgl8jJdC+rYb8M7Iw7iwWpkiZmX0dGSy8uaDasrUzs+vP +VlPYERuBm6+qsXp9/uEELdGW6uoBYkKkFJ+AODE5oU9N6MSr8xmh4rNufDnyAjQesroYZksTJHh MuUqms14/W0WIlNgJmEmYUTpICEERRuPKsUqSJcCeCa4JBajYBr8Ph4CZHlGUWp5xBQPxfD3khDN nl8NWhPjxATiIRBClJYJIU6KJc9YBYl26e0aCrEYxYKe72mNAIXZPuvUTN0dUWvX8vnICDz8dAXe XlzDHhYiw8VXRUigmSidJ4R4NcJb97gsJkGiHKSU1wJ4+LHnd2LOfN4stwdV/mAqAa9X4PS8DFxz qT2xfUxtjA2Zfb2qAXMXVuP9j+t4jcgG/slSZJssbdurj+57aVaWmBtrm2MWJCrw7oe3n7j0wzpa T+LEBJQTILPqIw9Jw+jhmTj+KOdtsFUOIMoCFhTXYt6iat2PX5S38uVMIGoCAuL+opnZYS2wW8s0 LkGizPPyS5dCILe1gvg8EzCTABlAnDE0ExPOZsu85lzJ6ek7S2qwoyLQ/BR/ZgKqCASE39OvaNa+ a+IpwARBKjsXQhbGUwm+lwnESsDrAY45PB3DT8nAicck76jpvY/r9FhQX5XU87RcrA8T3xczAQEx t2hm9pkxZ/D7jXELUm6u9Hq6lv0MIKKY6fFWmO9nAi0RoP1Mg0/I0GMFDTootaXLEub4N6sasPjD Wn1tqLKKR0MJ07EubIgATi2ambMg3qrHLUhUgSEFZTcJyCnxVobvZwJmEdinowcn/TEdJx+brscR Mitfu/MhH3sffV6H95bXYcs2ttm2uz+4fJ3AqsUzswcBIu6IY6YIUu5Zazt4UtPWA+AJfX5CHUeA zMePPCQVfzw8HUcflgba5+SmRNNxH6+ow8df1INHQm7queSoqwCuKJqZ85QZrTVFkKgiQwrKHhGQ 15hRKc6DCagk0LO7F4f2T8XB/VP10VOXfZzjcZsct67+sRG0FvTFtw1Y84u7I92q7EfO2xEEttZI /37LC3vWmlEb0wRpeP6mXn4R+AGA14yKcR5MwCoC7dtpOLB3CvockIJ+vVLQM8eD7vt6kZZm2tcj ZFPKNvvx60Yfftnow/c/NYBiMW36jafhQsLig84kIMUdiwuz7zKrcqZ+4/IKSl8GcJ5ZleN8mICd BMhIonMnD9q1FaBpP4p+2yZT6EKV4hWgDbspv//8CgRoszjg+X2wVVsndW8IdQ0S9fUSOysD+nTb zqqAbo69dbsfPtYeO7uXy46fQJVX+vdfUNhze/xZNeVg7mjG438Afs8EAKYKnVmN5XyYQDQEaPqM /nNiAkxgbwISeMpMMaISTF3dXfxaz29EFBFl924iH2ECTIAJMAEXEGjwavIhs+tpqiBR5aSG+8yu JOfHBJgAE2ACziEgIF5ZOL07WVabmkwXpMXTcz4A5BJTa8mZMQEmwASYgFMI+ODX7lZRGdMFiSop Ie5UUVnOkwkwASbABGwmIOTL8fqsa6kFSgRpycyc9wEsbalQPs4EmAATYAKuJODzBwL/UlVzJYJE lZXAZFWV5nyZABNgAkzAFgKvFBf2/ElVycoE6fdR0iJVFed8mQATYAJMwFICDR6pKV2OUSZIhEnT cFvTYMlSaFwYE2ACTIAJmE5APrWwsNta07MNylCpIC2anvM5gFlB5fFbJsAEmAATcB+BGq8vVdna kYFDqSBRIR4/bgfgMwrkVybABJgAE3AXASnwyILZXcpU11q5IC2clbMaAi+obgjnzwSYABNgAkoI bGvwND6gJOdmmSoXJCrP25hCo6TqZmXzRybABJgAE3A4ASlw97JX9y+3opqWCBIN9SQER5S1oke5 DCbABJiAWQQEfqxon/2YWdm1lo8lgkSVyKqlEOeitLUK8XkmwASYABNwBgEZwC0rnhSNVtXGMkGa Ny+nRorA361qGJfDBJgAE2ACsROQwHtLCnPmxJ5D9HdaJkhUtZP757wACTIF58QEmAATYALOJeD3 yMD1VlfP8kB6eWPLjoaUH5sdi8lqcFweE2ACTCBRCUghH18yo/vVVrfP0hESNW7xjOzPADxrdUO5 PCbABJgAE2idgAS2BOobyMuO5clyQaIWpkpBjd1heWu5QCbABJgAEwhPQOC24jd62fL32RZBml+Y vQVC/F94KnyWCTABJsAErCQgpPzk5P7Zts1g2SJIBPik/t2eAPS1JCt5c1lMgAkwASYQmoDPr8kr J08WgdCn1R+13KghuEmDx244VJMaWd15g4/zeybABJgAE7CYgJBTFs/o/leLS92jONtGSFSLpTN6 fA2IaXvUiD8wASbABJiA1QTWeVI9SmMdRdIgWwWJKphZKwmC0hgbkYDga5gAE2ACyUpACnH1wpe6 2e5v1HZBIg8Omha4nAP5JetXgdvNBJiAzQReXjIje77NddCLt12QqBaLpvdYDOBJJwDhOjABJsAE kojAJq/0X+eU9jpCkAhGXV3KzQB+dQoYrgcTYAJMINEJSImrFxT23O6UdjpGkD6c26VSCslTd055 MrgeTIAJJDgBMd1q56mtAXWMIFFFl8zovhCQj7dWaT7PBJgAE2AC8RAQpV7p+3M8Oai411GCRA30 pHnIDv4HFY3lPJkAE2ACTABSQF7qpKk6o08cJ0hkehjQtAsB+IxK8isTYAJMgAmYRUD+t2hmzgKz cjMzH8cJEjVu6fRun0DIe8xsKOfFBJgAE0h2AlJgdWatsNUbQ7g+cKQgUYX9m3P+CYmPwlWezzEB JsAEmEDEBBoAnEd7PyO+w+ILHStIxcXCJ/2YIIByi5lwcUyACTCBxCMg8dclM3K+cHLDHCtIBG3J 7JxfpBRXOBkg140JMAEm4AICby8uzH7E6fV0tCARvMWF2a8L4Amng+T6MQEmwAScSUCU+j24GBDS mfXbXSvHCxJVtVr6b4TEl7urze+YABNgAkwgAgI+aHJc8Ws5WyO41vZLXCFIywt71krNkw+gwnZi XAEmwASYgEsISIhbF0/P+cAl1YUrBIlgLpmx789CyIvYK7hbHi2uJxNgAjYTmLNkZrepNtchquJd I0jUqqIZ3d+EkA9G1UK+mAkwASaQbAQEfhQy/RI3rBsFd42rBIkq3imQc6uUgsJVcGICTIAJMIG9 CVSLgHZOUWEn1y1xuE6QCguFPw0YD4Ff9u4HPsIEmAATSGoCUgAXFRV2W+lGCq4TJII8vzB7SwCB MwE4dsexGx8GrjMTYALuJiCBfxXNzJnl1la4UpAI9tIZPb6WQlzMRg5uffS43kyACZhKQMq3Th6Q fYepeVqcmbC4PNOLyysoux2Qd5meMWfIBJgAE3APgZVCpp/kxnWjYMSuFyRAiryCspfIaWBww/g9 E2ACTCAZCEhgS8AXOKJ4do8Nbm+va6fsdoMXspMsvxSQH+8+xu+YABNgAklBoEZq2qhEECPqrQQY ITU9dCPzy7o0CnwkIfsmxWPIjWQCTCDZCUgJed6Smd1fSxQQCTBCauoKsrzzSd9IGr4mSudwO5gA E2ACLREQErclkhhROxNmhGR02uBxm/6oBQJLAGQax/iVCTABJpBIBKSQjy+Z0f3qRGoTtSVhRkhG x1D484CU4ynorHGMX5kAE2ACCUTglX0COdckUHt2NSXhBIlatrSw+1wJ/IX3KO3qZ37DBJhAQhCQ S8iIizzWJERzmjUi4absgts3NL/0b1Lg3uBj/J4JMAEm4FICnwqZPtzte43CsU/IEZLR4KLCnPsE xP3GZ35lAkyACbiUwKpUKc5IZDGifknoEVLTg6dvnH0cwJUufRC52kyACSQzAYFf/I2BExNlr1G4 rkzoEVJTw2njbPafAbwSDgSfYwJMgAk4kMAGf8A/NBnEiNgnwQip6RHLz5eebaL0VQFR4MCHjqvE BJgAE2hGQJZByNzFM3r80OxEwn5MGkGiHjzyCpnSvrxsuhAYk7A9yg1jAkzA9QRog78mcErRjJzv XN+YKBqQBFN2u2mseFI07oPy8QJi7u6j/I4JMAEm4BwCuhhJLS/ZxIh6IKkEiRpcWDiwoaPcni8l ZjvnEeSaMAEmwAR0ApulRwx2a8TXePswqabsgmHl5kqvZ99NL0PKscHH+T0TYAJMwB4CotTjl3kL Z+Wstqd8+0tNuhGSgby4WPg6BbpRDKWXjWP8ygSYABOwicB6v/SdksxiRNyTVpCo8eR+o5PMpjDo /7PpIeRimQATSHICAuInvyZOLi7s+VOSo0ges+/wHS3FkLFl9wiJv4W/js8yASbABEwkIMU3fnhG FBd23WRirq7NKqlHSLt7TcglM3JuFVL8lR2y7qbC75gAE1BIQOKj+pSGXBaj3YyT1qhhN4I93w0Z WzpRSDwBwLPnGf7EBJgAEzCNwLuZtThn3rycGtNyTICMWJBCdOKQgo2jBASFBc4KcZoPMQEmwARi JiAgnivv0O1K2hcZcyYJeiMLUgsdS5FnRSAwTwBdWriEDzMBJsAEoiQg/7l4Zs4dgJBR3pgUl7Mg henm3Pz1fb3CO19C9g1zGZ9iAkyACbRGwC+Aq4pm5jzV2oXJfJ4FqZXezx1f2tnjxxwAJ7ZyKZ9m AkyACYQiUCGAsUUzcxaEOsnHdhNgK7vdLEK+K34tZ2tqZfVQ3kAbEg8fZAJMIDyBtQGPOIHFKDwk 4yyPkAwSEbzmFZTeCuBfyRS2IwIsfAkTYAIhCYjlnkZx1sI53X4LeZoP7kWABWkvJOEPDMkvPVsI vAigTfgr+SwTYAJJS0DI51N31vxp/vx+9UnLIIaGsyDFAG342E2DAlLOYWOHGODxLUwgsQn4hJA3 Fs3o/p/Ebqaa1rEgxcj1xAm/dEzzpbwK4NQYs+DbmAATSCACehwjTRQUTc8uTqBmWdoUNmqIEfey V/cv7ySzzwBwL7sbihEi38YEEoWAxOfw4WgWo/g6lEdI8fHT7x6aX3a6FPIFAPuYkB1nwQSYgIsI CCEeTdlZdROvF8XfaSxI8TPUcxiev6mXH4GZEDjKpCw5GybABJxNoAoCf1o8I+cVZ1fTPbXjKTuT +mphYbe1qVXVJ9KvJZ7CMwkqZ8MEnEtgpcePo1mMzO0gHiGZy1PPbejYjWdKKZ7hKTwFcDlLJmAz ASnk44GMhhuLn+9VZ3NVEq54FiRFXZo7ZkMPzau9LIBTFBXB2TIBJmAtge1SYuKSwhxyJcZJAQGe slMAlbIsnt1jwz4yOw8Q/wAku5lXxJmzZQIWEVgqJA5nMVJLm0dIavnquQ8bV3pUIACywhtgQXFc BBNgAuYRqBPAbScOyH548mQRMC9bzikUARakUFQUHDsuf31Gpua5FxLXsi88BYA5SyZgNgGJLwFx /uLC7FVmZ835hSbAghSai7KjeQVlgwXw5P+3d26xVVRRGP7/PbW2oqYgSIGC8a6QoPLgJYKppYL1 Fi+p0AcUrw/6oDHxHhU1XuOLiYmJqVGjRvAIGjERQ9GCGNEgmGgUlYhSORRRqQhKy5lZZho1EUs9 9zMz5386ObPXWnutb03yZ86Zvbe2HSoZYgUWgUIJDBj48K8NjQ/rVNdCUebmL0HKjVdRrAeflliz ALBbANQUJaiCiIAIFEzAgA/DPS8CAAAGbElEQVRovF5PRQWjzCuABCkvbMVxap275RQL2AlgWnEi KooIiECeBHYCvGvG5Man9V9RngSL4CZBKgLEQkI0N1uNO7z3ZsIeAFBfSCz5ioAI5EOAS/2Mf0P4 Zmw+3vIpHgEJUvFYFhSp9bJtRwUueIa0mQUFkrMIiEC2BLbB7KYVqQmLsnWQXWkJSJBKyzfH6MaW Oen5ND4OYHSOzjIXARHIjoABfK7GMre+k5r4S3YusioHAQlSOSjnOMfs9p5RGboHAV6vlx5yhCdz ERiewDrS3di1qHHN8GYarQQBCVIlqGc55+BLDz6fBDEjSxeZiYAIDE3gJxrunj5lXKdeWhgaUBSu SpCi0IVhczC2XJ6eS/BRAJOGNdWgCIjAvgQGQDzl9/c/2P3GkX37Dup7tAhIkKLVj/1mE65dGgHv JiNuB9CwX0MNiIAI/E3gdd/827pTEzf+fUGf0SYgQYp2f/6TXXNHerTn2/0ArgN4wH8MdEEEqpwA zT6C5+7QceLxuxEkSPHr2WDGze09x3j0FgDoAKBd22PaR6VdVAJfkLina9G41wFaUSMrWFkISJDK grl0k8zs6JmKjHsI5AWlm0WRRSDCBIjvYbxvlDW+lErRj3CmSu1/CEiQ/gdQXIZb5/SebkFwL4i2 uOSsPEWgQAKbaXx0JH55NpWaMlBgLLlHgIAEKQJNKGYKLe1bTyXtXgDnFzOuYolAhAh8R8MjI7Hj eQlRhLpShFQkSEWAGMUQ4aGAvo87SVys/5ii2CHllAeBrwh7fEfD+Bd1LEQe9GLgIkGKQZMKSfHs 9i3HO8dbYZgHoLaQWPIVgQoR+JjEY9NPHPeGFrVWqANlmlaCVCbQlZ7mnLmbxwfm3QzjdVrHVOlu aP4sCAQElhn4xIpXx72Xhb1MEkBAgpSAJuZSwqx5vSP8Absy3OUYwHG5+MpWBMpAYDdgL4D25IpF TV+XYT5NESECEqQINaOcqSxYYG7lF73nerAbDZgNwCvn/JpLBP5FgPjGjM8EA3s6tcXPv8hU1RcJ UlW1e+hiW9vTkwLyKsKu1n55QzPS1ZIQ6AfwGh07uxY2rtRi1pIwjlVQCVKs2lXaZMOnplVf9s5G YNeSdqG2Jiot7yqO/hkMz9bAf1HnEVXxXTBE6RKkIaDoEjCzY9tY8zPzSV4Dw7FiIgIFEtgF8FWY 37ki1fRhgbHknlACEqSENrZ4ZYXHX2ydQeIKGi41YGTxYitSwgkEAFbC7OXa2rrU2y8ftjPh9aq8 AglIkAoEWE3ubW3fHDhwyIg2g3UQvBBAfTXVr1qzJGBYC+AV52UWLl84KZ2ll8xEABIk3QR5ETjz ou2H1B04cB7Jyww4D8CIvALJKQkEDLCPaG4JAre4a/HYb5NQlGooPwEJUvmZJ27G8PDAg+iFr45f YkAbgTGJK1IF7UsgA9gqI5cGe4PXupc0/bCvgb6LQK4EJEi5EpP9sATa28372UufxoAXwHg+aFOH ddBgnAj8BGAZzN4i6pd1pUb9GqfklWv0CUiQot+jWGfYfOkPTTU1nGVw5xhspp6eYtXO8EiHNQSW +84tH+2PXavzhmLVv9glK0GKXcvim3C4zmn1hvRJ8F2rITgL5HTtqxepfvowrIfDKgZ8NwNvZXfq 8F2RylDJJJqABCnR7Y12ceHPezu89FTzeRaIGQBOBzAh2lknKrvdMKwDsdrI9/v/qFn9wZtjfktU hSomVgQkSLFqV/KTbWnvmeCcdxrMzjDwVAAnAzg0+ZWXvMKMARsIfGLAGs+CNXu3T/i8u5uZks+s CUQgSwISpCxByaxSBIwtc348CgimATaNNihQk7Xn3rD92EmzL438FMT6gO7Tg3cHny1dOv73Yb00 KAIVJiBBqnADNH1+BMJ1ULUH+ZO9wJ9ixAk0d7yZHQ3iaAB1+UWNlZcBCF+13mjARho2mLPPXcAN Xanxm2NViZIVgb8ISJB0KySMgHHW3HSTH7hjBp+iDEcYrYmGpsHvxMSY/AQY7oS9zYAewLYQDD83 G7DJkRsz9f2bup8/ck/CmqdyqpyABKnKb4BqLL95/qa62j21Y3xzjQDHGvwxNDfKYA2ObDCzBpg1 GFy9ozWY8QA4Oxhm9QT/efraz75+vxMIxQTh9gUg+mDYS2CXGcKxPwJaH8k+GnfArC8g+hyw3Rx+ 9PZie+DqtmqNTzXemar5T7boKrYfCqI6AAAAAElFTkSuQmCC"/></symbol><symbol viewBox="0 0 24 24" id="webpack" xmlns="http://www.w3.org/2000/svg"><path d="M19.376 15.988l-7.709 4.45-7.708-4.45V7.087l7.708-4.45 7.709 4.45z" fill="#fff" fill-opacity=".785" stroke-width="0"/><path d="M12.286 1.98c-.21 0-.41.059-.57.179l-7.9 4.44c-.32.17-.53.5-.53.88v9c0 .38.21.711.53.881l7.9 4.44c.16.12.36.18.57.18.21 0 .41-.06.57-.18l7.9-4.44c.32-.17.53-.5.53-.88v-9c0-.38-.21-.712-.53-.882l-7.9-4.44a.945.945 0 0 0-.57-.179zm0 2.15l7 3.939v2.104h-.016v5.177h.016v.54l-7 3.939-7-3.94V8.07l7-3.94zm0 2.08l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#8ed6fb"/><path d="M12.286 6.21l-4.9 2.83 4.9 2.83 4.9-2.83-4.9-2.83zm-5 5.08v3.58l4 2.308v-3.58l-4-2.308zm10 0l-4 2.308v3.58l4-2.308v-3.58z" fill="#1c78c0"/></symbol><symbol viewBox="0 0 24 24" id="wolframlanguage" xmlns="http://www.w3.org/2000/svg"><title>wolframLanguage</title><g transform="scale(.12121)" fill="none" fill-rule="evenodd"><circle cx="99.197" cy="98.946" r="83.28" fill="#212121" stroke-width=".841"/><path d="M182.529 98.828a83.406 83.406 0 0 1-39.14 70.721.064.064 0 0 1-.038.019l-28.62-35.665 23.71 2.612s11.385 1.177 13.978 0c2.373-.938 15.175-18.963 15.175-18.963s-36.75-23.23-49.312-36.032c1.434-21.575-1.656-50.269-1.656-50.03-9.251 9.234-10.429 10.669-19.68 19.203-4.028-13.04-5.923-17.547-9.95-30.588-12.104 9.95-21.337 26.799-27.977 46.48a78.68 78.68 0 0 0-4.23 5.094 109.774 109.774 0 0 0-2.667 3.66 114.558 114.558 0 0 0-5.132 8.002 172.555 172.555 0 0 0-3.403 6.051c-7.706 14.475-14.034 31.066-19.515 46.001a.858.858 0 0 1-.092-.184c-14.988-30.912-9.502-67.85 13.822-93.072 23.325-25.223 59.723-33.575 91.71-21.045 31.988 12.53 53.029 43.382 53.017 77.736z" fill="#e53935"/><path d="M101.452 69.178s-1.416-8.295-2.373-11.367c6.401-6.18 7.357-7.118 13.52-13.04.477 11.845.238 18.006-.479 32.481-3.55-3.568-10.668-8.074-10.668-8.074zm-27.737 40.778s-6.64-4.029-11.624-4.728c1.435-3.329 5.223-7.596 6.18-8.773-1.913.699-15.653 6.86-17.087 12.084a74.804 74.804 0 0 1 11.385 3.79 35.993 35.993 0 0 0-8.774 20.158s21.815-3.33 38.185-1.196c.283.168.609.251.938.24l8.534.239 27.111 45.136.221.35c-.037.018-.055.037-.073.037-51.133 18.485-88.085-15.543-95.976-27.443.034-.102.058-.206.074-.313 7.1-30.017 15.855-65.939 30-76.552 7.356-12.82 9.49-31.783 22.751-41.734 3.33 9.951 8.553 30.588 12.103 40.539 15.653 15.652 39.361 35.094 55.234 43.15 1.656.956 3.79 7.596 3.79 7.596l-6.401 8.056-68.276-6.879a54.462 54.462 0 0 0-4.58-.183 86.848 86.848 0 0 0-14.144 1.36c3.311-8.295 10.43-14.935 10.43-14.935zm22.054-8.774c3.789-.46 7.817.956 12.323 3.568 4.267-1.195 4.745-1.434 9.013-2.612-5.463-4.028-11.386-8.295-19.442-7.118a47.249 47.249 0 0 0-1.894 6.162z" fill="#fff" stroke-width=".936"/></g></symbol><symbol viewBox="0 0 24 24" id="word" xmlns="http://www.w3.org/2000/svg"><path d="M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2m7 1.5V9h5.5L13 3.5M7 13l1.5 7h2l1.5-3 1.5 3h2l1.5-7h1v-2h-4v2h1l-.9 4.2L13 15h-2l-1.1 2.2L9 13h1v-2H6v2h1z" fill="#01579b"/></symbol><symbol viewBox="0 0 24 24" id="xaml" xmlns="http://www.w3.org/2000/svg"><path d="M18.93 12l-3.47 6H8.54l-3.47-6 3.47-6h6.92l3.47 6m4.84 0l-4.04 7L18 18l3.46-6L18 6l1.73-1 4.04 7M.23 12l4.04-7L6 6l-3.46 6L6 18l-1.73 1-4.04-7z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 24 24" id="xml" xmlns="http://www.w3.org/2000/svg"><path d="M13 9h5.5L13 3.5V9M6 2h8l6 6v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V4c0-1.11.89-2 2-2m.12 13.5l3.74 3.74 1.42-1.41-2.33-2.33 2.33-2.33-1.42-1.41-3.74 3.74m11.16 0l-3.74-3.74-1.42 1.41 2.33 2.33-2.33 2.33 1.42 1.41 3.74-3.74z" fill="#8bc34a"/></symbol><symbol viewBox="0 0 24 24" id="yaml" xmlns="http://www.w3.org/2000/svg"><path d="M5 3h2v2H5v5a2 2 0 0 1-2 2 2 2 0 0 1 2 2v5h2v2H5c-1.07-.27-2-.9-2-2v-4a2 2 0 0 0-2-2H0v-2h1a2 2 0 0 0 2-2V5a2 2 0 0 1 2-2m14 0a2 2 0 0 1 2 2v4a2 2 0 0 0 2 2h1v2h-1a2 2 0 0 0-2 2v4a2 2 0 0 1-2 2h-2v-2h2v-5a2 2 0 0 1 2-2 2 2 0 0 1-2-2V5h-2V3h2m-7 12a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m-4 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1m8 0a1 1 0 0 1 1 1 1 1 0 0 1-1 1 1 1 0 0 1-1-1 1 1 0 0 1 1-1z" fill="#f44336"/></symbol><symbol viewBox="0 0 24 24" id="yang" xmlns="http://www.w3.org/2000/svg"><path d="M12 2a10 10 0 0 1 10 10 10 10 0 0 1-10 10A10 10 0 0 1 2 12 10 10 0 0 1 12 2m0 2a8 8 0 0 0-8 8 8 8 0 0 0 8 8 4 4 0 0 1-4-4 4 4 0 0 1 4-4 4 4 0 0 0 4-4 4 4 0 0 0-4-4m0 2.5A1.5 1.5 0 0 1 13.5 8 1.5 1.5 0 0 1 12 9.5 1.5 1.5 0 0 1 10.5 8 1.5 1.5 0 0 1 12 6.5m0 8a1.5 1.5 0 0 0-1.5 1.5 1.5 1.5 0 0 0 1.5 1.5 1.5 1.5 0 0 0 1.5-1.5 1.5 1.5 0 0 0-1.5-1.5z" fill="#42a5f5"/></symbol><symbol viewBox="0 0 289.99999 290.00001" id="yarn" xmlns="http://www.w3.org/2000/svg"><path d="M250.733 218.418c-12.39 2.943-18.661 5.653-33.993 15.641-24.004 15.487-50.176 22.688-50.176 22.688s-2.168 3.252-8.44 4.723c-10.84 2.633-51.647 4.878-55.364 4.956-9.988.077-16.105-2.555-17.809-6.66-5.188-12.388 7.434-17.809 7.434-17.809s-2.788-1.703-4.414-3.252c-1.471-1.47-3.02-4.413-3.484-3.33-1.936 4.724-2.943 16.261-8.13 21.45-7.125 7.2-20.598 4.8-28.573.619-8.75-4.646.62-15.564.62-15.564s-4.724 2.788-8.518-2.942c-3.407-5.266-6.582-14.248-5.73-25.32 1.084-12.777 15.176-25.011 15.176-25.011s-2.477-18.661 5.653-37.787c7.356-17.422 27.179-31.437 27.179-31.437s-16.648-18.352-10.454-35c4.027-10.84 5.653-10.763 6.97-11.227 4.645-1.781 9.136-3.717 12.466-7.356 16.648-17.964 37.864-14.557 37.864-14.557s9.911-30.431 19.203-24.469c2.865 1.859 13.163 24.778 13.163 24.778s10.996-6.426 12.235-4.026c6.659 12.931 7.433 37.632 4.49 52.654-4.955 24.778-17.344 38.096-22.3 46.459-1.161 1.936 13.319 8.053 22.456 33.373 8.44 23.152.929 42.587 2.245 44.756.232.387.31.542.31.542s9.679.774 29.114-11.228c10.376-6.427 22.688-13.628 36.703-13.783 13.55-.232 14.247 15.719 4.104 18.12z" fill="#2c8ebb" stroke-width=".774"/></symbol><symbol viewBox="0 0 24 24" id="zip" xmlns="http://www.w3.org/2000/svg"><path d="M14 17h-2v-2h-2v-2h2v2h2m0-6h-2v2h2v2h-2v-2h-2V9h2V7h-2V5h2v2h2m5-4H5c-1.11 0-2 .89-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z" fill="#afb42b"/></symbol></svg>
diff --git a/app/assets/javascripts/admin/users/components/user_actions.vue b/app/assets/javascripts/admin/users/components/user_actions.vue
index 567d7151847..f5d21ece138 100644
--- a/app/assets/javascripts/admin/users/components/user_actions.vue
+++ b/app/assets/javascripts/admin/users/components/user_actions.vue
@@ -109,12 +109,13 @@ export default {
<div v-if="hasDropdownActions" class="gl-p-2">
<gl-dropdown
data-testid="dropdown-toggle"
- right
:text="$options.i18n.userAdministration"
:text-sr-only="!showButtonLabels"
icon="ellipsis_h"
data-qa-selector="user_actions_dropdown_toggle"
:data-qa-username="user.username"
+ no-caret
+ right
>
<gl-dropdown-section-header>{{
$options.i18n.userAdministration
diff --git a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
index 0bdb45d35c9..b3ae671d611 100644
--- a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
+++ b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
@@ -31,7 +31,8 @@ export default {
props: {
groupId: {
type: Number,
- required: true,
+ required: false,
+ default: null,
},
groupNamespace: {
type: String,
@@ -57,6 +58,11 @@ export default {
required: false,
default: () => [],
},
+ loadingDefaultProjects: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -111,6 +117,9 @@ export default {
searchTerm() {
this.search();
},
+ defaultProjects(projects) {
+ this.selectedProjects = [...projects];
+ },
},
mounted() {
this.search();
@@ -202,6 +211,7 @@ export default {
ref="projectsDropdown"
class="dropdown dropdown-projects"
toggle-class="gl-shadow-none"
+ :loading="loadingDefaultProjects"
:show-clear-all="hasSelectedProjects"
show-highlighted-items-title
highlighted-items-title-class="gl-p-3"
@@ -209,6 +219,7 @@ export default {
@hide="onHide"
>
<template #button-content>
+ <gl-loading-icon v-if="loadingDefaultProjects" class="gl-mr-2" />
<div class="gl-display-flex gl-flex-grow-1">
<gl-avatar
v-if="isOnlyOneProjectSelected"
diff --git a/app/assets/javascripts/api/packages_api.js b/app/assets/javascripts/api/packages_api.js
index 47f51c7e80e..138843a910a 100644
--- a/app/assets/javascripts/api/packages_api.js
+++ b/app/assets/javascripts/api/packages_api.js
@@ -19,13 +19,7 @@ export function publishPackage(
status: 'default',
};
- const formData = new FormData();
- formData.append('file', files[0]);
-
- return axios.put(url, formData, {
- headers: {
- 'Content-Type': 'multipart/form-data',
- },
+ return axios.put(url, files[0], {
params: Object.assign(defaults, options),
...axiosOptions,
});
diff --git a/app/assets/javascripts/behaviors/copy_code.js b/app/assets/javascripts/behaviors/copy_code.js
index a6e203ea5a2..6d2a4c245cc 100644
--- a/app/assets/javascripts/behaviors/copy_code.js
+++ b/app/assets/javascripts/behaviors/copy_code.js
@@ -29,7 +29,8 @@ class CopyCodeButton extends HTMLElement {
}
function addCodeButton() {
- [...document.querySelectorAll('pre.code.js-syntax-highlight')]
+ [...document.querySelectorAll('pre.code.js-syntax-highlight:not(.content-editor-code-block)')]
+ .filter((el) => el.getAttribute('lang') !== 'mermaid')
.filter((el) => !el.closest('.js-markdown-code'))
.forEach((el) => {
const copyCodeEl = document.createElement('copy-code');
diff --git a/app/assets/javascripts/behaviors/copy_to_clipboard.js b/app/assets/javascripts/behaviors/copy_to_clipboard.js
index de248340738..c3c28aeafc0 100644
--- a/app/assets/javascripts/behaviors/copy_to_clipboard.js
+++ b/app/assets/javascripts/behaviors/copy_to_clipboard.js
@@ -1,7 +1,13 @@
-import Clipboard from 'clipboard';
+import ClipboardJS from 'clipboard';
import $ from 'jquery';
-import { sprintf, __ } from '~/locale';
-import { fixTitle, add, show, once } from '~/tooltips';
+
+import { parseBoolean } from '~/lib/utils/common_utils';
+import { __ } from '~/locale';
+import { fixTitle, add, show, hide, once } from '~/tooltips';
+
+const CLIPBOARD_SUCCESS_EVENT = 'clipboard-success';
+const CLIPBOARD_ERROR_EVENT = 'clipboard-error';
+const I18N_ERROR_MESSAGE = __('Copy failed. Please manually copy the value.');
function showTooltip(target, title) {
const { title: originalTitle } = target.dataset;
@@ -9,20 +15,31 @@ function showTooltip(target, title) {
once('hidden', (tooltip) => {
if (tooltip.target === target) {
target.setAttribute('title', originalTitle);
+ target.setAttribute('aria-label', originalTitle);
fixTitle(target);
}
});
target.setAttribute('title', title);
+ target.setAttribute('aria-label', title);
fixTitle(target);
show(target);
- setTimeout(() => target.blur(), 1000);
+ setTimeout(() => {
+ hide(target);
+ }, 1000);
}
function genericSuccess(e) {
- // Clear the selection and blur the trigger so it loses its border
+ // Clear the selection
e.clearSelection();
- showTooltip(e.trigger, __('Copied'));
+ e.trigger.focus();
+ e.trigger.dispatchEvent(new Event(CLIPBOARD_SUCCESS_EVENT));
+
+ const { clipboardHandleTooltip = true } = e.trigger.dataset;
+ if (parseBoolean(clipboardHandleTooltip)) {
+ // Update tooltip
+ showTooltip(e.trigger, __('Copied'));
+ }
}
/**
@@ -30,17 +47,16 @@ function genericSuccess(e) {
* See http://clipboardjs.com/#browser-support
*/
function genericError(e) {
- let key;
- if (/Mac/i.test(navigator.userAgent)) {
- key = '&#8984;'; // Command
- } else {
- key = 'Ctrl';
+ e.trigger.dispatchEvent(new Event(CLIPBOARD_ERROR_EVENT));
+
+ const { clipboardHandleTooltip = true } = e.trigger.dataset;
+ if (parseBoolean(clipboardHandleTooltip)) {
+ showTooltip(e.trigger, I18N_ERROR_MESSAGE);
}
- showTooltip(e.trigger, sprintf(__(`Press %{key}-C to copy`), { key }));
}
export default function initCopyToClipboard() {
- const clipboard = new Clipboard('[data-clipboard-target], [data-clipboard-text]');
+ const clipboard = new ClipboardJS('[data-clipboard-target], [data-clipboard-text]');
clipboard.on('success', genericSuccess);
clipboard.on('error', genericError);
@@ -74,6 +90,8 @@ export default function initCopyToClipboard() {
clipboardData.setData('text/plain', json.text);
clipboardData.setData('text/x-gfm', json.gfm);
});
+
+ return clipboard;
}
/**
@@ -89,3 +107,5 @@ export function clickCopyToClipboardButton(btnElement) {
btnElement.click();
}
+
+export { CLIPBOARD_SUCCESS_EVENT, CLIPBOARD_ERROR_EVENT, I18N_ERROR_MESSAGE };
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index 4698fcd4d42..c4e09efe263 100644
--- a/app/assets/javascripts/behaviors/markdown/render_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -4,6 +4,7 @@ import initUserPopovers from '../../user_popovers';
import highlightCurrentUser from './highlight_current_user';
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
+import renderSandboxedMermaid from './render_sandboxed_mermaid';
import renderMetrics from './render_metrics';
// Render GitLab flavoured Markdown
@@ -13,7 +14,11 @@ import renderMetrics from './render_metrics';
$.fn.renderGFM = function renderGFM() {
syntaxHighlight(this.find('.js-syntax-highlight').get());
renderMath(this.find('.js-render-math'));
- renderMermaid(this.find('.js-render-mermaid'));
+ if (gon.features?.sandboxedMermaid) {
+ renderSandboxedMermaid(this.find('.js-render-mermaid'));
+ } else {
+ renderMermaid(this.find('.js-render-mermaid'));
+ }
highlightCurrentUser(this.find('.gfm-project_member').get());
initUserPopovers(this.find('.js-user-link').get());
diff --git a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
new file mode 100644
index 00000000000..1d54a1b0c04
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js
@@ -0,0 +1,234 @@
+import $ from 'jquery';
+import { once, countBy } from 'lodash';
+import { __ } from '~/locale';
+import {
+ getBaseURL,
+ relativePathToAbsolute,
+ setUrlParams,
+ joinPaths,
+} from '~/lib/utils/url_utility';
+import { darkModeEnabled } from '~/lib/utils/color_utils';
+import { setAttributes } from '~/lib/utils/dom_utils';
+
+// Renders diagrams and flowcharts from text using Mermaid in any element with the
+// `js-render-mermaid` class.
+//
+// Example markup:
+//
+// <pre class="js-render-mermaid">
+// graph TD;
+// A-- > B;
+// A-- > C;
+// B-- > D;
+// C-- > D;
+// </pre>
+//
+
+const SANDBOX_FRAME_PATH = '/-/sandbox/mermaid';
+// This is an arbitrary number; Can be iterated upon when suitable.
+const MAX_CHAR_LIMIT = 2000;
+// Max # of mermaid blocks that can be rendered in a page.
+const MAX_MERMAID_BLOCK_LIMIT = 50;
+// Max # of `&` allowed in Chaining of links syntax
+const MAX_CHAINING_OF_LINKS_LIMIT = 30;
+const BUFFER_IFRAME_HEIGHT = 10;
+// Keep a map of mermaid blocks we've already rendered.
+const elsProcessingMap = new WeakMap();
+let renderedMermaidBlocks = 0;
+
+// Pages without any restrictions on mermaid rendering
+const PAGES_WITHOUT_RESTRICTIONS = [
+ // Group wiki
+ 'groups:wikis:show',
+ 'groups:wikis:edit',
+ 'groups:wikis:create',
+
+ // Project wiki
+ 'projects:wikis:show',
+ 'projects:wikis:edit',
+ 'projects:wikis:create',
+
+ // Project files
+ 'projects:show',
+ 'projects:blob:show',
+];
+
+function shouldLazyLoadMermaidBlock(source) {
+ /**
+ * If source contains `&`, which means that it might
+ * contain Chaining of links a new syntax in Mermaid.
+ */
+ if (countBy(source)['&'] > MAX_CHAINING_OF_LINKS_LIMIT) {
+ return true;
+ }
+
+ return false;
+}
+
+function fixElementSource(el) {
+ // Mermaid doesn't like `<br />` tags, so collapse all like tags into `<br>`, which is parsed correctly.
+ const source = el.textContent?.replace(/<br\s*\/>/g, '<br>');
+
+ // Remove any extra spans added by the backend syntax highlighting.
+ Object.assign(el, { textContent: source });
+
+ return { source };
+}
+
+function getSandboxFrameSrc() {
+ const path = joinPaths(gon.relative_url_root || '', SANDBOX_FRAME_PATH);
+ if (!darkModeEnabled()) {
+ return path;
+ }
+ const absoluteUrl = relativePathToAbsolute(path, getBaseURL());
+ return setUrlParams({ darkMode: darkModeEnabled() }, absoluteUrl);
+}
+
+function renderMermaidEl(el, source) {
+ const iframeEl = document.createElement('iframe');
+ setAttributes(iframeEl, {
+ src: getSandboxFrameSrc(),
+ sandbox: 'allow-scripts',
+ frameBorder: 0,
+ scrolling: 'no',
+ width: '100%',
+ });
+
+ // Add the original source into the DOM
+ // to allow Copy-as-GFM to access it.
+ const sourceEl = document.createElement('text');
+ sourceEl.textContent = source;
+ sourceEl.classList.add('gl-display-none');
+
+ const wrapper = document.createElement('div');
+ wrapper.appendChild(iframeEl);
+ wrapper.appendChild(sourceEl);
+
+ el.closest('pre').replaceWith(wrapper);
+
+ // Event Listeners
+ iframeEl.addEventListener('load', () => {
+ // Potential risk associated with '*' discussed in below thread
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74414#note_735183398
+ iframeEl.contentWindow.postMessage(source, '*');
+ });
+
+ window.addEventListener(
+ 'message',
+ (event) => {
+ if (event.origin !== 'null' || event.source !== iframeEl.contentWindow) {
+ return;
+ }
+ const { h } = event.data;
+ iframeEl.height = `${h + BUFFER_IFRAME_HEIGHT}px`;
+ },
+ false,
+ );
+}
+
+function renderMermaids($els) {
+ if (!$els.length) return;
+
+ const pageName = document.querySelector('body').dataset.page;
+
+ // A diagram may have been truncated in search results which will cause errors, so abort the render.
+ if (pageName === 'search:show') return;
+
+ let renderedChars = 0;
+
+ $els.each((i, el) => {
+ // Skipping all the elements which we've already queued in requestIdleCallback
+ if (elsProcessingMap.has(el)) {
+ return;
+ }
+
+ const { source } = fixElementSource(el);
+ /**
+ * Restrict the rendering to a certain amount of character
+ * and mermaid blocks to prevent mermaidjs from hanging
+ * up the entire thread and causing a DoS.
+ */
+ if (
+ !PAGES_WITHOUT_RESTRICTIONS.includes(pageName) &&
+ ((source && source.length > MAX_CHAR_LIMIT) ||
+ renderedChars > MAX_CHAR_LIMIT ||
+ renderedMermaidBlocks >= MAX_MERMAID_BLOCK_LIMIT ||
+ shouldLazyLoadMermaidBlock(source))
+ ) {
+ const html = `
+ <div class="alert gl-alert gl-alert-warning alert-dismissible lazy-render-mermaid-container js-lazy-render-mermaid-container fade show" role="alert">
+ <div>
+ <div>
+ <div class="js-warning-text"></div>
+ <div class="gl-alert-actions">
+ <button type="button" class="js-lazy-render-mermaid btn gl-alert-action btn-warning btn-md gl-button">Display</button>
+ </div>
+ </div>
+ <button type="button" class="close" data-dismiss="alert" aria-label="Close">
+ <span aria-hidden="true">&times;</span>
+ </button>
+ </div>
+ </div>
+ `;
+
+ const $parent = $(el).parent();
+
+ if (!$parent.hasClass('lazy-alert-shown')) {
+ $parent.after(html);
+ $parent
+ .siblings()
+ .find('.js-warning-text')
+ .text(
+ __('Warning: Displaying this diagram might cause performance issues on this page.'),
+ );
+ $parent.addClass('lazy-alert-shown');
+ }
+
+ return;
+ }
+
+ renderedChars += source.length;
+ renderedMermaidBlocks += 1;
+
+ const requestId = window.requestIdleCallback(() => {
+ renderMermaidEl(el, source);
+ });
+
+ elsProcessingMap.set(el, requestId);
+ });
+}
+
+const hookLazyRenderMermaidEvent = once(() => {
+ $(document.body).on('click', '.js-lazy-render-mermaid', function eventHandler() {
+ const parent = $(this).closest('.js-lazy-render-mermaid-container');
+ const pre = parent.prev();
+
+ const el = pre.find('.js-render-mermaid');
+
+ parent.remove();
+
+ // sandbox update
+ const element = el.get(0);
+ const { source } = fixElementSource(element);
+
+ renderMermaidEl(element, source);
+ });
+});
+
+export default function renderMermaid($els) {
+ if (!$els.length) return;
+
+ const visibleMermaids = $els.filter(function filter() {
+ return $(this).closest('details').length === 0 && $(this).is(':visible');
+ });
+
+ renderMermaids(visibleMermaids);
+
+ $els.closest('details').one('toggle', function toggle() {
+ if (this.open) {
+ renderMermaids($(this).find('.js-render-mermaid'));
+ }
+ });
+
+ hookLazyRenderMermaidEvent();
+}
diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js
index a548b283142..679940d1317 100644
--- a/app/assets/javascripts/behaviors/preview_markdown.js
+++ b/app/assets/javascripts/behaviors/preview_markdown.js
@@ -124,13 +124,6 @@ const writeButtonSelector = '.js-md-write-button';
lastTextareaPreviewed = null;
const markdownToolbar = $('.md-header-toolbar');
-$.fn.setupMarkdownPreview = function () {
- const $form = $(this);
- $form.find('textarea.markdown-area').on('input', () => {
- markdownPreview.hideReferencedUsers($form);
- });
-};
-
$(document).on('markdown-preview:show', (e, $form) => {
if (!$form) {
return;
diff --git a/app/assets/javascripts/blob/blob_line_permalink_updater.js b/app/assets/javascripts/blob/blob_line_permalink_updater.js
index 11089b299c5..a3dd241604d 100644
--- a/app/assets/javascripts/blob/blob_line_permalink_updater.js
+++ b/app/assets/javascripts/blob/blob_line_permalink_updater.js
@@ -1,6 +1,6 @@
import { getLocationHash } from '../lib/utils/url_utility';
-const lineNumberRe = /^L[0-9]+/;
+const lineNumberRe = /^(L|LC)[0-9]+/;
const updateLineNumbersOnBlobPermalinks = (linksToUpdate) => {
const hash = getLocationHash();
diff --git a/app/assets/javascripts/blob/components/blob_header.vue b/app/assets/javascripts/blob/components/blob_header.vue
index 933ad448c77..1645469a218 100644
--- a/app/assets/javascripts/blob/components/blob_header.vue
+++ b/app/assets/javascripts/blob/components/blob_header.vue
@@ -81,7 +81,7 @@ export default {
</blob-filepath>
</div>
- <div class="gl-display-none gl-sm-display-flex">
+ <div class="gl-sm-display-flex file-actions">
<viewer-switcher v-if="showViewerSwitcher" v-model="viewer" />
<slot name="actions"></slot>
diff --git a/app/assets/javascripts/blob/components/blob_header_filepath.vue b/app/assets/javascripts/blob/components/blob_header_filepath.vue
index cb441a7e491..90d01358451 100644
--- a/app/assets/javascripts/blob/components/blob_header_filepath.vue
+++ b/app/assets/javascripts/blob/components/blob_header_filepath.vue
@@ -1,4 +1,5 @@
<script>
+import { GlBadge } from '@gitlab/ui';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
@@ -7,6 +8,7 @@ export default {
components: {
FileIcon,
ClipboardButton,
+ GlBadge,
},
props: {
blob: {
@@ -21,6 +23,9 @@ export default {
gfmCopyText() {
return `\`${this.blob.path}\``;
},
+ showLfsBadge() {
+ return this.blob.storedExternally && this.blob.externalStorage === 'lfs';
+ },
},
};
</script>
@@ -37,8 +42,6 @@ export default {
>
</template>
- <small class="mr-2">{{ blobSize }}</small>
-
<clipboard-button
:text="blob.path"
:gfm="gfmCopyText"
@@ -46,5 +49,9 @@ export default {
category="tertiary"
css-class="btn-clipboard btn-transparent lh-100 position-static"
/>
+
+ <small class="mr-2">{{ blobSize }}</small>
+
+ <gl-badge v-if="showLfsBadge">{{ __('LFS') }}</gl-badge>
</div>
</template>
diff --git a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
index a5b594fbd88..b2546d47694 100644
--- a/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
+++ b/app/assets/javascripts/blob/components/blob_header_viewer_switcher.vue
@@ -53,6 +53,8 @@ export default {
icon="code"
category="primary"
variant="default"
+ class="js-blob-viewer-switch-btn"
+ data-viewer="simple"
@click="switchToViewer($options.SIMPLE_BLOB_VIEWER)"
/>
<gl-button
@@ -63,6 +65,8 @@ export default {
icon="document"
category="primary"
variant="default"
+ class="js-blob-viewer-switch-btn"
+ data-viewer="rich"
@click="switchToViewer($options.RICH_BLOB_VIEWER)"
/>
</gl-button-group>
diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/blob/line_highlighter.js
index a1f59aa1b54..a1f59aa1b54 100644
--- a/app/assets/javascripts/line_highlighter.js
+++ b/app/assets/javascripts/blob/line_highlighter.js
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 563bed6a6b8..dc821cb9f58 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -72,7 +72,7 @@ export default {
data-qa-selector="board_card"
:class="{
'multi-select': multiSelectVisible,
- 'user-can-drag': isDraggable,
+ 'gl-cursor-grab': isDraggable,
'is-disabled': isDisabled,
'is-active': isActive,
'gl-cursor-not-allowed gl-bg-gray-10': item.isLoading,
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index f89f8e5feb8..156029b62b0 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -6,11 +6,12 @@ import SidebarDropdownWidget from 'ee_else_ce/sidebar/components/sidebar_dropdow
import { __, sprintf } from '~/locale';
import BoardSidebarTimeTracker from '~/boards/components/sidebar/board_sidebar_time_tracker.vue';
import BoardSidebarTitle from '~/boards/components/sidebar/board_sidebar_title.vue';
-import { ISSUABLE } from '~/boards/constants';
+import { ISSUABLE, INCIDENT } from '~/boards/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import SidebarAssigneesWidget from '~/sidebar/components/assignees/sidebar_assignees_widget.vue';
import SidebarConfidentialityWidget from '~/sidebar/components/confidential/sidebar_confidentiality_widget.vue';
import SidebarDateWidget from '~/sidebar/components/date/sidebar_date_widget.vue';
+import SidebarSeverity from '~/sidebar/components/severity/sidebar_severity.vue';
import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue';
import SidebarLabelsWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
@@ -29,6 +30,7 @@ export default {
SidebarSubscriptionsWidget,
SidebarDropdownWidget,
SidebarTodoWidget,
+ SidebarSeverity,
MountingPortal,
SidebarWeightWidget: () =>
import('ee_component/sidebar/components/weight/sidebar_weight_widget.vue'),
@@ -69,9 +71,15 @@ export default {
isIssuableSidebar() {
return this.sidebarType === ISSUABLE;
},
+ isIncidentSidebar() {
+ return this.activeBoardItem.type === INCIDENT;
+ },
showSidebar() {
return this.isIssuableSidebar && this.isSidebarOpen;
},
+ sidebarTitle() {
+ return this.isIncidentSidebar ? __('Incident details') : __('Issue details');
+ },
fullPath() {
return this.activeBoardItem?.referencePath?.split('#')[0] || '';
},
@@ -138,7 +146,7 @@ export default {
@close="handleClose"
>
<template #title>
- <h2 class="gl-my-0 gl-font-size-h2 gl-line-height-24">{{ __('Issue details') }}</h2>
+ <h2 class="gl-my-0 gl-font-size-h2 gl-line-height-24">{{ sidebarTitle }}</h2>
</template>
<template #header>
<sidebar-todo-widget
@@ -159,7 +167,7 @@ export default {
@assignees-updated="setAssignees"
/>
<sidebar-dropdown-widget
- v-if="epicFeatureAvailable"
+ v-if="epicFeatureAvailable && !isIncidentSidebar"
:iid="activeBoardItem.iid"
issuable-attribute="epic"
:workspace-path="projectPathForActiveIssue"
@@ -178,7 +186,7 @@ export default {
/>
<template v-if="!glFeatures.iterationCadences">
<sidebar-dropdown-widget
- v-if="iterationFeatureAvailable"
+ v-if="iterationFeatureAvailable && !isIncidentSidebar"
:iid="activeBoardItem.iid"
issuable-attribute="iteration"
:workspace-path="projectPathForActiveIssue"
@@ -190,7 +198,7 @@ export default {
</template>
<template v-else>
<iteration-sidebar-dropdown-widget
- v-if="iterationFeatureAvailable"
+ v-if="iterationFeatureAvailable && !isIncidentSidebar"
:iid="activeBoardItem.iid"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
@@ -200,7 +208,7 @@ export default {
/>
</template>
</div>
- <board-sidebar-time-tracker class="swimlanes-sidebar-time-tracker" />
+ <board-sidebar-time-tracker />
<sidebar-date-widget
:iid="activeBoardItem.iid"
:full-path="fullPath"
@@ -209,7 +217,6 @@ export default {
/>
<sidebar-labels-widget
class="block labels"
- data-testid="sidebar-labels"
:iid="activeBoardItem.iid"
:full-path="projectPathForActiveIssue"
:allow-label-remove="allowLabelEdit"
@@ -227,8 +234,14 @@ export default {
>
{{ __('None') }}
</sidebar-labels-widget>
+ <sidebar-severity
+ v-if="isIncidentSidebar"
+ :iid="activeBoardItem.iid"
+ :project-path="fullPath"
+ :initial-severity="activeBoardItem.severity"
+ />
<sidebar-weight-widget
- v-if="weightFeatureAvailable"
+ v-if="weightFeatureAvailable && !isIncidentSidebar"
:iid="activeBoardItem.iid"
:full-path="fullPath"
:issuable-type="issuableType"
diff --git a/app/assets/javascripts/boards/components/board_filtered_search.vue b/app/assets/javascripts/boards/components/board_filtered_search.vue
index 09ec385bbba..2599d1c80b8 100644
--- a/app/assets/javascripts/boards/components/board_filtered_search.vue
+++ b/app/assets/javascripts/boards/components/board_filtered_search.vue
@@ -6,6 +6,7 @@ import { updateHistory, setUrlParams } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { FILTERED_SEARCH_TERM } from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
+import { AssigneeFilterType } from '~/boards/constants';
export default {
i18n: {
@@ -37,6 +38,7 @@ export default {
authorUsername,
labelName,
assigneeUsername,
+ assigneeId,
search,
milestoneTitle,
iterationId,
@@ -63,6 +65,13 @@ export default {
});
}
+ if (assigneeId) {
+ filteredSearchValue.push({
+ type: 'assignee',
+ value: { data: assigneeId, operator: '=' },
+ });
+ }
+
if (types) {
filteredSearchValue.push({
type: 'type',
@@ -211,6 +220,7 @@ export default {
authorUsername,
labelName,
assigneeUsername,
+ assigneeId,
search,
milestoneTitle,
types,
@@ -246,6 +256,7 @@ export default {
author_username: authorUsername,
'label_name[]': labelName,
assignee_username: assigneeUsername,
+ assignee_id: assigneeId,
milestone_title: milestoneTitle,
iteration_id: iterationId,
search,
@@ -295,7 +306,11 @@ export default {
filterParams.authorUsername = filter.value.data;
break;
case 'assignee':
- filterParams.assigneeUsername = filter.value.data;
+ if (Object.values(AssigneeFilterType).includes(filter.value.data)) {
+ filterParams.assigneeId = filter.value.data;
+ } else {
+ filterParams.assigneeUsername = filter.value.data;
+ }
break;
case 'type':
filterParams.types = filter.value.data;
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index 19004518edf..6835d83a66c 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -263,7 +263,7 @@ export default {
>
<h3
:class="{
- 'user-can-drag': userCanDrag,
+ 'gl-cursor-grab': userCanDrag,
'gl-py-3 gl-h-full': list.collapsed && !isSwimlanesHeader,
'gl-border-b-0': list.collapsed || isSwimlanesHeader,
'gl-py-2': list.collapsed && isSwimlanesHeader,
diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
index e77aadfa50e..9d19fe57e7a 100644
--- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
+++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_title.vue
@@ -150,7 +150,7 @@ export default {
<div class="gl-display-flex gl-w-full gl-justify-content-space-between gl-mt-5">
<gl-button
- variant="success"
+ variant="confirm"
size="small"
data-testid="submit-button"
:disabled="!title"
diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js
index 851b5eca40d..0f290f566ba 100644
--- a/app/assets/javascripts/boards/constants.js
+++ b/app/assets/javascripts/boards/constants.js
@@ -50,10 +50,13 @@ export const toggleFormEventPrefix = {
issue: 'toggle-issue-form-',
};
+export const active = 'active';
+
export const inactiveId = 0;
export const ISSUABLE = 'issuable';
export const LIST = 'list';
+export const INCIDENT = 'INCIDENT';
export const flashAnimationDuration = 2000;
@@ -119,6 +122,7 @@ export const FilterFields = {
/* eslint-disable @gitlab/require-i18n-strings */
export const AssigneeFilterType = {
any: 'Any',
+ none: 'None',
};
export const MilestoneFilterType = {
diff --git a/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql b/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql
index 0963b3fbfaa..6fe8bb799d6 100644
--- a/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql
+++ b/app/assets/javascripts/boards/graphql/group_board_milestones.query.graphql
@@ -1,7 +1,7 @@
-query GroupBoardMilestones($fullPath: ID!, $searchTerm: String) {
+query GroupBoardMilestones($fullPath: ID!, $searchTerm: String, $state: MilestoneStateEnum) {
group(fullPath: $fullPath) {
id
- milestones(includeAncestors: true, searchTitle: $searchTerm) {
+ milestones(includeAncestors: true, searchTitle: $searchTerm, state: $state) {
nodes {
id
title
diff --git a/app/assets/javascripts/boards/graphql/issue.fragment.graphql b/app/assets/javascripts/boards/graphql/issue.fragment.graphql
index 314faae89f8..53fe6fdc59e 100644
--- a/app/assets/javascripts/boards/graphql/issue.fragment.graphql
+++ b/app/assets/javascripts/boards/graphql/issue.fragment.graphql
@@ -1,35 +1,6 @@
-#import "~/graphql_shared/fragments/milestone.fragment.graphql"
-#import "~/graphql_shared/fragments/user.fragment.graphql"
+#import "~/graphql_shared/fragments/issue.fragment.graphql"
-fragment IssueNode on Issue {
+fragment Issue on Issue {
id
- iid
- title
- referencePath: reference(full: true)
- dueDate
- timeEstimate
- totalTimeSpent
- humanTimeEstimate
- humanTotalTimeSpent
- emailsDisabled
- confidential
- hidden
- webUrl
- relativePosition
- milestone {
- ...MilestoneFragment
- }
- assignees {
- nodes {
- ...User
- }
- }
- labels {
- nodes {
- id
- title
- color
- description
- }
- }
+ ...IssueNode
}
diff --git a/app/assets/javascripts/boards/graphql/issue_create.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_create.mutation.graphql
index c1a2361a4e8..643d5dcfe4c 100644
--- a/app/assets/javascripts/boards/graphql/issue_create.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/issue_create.mutation.graphql
@@ -3,7 +3,7 @@
mutation CreateIssue($input: CreateIssueInput!) {
createIssue(input: $input) {
issue {
- ...IssueNode
+ ...Issue
}
errors
}
diff --git a/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql b/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql
index 570731ecac6..1658cf09085 100644
--- a/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql
+++ b/app/assets/javascripts/boards/graphql/issue_move_list.mutation.graphql
@@ -21,7 +21,7 @@ mutation issueMoveList(
}
) {
issue {
- ...IssueNode
+ ...Issue
}
errors
}
diff --git a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
index 105f2931caa..994ea894be3 100644
--- a/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
+++ b/app/assets/javascripts/boards/graphql/lists_issues.query.graphql
@@ -22,7 +22,7 @@ query BoardListsEE(
issues(first: $first, filters: $filters, after: $after) {
edges {
node {
- ...IssueNode
+ ...Issue
}
}
pageInfo {
@@ -46,7 +46,7 @@ query BoardListsEE(
issues(first: $first, filters: $filters, after: $after) {
edges {
node {
- ...IssueNode
+ ...Issue
}
}
pageInfo {
diff --git a/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql b/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql
index e456823d78a..d917c7e809d 100644
--- a/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql
+++ b/app/assets/javascripts/boards/graphql/project_board_milestones.query.graphql
@@ -1,7 +1,7 @@
-query ProjectBoardMilestones($fullPath: ID!, $searchTerm: String) {
+query ProjectBoardMilestones($fullPath: ID!, $searchTerm: String, $state: MilestoneStateEnum) {
project(fullPath: $fullPath) {
id
- milestones(searchTitle: $searchTerm, includeAncestors: true) {
+ milestones(searchTitle: $searchTerm, includeAncestors: true, state: $state) {
nodes {
id
title
diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js
index 1ebfcfc331b..48ca3239cfd 100644
--- a/app/assets/javascripts/boards/stores/actions.js
+++ b/app/assets/javascripts/boards/stores/actions.js
@@ -15,6 +15,7 @@ import {
FilterFields,
ListTypeTitles,
DraggableItemTypes,
+ active,
} from 'ee_else_ce/boards/constants';
import {
formatIssueInput,
@@ -209,6 +210,7 @@ export default {
const variables = {
fullPath,
searchTerm,
+ state: active,
};
let query;
diff --git a/app/assets/javascripts/branches/branches_delete_modal.js b/app/assets/javascripts/branches/branches_delete_modal.js
deleted file mode 100644
index f4c3fa185d8..00000000000
--- a/app/assets/javascripts/branches/branches_delete_modal.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import $ from 'jquery';
-
-const MODAL_SELECTOR = '#modal-delete-branch';
-
-class DeleteModal {
- constructor() {
- this.$modal = $(MODAL_SELECTOR);
- this.$toggleBtns = $(`[data-target="${MODAL_SELECTOR}"]`);
- this.$branchName = $('.js-branch-name', this.$modal);
- this.$confirmInput = $('.js-delete-branch-input', this.$modal);
- this.$deleteBtn = $('.js-delete-branch', this.$modal);
- this.$notMerged = $('.js-not-merged', this.$modal);
- this.bindEvents();
- }
-
- bindEvents() {
- this.$toggleBtns.on('click', this.setModalData.bind(this));
- this.$confirmInput.on('input', this.setDeleteDisabled.bind(this));
- this.$deleteBtn.on('click', this.setDisableDeleteButton.bind(this));
- }
-
- setModalData(e) {
- const branchData = e.currentTarget.dataset;
- this.branchName = branchData.branchName || '';
- this.deletePath = branchData.deletePath || '';
- this.isMerged = Boolean(branchData.isMerged);
- this.updateModal();
- }
-
- setDeleteDisabled(e) {
- this.$deleteBtn.attr('disabled', e.currentTarget.value !== this.branchName);
- }
-
- setDisableDeleteButton(e) {
- if (this.$deleteBtn.is('[disabled]')) {
- e.preventDefault();
- e.stopPropagation();
- return false;
- }
-
- return true;
- }
-
- updateModal() {
- this.$branchName.text(this.branchName);
- this.$confirmInput.val('');
- this.$deleteBtn.attr('href', this.deletePath);
- this.$deleteBtn.attr('disabled', true);
- this.$notMerged.toggleClass('hidden', this.isMerged);
- }
-}
-
-export default DeleteModal;
diff --git a/app/assets/javascripts/clusters/agents/components/show.vue b/app/assets/javascripts/clusters/agents/components/show.vue
index 9109c010500..a53bba6992d 100644
--- a/app/assets/javascripts/clusters/agents/components/show.vue
+++ b/app/assets/javascripts/clusters/agents/components/show.vue
@@ -51,16 +51,7 @@ export default {
TokenTable,
ActivityEvents,
},
- props: {
- agentName: {
- required: true,
- type: String,
- },
- projectPath: {
- required: true,
- type: String,
- },
- },
+ inject: ['agentName', 'projectPath'],
data() {
return {
cursor: {
@@ -135,7 +126,7 @@ export default {
<activity-events :agent-name="agentName" :project-path="projectPath" />
</gl-tab>
- <slot name="ee-security-tab"></slot>
+ <slot name="ee-security-tab" :cluster-agent-id="clusterAgent.id"></slot>
<gl-tab query-param-value="tokens">
<template #title>
diff --git a/app/assets/javascripts/clusters/agents/graphql/provider.js b/app/assets/javascripts/clusters/agents/graphql/provider.js
new file mode 100644
index 00000000000..8b068fa1eee
--- /dev/null
+++ b/app/assets/javascripts/clusters/agents/graphql/provider.js
@@ -0,0 +1,26 @@
+import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import createDefaultClient from '~/lib/graphql';
+import { vulnerabilityLocationTypes } from '~/graphql_shared/fragment_types/vulnerability_location_types';
+
+Vue.use(VueApollo);
+
+// We create a fragment matcher so that we can create a fragment from an interface
+// Without this, Apollo throws a heuristic fragment matcher warning
+const fragmentMatcher = new IntrospectionFragmentMatcher({
+ introspectionQueryResultData: vulnerabilityLocationTypes,
+});
+
+const defaultClient = createDefaultClient(
+ {},
+ {
+ cacheConfig: {
+ fragmentMatcher,
+ },
+ },
+);
+
+export default new VueApollo({
+ defaultClient,
+});
diff --git a/app/assets/javascripts/clusters/agents/index.js b/app/assets/javascripts/clusters/agents/index.js
index 5796c9e308d..6c7fae274f8 100644
--- a/app/assets/javascripts/clusters/agents/index.js
+++ b/app/assets/javascripts/clusters/agents/index.js
@@ -1,9 +1,6 @@
import Vue from 'vue';
-import VueApollo from 'vue-apollo';
-import createDefaultClient from '~/lib/graphql';
import AgentShowPage from 'ee_else_ce/clusters/agents/components/show.vue';
-
-Vue.use(VueApollo);
+import apolloProvider from './graphql/provider';
export default () => {
const el = document.querySelector('#js-cluster-agent-details');
@@ -12,20 +9,19 @@ export default () => {
return null;
}
- const defaultClient = createDefaultClient();
- const { agentName, projectPath, activityEmptyStateImage } = el.dataset;
+ const { activityEmptyStateImage, agentName, emptyStateSvgPath, projectPath } = el.dataset;
return new Vue({
el,
- apolloProvider: new VueApollo({ defaultClient }),
- provide: { agentName, projectPath, activityEmptyStateImage },
+ apolloProvider,
+ provide: {
+ activityEmptyStateImage,
+ agentName,
+ emptyStateSvgPath,
+ projectPath,
+ },
render(createElement) {
- return createElement(AgentShowPage, {
- props: {
- agentName,
- projectPath,
- },
- });
+ return createElement(AgentShowPage);
},
});
};
diff --git a/app/assets/javascripts/clusters_list/components/agent_options.vue b/app/assets/javascripts/clusters_list/components/agent_options.vue
new file mode 100644
index 00000000000..a364122ba56
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/components/agent_options.vue
@@ -0,0 +1,200 @@
+<script>
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlModal,
+ GlModalDirective,
+ GlSprintf,
+ GlFormGroup,
+ GlFormInput,
+} from '@gitlab/ui';
+import { s__, __, sprintf } from '~/locale';
+import { DELETE_AGENT_MODAL_ID } from '../constants';
+import deleteAgent from '../graphql/mutations/delete_agent.mutation.graphql';
+import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
+import { removeAgentFromStore } from '../graphql/cache_update';
+
+export default {
+ i18n: {
+ dropdownText: __('More options'),
+ deleteButton: s__('ClusterAgents|Delete agent'),
+ modalTitle: __('Are you sure?'),
+ modalBody: s__(
+ 'ClusterAgents|Are you sure you want to delete this agent? You cannot undo this.',
+ ),
+ modalInputLabel: s__('ClusterAgents|To delete the agent, type %{name} to confirm:'),
+ modalAction: s__('ClusterAgents|Delete'),
+ modalCancel: __('Cancel'),
+ successMessage: s__('ClusterAgents|%{name} successfully deleted'),
+ defaultError: __('An error occurred. Please try again.'),
+ },
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ GlModal,
+ GlSprintf,
+ GlFormGroup,
+ GlFormInput,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ inject: ['projectPath'],
+ props: {
+ agent: {
+ required: true,
+ type: Object,
+ validator: (value) => ['id', 'name'].every((prop) => value[prop]),
+ },
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ maxAgents: {
+ default: null,
+ required: false,
+ type: Number,
+ },
+ },
+ data() {
+ return {
+ loading: false,
+ error: null,
+ deleteConfirmText: null,
+ agentName: this.agent.name,
+ };
+ },
+ computed: {
+ getAgentsQueryVariables() {
+ return {
+ defaultBranchName: this.defaultBranchName,
+ first: this.maxAgents,
+ last: null,
+ projectPath: this.projectPath,
+ };
+ },
+ modalId() {
+ return sprintf(DELETE_AGENT_MODAL_ID, {
+ agentName: this.agent.name,
+ });
+ },
+ primaryModalProps() {
+ return {
+ text: this.$options.i18n.modalAction,
+ attributes: [
+ { disabled: this.loading || this.disableModalSubmit, loading: this.loading },
+ { variant: 'danger' },
+ ],
+ };
+ },
+ cancelModalProps() {
+ return {
+ text: this.$options.i18n.modalCancel,
+ attributes: [],
+ };
+ },
+ disableModalSubmit() {
+ return this.deleteConfirmText !== this.agent.name;
+ },
+ },
+ methods: {
+ async deleteAgent() {
+ if (this.disableModalSubmit || this.loading) {
+ return;
+ }
+
+ this.loading = true;
+ this.error = null;
+
+ try {
+ const { errors } = await this.deleteAgentMutation();
+
+ if (errors.length) {
+ throw new Error(errors[0]);
+ }
+ } catch (error) {
+ this.error = error?.message || this.$options.i18n.defaultError;
+ } finally {
+ this.loading = false;
+ const successMessage = sprintf(this.$options.i18n.successMessage, { name: this.agentName });
+
+ this.$toast.show(this.error || successMessage);
+
+ this.$refs.modal.hide();
+ }
+ },
+ deleteAgentMutation() {
+ return this.$apollo
+ .mutate({
+ mutation: deleteAgent,
+ variables: {
+ input: {
+ id: this.agent.id,
+ },
+ },
+ update: (store) => {
+ const deleteClusterAgent = this.agent;
+ removeAgentFromStore(
+ store,
+ deleteClusterAgent,
+ getAgentsQuery,
+ this.getAgentsQueryVariables,
+ );
+ },
+ })
+
+ .then(({ data: { clusterAgentDelete } }) => {
+ return clusterAgentDelete;
+ });
+ },
+ hideModal() {
+ this.loading = false;
+ this.error = null;
+ this.deleteConfirmText = null;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-dropdown
+ icon="ellipsis_v"
+ right
+ :disabled="loading"
+ :text="$options.i18n.dropdownText"
+ text-sr-only
+ category="tertiary"
+ no-caret
+ >
+ <gl-dropdown-item v-gl-modal-directive="modalId">
+ {{ $options.i18n.deleteButton }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+
+ <gl-modal
+ ref="modal"
+ :modal-id="modalId"
+ :title="$options.i18n.modalTitle"
+ :action-primary="primaryModalProps"
+ :action-cancel="cancelModalProps"
+ size="sm"
+ @primary="deleteAgent"
+ @hide="hideModal"
+ >
+ <p>{{ $options.i18n.modalBody }}</p>
+
+ <gl-form-group>
+ <template #label>
+ <gl-sprintf :message="$options.i18n.modalInputLabel">
+ <template #name>
+ <code>{{ agent.name }}</code>
+ </template>
+ </gl-sprintf>
+ </template>
+ <gl-form-input v-model="deleteConfirmText" @keydown.enter="deleteAgent" />
+ </gl-form-group>
+ </gl-modal>
+ </div>
+</template>
diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue
index 000730ac1ba..695e16b7b4b 100644
--- a/app/assets/javascripts/clusters_list/components/agent_table.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_table.vue
@@ -1,21 +1,23 @@
<script>
-import {
- GlLink,
- GlModalDirective,
- GlTable,
- GlIcon,
- GlSprintf,
- GlTooltip,
- GlPopover,
-} from '@gitlab/ui';
-import { s__ } from '~/locale';
+import { GlLink, GlTable, GlIcon, GlSprintf, GlTooltip, GlPopover } from '@gitlab/ui';
+import { s__, __ } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { helpPagePath } from '~/helpers/help_page_helper';
-import { INSTALL_AGENT_MODAL_ID, AGENT_STATUSES } from '../constants';
+import { AGENT_STATUSES } from '../constants';
import { getAgentConfigPath } from '../clusters_util';
+import AgentOptions from './agent_options.vue';
export default {
+ i18n: {
+ nameLabel: s__('ClusterAgents|Name'),
+ statusLabel: s__('ClusterAgents|Connection status'),
+ lastContactLabel: s__('ClusterAgents|Last contact'),
+ configurationLabel: s__('ClusterAgents|Configuration'),
+ optionsLabel: __('Options'),
+ troubleshootingText: s__('ClusterAgents|Learn how to troubleshoot'),
+ neverConnectedText: s__('ClusterAgents|Never'),
+ },
components: {
GlLink,
GlTable,
@@ -24,14 +26,10 @@ export default {
GlTooltip,
GlPopover,
TimeAgoTooltip,
- },
- directives: {
- GlModalDirective,
+ AgentOptions,
},
mixins: [timeagoMixin],
- INSTALL_AGENT_MODAL_ID,
AGENT_STATUSES,
-
troubleshooting_link: helpPagePath('user/clusters/agent/index', {
anchor: 'troubleshooting',
}),
@@ -40,6 +38,16 @@ export default {
required: true,
type: Array,
},
+ defaultBranchName: {
+ default: '.noBranch',
+ required: false,
+ type: String,
+ },
+ maxAgents: {
+ default: null,
+ required: false,
+ type: Number,
+ },
},
computed: {
fields() {
@@ -47,22 +55,27 @@ export default {
return [
{
key: 'name',
- label: s__('ClusterAgents|Name'),
+ label: this.$options.i18n.nameLabel,
tdClass,
},
{
key: 'status',
- label: s__('ClusterAgents|Connection status'),
+ label: this.$options.i18n.statusLabel,
tdClass,
},
{
key: 'lastContact',
- label: s__('ClusterAgents|Last contact'),
+ label: this.$options.i18n.lastContactLabel,
tdClass,
},
{
key: 'configuration',
- label: s__('ClusterAgents|Configuration'),
+ label: this.$options.i18n.configurationLabel,
+ tdClass,
+ },
+ {
+ key: 'options',
+ label: this.$options.i18n.optionsLabel,
tdClass,
},
];
@@ -118,7 +131,7 @@ export default {
</p>
<p class="gl-mb-0">
<gl-link :href="$options.troubleshooting_link" target="_blank" class="gl-font-sm">
- {{ s__('ClusterAgents|Learn how to troubleshoot') }}</gl-link
+ {{ $options.i18n.troubleshootingText }}</gl-link
>
</p>
</gl-popover>
@@ -127,7 +140,7 @@ export default {
<template #cell(lastContact)="{ item }">
<span data-testid="cluster-agent-last-contact">
<time-ago-tooltip v-if="item.lastContact" :time="item.lastContact" />
- <span v-else>{{ s__('ClusterAgents|Never') }}</span>
+ <span v-else>{{ $options.i18n.neverConnectedText }}</span>
</span>
</template>
@@ -140,5 +153,13 @@ export default {
<span v-else>{{ getAgentConfigPath(item.name) }}</span>
</span>
</template>
+
+ <template #cell(options)="{ item }">
+ <agent-options
+ :agent="item"
+ :default-branch-name="defaultBranchName"
+ :max-agents="maxAgents"
+ />
+ </template>
</gl-table>
</template>
diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue
index 45108a28e37..4fc421e7c31 100644
--- a/app/assets/javascripts/clusters_list/components/agents.vue
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -151,7 +151,11 @@ export default {
<section v-else-if="agentList">
<div v-if="agentList.length">
- <agent-table :agents="agentList" />
+ <agent-table
+ :agents="agentList"
+ :default-branch-name="defaultBranchName"
+ :max-agents="cursor.first"
+ />
<div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
<gl-keyset-pagination v-bind="agentPageInfo" @prev="prevPage" @next="nextPage" />
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index 9b52df74fc5..380a5d0aada 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -106,7 +106,7 @@ export const I18N_AGENT_MODAL = {
empty_state: {
modalTitle: s__('ClusterAgents|Connect your cluster through the Agent'),
modalBody: s__(
- "ClusterAgents|To install a new agent, first add the agent's configuration file to this repository. %{linkStart}What's the agent's configuration file?%{linkEnd}",
+ "ClusterAgents|To install a new agent, first add the agent's configuration file to this repository. %{linkStart}Learn more about installing GitLab Agent.%{linkEnd}",
),
enableKasText: s__(
"ClusterAgents|Your instance doesn't have the %{linkStart}GitLab Agent Server (KAS)%{linkEnd} set up. Ask a GitLab Administrator to install it.",
@@ -242,3 +242,5 @@ export const EVENT_ACTIONS_CHANGE = 'change_tab';
export const MODAL_TYPE_EMPTY = 'empty_state';
export const MODAL_TYPE_REGISTER = 'agent_registration';
+
+export const DELETE_AGENT_MODAL_ID = 'delete-agent-modal-%{agentName}';
diff --git a/app/assets/javascripts/clusters_list/graphql/cache_update.js b/app/assets/javascripts/clusters_list/graphql/cache_update.js
index 4d12bc8151c..6476b7a6c2f 100644
--- a/app/assets/javascripts/clusters_list/graphql/cache_update.js
+++ b/app/assets/javascripts/clusters_list/graphql/cache_update.js
@@ -63,3 +63,25 @@ export function addAgentConfigToStore(
});
}
}
+
+export function removeAgentFromStore(store, deleteClusterAgent, query, variables) {
+ if (!hasErrors(deleteClusterAgent)) {
+ const sourceData = store.readQuery({
+ query,
+ variables,
+ });
+
+ const data = produce(sourceData, (draftData) => {
+ draftData.project.clusterAgents.nodes = draftData.project.clusterAgents.nodes.filter(
+ ({ id }) => id !== deleteClusterAgent.id,
+ );
+ draftData.project.clusterAgents.count -= 1;
+ });
+
+ store.writeQuery({
+ query,
+ variables,
+ data,
+ });
+ }
+}
diff --git a/app/assets/javascripts/clusters_list/graphql/mutations/delete_agent.mutation.graphql b/app/assets/javascripts/clusters_list/graphql/mutations/delete_agent.mutation.graphql
new file mode 100644
index 00000000000..28387b2a36c
--- /dev/null
+++ b/app/assets/javascripts/clusters_list/graphql/mutations/delete_agent.mutation.graphql
@@ -0,0 +1,5 @@
+mutation deleteClusterAgent($input: ClusterAgentDeleteInput!) {
+ clusterAgentDelete(input: $input) {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/clusters_list/index.js b/app/assets/javascripts/clusters_list/index.js
index 7f1ef37814b..6148483dcb0 100644
--- a/app/assets/javascripts/clusters_list/index.js
+++ b/app/assets/javascripts/clusters_list/index.js
@@ -1,8 +1,10 @@
+import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import loadClusters from './load_clusters';
import loadMainView from './load_main_view';
+Vue.use(GlToast);
Vue.use(VueApollo);
export default () => {
diff --git a/app/assets/javascripts/confirm_danger_modal.js b/app/assets/javascripts/confirm_danger_modal.js
deleted file mode 100644
index ad70d9be16f..00000000000
--- a/app/assets/javascripts/confirm_danger_modal.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import $ from 'jquery';
-import { Rails } from '~/lib/utils/rails_ujs';
-import { rstrip } from './lib/utils/common_utils';
-
-function openConfirmDangerModal($form, $modal, text) {
- const $input = $('.js-legacy-confirm-danger-input', $modal);
- $input.val('');
-
- $('.js-confirm-text', $modal).text(text || '');
- $modal.modal('show');
-
- const confirmTextMatch = $('.js-legacy-confirm-danger-match', $modal).text();
- const $submit = $('.js-legacy-confirm-danger-submit', $modal);
- $submit.disable();
- $input.focus();
-
- // eslint-disable-next-line @gitlab/no-global-event-off
- $input.off('input').on('input', function handleInput() {
- const confirmText = rstrip($(this).val());
- if (confirmText === confirmTextMatch) {
- $submit.enable();
- } else {
- $submit.disable();
- }
- });
-
- // eslint-disable-next-line @gitlab/no-global-event-off
- $('.js-legacy-confirm-danger-submit', $modal)
- .off('click')
- .on('click', () => {
- if ($form.data('remote')) {
- Rails.fire($form[0], 'submit');
- } else {
- $form.submit();
- }
- });
-}
-
-function getModal($btn) {
- const $modal = $btn.prev('.modal');
-
- if ($modal.length) {
- return $modal;
- }
-
- return $('#modal-confirm-danger');
-}
-
-export default function initConfirmDangerModal() {
- $(document).on('click', '.js-legacy-confirm-danger', (e) => {
- const $btn = $(e.target);
- const checkFieldName = $btn.data('checkFieldName');
- const checkFieldCompareValue = $btn.data('checkCompareValue');
- const checkFieldVal = parseInt($(`[name="${checkFieldName}"]`).val(), 10);
-
- if (!checkFieldName || checkFieldVal < checkFieldCompareValue) {
- e.preventDefault();
- const $form = $btn.closest('form');
- const $modal = getModal($btn);
- const text = $btn.data('confirmDangerMessage');
- openConfirmDangerModal($form, $modal, text);
- }
- });
-}
diff --git a/app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue b/app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue
index 97b69afd12e..e8829d00986 100644
--- a/app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue
+++ b/app/assets/javascripts/content_editor/components/wrappers/frontmatter.vue
@@ -20,7 +20,7 @@ export default {
};
</script>
<template>
- <node-view-wrapper class="gl-relative code highlight" as="pre">
+ <node-view-wrapper class="content-editor-code-block gl-relative code highlight" as="pre">
<span
data-testid="frontmatter-label"
class="gl-absolute gl-top-0 gl-right-3"
diff --git a/app/assets/javascripts/content_editor/constants.js b/app/assets/javascripts/content_editor/constants.js
index 4af9dc8e405..5e56078df01 100644
--- a/app/assets/javascripts/content_editor/constants.js
+++ b/app/assets/javascripts/content_editor/constants.js
@@ -49,3 +49,10 @@ export const LOADING_ERROR_EVENT = 'loadingError';
export const PARSE_HTML_PRIORITY_LOWEST = 1;
export const PARSE_HTML_PRIORITY_DEFAULT = 50;
export const PARSE_HTML_PRIORITY_HIGHEST = 100;
+
+export const EXTENSION_PRIORITY_LOWER = 75;
+/**
+ * 100 is the default priority in Tiptap
+ * https://tiptap.dev/guide/custom-extensions/#priority
+ */
+export const EXTENSION_PRIORITY_DEFAULT = 100;
diff --git a/app/assets/javascripts/content_editor/extensions/code.js b/app/assets/javascripts/content_editor/extensions/code.js
index f93c22ad10e..53f6d9b995c 100644
--- a/app/assets/javascripts/content_editor/extensions/code.js
+++ b/app/assets/javascripts/content_editor/extensions/code.js
@@ -1 +1,12 @@
-export { Code as default } from '@tiptap/extension-code';
+import Code from '@tiptap/extension-code';
+import { EXTENSION_PRIORITY_LOWER } from '../constants';
+
+export default Code.extend({
+ excludes: null,
+ /**
+ * Reduce the rendering priority of the code mark to
+ * ensure the bold, italic, and strikethrough marks
+ * are rendered first.
+ */
+ priority: EXTENSION_PRIORITY_LOWER,
+});
diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js
index ea51bee3ba9..9dc17fcd570 100644
--- a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js
+++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js
@@ -19,7 +19,14 @@ export default CodeBlockLowlight.extend({
};
},
renderHTML({ HTMLAttributes }) {
- return ['div', ['pre', HTMLAttributes, ['code', {}, 0]]];
+ return [
+ 'pre',
+ {
+ ...HTMLAttributes,
+ class: `content-editor-code-block ${HTMLAttributes.class}`,
+ },
+ ['code', {}, 0],
+ ];
},
}).configure({
lowlight,
diff --git a/app/assets/javascripts/content_editor/extensions/frontmatter.js b/app/assets/javascripts/content_editor/extensions/frontmatter.js
index c09c10bc524..9842027e192 100644
--- a/app/assets/javascripts/content_editor/extensions/frontmatter.js
+++ b/app/assets/javascripts/content_editor/extensions/frontmatter.js
@@ -14,9 +14,20 @@ export default CodeBlockHighlight.extend({
},
];
},
+ addCommands() {
+ return {
+ setFrontmatter: (attributes) => ({ commands }) => {
+ return commands.setNode(this.name, attributes);
+ },
+ toggleFrontmatter: (attributes) => ({ commands }) => {
+ return commands.toggleNode(this.name, 'paragraph', attributes);
+ },
+ };
+ },
addNodeView() {
return new VueNodeViewRenderer(FrontmatterWrapper);
},
+
addInputRules() {
return [];
},
diff --git a/app/assets/javascripts/content_editor/extensions/image.js b/app/assets/javascripts/content_editor/extensions/image.js
index d7fb617f7ee..519f7f168ce 100644
--- a/app/assets/javascripts/content_editor/extensions/image.js
+++ b/app/assets/javascripts/content_editor/extensions/image.js
@@ -66,6 +66,17 @@ export default Image.extend({
},
];
},
+ renderHTML({ HTMLAttributes }) {
+ return [
+ 'img',
+ {
+ src: HTMLAttributes.src,
+ alt: HTMLAttributes.alt,
+ title: HTMLAttributes.title,
+ 'data-canonical-src': HTMLAttributes.canonicalSrc,
+ },
+ ];
+ },
addNodeView() {
return VueNodeViewRenderer(ImageWrapper);
},
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
index 278ef326c7a..d54fb7cded2 100644
--- a/app/assets/javascripts/content_editor/services/markdown_serializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -65,8 +65,8 @@ import {
const defaultSerializerConfig = {
marks: {
[Bold.name]: defaultMarkdownSerializer.marks.strong,
- [Code.name]: defaultMarkdownSerializer.marks.code,
[Italic.name]: { open: '_', close: '_', mixable: true, expelEnclosingWhitespace: true },
+ [Code.name]: defaultMarkdownSerializer.marks.code,
[Subscript.name]: { open: '<sub>', close: '</sub>', mixable: true },
[Superscript.name]: { open: '<sup>', close: '</sup>', mixable: true },
[InlineDiff.name]: {
diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js
index ed5910fca18..4d5a54c0347 100644
--- a/app/assets/javascripts/content_editor/services/serialization_helpers.js
+++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js
@@ -1,4 +1,4 @@
-import { uniq } from 'lodash';
+import { uniq, isString } from 'lodash';
const defaultAttrs = {
td: { colspan: 1, rowspan: 1, colwidth: null },
@@ -325,9 +325,12 @@ export function renderHardBreak(state, node, parent, index) {
export function renderImage(state, node) {
const { alt, canonicalSrc, src, title } = node.attrs;
- const quotedTitle = title ? ` ${state.quote(title)}` : '';
- state.write(`![${state.esc(alt || '')}](${state.esc(canonicalSrc || src)}${quotedTitle})`);
+ if (isString(src) || isString(canonicalSrc)) {
+ const quotedTitle = title ? ` ${state.quote(title)}` : '';
+
+ state.write(`![${state.esc(alt || '')}](${state.esc(canonicalSrc || src)}${quotedTitle})`);
+ }
}
export function renderPlayable(state, node) {
diff --git a/app/assets/javascripts/content_editor/services/track_input_rules_and_shortcuts.js b/app/assets/javascripts/content_editor/services/track_input_rules_and_shortcuts.js
index 9b1cb76f845..eb1e4885ba6 100644
--- a/app/assets/javascripts/content_editor/services/track_input_rules_and_shortcuts.js
+++ b/app/assets/javascripts/content_editor/services/track_input_rules_and_shortcuts.js
@@ -35,31 +35,33 @@ const trackInputRule = (contentType, inputRule) => {
};
const trackInputRulesAndShortcuts = (tiptapExtension) => {
- return tiptapExtension.extend({
- addKeyboardShortcuts() {
- const shortcuts = this.parent?.() || {};
- const { name } = this;
- /**
- * We don’t want to track keyboard shortcuts
- * that are not deliberately executed to create
- * new types of content
- */
- const dotNotTrackKeys = [ENTER_KEY, BACKSPACE_KEY];
- const decorated = mapValues(shortcuts, (commandFn, shortcut) =>
- dotNotTrackKeys.includes(shortcut)
- ? commandFn
- : trackKeyboardShortcut(name, commandFn, shortcut),
- );
+ return tiptapExtension
+ .extend({
+ addKeyboardShortcuts() {
+ const shortcuts = this.parent?.() || {};
+ const { name } = this;
+ /**
+ * We don’t want to track keyboard shortcuts
+ * that are not deliberately executed to create
+ * new types of content
+ */
+ const dotNotTrackKeys = [ENTER_KEY, BACKSPACE_KEY];
+ const decorated = mapValues(shortcuts, (commandFn, shortcut) =>
+ dotNotTrackKeys.includes(shortcut)
+ ? commandFn
+ : trackKeyboardShortcut(name, commandFn, shortcut),
+ );
- return decorated;
- },
- addInputRules() {
- const inputRules = this.parent?.() || [];
- const { name } = this;
+ return decorated;
+ },
+ addInputRules() {
+ const inputRules = this.parent?.() || [];
+ const { name } = this;
- return inputRules.map((inputRule) => trackInputRule(name, inputRule));
- },
- });
+ return inputRules.map((inputRule) => trackInputRule(name, inputRule));
+ },
+ })
+ .configure(tiptapExtension.options);
};
export default trackInputRulesAndShortcuts;
diff --git a/app/assets/javascripts/crm/components/form.vue b/app/assets/javascripts/crm/components/form.vue
new file mode 100644
index 00000000000..b24de1e95e8
--- /dev/null
+++ b/app/assets/javascripts/crm/components/form.vue
@@ -0,0 +1,232 @@
+<script>
+import { GlAlert, GlButton, GlDrawer, GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { get as getPropValueByPath, isEmpty } from 'lodash';
+import { produce } from 'immer';
+import { MountingPortal } from 'portal-vue';
+import { __ } from '~/locale';
+import { logError } from '~/lib/logger';
+import { getFirstPropertyValue } from '~/lib/utils/common_utils';
+import { INDEX_ROUTE_NAME } from '../constants';
+
+const MSG_SAVE_CHANGES = __('Save changes');
+const MSG_ERROR = __('Something went wrong. Please try again.');
+const MSG_OPTIONAL = __('(optional)');
+const MSG_CANCEL = __('Cancel');
+
+/**
+ * This component is a first iteration towards a general reusable Create/Update component
+ *
+ * There's some opportunity to improve cohesion of this module which we are planning
+ * to address after solidifying the abstraction's requirements.
+ *
+ * Please see https://gitlab.com/gitlab-org/gitlab/-/issues/349441
+ */
+export default {
+ components: {
+ GlAlert,
+ GlButton,
+ GlDrawer,
+ GlFormGroup,
+ GlFormInput,
+ MountingPortal,
+ },
+ props: {
+ drawerOpen: {
+ type: Boolean,
+ required: true,
+ },
+ fields: {
+ type: Array,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ successMessage: {
+ type: String,
+ required: true,
+ },
+ mutation: {
+ type: Object,
+ required: true,
+ },
+ getQuery: {
+ type: Object,
+ required: false,
+ default: null,
+ },
+ getQueryNodePath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ existingModel: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ additionalCreateParams: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
+ buttonLabel: {
+ type: String,
+ required: false,
+ default: () => MSG_SAVE_CHANGES,
+ },
+ },
+ data() {
+ const initialModel = this.fields.reduce(
+ (map, field) =>
+ Object.assign(map, {
+ [field.name]: this.existingModel ? this.existingModel[field.name] : null,
+ }),
+ {},
+ );
+
+ return {
+ model: initialModel,
+ submitting: false,
+ errorMessages: [],
+ };
+ },
+ computed: {
+ isEditMode() {
+ return this.existingModel?.id;
+ },
+ isInvalid() {
+ const { fields, model } = this;
+
+ return fields.some((field) => {
+ return field.required && isEmpty(model[field.name]);
+ });
+ },
+ variables() {
+ const { additionalCreateParams, fields, isEditMode, model } = this;
+
+ const variables = fields.reduce(
+ (map, field) =>
+ Object.assign(map, {
+ [field.name]: this.formatValue(model, field),
+ }),
+ {},
+ );
+
+ if (isEditMode) {
+ return { input: { id: this.existingModel.id, ...variables } };
+ }
+
+ return { input: { ...additionalCreateParams, ...variables } };
+ },
+ },
+ methods: {
+ formatValue(model, field) {
+ if (!isEmpty(model[field.name]) && field.input?.type === 'number') {
+ return parseFloat(model[field.name]);
+ }
+
+ return model[field.name];
+ },
+ save() {
+ const { mutation, variables, close } = this;
+
+ this.submitting = true;
+
+ return this.$apollo
+ .mutate({
+ mutation,
+ variables,
+ update: (store, { data }) => {
+ const { errors, ...result } = getFirstPropertyValue(data);
+
+ if (errors?.length) {
+ this.errorMessages = errors;
+ } else {
+ this.updateCache(store, result);
+ close(true);
+ }
+ },
+ })
+ .catch((e) => {
+ logError(e);
+ this.errorMessages = [MSG_ERROR];
+ })
+ .finally(() => {
+ this.submitting = false;
+ });
+ },
+ close(success) {
+ if (success) {
+ // This is needed so toast perists when route is changed
+ this.$root.$toast.show(this.successMessage);
+ }
+
+ this.$router.replace({ name: this.$options.INDEX_ROUTE_NAME });
+ },
+ updateCache(store, result) {
+ const { getQuery, isEditMode, getQueryNodePath } = this;
+
+ if (isEditMode || !getQuery) return;
+
+ const sourceData = store.readQuery(getQuery);
+
+ const newData = produce(sourceData, (draftState) => {
+ getPropValueByPath(draftState, getQueryNodePath).nodes.push(getFirstPropertyValue(result));
+ });
+
+ store.writeQuery({
+ ...getQuery,
+ data: newData,
+ });
+ },
+ getFieldLabel(field) {
+ const optionalSuffix = field.required ? '' : ` ${MSG_OPTIONAL}`;
+ return field.label + optionalSuffix;
+ },
+ },
+ MSG_CANCEL,
+ INDEX_ROUTE_NAME,
+};
+</script>
+
+<template>
+ <mounting-portal mount-to="#js-crm-form-portal" append>
+ <gl-drawer class="gl-drawer-responsive gl-absolute" :open="drawerOpen" @close="close(false)">
+ <template #title>
+ <h3>{{ title }}</h3>
+ </template>
+ <gl-alert v-if="errorMessages.length" variant="danger" @dismiss="errorMessages = []">
+ <ul class="gl-mb-0! gl-ml-5">
+ <li v-for="error in errorMessages" :key="error">
+ {{ error }}
+ </li>
+ </ul>
+ </gl-alert>
+ <form @submit.prevent="save">
+ <gl-form-group
+ v-for="field in fields"
+ :key="field.name"
+ :label="getFieldLabel(field)"
+ :label-for="field.name"
+ >
+ <gl-form-input :id="field.name" v-bind="field.input" v-model="model[field.name]" />
+ </gl-form-group>
+ <span class="gl-float-right">
+ <gl-button data-testid="cancel-button" @click="close(false)">
+ {{ $options.MSG_CANCEL }}
+ </gl-button>
+ <gl-button
+ variant="confirm"
+ :disabled="isInvalid"
+ :loading="submitting"
+ data-testid="save-button"
+ type="submit"
+ >{{ buttonLabel }}</gl-button
+ >
+ </span>
+ </form>
+ </gl-drawer>
+ </mounting-portal>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/base.vue b/app/assets/javascripts/cycle_analytics/components/base.vue
index 36430e51dd2..bdfabb8e846 100644
--- a/app/assets/javascripts/cycle_analytics/components/base.vue
+++ b/app/assets/javascripts/cycle_analytics/components/base.vue
@@ -153,7 +153,7 @@ export default {
};
</script>
<template>
- <div class="cycle-analytics">
+ <div>
<h3>{{ $options.i18n.pageTitle }}</h3>
<div class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row">
<path-navigation
diff --git a/app/assets/javascripts/cycle_analytics/components/path_navigation.vue b/app/assets/javascripts/cycle_analytics/components/path_navigation.vue
index f8f89772fd6..af7334ecf2e 100644
--- a/app/assets/javascripts/cycle_analytics/components/path_navigation.vue
+++ b/app/assets/javascripts/cycle_analytics/components/path_navigation.vue
@@ -57,7 +57,7 @@ export default {
};
</script>
<template>
- <gl-skeleton-loading v-if="loading" :lines="2" class="h-auto pt-2 pb-1" />
+ <gl-skeleton-loading v-if="loading" :lines="2" />
<gl-path v-else :key="selectedStage.id" :items="stages" @selected="onSelectStage">
<template #default="{ pathItem, pathId }">
<gl-popover
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_table.vue b/app/assets/javascripts/cycle_analytics/components/stage_table.vue
index fc4dfafb809..8f7a3f99bab 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_table.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_table.vue
@@ -32,6 +32,9 @@ const WORKFLOW_COLUMN_TITLES = {
mergeRequests: { ...DEFAULT_WORKFLOW_TITLE_PROPERTIES, label: __('Merge requests') },
};
+const fullProjectPath = ({ namespaceFullPath = '', projectPath }) =>
+ namespaceFullPath.split('/').length > 1 ? `${namespaceFullPath}/${projectPath}` : projectPath;
+
export default {
name: 'StageTable',
components: {
@@ -89,6 +92,11 @@ export default {
required: false,
default: true,
},
+ includeProjectName: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
if (this.pagination) {
@@ -144,8 +152,15 @@ export default {
isMrLink(url = '') {
return url.includes('/merge_request');
},
- itemId({ url, iid }) {
- return this.isMrLink(url) ? `!${iid}` : `#${iid}`;
+ itemId({ iid, projectPath, namespaceFullPath = '' }, separator = '#') {
+ const prefix = this.includeProjectName
+ ? fullProjectPath({ namespaceFullPath, projectPath })
+ : '';
+ return `${prefix}${separator}${iid}`;
+ },
+ itemDisplayName(item) {
+ const separator = this.isMrLink(item.url) ? '!' : '#';
+ return this.itemId(item, separator);
},
itemTitle(item) {
return item.title || item.name;
@@ -201,8 +216,11 @@ export default {
<div data-testid="vsa-stage-event">
<div v-if="item.id" data-testid="vsa-stage-content">
<p class="gl-m-0">
- <gl-link class="gl-text-black-normal pipeline-id" :href="item.url"
- >#{{ item.id }}</gl-link
+ <gl-link
+ data-testid="vsa-stage-event-link"
+ class="gl-text-black-normal"
+ :href="item.url"
+ >{{ itemId(item.id, '#') }}</gl-link
>
<gl-icon :size="16" name="fork" />
<gl-link
@@ -240,7 +258,12 @@ export default {
<gl-link class="gl-text-black-normal" :href="item.url">{{ itemTitle(item) }}</gl-link>
</h5>
<p class="gl-m-0">
- <gl-link class="gl-text-black-normal" :href="item.url">{{ itemId(item) }}</gl-link>
+ <gl-link
+ data-testid="vsa-stage-event-link"
+ class="gl-text-black-normal"
+ :href="item.url"
+ >{{ itemDisplayName(item) }}</gl-link
+ >
<span class="gl-font-lg">&middot;</span>
<span data-testid="vsa-stage-event-date">
{{ s__('OpenedNDaysAgo|Opened') }}
diff --git a/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue b/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue
index 8610dfc2b03..64461797c46 100644
--- a/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue
+++ b/app/assets/javascripts/cycle_analytics/components/value_stream_filters.vue
@@ -59,7 +59,9 @@ export default {
};
</script>
<template>
- <div class="gl-mt-3 gl-py-2 gl-px-3 bg-gray-light border-top border-bottom">
+ <div
+ class="gl-mt-3 gl-py-2 gl-px-3 gl-bg-gray-10 gl-border-b-1 gl-border-b-solid gl-border-t-1 gl-border-t-solid gl-border-gray-100"
+ >
<filter-bar
data-testid="vsa-filter-bar"
class="filtered-search-box gl-display-flex gl-mb-2 gl-mr-3 gl-border-none"
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
index 10976202d06..7fefbab977d 100644
--- a/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
+++ b/app/assets/javascripts/design_management/components/design_notes/design_discussion.vue
@@ -7,6 +7,7 @@ import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vu
import { updateGlobalTodoCount } from '~/vue_shared/components/sidebar/todo_toggle/utils';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import DesignNotePin from '~/vue_shared/components/design_management/design_note_pin.vue';
+import { isLoggedIn } from '~/lib/utils/common_utils';
import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants';
import createNoteMutation from '../../graphql/mutations/create_note.mutation.graphql';
import toggleResolveDiscussionMutation from '../../graphql/mutations/toggle_resolve_discussion.mutation.graphql';
@@ -17,6 +18,7 @@ import { hasErrors } from '../../utils/cache_update';
import { extractDesign } from '../../utils/design_management_utils';
import { ADD_DISCUSSION_COMMENT_ERROR } from '../../utils/error_messages';
import DesignNote from './design_note.vue';
+import DesignNoteSignedOut from './design_note_signed_out.vue';
import DesignReplyForm from './design_reply_form.vue';
import ToggleRepliesWidget from './toggle_replies_widget.vue';
@@ -24,6 +26,7 @@ export default {
components: {
ApolloMutation,
DesignNote,
+ DesignNoteSignedOut,
ReplyPlaceholder,
DesignReplyForm,
GlIcon,
@@ -55,6 +58,14 @@ export default {
required: false,
default: '',
},
+ registerPath: {
+ type: String,
+ required: true,
+ },
+ signInPath: {
+ type: String,
+ required: true,
+ },
resolvedDiscussionsExpanded: {
type: Boolean,
required: true,
@@ -93,6 +104,7 @@ export default {
isResolving: false,
shouldChangeResolvedStatus: false,
areRepliesCollapsed: this.discussion.resolved,
+ isLoggedIn: isLoggedIn(),
};
},
computed: {
@@ -226,7 +238,7 @@ export default {
:class="{ 'gl-bg-blue-50': isDiscussionActive }"
@error="$emit('update-note-error', $event)"
>
- <template v-if="discussion.resolvable" #resolve-discussion>
+ <template v-if="isLoggedIn && discussion.resolvable" #resolve-discussion>
<button
v-gl-tooltip
:class="{ 'is-active': discussion.resolved }"
@@ -269,38 +281,47 @@ export default {
:class="{ 'gl-bg-blue-50': isDiscussionActive }"
@error="$emit('update-note-error', $event)"
/>
- <li v-show="isReplyPlaceholderVisible" class="reply-wrapper discussion-reply-holder">
- <reply-placeholder
- v-if="!isFormVisible"
- class="qa-discussion-reply"
- :placeholder-text="__('Reply…')"
- @focus="showForm"
- />
- <apollo-mutation
- v-else
- #default="{ mutate, loading }"
- :mutation="$options.createNoteMutation"
- :variables="{
- input: mutationPayload,
- }"
- @done="onDone"
- @error="onCreateNoteError"
- >
- <design-reply-form
- v-model="discussionComment"
- :is-saving="loading"
- :markdown-preview-path="markdownPreviewPath"
- @submit-form="mutate"
- @cancel-form="hideForm"
+ <li
+ v-show="isReplyPlaceholderVisible"
+ class="reply-wrapper discussion-reply-holder"
+ :class="{ 'gl-bg-gray-10': !isLoggedIn }"
+ >
+ <template v-if="!isLoggedIn">
+ <design-note-signed-out :register-path="registerPath" :sign-in-path="signInPath" />
+ </template>
+ <template v-else>
+ <reply-placeholder
+ v-if="!isFormVisible"
+ class="qa-discussion-reply"
+ :placeholder-text="__('Reply…')"
+ @focus="showForm"
+ />
+ <apollo-mutation
+ v-else
+ #default="{ mutate, loading }"
+ :mutation="$options.createNoteMutation"
+ :variables="{
+ input: mutationPayload,
+ }"
+ @done="onDone"
+ @error="onCreateNoteError"
>
- <template v-if="discussion.resolvable" #resolve-checkbox>
- <label data-testid="resolve-checkbox">
- <input v-model="shouldChangeResolvedStatus" type="checkbox" />
- {{ resolveCheckboxText }}
- </label>
- </template>
- </design-reply-form>
- </apollo-mutation>
+ <design-reply-form
+ v-model="discussionComment"
+ :is-saving="loading"
+ :markdown-preview-path="markdownPreviewPath"
+ @submit-form="mutate"
+ @cancel-form="hideForm"
+ >
+ <template v-if="discussion.resolvable" #resolve-checkbox>
+ <label data-testid="resolve-checkbox">
+ <input v-model="shouldChangeResolvedStatus" type="checkbox" />
+ {{ resolveCheckboxText }}
+ </label>
+ </template>
+ </design-reply-form>
+ </apollo-mutation>
+ </template>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/design_management/components/design_notes/design_note_signed_out.vue b/app/assets/javascripts/design_management/components/design_notes/design_note_signed_out.vue
new file mode 100644
index 00000000000..f0812e62bba
--- /dev/null
+++ b/app/assets/javascripts/design_management/components/design_notes/design_note_signed_out.vue
@@ -0,0 +1,50 @@
+<script>
+import { GlSprintf, GlLink } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+export default {
+ components: {
+ GlSprintf,
+ GlLink,
+ },
+ props: {
+ registerPath: {
+ type: String,
+ required: true,
+ },
+ signInPath: {
+ type: String,
+ required: true,
+ },
+ isAddDiscussion: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ signedOutText() {
+ return this.isAddDiscussion
+ ? __(
+ 'Please %{registerLinkStart}register%{registerLinkEnd} or %{signInLinkStart}sign in%{signInLinkEnd} to start a new discussion.',
+ )
+ : __(
+ 'Please %{registerLinkStart}register%{registerLinkEnd} or %{signInLinkStart}sign in%{signInLinkEnd} to reply.',
+ );
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="disabled-comment text-center">
+ <gl-sprintf :message="signedOutText">
+ <template #registerLink="{ content }">
+ <gl-link :href="registerPath">{{ content }}</gl-link>
+ </template>
+ <template #signInLink="{ content }">
+ <gl-link :href="signInPath">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </div>
+</template>
diff --git a/app/assets/javascripts/design_management/components/design_presentation.vue b/app/assets/javascripts/design_management/components/design_presentation.vue
index 11d2f3b2e37..5116bacefa5 100644
--- a/app/assets/javascripts/design_management/components/design_presentation.vue
+++ b/app/assets/javascripts/design_management/components/design_presentation.vue
@@ -1,5 +1,6 @@
<script>
import { throttle } from 'lodash';
+import { isLoggedIn } from '~/lib/utils/common_utils';
import DesignOverlay from './design_overlay.vue';
import DesignImage from './image.vue';
@@ -54,6 +55,7 @@ export default {
initialLoad: true,
lastDragPosition: null,
isDraggingDesign: false,
+ isLoggedIn: isLoggedIn(),
};
},
computed: {
@@ -311,7 +313,7 @@ export default {
:position="overlayPosition"
:notes="discussionStartingNotes"
:current-comment-form="currentCommentForm"
- :disable-commenting="isDraggingDesign"
+ :disable-commenting="!isLoggedIn || isDraggingDesign"
:resolved-discussions-expanded="resolvedDiscussionsExpanded"
@openCommentForm="openCommentForm"
@closeCommentForm="closeCommentForm"
diff --git a/app/assets/javascripts/design_management/components/design_sidebar.vue b/app/assets/javascripts/design_management/components/design_sidebar.vue
index ced76eb4843..6d0ed3b08a3 100644
--- a/app/assets/javascripts/design_management/components/design_sidebar.vue
+++ b/app/assets/javascripts/design_management/components/design_sidebar.vue
@@ -1,7 +1,7 @@
<script>
import { GlCollapse, GlButton, GlPopover } from '@gitlab/ui';
import Cookies from 'js-cookie';
-import { parseBoolean } from '~/lib/utils/common_utils';
+import { parseBoolean, isLoggedIn } from '~/lib/utils/common_utils';
import { s__ } from '~/locale';
import Participants from '~/sidebar/components/participants/participants.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -9,11 +9,13 @@ import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants';
import updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql';
import { extractDiscussions, extractParticipants } from '../utils/design_management_utils';
import DesignDiscussion from './design_notes/design_discussion.vue';
+import DesignNoteSignedOut from './design_notes/design_note_signed_out.vue';
import DesignTodoButton from './design_todo_button.vue';
export default {
components: {
DesignDiscussion,
+ DesignNoteSignedOut,
Participants,
GlCollapse,
GlButton,
@@ -28,6 +30,12 @@ export default {
issueIid: {
default: '',
},
+ registerPath: {
+ default: '',
+ },
+ signInPath: {
+ default: '',
+ },
},
props: {
design: {
@@ -47,6 +55,7 @@ export default {
return {
isResolvedCommentsPopoverHidden: parseBoolean(Cookies.get(this.$options.cookieKey)),
discussionWithOpenForm: '',
+ isLoggedIn: isLoggedIn(),
};
},
computed: {
@@ -134,12 +143,19 @@ export default {
class="gl-mb-4"
/>
<h2
- v-if="unresolvedDiscussions.length === 0"
+ v-if="isLoggedIn && unresolvedDiscussions.length === 0"
class="new-discussion-disclaimer gl-font-base gl-m-0 gl-mb-4"
data-testid="new-discussion-disclaimer"
>
{{ s__("DesignManagement|Click the image where you'd like to start a new discussion") }}
</h2>
+ <design-note-signed-out
+ v-if="!isLoggedIn"
+ class="gl-mb-4"
+ :register-path="registerPath"
+ :sign-in-path="signInPath"
+ :is-add-discussion="true"
+ />
<design-discussion
v-for="discussion in unresolvedDiscussions"
:key="discussion.id"
@@ -147,6 +163,8 @@ export default {
:design-id="$route.params.id"
:noteable-id="design.id"
:markdown-preview-path="markdownPreviewPath"
+ :register-path="registerPath"
+ :sign-in-path="signInPath"
:resolved-discussions-expanded="resolvedDiscussionsExpanded"
:discussion-with-open-form="discussionWithOpenForm"
data-testid="unresolved-discussion"
@@ -197,6 +215,8 @@ export default {
:design-id="$route.params.id"
:noteable-id="design.id"
:markdown-preview-path="markdownPreviewPath"
+ :register-path="registerPath"
+ :sign-in-path="signInPath"
:resolved-discussions-expanded="resolvedDiscussionsExpanded"
:discussion-with-open-form="discussionWithOpenForm"
data-testid="resolved-discussion"
diff --git a/app/assets/javascripts/design_management/index.js b/app/assets/javascripts/design_management/index.js
index 11666587265..4ae76050aa5 100644
--- a/app/assets/javascripts/design_management/index.js
+++ b/app/assets/javascripts/design_management/index.js
@@ -8,7 +8,7 @@ import createRouter from './router';
export default () => {
const el = document.querySelector('.js-design-management');
- const { issueIid, projectPath, issuePath } = el.dataset;
+ const { issueIid, projectPath, issuePath, registerPath, signInPath } = el.dataset;
const router = createRouter(issuePath);
apolloProvider.clients.defaultClient.cache.writeQuery({
@@ -29,6 +29,8 @@ export default () => {
provide: {
projectPath,
issueIid,
+ registerPath,
+ signInPath,
},
mounted() {
performanceMarkAndMeasure({
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index da918947cc5..442807587d5 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -182,7 +182,7 @@ export default {
class="gl-mr-3"
@click="expandAllFiles"
>
- {{ __('Expand all') }}
+ {{ __('Expand all files') }}
</gl-button>
<settings-dropdown />
</div>
diff --git a/app/assets/javascripts/diffs/components/image_diff_overlay.vue b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
index 5572338908f..eede8e52292 100644
--- a/app/assets/javascripts/diffs/components/image_diff_overlay.vue
+++ b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
@@ -4,8 +4,8 @@ import { isArray } from 'lodash';
import { mapActions, mapGetters } from 'vuex';
import imageDiffMixin from 'ee_else_ce/diffs/mixins/image_diff';
-function calcPercent(pos, size, renderedSize) {
- return (((pos / size) * 100) / ((renderedSize / size) * 100)) * 100;
+function calcPercent(pos, renderedSize) {
+ return (100 * pos) / renderedSize;
}
export default {
@@ -65,8 +65,8 @@ export default {
...mapActions('diffs', ['openDiffFileCommentForm']),
getImageDimensions() {
return {
- width: this.$parent.width,
- height: this.$parent.height,
+ width: Math.round(this.$parent.width),
+ height: Math.round(this.$parent.height),
};
},
getPositionForObject(meta) {
@@ -87,15 +87,15 @@ export default {
},
clickedImage(x, y) {
const { width, height } = this.getImageDimensions();
- const xPercent = calcPercent(x, width, this.renderedWidth);
- const yPercent = calcPercent(y, height, this.renderedHeight);
+ const xPercent = calcPercent(x, this.renderedWidth);
+ const yPercent = calcPercent(y, this.renderedHeight);
this.openDiffFileCommentForm({
fileHash: this.fileHash,
width,
height,
- x: width * (xPercent / 100),
- y: height * (yPercent / 100),
+ x: Math.round(width * (xPercent / 100)),
+ y: Math.round(height * (yPercent / 100)),
xPercent,
yPercent,
});
diff --git a/app/assets/javascripts/dropzone_input.js b/app/assets/javascripts/dropzone_input.js
index 7c7127dfa44..491c2ced358 100644
--- a/app/assets/javascripts/dropzone_input.js
+++ b/app/assets/javascripts/dropzone_input.js
@@ -51,7 +51,6 @@ export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
// Add dropzone area to the form.
const $mdArea = formTextarea.closest('.md-area');
- form.setupMarkdownPreview();
const $formDropzone = form.find('.div-dropzone');
$formDropzone.parent().addClass('div-dropzone-wrapper');
$formDropzone.append(divHover);
diff --git a/app/assets/javascripts/editor/source_editor.js b/app/assets/javascripts/editor/source_editor.js
index 57e2b0da565..fa749112ab5 100644
--- a/app/assets/javascripts/editor/source_editor.js
+++ b/app/assets/javascripts/editor/source_editor.js
@@ -149,7 +149,7 @@ export default class SourceEditor {
});
this.instances.push(instance);
- el.dispatchEvent(new CustomEvent(EDITOR_READY_EVENT, { instance }));
+ el.dispatchEvent(new CustomEvent(EDITOR_READY_EVENT, { detail: { instance } }));
return instance;
}
diff --git a/app/assets/javascripts/emoji/components/picker.vue b/app/assets/javascripts/emoji/components/picker.vue
index dc3eac0cd0c..686b5ffff9e 100644
--- a/app/assets/javascripts/emoji/components/picker.vue
+++ b/app/assets/javascripts/emoji/components/picker.vue
@@ -28,6 +28,16 @@ export default {
required: false,
default: () => [],
},
+ right: {
+ type: Boolean,
+ required: false,
+ default: true,
+ },
+ boundary: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
return {
@@ -62,7 +72,7 @@ export default {
addToFrequentlyUsed(name);
},
getBoundaryElement() {
- return document.querySelector('.content-wrapper') || 'scrollParent';
+ return this.boundary || document.querySelector('.content-wrapper') || 'scrollParent';
},
onSearchInput() {
this.$refs.virtualScoller.setScrollTop(0);
@@ -87,7 +97,7 @@ export default {
menu-class="dropdown-extended-height"
category="secondary"
no-flip
- right
+ :right="right"
lazy
@shown="$emit('shown')"
@hidden="$emit('hidden')"
@@ -115,7 +125,7 @@ export default {
:aria-label="category.name"
@click="scrollToCategory(category.name)"
>
- <gl-icon :name="category.icon" :size="12" />
+ <gl-icon :name="category.icon" />
</button>
</div>
<emoji-list :search-value="searchValue">
diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue
index 0e556f093e2..ce919f73858 100644
--- a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue
+++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue
@@ -99,8 +99,7 @@ export default {
};
},
isLastDeployment() {
- // eslint-disable-next-line @gitlab/require-i18n-strings
- return this.environment?.isLastDeployment || this.environment?.lastDeployment?.['last?'];
+ return this.environment?.isLastDeployment || this.environment?.lastDeployment?.isLast;
},
},
methods: {
diff --git a/app/assets/javascripts/environments/components/deployment.vue b/app/assets/javascripts/environments/components/deployment.vue
new file mode 100644
index 00000000000..ef43ca6bc33
--- /dev/null
+++ b/app/assets/javascripts/environments/components/deployment.vue
@@ -0,0 +1,25 @@
+<script>
+import DeploymentStatusBadge from './deployment_status_badge.vue';
+
+export default {
+ components: {
+ DeploymentStatusBadge,
+ },
+ props: {
+ deployment: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ status() {
+ return this.deployment?.status;
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <deployment-status-badge v-if="status" :status="status" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/components/deployment_status_badge.vue b/app/assets/javascripts/environments/components/deployment_status_badge.vue
new file mode 100644
index 00000000000..5a026911766
--- /dev/null
+++ b/app/assets/javascripts/environments/components/deployment_status_badge.vue
@@ -0,0 +1,60 @@
+<script>
+import { GlBadge } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+const STATUS_TEXT = {
+ created: s__('Deployment|Created'),
+ running: s__('Deployment|Running'),
+ success: s__('Deployment|Success'),
+ failed: s__('Deployment|Failed'),
+ canceled: s__('Deployment|Cancelled'),
+ skipped: s__('Deployment|Skipped'),
+ blocked: s__('Deployment|Waiting'),
+};
+
+const STATUS_VARIANT = {
+ success: 'success',
+ running: 'info',
+ failed: 'danger',
+ created: 'neutral',
+ canceled: 'neutral',
+ skipped: 'neutral',
+ blocked: 'neutral',
+};
+
+const STATUS_ICON = {
+ success: 'status_success',
+ running: 'status_running',
+ failed: 'status_failed',
+ created: 'status_created',
+ canceled: 'status_canceled',
+ skipped: 'status_skipped',
+ blocked: 'status_manual',
+};
+
+export default {
+ components: {
+ GlBadge,
+ },
+ props: {
+ status: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ icon() {
+ return STATUS_ICON[this.status];
+ },
+ text() {
+ return STATUS_TEXT[this.status];
+ },
+ variant() {
+ return STATUS_VARIANT[this.status];
+ },
+ },
+};
+</script>
+<template>
+ <gl-badge v-if="status" :icon="icon" :variant="variant">{{ text }}</gl-badge>
+</template>
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index 2d98f00433a..98c95507168 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -1,8 +1,9 @@
<script>
-import { GlDropdown, GlDropdownItem, GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlDropdown, GlDropdownItem, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { formatTime } from '~/lib/utils/datetime_utility';
import { __, s__, sprintf } from '~/locale';
import eventHub from '../event_hub';
+import actionMutation from '../graphql/mutations/action.mutation.graphql';
export default {
directives: {
@@ -12,7 +13,6 @@ export default {
GlDropdown,
GlDropdownItem,
GlIcon,
- GlLoadingIcon,
},
props: {
actions: {
@@ -20,6 +20,11 @@ export default {
required: false,
default: () => [],
},
+ graphql: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -49,7 +54,11 @@ export default {
this.isLoading = true;
- eventHub.$emit('postAction', { endpoint: action.playPath });
+ if (this.graphql) {
+ this.$apollo.mutate({ mutation: actionMutation, variables: { action } });
+ } else {
+ eventHub.$emit('postAction', { endpoint: action.playPath });
+ }
},
isActionDisabled(action) {
@@ -70,18 +79,16 @@ export default {
<template>
<gl-dropdown
v-gl-tooltip
+ :text="title"
:title="title"
+ :loading="isLoading"
:aria-label="title"
- :disabled="isLoading"
+ icon="play"
+ text-sr-only
right
data-container="body"
data-testid="environment-actions-button"
>
- <template #button-content>
- <gl-icon name="play" />
- <gl-icon name="chevron-down" />
- <gl-loading-icon v-if="isLoading" size="sm" />
- </template>
<gl-dropdown-item
v-for="(action, i) in actions"
:key="i"
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index be9bfb50de5..cfe35d26b94 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -1,5 +1,5 @@
<script>
-import { GlDropdown, GlTooltipDirective, GlIcon, GlLink, GlSprintf } from '@gitlab/ui';
+import { GlDropdown, GlTooltipDirective, GlIcon, GlLink, GlSprintf, GlBadge } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { __, s__, sprintf } from '~/locale';
@@ -30,6 +30,7 @@ export default {
CommitComponent,
ExternalUrlComponent,
GlDropdown,
+ GlBadge,
GlIcon,
GlLink,
GlSprintf,
@@ -621,9 +622,9 @@ export default {
<span v-if="model.size === 1">{{ model.name }}</span>
<span v-else>{{ model.name_without_type }}</span>
</a>
- <span v-if="isProtected" class="badge badge-success">
- {{ s__('Environments|protected') }}
- </span>
+ <gl-badge v-if="isProtected" variant="success">{{
+ s__('Environments|protected')
+ }}</gl-badge>
</span>
<span
v-else
@@ -639,7 +640,7 @@ export default {
<span> {{ model.folderName }} </span>
- <span class="badge badge-pill"> {{ model.size }} </span>
+ <gl-badge>{{ model.size }}</gl-badge>
</span>
</div>
diff --git a/app/assets/javascripts/environments/components/environment_stop.vue b/app/assets/javascripts/environments/components/environment_stop.vue
index 0d4a1e76eb8..17a70fd0c34 100644
--- a/app/assets/javascripts/environments/components/environment_stop.vue
+++ b/app/assets/javascripts/environments/components/environment_stop.vue
@@ -8,6 +8,8 @@ import { GlTooltipDirective, GlButton, GlModalDirective } from '@gitlab/ui';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { s__ } from '~/locale';
import eventHub from '../event_hub';
+import setEnvironmentToStopMutation from '../graphql/mutations/set_environment_to_stop.mutation.graphql';
+import isEnvironmentStoppingQuery from '../graphql/queries/is_environment_stopping.query.graphql';
export default {
components: {
@@ -22,6 +24,19 @@ export default {
type: Object,
required: true,
},
+ graphql: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ apollo: {
+ isEnvironmentStopping: {
+ query: isEnvironmentStoppingQuery,
+ variables() {
+ return { environment: this.environment };
+ },
+ },
},
i18n: {
title: s__('Environments|Stop environment'),
@@ -30,6 +45,7 @@ export default {
data() {
return {
isLoading: false,
+ isEnvironmentStopping: false,
};
},
mounted() {
@@ -41,7 +57,14 @@ export default {
methods: {
onClick() {
this.$root.$emit(BV_HIDE_TOOLTIP, this.$options.stopEnvironmentTooltipId);
- eventHub.$emit('requestStopEnvironment', this.environment);
+ if (this.graphql) {
+ this.$apollo.mutate({
+ mutation: setEnvironmentToStopMutation,
+ variables: { environment: this.environment },
+ });
+ } else {
+ eventHub.$emit('requestStopEnvironment', this.environment);
+ }
},
onStopEnvironment(environment) {
if (this.environment.id === environment.id) {
@@ -56,7 +79,7 @@ export default {
<gl-button
v-gl-tooltip="{ id: $options.stopEnvironmentTooltipId }"
v-gl-modal-directive="'stop-environment-modal'"
- :loading="isLoading"
+ :loading="isLoading || isEnvironmentStopping"
:title="$options.i18n.title"
:aria-label="$options.i18n.title"
icon="stop"
diff --git a/app/assets/javascripts/environments/components/new_environment_folder.vue b/app/assets/javascripts/environments/components/new_environment_folder.vue
index fe3d6f1e8ca..0d3867a4d74 100644
--- a/app/assets/javascripts/environments/components/new_environment_folder.vue
+++ b/app/assets/javascripts/environments/components/new_environment_folder.vue
@@ -2,9 +2,11 @@
import { GlButton, GlCollapse, GlIcon, GlBadge, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import folderQuery from '../graphql/queries/folder.query.graphql';
+import EnvironmentItem from './new_environment_item.vue';
export default {
components: {
+ EnvironmentItem,
GlButton,
GlCollapse,
GlIcon,
@@ -51,17 +53,26 @@ export default {
folderPath() {
return this.nestedEnvironment.latest.folderPath;
},
+ environments() {
+ return this.folder?.environments;
+ },
},
methods: {
toggleCollapse() {
this.visible = !this.visible;
},
+ isFirstEnvironment(index) {
+ return index === 0;
+ },
},
};
</script>
<template>
- <div class="gl-border-b-solid gl-border-gray-100 gl-border-1 gl-px-3 gl-pt-3 gl-pb-5">
- <div class="gl-w-full gl-display-flex gl-align-items-center">
+ <div
+ :class="{ 'gl-pb-5': !visible }"
+ class="gl-border-b-solid gl-border-gray-100 gl-border-1 gl-pt-3"
+ >
+ <div class="gl-w-full gl-display-flex gl-align-items-center gl-px-3">
<gl-button
class="gl-mr-4 gl-fill-current-color gl-text-gray-500"
:aria-label="label"
@@ -77,6 +88,15 @@ export default {
<gl-badge size="sm" class="gl-mr-auto">{{ count }}</gl-badge>
<gl-link v-if="visible" :href="folderPath">{{ $options.i18n.link }}</gl-link>
</div>
- <gl-collapse :visible="visible" />
+ <gl-collapse :visible="visible">
+ <environment-item
+ v-for="(environment, index) in environments"
+ :key="environment.name"
+ :environment="environment"
+ :class="{ 'gl-mt-5': isFirstEnvironment(index) }"
+ class="gl-border-gray-100 gl-border-t-solid gl-border-1 gl-pt-3"
+ in-folder
+ />
+ </gl-collapse>
</div>
</template>
diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue
new file mode 100644
index 00000000000..d3624103c13
--- /dev/null
+++ b/app/assets/javascripts/environments/components/new_environment_item.vue
@@ -0,0 +1,265 @@
+<script>
+import {
+ GlCollapse,
+ GlDropdown,
+ GlButton,
+ GlLink,
+ GlTooltipDirective as GlTooltip,
+} from '@gitlab/ui';
+import { __ } from '~/locale';
+import { truncate } from '~/lib/utils/text_utility';
+import isLastDeployment from '../graphql/queries/is_last_deployment.query.graphql';
+import ExternalUrl from './environment_external_url.vue';
+import Actions from './environment_actions.vue';
+import StopComponent from './environment_stop.vue';
+import Rollback from './environment_rollback.vue';
+import Pin from './environment_pin.vue';
+import Monitoring from './environment_monitoring.vue';
+import Terminal from './environment_terminal_button.vue';
+import Delete from './environment_delete.vue';
+import Deployment from './deployment.vue';
+
+export default {
+ components: {
+ GlCollapse,
+ GlDropdown,
+ GlButton,
+ GlLink,
+ Actions,
+ Deployment,
+ ExternalUrl,
+ StopComponent,
+ Rollback,
+ Monitoring,
+ Pin,
+ Terminal,
+ Delete,
+ },
+ directives: {
+ GlTooltip,
+ },
+ props: {
+ environment: {
+ required: true,
+ type: Object,
+ },
+ inFolder: {
+ required: false,
+ default: false,
+ type: Boolean,
+ },
+ },
+ apollo: {
+ isLastDeployment: {
+ query: isLastDeployment,
+ variables() {
+ return { environment: this.environment };
+ },
+ },
+ },
+ i18n: {
+ collapse: __('Collapse'),
+ expand: __('Expand'),
+ },
+ data() {
+ return { visible: false };
+ },
+ computed: {
+ icon() {
+ return this.visible ? 'angle-down' : 'angle-right';
+ },
+ externalUrl() {
+ return this.environment.externalUrl;
+ },
+ name() {
+ return this.inFolder ? this.environment.nameWithoutType : this.environment.name;
+ },
+ label() {
+ return this.visible ? this.$options.i18n.collapse : this.$options.i18n.expand;
+ },
+ lastDeployment() {
+ return this.environment?.lastDeployment;
+ },
+ upcomingDeployment() {
+ return this.environment?.upcomingDeployment;
+ },
+ actions() {
+ if (!this.lastDeployment) {
+ return [];
+ }
+ const { manualActions = [], scheduledActions = [] } = this.lastDeployment;
+ const combinedActions = [...manualActions, ...scheduledActions];
+ return combinedActions.map((action) => ({
+ ...action,
+ }));
+ },
+ canStop() {
+ return this.environment?.canStop;
+ },
+ retryPath() {
+ return this.lastDeployment?.deployable?.retryPath;
+ },
+ hasExtraActions() {
+ return Boolean(
+ this.retryPath ||
+ this.canShowAutoStopDate ||
+ this.metricsPath ||
+ this.terminalPath ||
+ this.canDeleteEnvironment,
+ );
+ },
+ canShowAutoStopDate() {
+ if (!this.environment?.autoStopAt) {
+ return false;
+ }
+
+ const autoStopDate = new Date(this.environment?.autoStopAt);
+ const now = new Date();
+
+ return now < autoStopDate;
+ },
+ autoStopPath() {
+ return this.environment?.cancelAutoStopPath ?? '';
+ },
+ metricsPath() {
+ return this.environment?.metricsPath ?? '';
+ },
+ terminalPath() {
+ return this.environment?.terminalPath ?? '';
+ },
+ canDeleteEnvironment() {
+ return Boolean(this.environment?.canDelete && this.environment?.deletePath);
+ },
+ displayName() {
+ return truncate(this.name, 80);
+ },
+ },
+ methods: {
+ toggleCollapse() {
+ this.visible = !this.visible;
+ },
+ },
+ deploymentClasses: [
+ 'gl-border-gray-100',
+ 'gl-border-t-solid',
+ 'gl-border-1',
+ 'gl-py-5',
+ 'gl-pl-7',
+ 'gl-bg-gray-10',
+ ],
+};
+</script>
+<template>
+ <div>
+ <div
+ class="gl-px-3 gl-pt-3 gl-pb-5 gl-display-flex gl-justify-content-space-between gl-align-items-center"
+ >
+ <div
+ :class="{ 'gl-ml-7': inFolder }"
+ class="gl-min-w-0 gl-mr-4 gl-display-flex gl-align-items-center"
+ >
+ <gl-button
+ class="gl-mr-4 gl-min-w-fit-content"
+ :icon="icon"
+ :aria-label="label"
+ size="small"
+ category="tertiary"
+ @click="toggleCollapse"
+ />
+ <gl-link
+ v-gl-tooltip
+ :href="environment.environmentPath"
+ class="gl-text-blue-500 gl-text-truncate"
+ :class="{ 'gl-font-weight-bold': visible }"
+ :title="name"
+ >
+ {{ displayName }}
+ </gl-link>
+ </div>
+ <div>
+ <div class="btn-group table-action-buttons" role="group">
+ <external-url
+ v-if="externalUrl"
+ :external-url="externalUrl"
+ data-track-action="click_button"
+ data-track-label="environment_url"
+ />
+
+ <actions
+ v-if="actions.length > 0"
+ :actions="actions"
+ data-track-action="click_dropdown"
+ data-track-label="environment_actions"
+ graphql
+ />
+
+ <stop-component
+ v-if="canStop"
+ :environment="environment"
+ class="gl-z-index-2"
+ data-track-action="click_button"
+ data-track-label="environment_stop"
+ graphql
+ />
+
+ <gl-dropdown
+ v-if="hasExtraActions"
+ icon="ellipsis_v"
+ text-sr-only
+ :text="__('More actions')"
+ category="secondary"
+ no-caret
+ right
+ >
+ <rollback
+ v-if="retryPath"
+ :environment="environment"
+ :is-last-deployment="isLastDeployment"
+ :retry-url="retryPath"
+ graphql
+ data-track-action="click_button"
+ data-track-label="environment_rollback"
+ />
+
+ <pin
+ v-if="canShowAutoStopDate"
+ :auto-stop-url="autoStopPath"
+ data-track-action="click_button"
+ data-track-label="environment_pin"
+ />
+
+ <monitoring
+ v-if="metricsPath"
+ :monitoring-url="metricsPath"
+ data-track-action="click_button"
+ data-track-label="environment_monitoring"
+ />
+
+ <terminal
+ v-if="terminalPath"
+ :terminal-path="terminalPath"
+ data-track-action="click_button"
+ data-track-label="environment_terminal"
+ />
+
+ <delete
+ v-if="canDeleteEnvironment"
+ :environment="environment"
+ data-track-action="click_button"
+ data-track-label="environment_delete"
+ graphql
+ />
+ </gl-dropdown>
+ </div>
+ </div>
+ </div>
+ <gl-collapse :visible="visible">
+ <div v-if="lastDeployment" :class="$options.deploymentClasses">
+ <deployment :deployment="lastDeployment" :class="{ 'gl-ml-7': inFolder }" />
+ </div>
+ <div v-if="upcomingDeployment" :class="$options.deploymentClasses">
+ <deployment :deployment="upcomingDeployment" :class="{ 'gl-ml-7': inFolder }" />
+ </div>
+ </gl-collapse>
+ </div>
+</template>
diff --git a/app/assets/javascripts/environments/components/new_environments_app.vue b/app/assets/javascripts/environments/components/new_environments_app.vue
index 8d94e7021ca..cb36e226d0e 100644
--- a/app/assets/javascripts/environments/components/new_environments_app.vue
+++ b/app/assets/javascripts/environments/components/new_environments_app.vue
@@ -5,13 +5,24 @@ import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_util
import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
import pageInfoQuery from '../graphql/queries/page_info.query.graphql';
+import environmentToDeleteQuery from '../graphql/queries/environment_to_delete.query.graphql';
+import environmentToRollbackQuery from '../graphql/queries/environment_to_rollback.query.graphql';
+import environmentToStopQuery from '../graphql/queries/environment_to_stop.query.graphql';
import EnvironmentFolder from './new_environment_folder.vue';
import EnableReviewAppModal from './enable_review_app_modal.vue';
+import StopEnvironmentModal from './stop_environment_modal.vue';
+import EnvironmentItem from './new_environment_item.vue';
+import ConfirmRollbackModal from './confirm_rollback_modal.vue';
+import DeleteEnvironmentModal from './delete_environment_modal.vue';
export default {
components: {
+ DeleteEnvironmentModal,
+ ConfirmRollbackModal,
EnvironmentFolder,
EnableReviewAppModal,
+ EnvironmentItem,
+ StopEnvironmentModal,
GlBadge,
GlPagination,
GlTab,
@@ -36,6 +47,15 @@ export default {
pageInfo: {
query: pageInfoQuery,
},
+ environmentToDelete: {
+ query: environmentToDeleteQuery,
+ },
+ environmentToRollback: {
+ query: environmentToRollbackQuery,
+ },
+ environmentToStop: {
+ query: environmentToStopQuery,
+ },
},
inject: ['newEnvironmentPath', 'canCreateEnvironment'],
i18n: {
@@ -57,6 +77,9 @@ export default {
isReviewAppModalVisible: false,
page: parseInt(page, 10),
scope,
+ environmentToDelete: {},
+ environmentToRollback: {},
+ environmentToStop: {},
};
},
computed: {
@@ -64,7 +87,10 @@ export default {
return this.environmentApp?.reviewApp?.canSetupReviewApp;
},
folders() {
- return this.environmentApp?.environments.filter((e) => e.size > 1) ?? [];
+ return this.environmentApp?.environments?.filter((e) => e.size > 1) ?? [];
+ },
+ environments() {
+ return this.environmentApp?.environments?.filter((e) => e.size === 1) ?? [];
},
availableCount() {
return this.environmentApp?.availableCount;
@@ -119,7 +145,7 @@ export default {
},
setScope(scope) {
this.scope = scope;
- this.resetPolling();
+ this.moveToPage(1);
},
movePage(direction) {
this.moveToPage(this.pageInfo[`${direction}Page`]);
@@ -157,6 +183,9 @@ export default {
:modal-id="$options.modalId"
data-testid="enable-review-app-modal"
/>
+ <delete-environment-modal :environment="environmentToDelete" graphql />
+ <stop-environment-modal :environment="environmentToStop" graphql />
+ <confirm-rollback-modal :environment="environmentToRollback" graphql />
<gl-tabs
:action-secondary="addEnvironment"
:action-primary="openReviewAppModal"
@@ -187,6 +216,12 @@ export default {
class="gl-mb-3"
:nested-environment="folder"
/>
+ <environment-item
+ v-for="environment in environments"
+ :key="environment.name"
+ class="gl-mb-3 gl-border-gray-100 gl-border-1 gl-border-b-solid"
+ :environment="environment.latest"
+ />
<gl-pagination
align="center"
:total-items="totalItems"
diff --git a/app/assets/javascripts/environments/components/stop_environment_modal.vue b/app/assets/javascripts/environments/components/stop_environment_modal.vue
index 7a9233048a9..162ad598c8c 100644
--- a/app/assets/javascripts/environments/components/stop_environment_modal.vue
+++ b/app/assets/javascripts/environments/components/stop_environment_modal.vue
@@ -2,6 +2,7 @@
import { GlSprintf, GlTooltipDirective, GlModal } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import eventHub from '../event_hub';
+import stopEnvironmentMutation from '../graphql/mutations/stop_environment.mutation.graphql';
export default {
id: 'stop-environment-modal',
@@ -21,6 +22,11 @@ export default {
type: Object,
required: true,
},
+ graphql: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
computed: {
@@ -39,7 +45,14 @@ export default {
methods: {
onSubmit() {
- eventHub.$emit('stopEnvironment', this.environment);
+ if (this.graphql) {
+ this.$apollo.mutate({
+ mutation: stopEnvironmentMutation,
+ variables: { environment: this.environment },
+ });
+ } else {
+ eventHub.$emit('stopEnvironment', this.environment);
+ }
},
},
};
diff --git a/app/assets/javascripts/environments/graphql/mutations/action.mutation.graphql b/app/assets/javascripts/environments/graphql/mutations/action.mutation.graphql
new file mode 100644
index 00000000000..bc2c9b33367
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/mutations/action.mutation.graphql
@@ -0,0 +1,5 @@
+mutation action($action: LocalAction) {
+ action(action: $action) @client {
+ errors
+ }
+}
diff --git a/app/assets/javascripts/environments/graphql/mutations/set_environment_to_stop.mutation.graphql b/app/assets/javascripts/environments/graphql/mutations/set_environment_to_stop.mutation.graphql
new file mode 100644
index 00000000000..2891f4c5101
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/mutations/set_environment_to_stop.mutation.graphql
@@ -0,0 +1,3 @@
+mutation SetEnvironmentToStop($environment: LocalEnvironmentInput) {
+ setEnvironmentToStop(environment: $environment) @client
+}
diff --git a/app/assets/javascripts/environments/graphql/queries/environment_to_stop.query.graphql b/app/assets/javascripts/environments/graphql/queries/environment_to_stop.query.graphql
new file mode 100644
index 00000000000..128846145e8
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/environment_to_stop.query.graphql
@@ -0,0 +1,3 @@
+query environmentToStop {
+ environmentToStop @client
+}
diff --git a/app/assets/javascripts/environments/graphql/queries/is_environment_stopping.query.graphql b/app/assets/javascripts/environments/graphql/queries/is_environment_stopping.query.graphql
new file mode 100644
index 00000000000..ad05e252e6f
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/is_environment_stopping.query.graphql
@@ -0,0 +1,3 @@
+query isEnvironmentStopping($environment: LocalEnvironment) {
+ isEnvironmentStopping(environment: $environment) @client
+}
diff --git a/app/assets/javascripts/environments/graphql/queries/is_last_deployment.query.graphql b/app/assets/javascripts/environments/graphql/queries/is_last_deployment.query.graphql
new file mode 100644
index 00000000000..5eda2f18567
--- /dev/null
+++ b/app/assets/javascripts/environments/graphql/queries/is_last_deployment.query.graphql
@@ -0,0 +1,3 @@
+query isLastDeployment($environment: LocalEnvironment) {
+ isLastDeployment(environment: $environment) @client
+}
diff --git a/app/assets/javascripts/environments/graphql/resolvers.js b/app/assets/javascripts/environments/graphql/resolvers.js
index 9ebbc0ad1f8..812fa0c81f0 100644
--- a/app/assets/javascripts/environments/graphql/resolvers.js
+++ b/app/assets/javascripts/environments/graphql/resolvers.js
@@ -8,6 +8,7 @@ import {
import pollIntervalQuery from './queries/poll_interval.query.graphql';
import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql';
+import environmentToStopQuery from './queries/environment_to_stop.query.graphql';
import environmentToDeleteQuery from './queries/environment_to_delete.query.graphql';
import pageInfoQuery from './queries/page_info.query.graphql';
@@ -65,8 +66,7 @@ export const resolvers = (endpoint) => ({
}));
},
isLastDeployment(_, { environment }) {
- // eslint-disable-next-line @gitlab/require-i18n-strings
- return environment?.lastDeployment?.['last?'];
+ return environment?.lastDeployment?.isLast;
},
},
Mutation: {
@@ -108,6 +108,20 @@ export const resolvers = (endpoint) => ({
]);
});
},
+ setEnvironmentToStop(_, { environment }, { client }) {
+ client.writeQuery({
+ query: environmentToStopQuery,
+ data: { environmentToStop: environment },
+ });
+ },
+ action(_, { action: { playPath } }) {
+ return axios
+ .post(playPath)
+ .then(() => buildErrors())
+ .catch(() =>
+ buildErrors([s__('Environments|An error occurred while making the request.')]),
+ );
+ },
setEnvironmentToDelete(_, { environment }, { client }) {
client.writeQuery({
query: environmentToDeleteQuery,
diff --git a/app/assets/javascripts/environments/graphql/typedefs.graphql b/app/assets/javascripts/environments/graphql/typedefs.graphql
index 4a3abb0e89f..c02f6b2838a 100644
--- a/app/assets/javascripts/environments/graphql/typedefs.graphql
+++ b/app/assets/javascripts/environments/graphql/typedefs.graphql
@@ -68,7 +68,9 @@ extend type Query {
environmentToDelete: LocalEnvironment
pageInfo: LocalPageInfo
environmentToRollback: LocalEnvironment
- isLastDeployment: Boolean
+ environmentToStop: LocalEnvironment
+ isEnvironmentStopping(environment: LocalEnvironmentInput): Boolean
+ isLastDeployment(environment: LocalEnvironmentInput): Boolean
}
extend type Mutation {
@@ -78,4 +80,6 @@ extend type Mutation {
cancelAutoStop(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToDelete(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors
+ setEnvironmentToStop(environment: LocalEnvironmentInput): LocalErrors
+ action(environment: LocalEnvironmentInput): LocalErrors
}
diff --git a/app/assets/javascripts/experimental_flags.js b/app/assets/javascripts/experimental_flags.js
deleted file mode 100644
index 1d60847147b..00000000000
--- a/app/assets/javascripts/experimental_flags.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import $ from 'jquery';
-import Cookies from 'js-cookie';
-
-export default () => {
- $('.js-experiment-feature-toggle').on('change', (e) => {
- const el = e.target;
-
- Cookies.set(el.name, el.value, {
- expires: 365 * 10,
- });
-
- document.body.scrollTop = 0;
- window.location.reload();
- });
-};
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index f0ef55f73eb..d9c2e55cffe 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -1,5 +1,8 @@
import * as Sentry from '@sentry/browser';
import { escape } from 'lodash';
+import Vue from 'vue';
+import { GlAlert } from '@gitlab/ui';
+import { __ } from '~/locale';
import { spriteIcon } from './lib/utils/common_utils';
const FLASH_TYPES = {
@@ -9,6 +12,12 @@ const FLASH_TYPES = {
WARNING: 'warning',
};
+const VARIANT_SUCCESS = 'success';
+const VARIANT_WARNING = 'warning';
+const VARIANT_DANGER = 'danger';
+const VARIANT_INFO = 'info';
+const VARIANT_TIP = 'tip';
+
const FLASH_CLOSED_EVENT = 'flashClosed';
const getCloseEl = (flashEl) => {
@@ -68,6 +77,126 @@ const addDismissFlashClickListener = (flashEl, fadeTransition) => {
getCloseEl(flashEl)?.addEventListener('click', () => hideFlash(flashEl, fadeTransition));
};
+/**
+ * Render an alert at the top of the page, or, optionally an
+ * arbitrary existing container.
+ *
+ * This alert is always dismissible.
+ *
+ * Usage:
+ *
+ * 1. Render a new alert
+ *
+ * import { createAlert, ALERT_VARIANTS } from '~/flash';
+ *
+ * createAlert({ message: 'My error message' });
+ * createAlert({ message: 'My warning message', variant: ALERT_VARIANTS.WARNING });
+ *
+ * 2. Dismiss this alert programmatically
+ *
+ * const alert = createAlert({ message: 'Message' });
+ *
+ * // ...
+ *
+ * alert.dismiss();
+ *
+ * 3. Respond to the alert being dismissed
+ *
+ * createAlert({ message: 'Message', onDismiss: () => { ... }});
+ *
+ * @param {Object} options Options to control the flash message
+ * @param {String} options.message Alert message text
+ * @param {String?} options.variant Which GlAlert variant to use, should be VARIANT_SUCCESS, VARIANT_WARNING, VARIANT_DANGER, VARIANT_INFO or VARIANT_TIP. Defaults to VARIANT_DANGER.
+ * @param {Object?} options.parent Reference to parent element under which alert needs to appear. Defaults to `document`.
+ * @param {Function?} options.onDismiss Handler to call when this alert is dismissed.
+ * @param {Object?} options.containerSelector Selector for the container of the alert
+ * @param {Object?} options.primaryButton Object describing primary button of alert
+ * @param {String?} link Href of primary button
+ * @param {String?} text Text of primary button
+ * @param {Function?} clickHandler Handler to call when primary button is clicked on. The click event is sent as an argument.
+ * @param {Object?} options.secondaryButton Object describing secondary button of alert
+ * @param {String?} link Href of secondary button
+ * @param {String?} text Text of secondary button
+ * @param {Function?} clickHandler Handler to call when secondary button is clicked on. The click event is sent as an argument.
+ * @param {Boolean?} options.captureError Whether to send error to Sentry
+ * @param {Object} options.error Error to be captured in Sentry
+ * @returns
+ */
+const createAlert = function createAlert({
+ message,
+ variant = VARIANT_DANGER,
+ parent = document,
+ containerSelector = '.flash-container',
+ primaryButton = null,
+ secondaryButton = null,
+ onDismiss = null,
+ captureError = false,
+ error = null,
+}) {
+ if (captureError && error) Sentry.captureException(error);
+
+ const alertContainer = parent.querySelector(containerSelector);
+ if (!alertContainer) return null;
+
+ const el = document.createElement('div');
+ alertContainer.appendChild(el);
+
+ return new Vue({
+ el,
+ components: {
+ GlAlert,
+ },
+ methods: {
+ /**
+ * Public method to dismiss this alert and removes
+ * this Vue instance.
+ */
+ dismiss() {
+ if (onDismiss) {
+ onDismiss();
+ }
+ this.$destroy();
+ this.$el.parentNode.removeChild(this.$el);
+ },
+ },
+ render(h) {
+ const on = {};
+
+ on.dismiss = () => {
+ this.dismiss();
+ };
+
+ if (primaryButton?.clickHandler) {
+ on.primaryAction = (e) => {
+ primaryButton.clickHandler(e);
+ };
+ }
+ if (secondaryButton?.clickHandler) {
+ on.secondaryAction = (e) => {
+ secondaryButton.clickHandler(e);
+ };
+ }
+
+ return h(
+ GlAlert,
+ {
+ props: {
+ dismissible: true,
+ dismissLabel: __('Dismiss'),
+ variant,
+ primaryButtonLink: primaryButton?.link,
+ primaryButtonText: primaryButton?.text,
+ secondaryButtonLink: secondaryButton?.link,
+ secondaryButtonText: secondaryButton?.text,
+ },
+ on,
+ },
+ message,
+ );
+ },
+ });
+};
+
/*
* Flash banner supports different types of Flash configurations
* along with ability to provide actionConfig which can be used to show
@@ -82,8 +211,8 @@ const addDismissFlashClickListener = (flashEl, fadeTransition) => {
* @param {String} title Title of action
* @param {Function} clickHandler Method to call when action is clicked on
* @param {Boolean} options.fadeTransition Boolean to determine whether to fade the alert out
- * @param {Boolean} options.captureError Boolean to determine whether to send error to sentry
- * @param {Object} options.error Error to be captured in sentry
+ * @param {Boolean} options.captureError Boolean to determine whether to send error to Sentry
+ * @param {Object} options.error Error to be captured in Sentry
*/
const createFlash = function createFlash({
message,
@@ -134,4 +263,10 @@ export {
addDismissFlashClickListener,
FLASH_TYPES,
FLASH_CLOSED_EVENT,
+ createAlert,
+ VARIANT_SUCCESS,
+ VARIANT_WARNING,
+ VARIANT_DANGER,
+ VARIANT_INFO,
+ VARIANT_TIP,
};
diff --git a/app/assets/javascripts/gitlab_version_check.js b/app/assets/javascripts/gitlab_version_check.js
new file mode 100644
index 00000000000..2892aded7c5
--- /dev/null
+++ b/app/assets/javascripts/gitlab_version_check.js
@@ -0,0 +1,20 @@
+import Vue from 'vue';
+import GitlabVersionCheck from '~/vue_shared/components/gitlab_version_check.vue';
+
+const mountGitlabVersionCheck = (el) => {
+ const { size } = el.dataset;
+
+ return new Vue({
+ el,
+ render(createElement) {
+ return createElement(GitlabVersionCheck, {
+ props: {
+ size,
+ },
+ });
+ },
+ });
+};
+
+export default () =>
+ [...document.querySelectorAll('.js-gitlab-version-check')].map(mountGitlabVersionCheck);
diff --git a/app/assets/javascripts/google_cloud/components/deployments_service_table.vue b/app/assets/javascripts/google_cloud/components/deployments_service_table.vue
new file mode 100644
index 00000000000..7d27d7cf6b2
--- /dev/null
+++ b/app/assets/javascripts/google_cloud/components/deployments_service_table.vue
@@ -0,0 +1,61 @@
+<script>
+import { GlButton, GlTable } from '@gitlab/ui';
+import { __ } from '~/locale';
+
+const i18n = {
+ cloudRun: __('Cloud Run'),
+ cloudRunDescription: __('Deploy container based web apps on Google managed clusters'),
+ cloudStorage: __('Cloud Storage'),
+ cloudStorageDescription: __('Deploy static assets and resources to Google managed CDN'),
+ deployments: __('Deployments'),
+ deploymentsDescription: __(
+ 'Configure pipelines to deploy web apps, backend services, APIs and static resources to Google Cloud',
+ ),
+ configureViaMergeRequest: __('Configure via Merge Request'),
+ service: __('Service'),
+ description: __('Description'),
+};
+
+export default {
+ components: { GlButton, GlTable },
+ props: {
+ cloudRunUrl: {
+ type: String,
+ required: true,
+ },
+ cloudStorageUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ fields: [
+ { key: 'title', label: i18n.service },
+ { key: 'description', label: i18n.description },
+ { key: 'action', label: '' },
+ ],
+ items: [
+ {
+ title: i18n.cloudRun,
+ description: i18n.cloudRunDescription,
+ action: { title: i18n.configureViaMergeRequest, disabled: true },
+ },
+ {
+ title: i18n.cloudStorage,
+ description: i18n.cloudStorageDescription,
+ action: { title: i18n.configureViaMergeRequest, disabled: true },
+ },
+ ],
+ i18n,
+};
+</script>
+<template>
+ <div class="gl-mx-3">
+ <h2 class="gl-font-size-h2">{{ $options.i18n.deployments }}</h2>
+ <p>{{ $options.i18n.deploymentsDescription }}</p>
+ <gl-table :fields="$options.fields" :items="$options.items">
+ <template #cell(action)="{ value }">
+ <gl-button :disabled="value.disabled">{{ value.title }}</gl-button>
+ </template>
+ </gl-table>
+ </div>
+</template>
diff --git a/app/assets/javascripts/google_cloud/components/home.vue b/app/assets/javascripts/google_cloud/components/home.vue
index 05f39de66ee..8ef110dcf22 100644
--- a/app/assets/javascripts/google_cloud/components/home.vue
+++ b/app/assets/javascripts/google_cloud/components/home.vue
@@ -1,11 +1,13 @@
<script>
import { GlTabs, GlTab } from '@gitlab/ui';
+import DeploymentsServiceTable from './deployments_service_table.vue';
import ServiceAccountsList from './service_accounts_list.vue';
export default {
components: {
GlTabs,
GlTab,
+ DeploymentsServiceTable,
ServiceAccountsList,
},
props: {
@@ -21,6 +23,14 @@ export default {
type: String,
required: true,
},
+ deploymentsCloudRunUrl: {
+ type: String,
+ required: true,
+ },
+ deploymentsCloudStorageUrl: {
+ type: String,
+ required: true,
+ },
},
};
</script>
@@ -35,7 +45,12 @@ export default {
:empty-illustration-url="emptyIllustrationUrl"
/>
</gl-tab>
- <gl-tab :title="__('Deployments')" disabled />
+ <gl-tab :title="__('Deployments')">
+ <deployments-service-table
+ :cloud-run-url="deploymentsCloudRunUrl"
+ :cloud-storage-url="deploymentsCloudStorageUrl"
+ />
+ </gl-tab>
<gl-tab :title="__('Services')" disabled />
</gl-tabs>
</template>
diff --git a/app/assets/javascripts/google_tag_manager/index.js b/app/assets/javascripts/google_tag_manager/index.js
new file mode 100644
index 00000000000..ab80e15c2ec
--- /dev/null
+++ b/app/assets/javascripts/google_tag_manager/index.js
@@ -0,0 +1,122 @@
+import { logError } from '~/lib/logger';
+
+const isSupported = () => Boolean(window.dataLayer) && gon.features?.gitlabGtmDatalayer;
+
+const pushEvent = (event, args = {}) => {
+ if (!window.dataLayer) {
+ return;
+ }
+
+ try {
+ window.dataLayer.push({
+ event,
+ ...args,
+ });
+ } catch (e) {
+ logError('Unexpected error while pushing to dataLayer', e);
+ }
+};
+
+const pushAccountSubmit = (accountType, accountMethod) =>
+ pushEvent('accountSubmit', { accountType, accountMethod });
+
+const trackFormSubmission = (accountType) => {
+ const form = document.getElementById('new_new_user');
+ form.addEventListener('submit', () => {
+ pushAccountSubmit(accountType, 'form');
+ });
+};
+
+const trackOmniAuthSubmission = (accountType) => {
+ const links = document.querySelectorAll('.js-oauth-login');
+ links.forEach((link) => {
+ const { provider } = link.dataset;
+ link.addEventListener('click', () => {
+ pushAccountSubmit(accountType, provider);
+ });
+ });
+};
+
+export const trackFreeTrialAccountSubmissions = () => {
+ if (!isSupported()) {
+ return;
+ }
+
+ trackFormSubmission('freeThirtyDayTrial');
+ trackOmniAuthSubmission('freeThirtyDayTrial');
+};
+
+export const trackNewRegistrations = () => {
+ if (!isSupported()) {
+ return;
+ }
+
+ trackFormSubmission('standardSignUp');
+ trackOmniAuthSubmission('standardSignUp');
+};
+
+export const trackSaasTrialSubmit = () => {
+ if (!isSupported()) {
+ return;
+ }
+
+ pushEvent('saasTrialSubmit');
+};
+
+export const trackSaasTrialSkip = () => {
+ if (!isSupported()) {
+ return;
+ }
+
+ const skipLink = document.querySelector('.js-skip-trial');
+ skipLink.addEventListener('click', () => {
+ pushEvent('saasTrialSkip');
+ });
+};
+
+export const trackSaasTrialGroup = () => {
+ if (!isSupported()) {
+ return;
+ }
+
+ const form = document.querySelector('.js-saas-trial-group');
+ form.addEventListener('submit', () => {
+ pushEvent('saasTrialGroup');
+ });
+};
+
+export const trackSaasTrialProject = () => {
+ if (!isSupported()) {
+ return;
+ }
+
+ const form = document.getElementById('new_project');
+ form.addEventListener('submit', () => {
+ pushEvent('saasTrialProject');
+ });
+};
+
+export const trackSaasTrialProjectImport = () => {
+ if (!isSupported()) {
+ return;
+ }
+
+ const importButtons = document.querySelectorAll('.js-import-project-btn');
+ importButtons.forEach((button) => {
+ button.addEventListener('click', () => {
+ const { platform } = button.dataset;
+ pushEvent('saasTrialProjectImport', { saasProjectImport: platform });
+ });
+ });
+};
+
+export const trackSaasTrialGetStarted = () => {
+ if (!isSupported()) {
+ return;
+ }
+
+ const getStartedButton = document.querySelector('.js-get-started-btn');
+ getStartedButton.addEventListener('click', () => {
+ pushEvent('saasTrialGetStarted');
+ });
+};
diff --git a/app/assets/javascripts/graphql_shared/fragment_types/vulnerability_location_types.js b/app/assets/javascripts/graphql_shared/fragment_types/vulnerability_location_types.js
new file mode 100644
index 00000000000..30888e20a46
--- /dev/null
+++ b/app/assets/javascripts/graphql_shared/fragment_types/vulnerability_location_types.js
@@ -0,0 +1,17 @@
+export const vulnerabilityLocationTypes = {
+ __schema: {
+ types: [
+ {
+ kind: 'UNION',
+ name: 'VulnerabilityLocation',
+ possibleTypes: [
+ { name: 'VulnerabilityLocationContainerScanning' },
+ { name: 'VulnerabilityLocationDast' },
+ { name: 'VulnerabilityLocationDependencyScanning' },
+ { name: 'VulnerabilityLocationSast' },
+ { name: 'VulnerabilityLocationSecretDetection' },
+ ],
+ },
+ ],
+ },
+};
diff --git a/app/assets/javascripts/graphql_shared/fragments/issue.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/issue.fragment.graphql
new file mode 100644
index 00000000000..85a28fe1f71
--- /dev/null
+++ b/app/assets/javascripts/graphql_shared/fragments/issue.fragment.graphql
@@ -0,0 +1,37 @@
+#import "~/graphql_shared/fragments/milestone.fragment.graphql"
+#import "~/graphql_shared/fragments/user.fragment.graphql"
+
+fragment IssueNode on Issue {
+ id
+ iid
+ title
+ referencePath: reference(full: true)
+ dueDate
+ timeEstimate
+ totalTimeSpent
+ humanTimeEstimate
+ humanTotalTimeSpent
+ emailsDisabled
+ confidential
+ hidden
+ webUrl
+ relativePosition
+ type
+ severity
+ milestone {
+ ...MilestoneFragment
+ }
+ assignees {
+ nodes {
+ ...User
+ }
+ }
+ labels {
+ nodes {
+ id
+ title
+ color
+ description
+ }
+ }
+}
diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js
index f255f8a084c..b6a6720e7a1 100644
--- a/app/assets/javascripts/group.js
+++ b/app/assets/javascripts/group.js
@@ -13,11 +13,8 @@ export default class Group {
this.updateGroupPathSlugHandler = this.updateGroupPathSlug.bind(this);
this.groupNames.forEach((groupName) => {
- if (groupName.value === '') {
- groupName.addEventListener('keyup', this.updateHandler);
-
- groupName.addEventListener('keyup', this.updateGroupPathSlugHandler);
- }
+ groupName.addEventListener('keyup', this.updateHandler);
+ groupName.addEventListener('keyup', this.updateGroupPathSlugHandler);
});
this.groupPaths.forEach((groupPath) => {
diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue
index 46e9d2bec99..c24eeed9f03 100644
--- a/app/assets/javascripts/groups/components/item_stats.vue
+++ b/app/assets/javascripts/groups/components/item_stats.vue
@@ -83,7 +83,7 @@ export default {
<gl-badge variant="warning">{{ __('pending deletion') }}</gl-badge>
</div>
<div v-if="isProject" class="last-updated">
- <time-ago-tooltip :time="item.updatedAt" tooltip-placement="bottom" />
+ <time-ago-tooltip :time="item.lastActivityAt" tooltip-placement="bottom" />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/groups_list.js b/app/assets/javascripts/groups/groups_list.js
index 56a8cbf6d03..866dd7a61ff 100644
--- a/app/assets/javascripts/groups_list.js
+++ b/app/assets/javascripts/groups/groups_list.js
@@ -1,4 +1,4 @@
-import FilterableList from './filterable_list';
+import FilterableList from '~/filterable_list';
/**
* Makes search request for groups when user types a value in the search input.
diff --git a/app/assets/javascripts/landing.js b/app/assets/javascripts/groups/landing.js
index bfb4d9ce67b..bfb4d9ce67b 100644
--- a/app/assets/javascripts/landing.js
+++ b/app/assets/javascripts/groups/landing.js
diff --git a/app/assets/javascripts/groups/store/groups_store.js b/app/assets/javascripts/groups/store/groups_store.js
index 93fbd8be47d..d3600bd223a 100644
--- a/app/assets/javascripts/groups/store/groups_store.js
+++ b/app/assets/javascripts/groups/store/groups_store.js
@@ -98,6 +98,9 @@ export default class GroupsStore {
updatedAt: rawGroupItem.updated_at,
pendingRemoval: rawGroupItem.marked_for_deletion,
microdata: this.showSchemaMarkup ? getGroupItemMicrodata(rawGroupItem) : {},
+ lastActivityAt: rawGroupItem.last_activity_at
+ ? rawGroupItem.last_activity_at
+ : rawGroupItem.updated_at,
};
if (!isEmpty(rawGroupItem.compliance_management_framework)) {
diff --git a/app/assets/javascripts/transfer_edit.js b/app/assets/javascripts/groups/transfer_edit.js
index bb15e11fd4c..bb15e11fd4c 100644
--- a/app/assets/javascripts/transfer_edit.js
+++ b/app/assets/javascripts/groups/transfer_edit.js
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index edc6573a489..36fc48a2ba8 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -19,8 +19,7 @@ import HeaderSearchScopedItems from './header_search_scoped_items.vue';
export default {
name: 'HeaderSearchApp',
i18n: {
- searchPlaceholder: s__('GlobalSearch|Search or jump to...'),
- searchAria: s__('GlobalSearch|Search GitLab'),
+ searchGitlab: s__('GlobalSearch|Search GitLab'),
searchInputDescribeByNoDropdown: s__(
'GlobalSearch|Type and press the enter key to submit search.',
),
@@ -136,7 +135,7 @@ export default {
<form
v-outside="closeDropdown"
role="search"
- :aria-label="$options.i18n.searchAria"
+ :aria-label="$options.i18n.searchGitlab"
class="header-search gl-relative"
>
<gl-search-box-by-type
@@ -145,7 +144,7 @@ export default {
role="searchbox"
class="gl-z-index-1"
autocomplete="off"
- :placeholder="$options.i18n.searchPlaceholder"
+ :placeholder="$options.i18n.searchGitlab"
:aria-activedescendant="currentFocusedId"
:aria-describedby="$options.SEARCH_INPUT_DESCRIPTION"
@focus="openDropdown"
diff --git a/app/assets/javascripts/helpers/event_hub_factory.js b/app/assets/javascripts/helpers/event_hub_factory.js
index 62af67d3ef3..ddfae7e9de3 100644
--- a/app/assets/javascripts/helpers/event_hub_factory.js
+++ b/app/assets/javascripts/helpers/event_hub_factory.js
@@ -1,16 +1,12 @@
/**
* An event hub with a Vue instance like API
*
- * NOTE: There's an [issue open][4] to eventually remove this when some
- * coupling in our codebase has been fixed.
- *
* NOTE: This is a derivative work from [mitt][1] v1.2.0 which is licensed by
* [MIT License][2] © [Jason Miller][3]
*
* [1]: https://github.com/developit/mitt
* [2]: https://opensource.org/licenses/MIT
* [3]: https://jasonformat.com/
- * [4]: https://gitlab.com/gitlab-org/gitlab/-/issues/223864
*/
class EventHub {
constructor() {
@@ -91,9 +87,6 @@ class EventHub {
* - $once
* - $emit
*
- * Please note, this was once implemented with `mitt`, but since then has been reverted
- * because of some API issues. https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35074
- *
* We'd like to shy away from using a full fledged Vue instance from this in the future.
*/
export default () => {
diff --git a/app/assets/javascripts/ide/components/jobs/stage.vue b/app/assets/javascripts/ide/components/jobs/stage.vue
index 938385f0b81..796ca1349c5 100644
--- a/app/assets/javascripts/ide/components/jobs/stage.vue
+++ b/app/assets/javascripts/ide/components/jobs/stage.vue
@@ -1,5 +1,5 @@
<script>
-import { GlLoadingIcon, GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { GlLoadingIcon, GlIcon, GlTooltipDirective, GlBadge } from '@gitlab/ui';
import CiIcon from '../../../vue_shared/components/ci_icon.vue';
import Item from './item.vue';
@@ -9,6 +9,7 @@ export default {
},
components: {
GlIcon,
+ GlBadge,
CiIcon,
Item,
GlLoadingIcon,
@@ -74,7 +75,7 @@ export default {
{{ stage.name }}
</strong>
<div v-if="!stage.isLoading || stage.jobs.length" class="gl-mr-3 gl-ml-2">
- <span class="badge badge-pill"> {{ jobsCount }} </span>
+ <gl-badge>{{ jobsCount }}</gl-badge>
</div>
<gl-icon :name="collapseIcon" class="ide-stage-collapse-icon" />
</div>
diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
index 9cf8d5a360e..51872993f16 100644
--- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js
@@ -53,9 +53,15 @@ export const receiveLatestPipelineSuccess = ({ rootGetters, commit }, { pipeline
commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, lastCommitPipeline);
};
-export const fetchLatestPipeline = ({ dispatch, rootGetters }) => {
+export const fetchLatestPipeline = ({ commit, dispatch, rootGetters }) => {
if (eTagPoll) return;
+ if (!rootGetters.lastCommit) {
+ commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, null);
+ dispatch('stopPipelinePolling');
+ return;
+ }
+
dispatch('requestLatestPipeline');
eTagPoll = new Poll({
diff --git a/app/assets/javascripts/init_confirm_danger.js b/app/assets/javascripts/init_confirm_danger.js
index a8833a17467..98bfa48740c 100644
--- a/app/assets/javascripts/init_confirm_danger.js
+++ b/app/assets/javascripts/init_confirm_danger.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { pickBy } from 'lodash';
import { parseBoolean } from './lib/utils/common_utils';
import ConfirmDanger from './vue_shared/components/confirm_danger/confirm_danger.vue';
@@ -12,21 +13,32 @@ export default () => {
buttonText,
buttonClass = '',
buttonTestid = null,
+ buttonVariant = null,
confirmDangerMessage,
+ confirmButtonText = null,
disabled = false,
+ additionalInformation,
+ htmlConfirmationMessage,
} = el.dataset;
return new Vue({
el,
- provide: {
- confirmDangerMessage,
- },
+ provide: pickBy(
+ {
+ htmlConfirmationMessage,
+ confirmDangerMessage,
+ additionalInformation,
+ confirmButtonText,
+ },
+ (v) => Boolean(v),
+ ),
render: (createElement) =>
createElement(ConfirmDanger, {
props: {
phrase,
buttonText,
buttonClass,
+ buttonVariant,
buttonTestid,
disabled: parseBoolean(disabled),
},
diff --git a/app/assets/javascripts/integrations/constants.js b/app/assets/javascripts/integrations/constants.js
index 84656bd41bb..b90658fb13c 100644
--- a/app/assets/javascripts/integrations/constants.js
+++ b/app/assets/javascripts/integrations/constants.js
@@ -23,3 +23,8 @@ export const I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE = s__(
);
export const I18N_DEFAULT_ERROR_MESSAGE = __('Something went wrong on our end.');
export const I18N_SUCCESSFUL_CONNECTION_MESSAGE = s__('Integrations|Connection successful.');
+
+export const settingsTabTitle = __('Settings');
+export const overridesTabTitle = s__('Integrations|Projects using custom settings');
+
+export const INTEGRATION_FORM_SELECTOR = '.js-integration-settings-form';
diff --git a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue
index 258cd1bf365..4b0579a5beb 100644
--- a/app/assets/javascripts/integrations/edit/components/dynamic_field.vue
+++ b/app/assets/javascripts/integrations/edit/components/dynamic_field.vue
@@ -153,7 +153,7 @@ export default {
:invalid-feedback="__('This field is required.')"
:state="valid"
>
- <template #description>
+ <template v-if="!isCheckbox" #description>
<span v-safe-html:[$options.helpHtmlConfig]="help"></span>
</template>
@@ -161,6 +161,9 @@ export default {
<input :name="fieldName" type="hidden" :value="model || false" />
<gl-form-checkbox :id="fieldId" v-model="model" :disabled="isInheriting">
{{ checkboxLabel || humanizedTitle }}
+ <template #help>
+ <span v-safe-html:[$options.helpHtmlConfig]="help"></span>
+ </template>
</gl-form-checkbox>
</template>
<template v-else-if="isSelect">
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index e570a468944..c3cc35adfa5 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -1,5 +1,6 @@
<script>
-import { GlButton, GlModalDirective, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
+import { GlButton, GlModalDirective, GlSafeHtmlDirective as SafeHtml, GlForm } from '@gitlab/ui';
+import axios from 'axios';
import * as Sentry from '@sentry/browser';
import { mapState, mapActions, mapGetters } from 'vuex';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -8,8 +9,11 @@ import {
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
I18N_DEFAULT_ERROR_MESSAGE,
I18N_SUCCESSFUL_CONNECTION_MESSAGE,
+ INTEGRATION_FORM_SELECTOR,
integrationLevels,
} from '~/integrations/constants';
+import { refreshCurrentPage } from '~/lib/utils/url_utility';
+import csrf from '~/lib/utils/csrf';
import eventHub from '../event_hub';
import { testIntegrationSettings } from '../api';
import ActiveCheckbox from './active_checkbox.vue';
@@ -33,6 +37,7 @@ export default {
ConfirmationModal,
ResetConfirmationModal,
GlButton,
+ GlForm,
},
directives: {
GlModal: GlModalDirective,
@@ -40,10 +45,6 @@ export default {
},
mixins: [glFeatureFlagsMixin()],
props: {
- formSelector: {
- type: String,
- required: true,
- },
helpHtml: {
type: String,
required: false,
@@ -55,11 +56,12 @@ export default {
integrationActive: false,
isTesting: false,
isSaving: false,
+ isResetting: false,
};
},
computed: {
...mapGetters(['currentKey', 'propsSource']),
- ...mapState(['defaultState', 'customState', 'override', 'isResetting']),
+ ...mapState(['defaultState', 'customState', 'override']),
isEditable() {
return this.propsSource.editable;
},
@@ -81,10 +83,28 @@ export default {
disableButtons() {
return Boolean(this.isSaving || this.isResetting || this.isTesting);
},
+ useVueForm() {
+ return this.glFeatures?.vueIntegrationForm;
+ },
+ formContainerProps() {
+ return this.useVueForm
+ ? {
+ ref: 'integrationForm',
+ method: 'post',
+ class: 'gl-mb-3 gl-show-field-errors integration-settings-form',
+ action: this.propsSource.formPath,
+ novalidate: !this.integrationActive,
+ }
+ : {};
+ },
+ formContainer() {
+ return this.useVueForm ? GlForm : 'div';
+ },
},
mounted() {
- // this form element is defined in Haml
- this.form = document.querySelector(this.formSelector);
+ this.form = this.useVueForm
+ ? this.$refs.integrationForm.$el
+ : document.querySelector(INTEGRATION_FORM_SELECTOR);
},
methods: {
...mapActions(['setOverride', 'fetchResetIntegration', 'requestJiraIssueTypes']),
@@ -126,7 +146,20 @@ export default {
});
},
onResetClick() {
- this.fetchResetIntegration();
+ this.isResetting = true;
+
+ return axios
+ .post(this.propsSource.resetPath)
+ .then(() => {
+ refreshCurrentPage();
+ })
+ .catch((error) => {
+ this.$toast.show(I18N_DEFAULT_ERROR_MESSAGE);
+ Sentry.captureException(error);
+ })
+ .finally(() => {
+ this.isResetting = false;
+ });
},
onRequestJiraIssueTypes() {
this.requestJiraIssueTypes(this.getFormData());
@@ -136,7 +169,7 @@ export default {
},
onToggleIntegrationState(integrationActive) {
this.integrationActive = integrationActive;
- if (!this.form) {
+ if (!this.form || this.useVueForm) {
return;
}
@@ -153,11 +186,23 @@ export default {
ADD_TAGS: ['use'], // to support icon SVGs
FORBID_ATTR: [], // This is trusted input so we can override the default config to allow data-* attributes
},
+ csrf,
};
</script>
<template>
- <div class="gl-mb-3">
+ <component :is="formContainer" v-bind="formContainerProps">
+ <template v-if="useVueForm">
+ <input type="hidden" name="_method" value="put" />
+ <input type="hidden" name="authenticity_token" :value="$options.csrf.token" />
+ <input
+ type="hidden"
+ name="redirect_to"
+ :value="propsSource.redirectTo"
+ data-testid="redirect-to-field"
+ />
+ </template>
+
<override-dropdown
v-if="defaultState !== null"
:inherit-from-id="defaultState.id"
@@ -200,63 +245,71 @@ export default {
v-bind="propsSource.jiraIssuesProps"
@request-jira-issue-types="onRequestJiraIssueTypes"
/>
- <div v-if="isEditable" class="footer-block row-content-block">
- <template v-if="isInstanceOrGroupLevel">
+
+ <div
+ v-if="isEditable"
+ class="footer-block row-content-block gl-display-flex gl-justify-content-space-between"
+ >
+ <div>
+ <template v-if="isInstanceOrGroupLevel">
+ <gl-button
+ v-gl-modal.confirmSaveIntegration
+ category="primary"
+ variant="confirm"
+ :loading="isSaving"
+ :disabled="disableButtons"
+ data-testid="save-button-instance-group"
+ data-qa-selector="save_changes_button"
+ >
+ {{ __('Save changes') }}
+ </gl-button>
+ <confirmation-modal @submit="onSaveClick" />
+ </template>
<gl-button
- v-gl-modal.confirmSaveIntegration
+ v-else
category="primary"
variant="confirm"
+ type="submit"
:loading="isSaving"
:disabled="disableButtons"
+ data-testid="save-button"
data-qa-selector="save_changes_button"
+ @click.prevent="onSaveClick"
>
{{ __('Save changes') }}
</gl-button>
- <confirmation-modal @submit="onSaveClick" />
- </template>
- <gl-button
- v-else
- category="primary"
- variant="confirm"
- type="submit"
- :loading="isSaving"
- :disabled="disableButtons"
- data-testid="save-button"
- data-qa-selector="save_changes_button"
- @click.prevent="onSaveClick"
- >
- {{ __('Save changes') }}
- </gl-button>
- <gl-button
- v-if="showTestButton"
- category="secondary"
- variant="confirm"
- :loading="isTesting"
- :disabled="disableButtons"
- data-testid="test-button"
- @click.prevent="onTestClick"
- >
- {{ __('Test settings') }}
- </gl-button>
+ <gl-button
+ v-if="showTestButton"
+ category="secondary"
+ variant="confirm"
+ :loading="isTesting"
+ :disabled="disableButtons"
+ data-testid="test-button"
+ @click.prevent="onTestClick"
+ >
+ {{ __('Test settings') }}
+ </gl-button>
+
+ <gl-button :href="propsSource.cancelPath">{{ __('Cancel') }}</gl-button>
+ </div>
<template v-if="showResetButton">
<gl-button
v-gl-modal.confirmResetIntegration
- category="secondary"
- variant="confirm"
+ category="tertiary"
+ variant="danger"
:loading="isResetting"
:disabled="disableButtons"
data-testid="reset-button"
>
{{ __('Reset') }}
</gl-button>
+
<reset-confirmation-modal @reset="onResetClick" />
</template>
-
- <gl-button :href="propsSource.cancelPath">{{ __('Cancel') }}</gl-button>
</div>
</div>
</div>
- </div>
+ </component>
</template>
diff --git a/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue b/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue
index 5a445235219..403bad3db11 100644
--- a/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue
+++ b/app/assets/javascripts/integrations/edit/components/reset_confirmation_modal.vue
@@ -11,7 +11,7 @@ export default {
primaryProps() {
return {
text: __('Reset'),
- attributes: [{ variant: 'warning' }, { category: 'primary' }],
+ attributes: [{ variant: 'danger' }, { category: 'primary' }],
};
},
cancelProps() {
diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js
index 9c9e3edbeb8..fbda8c1e3d0 100644
--- a/app/assets/javascripts/integrations/edit/index.js
+++ b/app/assets/javascripts/integrations/edit/index.js
@@ -28,9 +28,11 @@ function parseDatasetToProps(data) {
cancelPath,
testPath,
resetPath,
+ formPath,
vulnerabilitiesIssuetype,
jiraIssueTransitionAutomatic,
jiraIssueTransitionId,
+ redirectTo,
...booleanAttributes
} = data;
const {
@@ -57,6 +59,7 @@ function parseDatasetToProps(data) {
canTest,
testPath,
resetPath,
+ formPath,
triggerFieldsProps: {
initialTriggerCommit: commitEvents,
initialTriggerMergeRequest: mergeRequestEvents,
@@ -82,10 +85,11 @@ function parseDatasetToProps(data) {
inheritFromId: parseInt(inheritFromId, 10),
integrationLevel,
id: parseInt(id, 10),
+ redirectTo,
};
}
-export default function initIntegrationSettingsForm(formSelector) {
+export default function initIntegrationSettingsForm() {
const customSettingsEl = document.querySelector('.js-vue-integration-settings');
const defaultSettingsEl = document.querySelector('.js-vue-default-integration-settings');
@@ -115,7 +119,6 @@ export default function initIntegrationSettingsForm(formSelector) {
return createElement(IntegrationForm, {
props: {
helpHtml,
- formSelector,
},
});
},
diff --git a/app/assets/javascripts/integrations/edit/store/actions.js b/app/assets/javascripts/integrations/edit/store/actions.js
index 97565a3a69c..1398b710d1d 100644
--- a/app/assets/javascripts/integrations/edit/store/actions.js
+++ b/app/assets/javascripts/integrations/edit/store/actions.js
@@ -1,5 +1,3 @@
-import axios from 'axios';
-import { refreshCurrentPage } from '~/lib/utils/url_utility';
import {
VALIDATE_INTEGRATION_FORM_EVENT,
I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE,
@@ -10,27 +8,6 @@ import eventHub from '../event_hub';
import * as types from './mutation_types';
export const setOverride = ({ commit }, override) => commit(types.SET_OVERRIDE, override);
-export const setIsResetting = ({ commit }, isResetting) =>
- commit(types.SET_IS_RESETTING, isResetting);
-
-export const requestResetIntegration = ({ commit }) => {
- commit(types.REQUEST_RESET_INTEGRATION);
-};
-export const receiveResetIntegrationSuccess = () => {
- refreshCurrentPage();
-};
-export const receiveResetIntegrationError = ({ commit }) => {
- commit(types.RECEIVE_RESET_INTEGRATION_ERROR);
-};
-
-export const fetchResetIntegration = ({ dispatch, getters }) => {
- dispatch('requestResetIntegration');
-
- return axios
- .post(getters.propsSource.resetPath, { params: { format: 'json' } })
- .then(() => dispatch('receiveResetIntegrationSuccess'))
- .catch(() => dispatch('receiveResetIntegrationError'));
-};
export const requestJiraIssueTypes = ({ commit, dispatch, getters }, formData) => {
commit(types.SET_JIRA_ISSUE_TYPES_ERROR_MESSAGE, '');
diff --git a/app/assets/javascripts/integrations/edit/store/mutations.js b/app/assets/javascripts/integrations/edit/store/mutations.js
index e7e312ce650..6ca644f8821 100644
--- a/app/assets/javascripts/integrations/edit/store/mutations.js
+++ b/app/assets/javascripts/integrations/edit/store/mutations.js
@@ -4,15 +4,6 @@ export default {
[types.SET_OVERRIDE](state, override) {
state.override = override;
},
- [types.SET_IS_RESETTING](state, isResetting) {
- state.isResetting = isResetting;
- },
- [types.REQUEST_RESET_INTEGRATION](state) {
- state.isResetting = true;
- },
- [types.RECEIVE_RESET_INTEGRATION_ERROR](state) {
- state.isResetting = false;
- },
[types.SET_JIRA_ISSUE_TYPES](state, jiraIssueTypes) {
state.jiraIssueTypes = jiraIssueTypes;
},
diff --git a/app/assets/javascripts/integrations/edit/store/state.js b/app/assets/javascripts/integrations/edit/store/state.js
index 3d40d1b90d5..088476b2b37 100644
--- a/app/assets/javascripts/integrations/edit/store/state.js
+++ b/app/assets/javascripts/integrations/edit/store/state.js
@@ -5,8 +5,6 @@ export default ({ defaultState = null, customState = {} } = {}) => {
override,
defaultState,
customState,
- isSaving: false,
- isResetting: false,
isLoadingJiraIssueTypes: false,
loadingJiraIssueTypesErrorMessage: '',
jiraIssueTypes: [],
diff --git a/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue b/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue
index 3fc554c5371..f2d3e6489ee 100644
--- a/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue
+++ b/app/assets/javascripts/integrations/overrides/components/integration_overrides.vue
@@ -11,6 +11,8 @@ import { __, s__ } from '~/locale';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import UrlSync from '~/vue_shared/components/url_sync.vue';
+import IntegrationTabs from './integration_tabs.vue';
+
const DEFAULT_PAGE = 1;
export default {
@@ -23,6 +25,7 @@ export default {
GlAlert,
ProjectAvatar,
UrlSync,
+ IntegrationTabs,
},
props: {
overridesPath: {
@@ -46,6 +49,9 @@ export default {
};
},
computed: {
+ overridesCount() {
+ return this.isLoading ? null : this.totalItems;
+ },
showPagination() {
return this.totalItems > this.$options.DEFAULT_PER_PAGE && this.overrides.length > 0;
},
@@ -100,6 +106,7 @@ export default {
<template>
<div>
+ <integration-tabs :project-overrides-count="overridesCount" />
<gl-alert v-if="errorMessage" variant="danger" :dismissible="false">
{{ errorMessage }}
</gl-alert>
diff --git a/app/assets/javascripts/integrations/overrides/components/integration_tabs.vue b/app/assets/javascripts/integrations/overrides/components/integration_tabs.vue
new file mode 100644
index 00000000000..3f67c987231
--- /dev/null
+++ b/app/assets/javascripts/integrations/overrides/components/integration_tabs.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlBadge, GlNavItem, GlTabs, GlTab } from '@gitlab/ui';
+import { settingsTabTitle, overridesTabTitle } from '~/integrations/constants';
+
+export default {
+ components: {
+ GlBadge,
+ GlNavItem,
+ GlTabs,
+ GlTab,
+ },
+ inject: {
+ editPath: {
+ default: '',
+ },
+ },
+ props: {
+ projectOverridesCount: {
+ type: [Number, String],
+ required: false,
+ default: null,
+ },
+ },
+ i18n: {
+ settingsTabTitle,
+ overridesTabTitle,
+ },
+};
+</script>
+
+<template>
+ <gl-tabs>
+ <template #tabs-start>
+ <gl-nav-item role="presentation" link-classes="gl-tab-nav-item" :href="editPath">{{
+ $options.i18n.settingsTabTitle
+ }}</gl-nav-item>
+ </template>
+
+ <gl-tab active>
+ <template #title>
+ {{ $options.i18n.overridesTabTitle }}
+ <gl-badge
+ v-if="projectOverridesCount !== null"
+ variant="muted"
+ size="sm"
+ class="gl-tab-counter-badge"
+ >{{ projectOverridesCount }}</gl-badge
+ >
+ </template>
+ </gl-tab>
+ </gl-tabs>
+</template>
diff --git a/app/assets/javascripts/integrations/overrides/index.js b/app/assets/javascripts/integrations/overrides/index.js
index 0f03b23ba21..f289a2d3d1a 100644
--- a/app/assets/javascripts/integrations/overrides/index.js
+++ b/app/assets/javascripts/integrations/overrides/index.js
@@ -8,10 +8,13 @@ export default () => {
return null;
}
- const { overridesPath } = el.dataset;
+ const { editPath, overridesPath } = el.dataset;
return new Vue({
el,
+ provide: {
+ editPath,
+ },
render(createElement) {
return createElement(IntegrationOverrides, {
props: {
diff --git a/app/assets/javascripts/issuable/bulk_update_sidebar/index.js b/app/assets/javascripts/issuable/bulk_update_sidebar/index.js
new file mode 100644
index 00000000000..dca606556d0
--- /dev/null
+++ b/app/assets/javascripts/issuable/bulk_update_sidebar/index.js
@@ -0,0 +1,28 @@
+import Vue from 'vue';
+import StatusSelect from './components/status_select.vue';
+import issuableBulkUpdateActions from './issuable_bulk_update_actions';
+import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
+
+export function initBulkUpdateSidebar(prefixId) {
+ const el = document.querySelector('.issues-bulk-update');
+
+ if (!el) {
+ return;
+ }
+
+ issuableBulkUpdateActions.init({ prefixId });
+ new IssuableBulkUpdateSidebar(); // eslint-disable-line no-new
+}
+
+export function initIssueStatusSelect() {
+ const el = document.querySelector('.js-issue-status');
+
+ if (!el) {
+ return null;
+ }
+
+ return new Vue({
+ el,
+ render: (createElement) => createElement(StatusSelect),
+ });
+}
diff --git a/app/assets/javascripts/issuable/bulk_update_sidebar/init_issue_status_select.js b/app/assets/javascripts/issuable/bulk_update_sidebar/init_issue_status_select.js
deleted file mode 100644
index 43179a86d70..00000000000
--- a/app/assets/javascripts/issuable/bulk_update_sidebar/init_issue_status_select.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import Vue from 'vue';
-import StatusSelect from './components/status_select.vue';
-
-export default function initIssueStatusSelect() {
- const el = document.querySelector('.js-issue-status');
-
- if (!el) {
- return null;
- }
-
- return new Vue({
- el,
- render(h) {
- return h(StatusSelect);
- },
- });
-}
diff --git a/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js
index 1eb3ffc9808..d46354e240a 100644
--- a/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_bulk_update_sidebar.js
@@ -1,12 +1,9 @@
/* eslint-disable class-methods-use-this, no-new */
import $ from 'jquery';
-import { property } from 'lodash';
-
-import issuableEventHub from '~/issues_list/eventhub';
+import issuableEventHub from '~/issues/list/eventhub';
import LabelsSelect from '~/labels/labels_select';
import MilestoneSelect from '~/milestones/milestone_select';
-import initIssueStatusSelect from './init_issue_status_select';
import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
import subscriptionSelect from './subscription_select';
@@ -17,8 +14,6 @@ const SIDEBAR_COLLAPSED_CLASS = 'right-sidebar-collapsed issuable-bulk-update-si
export default class IssuableBulkUpdateSidebar {
constructor() {
- this.vueIssuablesListFeature = property(['gon', 'features', 'vueIssuablesList'])(window);
-
this.initDomElements();
this.bindEvents();
this.initDropdowns();
@@ -57,7 +52,6 @@ export default class IssuableBulkUpdateSidebar {
initDropdowns() {
new LabelsSelect();
new MilestoneSelect();
- initIssueStatusSelect();
subscriptionSelect();
if (IS_EE) {
@@ -145,7 +139,7 @@ export default class IssuableBulkUpdateSidebar {
}
toggleCheckboxDisplay(show) {
- this.$checkAllContainer.toggleClass(HIDDEN_CLASS, !show || this.vueIssuablesListFeature);
+ this.$checkAllContainer.toggleClass(HIDDEN_CLASS, !show);
this.$issueChecks.toggleClass(HIDDEN_CLASS, !show);
}
diff --git a/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar.js b/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar.js
deleted file mode 100644
index 179c2b83c6c..00000000000
--- a/app/assets/javascripts/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import issuableBulkUpdateActions from './issuable_bulk_update_actions';
-import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
-
-export default {
- bulkUpdateSidebar: null,
-
- init(prefixId) {
- const bulkUpdateEl = document.querySelector('.issues-bulk-update');
- const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
-
- if (bulkUpdateEl && !alreadyInitialized) {
- issuableBulkUpdateActions.init({ prefixId });
-
- this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
- }
-
- return this.bulkUpdateSidebar;
- },
-};
diff --git a/app/assets/javascripts/issuable/components/csv_import_modal.vue b/app/assets/javascripts/issuable/components/csv_import_modal.vue
index b72abe14ee1..7e2cbf03801 100644
--- a/app/assets/javascripts/issuable/components/csv_import_modal.vue
+++ b/app/assets/javascripts/issuable/components/csv_import_modal.vue
@@ -60,7 +60,11 @@ export default {
<form ref="form" :action="importCsvIssuesPath" enctype="multipart/form-data" method="post">
<input :value="$options.csrf.token" type="hidden" name="authenticity_token" />
<p>{{ $options.i18n.mainText }}</p>
- <gl-form-group :label="$options.i18n.uploadCsvFileText" label-for="file">
+ <gl-form-group
+ :label="$options.i18n.uploadCsvFileText"
+ class="gl-text-truncate"
+ label-for="file"
+ >
<input id="file" type="file" name="file" accept=".csv,text/csv" />
</gl-form-group>
<p class="text-secondary">
diff --git a/app/assets/javascripts/issuable/index.js b/app/assets/javascripts/issuable/index.js
index 072422944f5..57bad5182e7 100644
--- a/app/assets/javascripts/issuable/index.js
+++ b/app/assets/javascripts/issuable/index.js
@@ -11,7 +11,9 @@ import IssuableHeaderWarnings from './components/issuable_header_warnings.vue';
export function initCsvImportExportButtons() {
const el = document.querySelector('.js-csv-import-export-buttons');
- if (!el) return null;
+ if (!el) {
+ return null;
+ }
const {
showExportButton,
@@ -42,23 +44,24 @@ export function initCsvImportExportButtons() {
maxAttachmentSize,
showLabel,
},
- render(h) {
- return h(CsvImportExportButtons, {
+ render: (createElement) =>
+ createElement(CsvImportExportButtons, {
props: {
exportCsvPath,
issuableCount: parseInt(issuableCount, 10),
},
- });
- },
+ }),
});
}
export function initIssuableByEmail() {
- Vue.use(GlToast);
-
const el = document.querySelector('.js-issuable-by-email');
- if (!el) return null;
+ if (!el) {
+ return null;
+ }
+
+ Vue.use(GlToast);
const {
initialEmail,
@@ -79,9 +82,7 @@ export function initIssuableByEmail() {
markdownHelpPath,
resetPath,
},
- render(h) {
- return h(IssuableByEmail);
- },
+ render: (createElement) => createElement(IssuableByEmail),
});
}
@@ -89,7 +90,7 @@ export function initIssuableHeaderWarnings(store) {
const el = document.getElementById('js-issuable-header-warnings');
if (!el) {
- return false;
+ return null;
}
const { hidden } = el.dataset;
@@ -98,18 +99,18 @@ export function initIssuableHeaderWarnings(store) {
el,
store,
provide: { hidden: parseBoolean(hidden) },
- render(createElement) {
- return createElement(IssuableHeaderWarnings);
- },
+ render: (createElement) => createElement(IssuableHeaderWarnings),
});
}
export function initIssuableSidebar() {
- const sidebarOptEl = document.querySelector('.js-sidebar-options');
+ const el = document.querySelector('.js-sidebar-options');
- if (!sidebarOptEl) return;
+ if (!el) {
+ return;
+ }
- const sidebarOptions = getSidebarOptions(sidebarOptEl);
+ const sidebarOptions = getSidebarOptions(el);
new IssuableContext(sidebarOptions.currentUser); // eslint-disable-line no-new
Sidebar.initialize();
diff --git a/app/assets/javascripts/issues/constants.js b/app/assets/javascripts/issues/constants.js
index b7b123dfd5f..4b9a42da178 100644
--- a/app/assets/javascripts/issues/constants.js
+++ b/app/assets/javascripts/issues/constants.js
@@ -19,6 +19,12 @@ export const IssuableType = {
Alert: 'alert',
};
+export const IssueType = {
+ Issue: 'issue',
+ Incident: 'incident',
+ TestCase: 'test_case',
+};
+
export const WorkspaceType = {
project: 'project',
group: 'group',
diff --git a/app/assets/javascripts/create_merge_request_dropdown.js b/app/assets/javascripts/issues/create_merge_request_dropdown.js
index ae6e6bf02e4..5d36396bc6e 100644
--- a/app/assets/javascripts/create_merge_request_dropdown.js
+++ b/app/assets/javascripts/issues/create_merge_request_dropdown.js
@@ -3,13 +3,13 @@ import {
init as initConfidentialMergeRequest,
isConfidentialIssue,
canCreateConfidentialMergeRequest,
-} from './confidential_merge_request';
-import confidentialMergeRequestState from './confidential_merge_request/state';
-import DropLab from './filtered_search/droplab/drop_lab_deprecated';
-import ISetter from './filtered_search/droplab/plugins/input_setter';
-import createFlash from './flash';
-import axios from './lib/utils/axios_utils';
-import { __, sprintf } from './locale';
+} from '~/confidential_merge_request';
+import confidentialMergeRequestState from '~/confidential_merge_request/state';
+import DropLab from '~/filtered_search/droplab/drop_lab_deprecated';
+import ISetter from '~/filtered_search/droplab/plugins/input_setter';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { __, sprintf } from '~/locale';
// Todo: Remove this when fixing issue in input_setter plugin
const InputSetter = { ...ISetter };
diff --git a/app/assets/javascripts/issues/form.js b/app/assets/javascripts/issues/form.js
deleted file mode 100644
index 33371d065f9..00000000000
--- a/app/assets/javascripts/issues/form.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/* eslint-disable no-new */
-
-import $ from 'jquery';
-import IssuableForm from 'ee_else_ce/issuable/issuable_form';
-import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
-import GLForm from '~/gl_form';
-import { initTitleSuggestions, initTypePopover } from '~/issues/new';
-import LabelsSelect from '~/labels/labels_select';
-import MilestoneSelect from '~/milestones/milestone_select';
-import IssuableTemplateSelectors from '~/issuable/issuable_template_selectors';
-
-export default () => {
- new ShortcutsNavigation();
- new GLForm($('.issue-form'));
- new IssuableForm($('.issue-form'));
- new LabelsSelect();
- new MilestoneSelect();
- new IssuableTemplateSelectors({
- warnTemplateOverride: true,
- });
-
- initTitleSuggestions();
- initTypePopover();
-};
diff --git a/app/assets/javascripts/issues/index.js b/app/assets/javascripts/issues/index.js
new file mode 100644
index 00000000000..2ee9ac2a682
--- /dev/null
+++ b/app/assets/javascripts/issues/index.js
@@ -0,0 +1,88 @@
+import $ from 'jquery';
+import IssuableForm from 'ee_else_ce/issuable/issuable_form';
+import loadAwardsHandler from '~/awards_handler';
+import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
+import GLForm from '~/gl_form';
+import { initIssuableHeaderWarnings, initIssuableSidebar } from '~/issuable';
+import IssuableTemplateSelectors from '~/issuable/issuable_template_selectors';
+import { IssueType } from '~/issues/constants';
+import Issue from '~/issues/issue';
+import { initTitleSuggestions, initTypePopover } from '~/issues/new';
+import { initRelatedMergeRequests } from '~/issues/related_merge_requests';
+import {
+ initHeaderActions,
+ initIncidentApp,
+ initIssueApp,
+ initSentryErrorStackTrace,
+} from '~/issues/show';
+import { parseIssuableData } from '~/issues/show/utils/parse_data';
+import LabelsSelect from '~/labels/labels_select';
+import MilestoneSelect from '~/milestones/milestone_select';
+import initNotesApp from '~/notes';
+import { store } from '~/notes/stores';
+import ZenMode from '~/zen_mode';
+import FilteredSearchServiceDesk from './filtered_search_service_desk';
+
+export function initFilteredSearchServiceDesk() {
+ if (document.querySelector('.filtered-search')) {
+ const supportBotData = JSON.parse(
+ document.querySelector('.js-service-desk-issues').dataset.supportBot,
+ );
+ const filteredSearchManager = new FilteredSearchServiceDesk(supportBotData);
+ filteredSearchManager.setup();
+ }
+}
+
+export function initForm() {
+ new GLForm($('.issue-form')); // eslint-disable-line no-new
+ new IssuableForm($('.issue-form')); // eslint-disable-line no-new
+ new IssuableTemplateSelectors({ warnTemplateOverride: true }); // eslint-disable-line no-new
+ new LabelsSelect(); // eslint-disable-line no-new
+ new MilestoneSelect(); // eslint-disable-line no-new
+ new ShortcutsNavigation(); // eslint-disable-line no-new
+
+ initTitleSuggestions();
+ initTypePopover();
+}
+
+export function initShow() {
+ const el = document.getElementById('js-issuable-app');
+
+ if (!el) {
+ return;
+ }
+
+ const { issueType, ...issuableData } = parseIssuableData(el);
+
+ if (issueType === IssueType.Incident) {
+ initIncidentApp(issuableData);
+ initHeaderActions(store, IssueType.Incident);
+ } else {
+ initIssueApp(issuableData, store);
+ initHeaderActions(store);
+ }
+
+ new Issue(); // eslint-disable-line no-new
+ new ShortcutsIssuable(); // eslint-disable-line no-new
+ new ZenMode(); // eslint-disable-line no-new
+ initIssuableHeaderWarnings(store);
+ initIssuableSidebar();
+ initNotesApp();
+ initRelatedMergeRequests();
+ initSentryErrorStackTrace();
+
+ const awardEmojiEl = document.getElementById('js-vue-awards-block');
+
+ if (awardEmojiEl) {
+ import('~/emoji/awards_app')
+ .then((m) => m.default(awardEmojiEl))
+ .catch(() => {});
+ } else {
+ loadAwardsHandler();
+ }
+
+ import(/* webpackChunkName: 'design_management' */ '~/design_management')
+ .then((module) => module.default())
+ .catch(() => {});
+}
diff --git a/app/assets/javascripts/issues/init_filtered_search_service_desk.js b/app/assets/javascripts/issues/init_filtered_search_service_desk.js
deleted file mode 100644
index 1901802c11c..00000000000
--- a/app/assets/javascripts/issues/init_filtered_search_service_desk.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import FilteredSearchServiceDesk from './filtered_search_service_desk';
-
-export function initFilteredSearchServiceDesk() {
- if (document.querySelector('.filtered-search')) {
- const supportBotData = JSON.parse(
- document.querySelector('.js-service-desk-issues').dataset.supportBot,
- );
- const filteredSearchManager = new FilteredSearchServiceDesk(supportBotData);
- filteredSearchManager.setup();
- }
-}
diff --git a/app/assets/javascripts/issues/issue.js b/app/assets/javascripts/issues/issue.js
index c471875654b..8e27f547b5c 100644
--- a/app/assets/javascripts/issues/issue.js
+++ b/app/assets/javascripts/issues/issue.js
@@ -1,11 +1,11 @@
import $ from 'jquery';
import { joinPaths } from '~/lib/utils/url_utility';
-import CreateMergeRequestDropdown from '~/create_merge_request_dropdown';
import createFlash from '~/flash';
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
import axios from '~/lib/utils/axios_utils';
import { addDelimiter } from '~/lib/utils/text_utility';
import { __ } from '~/locale';
+import CreateMergeRequestDropdown from './create_merge_request_dropdown';
export default class Issue {
constructor() {
diff --git a/app/assets/javascripts/issues_list/components/issue_card_time_info.vue b/app/assets/javascripts/issues/list/components/issue_card_time_info.vue
index aece7372182..aece7372182 100644
--- a/app/assets/javascripts/issues_list/components/issue_card_time_info.vue
+++ b/app/assets/javascripts/issues/list/components/issue_card_time_info.vue
diff --git a/app/assets/javascripts/issues_list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index 6ced1080b71..8b15e801f02 100644
--- a/app/assets/javascripts/issues_list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -11,9 +11,9 @@ import {
import * as Sentry from '@sentry/browser';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
import { orderBy } from 'lodash';
-import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
-import getIssuesCountsQuery from 'ee_else_ce/issues_list/queries/get_issues_counts.query.graphql';
-import IssueCardTimeInfo from 'ee_else_ce/issues_list/components/issue_card_time_info.vue';
+import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
+import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql';
+import IssueCardTimeInfo from 'ee_else_ce/issues/list/components/issue_card_time_info.vue';
import createFlash, { FLASH_TYPES } from '~/flash';
import { TYPE_USER } from '~/graphql_shared/constants';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
@@ -41,7 +41,7 @@ import {
TOKEN_TYPE_TYPE,
UPDATED_DESC,
urlSortParams,
-} from '~/issues_list/constants';
+} from '~/issues/list/constants';
import {
convertToApiParams,
convertToSearchQuery,
@@ -51,7 +51,7 @@ import {
getInitialPageParams,
getSortKey,
getSortOptions,
-} from '~/issues_list/utils';
+} from '~/issues/list/utils';
import axios from '~/lib/utils/axios_utils';
import { scrollUp } from '~/lib/utils/scroll_utils';
import { getParameterByName, joinPaths } from '~/lib/utils/url_utility';
@@ -517,10 +517,9 @@ export default {
},
async handleBulkUpdateClick() {
if (!this.hasInitBulkEdit) {
- const initBulkUpdateSidebar = await import(
- '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar'
- );
- initBulkUpdateSidebar.default.init('issuable_');
+ const bulkUpdateSidebar = await import('~/issuable/bulk_update_sidebar');
+ bulkUpdateSidebar.initBulkUpdateSidebar('issuable_');
+ bulkUpdateSidebar.initIssueStatusSelect();
const usersSelect = await import('~/users_select');
const UsersSelect = usersSelect.default;
diff --git a/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue b/app/assets/javascripts/issues/list/components/jira_issues_import_status_app.vue
index fb1dbef666c..fb1dbef666c 100644
--- a/app/assets/javascripts/issues_list/components/jira_issues_import_status_app.vue
+++ b/app/assets/javascripts/issues/list/components/jira_issues_import_status_app.vue
diff --git a/app/assets/javascripts/issues_list/components/new_issue_dropdown.vue b/app/assets/javascripts/issues/list/components/new_issue_dropdown.vue
index e749579af80..71f84050ba8 100644
--- a/app/assets/javascripts/issues_list/components/new_issue_dropdown.vue
+++ b/app/assets/javascripts/issues/list/components/new_issue_dropdown.vue
@@ -7,7 +7,7 @@ import {
GlSearchBoxByType,
} from '@gitlab/ui';
import createFlash from '~/flash';
-import searchProjectsQuery from '~/issues_list/queries/search_projects.query.graphql';
+import searchProjectsQuery from '~/issues/list/queries/search_projects.query.graphql';
import { DASH_SCOPE, joinPaths } from '~/lib/utils/url_utility';
import { __, sprintf } from '~/locale';
import { DEBOUNCE_DELAY } from '~/vue_shared/components/filtered_search_bar/constants';
diff --git a/app/assets/javascripts/issues_list/constants.js b/app/assets/javascripts/issues/list/constants.js
index c9eaf0b9908..4a380848b4f 100644
--- a/app/assets/javascripts/issues_list/constants.js
+++ b/app/assets/javascripts/issues/list/constants.js
@@ -9,62 +9,6 @@ import {
OPERATOR_IS_NOT,
} from '~/vue_shared/components/filtered_search_bar/constants';
-// Maps sort order as it appears in the URL query to API `order_by` and `sort` params.
-const PRIORITY = 'priority';
-const ASC = 'asc';
-const DESC = 'desc';
-const CREATED_AT = 'created_at';
-const UPDATED_AT = 'updated_at';
-const DUE_DATE = 'due_date';
-const MILESTONE_DUE = 'milestone_due';
-const POPULARITY = 'popularity';
-const WEIGHT = 'weight';
-const LABEL_PRIORITY = 'label_priority';
-const TITLE = 'title';
-export const RELATIVE_POSITION = 'relative_position';
-export const LOADING_LIST_ITEMS_LENGTH = 8;
-export const PAGE_SIZE = 20;
-export const PAGE_SIZE_MANUAL = 100;
-
-export const sortOrderMap = {
- priority: { order_by: PRIORITY, sort: ASC }, // asc and desc are flipped for some reason
- created_date: { order_by: CREATED_AT, sort: DESC },
- created_asc: { order_by: CREATED_AT, sort: ASC },
- updated_desc: { order_by: UPDATED_AT, sort: DESC },
- updated_asc: { order_by: UPDATED_AT, sort: ASC },
- milestone_due_desc: { order_by: MILESTONE_DUE, sort: DESC },
- milestone: { order_by: MILESTONE_DUE, sort: ASC },
- due_date_desc: { order_by: DUE_DATE, sort: DESC },
- due_date: { order_by: DUE_DATE, sort: ASC },
- popularity: { order_by: POPULARITY, sort: DESC },
- popularity_asc: { order_by: POPULARITY, sort: ASC },
- label_priority: { order_by: LABEL_PRIORITY, sort: ASC }, // asc and desc are flipped
- relative_position: { order_by: RELATIVE_POSITION, sort: ASC },
- weight_desc: { order_by: WEIGHT, sort: DESC },
- weight: { order_by: WEIGHT, sort: ASC },
- title: { order_by: TITLE, sort: ASC },
- title_desc: { order_by: TITLE, sort: DESC },
-};
-
-export const availableSortOptionsJira = [
- {
- id: 1,
- title: __('Created date'),
- sortDirection: {
- descending: 'created_desc',
- ascending: 'created_asc',
- },
- },
- {
- id: 2,
- title: __('Last updated'),
- sortDirection: {
- descending: 'updated_desc',
- ascending: 'updated_asc',
- },
- },
-];
-
export const i18n = {
anonymousSearchingMessage: __('You must sign in to search for specific terms.'),
calendarLabel: __('Subscribe to calendar'),
@@ -108,11 +52,13 @@ export const i18n = {
upvotes: __('Upvotes'),
};
-export const JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY = 'jira-import-success-alert-hide-map';
-
+export const MAX_LIST_SIZE = 10;
+export const PAGE_SIZE = 20;
+export const PAGE_SIZE_MANUAL = 100;
export const PARAM_DUE_DATE = 'due_date';
export const PARAM_SORT = 'sort';
export const PARAM_STATE = 'state';
+export const RELATIVE_POSITION = 'relative_position';
export const defaultPageSizeParams = {
firstPageSize: PAGE_SIZE,
@@ -183,8 +129,6 @@ export const urlSortParams = {
[TITLE_DESC]: 'title_desc',
};
-export const MAX_LIST_SIZE = 10;
-
export const API_PARAM = 'apiParam';
export const URL_PARAM = 'urlParam';
export const NORMAL_FILTER = 'normalFilter';
diff --git a/app/assets/javascripts/issues_list/eventhub.js b/app/assets/javascripts/issues/list/eventhub.js
index e31806ad199..e31806ad199 100644
--- a/app/assets/javascripts/issues_list/eventhub.js
+++ b/app/assets/javascripts/issues/list/eventhub.js
diff --git a/app/assets/javascripts/issues_list/index.js b/app/assets/javascripts/issues/list/index.js
index 9d2ec8b32d2..01cc82ed8fd 100644
--- a/app/assets/javascripts/issues_list/index.js
+++ b/app/assets/javascripts/issues/list/index.js
@@ -1,11 +1,10 @@
import produce from 'immer';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import getIssuesQuery from 'ee_else_ce/issues_list/queries/get_issues.query.graphql';
-import IssuesListApp from 'ee_else_ce/issues_list/components/issues_list_app.vue';
+import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
+import IssuesListApp from 'ee_else_ce/issues/list/components/issues_list_app.vue';
import createDefaultClient from '~/lib/graphql';
-import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
-import IssuablesListApp from './components/issuables_list_app.vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
import JiraIssuesImportStatusRoot from './components/jira_issues_import_status_app.vue';
export function mountJiraIssuesListApp() {
@@ -45,35 +44,6 @@ export function mountJiraIssuesListApp() {
});
}
-export function mountIssuablesListApp() {
- if (!gon.features?.vueIssuablesList) {
- return;
- }
-
- document.querySelectorAll('.js-issuables-list').forEach((el) => {
- const { canBulkEdit, emptyStateMeta = {}, scopedLabelsAvailable, ...data } = el.dataset;
-
- return new Vue({
- el,
- provide: {
- scopedLabelsAvailable: parseBoolean(scopedLabelsAvailable),
- },
- render(createElement) {
- return createElement(IssuablesListApp, {
- props: {
- ...data,
- emptyStateMeta:
- Object.keys(emptyStateMeta).length !== 0
- ? convertObjectPropsToCamelCase(JSON.parse(emptyStateMeta))
- : {},
- canBulkEdit: Boolean(canBulkEdit),
- },
- });
- },
- });
- });
-}
-
export function mountIssuesListApp() {
const el = document.querySelector('.js-issues-list');
diff --git a/app/assets/javascripts/issues_list/queries/get_issues.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql
index be8deb3fe97..be8deb3fe97 100644
--- a/app/assets/javascripts/issues_list/queries/get_issues.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/get_issues.query.graphql
diff --git a/app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql
index 1a345fd2877..1a345fd2877 100644
--- a/app/assets/javascripts/issues_list/queries/get_issues_counts.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/get_issues_counts.query.graphql
diff --git a/app/assets/javascripts/issues_list/queries/get_issues_list_details.query.graphql b/app/assets/javascripts/issues/list/queries/get_issues_list_details.query.graphql
index a53dba8c7c8..a53dba8c7c8 100644
--- a/app/assets/javascripts/issues_list/queries/get_issues_list_details.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/get_issues_list_details.query.graphql
diff --git a/app/assets/javascripts/issues_list/queries/issue.fragment.graphql b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
index 07dae3fd756..07dae3fd756 100644
--- a/app/assets/javascripts/issues_list/queries/issue.fragment.graphql
+++ b/app/assets/javascripts/issues/list/queries/issue.fragment.graphql
diff --git a/app/assets/javascripts/issues_list/queries/label.fragment.graphql b/app/assets/javascripts/issues/list/queries/label.fragment.graphql
index bb1d8f1ac9b..bb1d8f1ac9b 100644
--- a/app/assets/javascripts/issues_list/queries/label.fragment.graphql
+++ b/app/assets/javascripts/issues/list/queries/label.fragment.graphql
diff --git a/app/assets/javascripts/issues_list/queries/milestone.fragment.graphql b/app/assets/javascripts/issues/list/queries/milestone.fragment.graphql
index 3cdf69bf585..3cdf69bf585 100644
--- a/app/assets/javascripts/issues_list/queries/milestone.fragment.graphql
+++ b/app/assets/javascripts/issues/list/queries/milestone.fragment.graphql
diff --git a/app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql b/app/assets/javascripts/issues/list/queries/reorder_issues.mutation.graphql
index 160026a4742..160026a4742 100644
--- a/app/assets/javascripts/issues_list/queries/reorder_issues.mutation.graphql
+++ b/app/assets/javascripts/issues/list/queries/reorder_issues.mutation.graphql
diff --git a/app/assets/javascripts/issues_list/queries/search_labels.query.graphql b/app/assets/javascripts/issues/list/queries/search_labels.query.graphql
index 44b57317161..44b57317161 100644
--- a/app/assets/javascripts/issues_list/queries/search_labels.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/search_labels.query.graphql
diff --git a/app/assets/javascripts/issues_list/queries/search_milestones.query.graphql b/app/assets/javascripts/issues/list/queries/search_milestones.query.graphql
index e7eb08104a6..e7eb08104a6 100644
--- a/app/assets/javascripts/issues_list/queries/search_milestones.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/search_milestones.query.graphql
diff --git a/app/assets/javascripts/issues_list/queries/search_projects.query.graphql b/app/assets/javascripts/issues/list/queries/search_projects.query.graphql
index bd2f9bc2340..bd2f9bc2340 100644
--- a/app/assets/javascripts/issues_list/queries/search_projects.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/search_projects.query.graphql
diff --git a/app/assets/javascripts/issues_list/queries/search_users.query.graphql b/app/assets/javascripts/issues/list/queries/search_users.query.graphql
index 92517ad35d0..92517ad35d0 100644
--- a/app/assets/javascripts/issues_list/queries/search_users.query.graphql
+++ b/app/assets/javascripts/issues/list/queries/search_users.query.graphql
diff --git a/app/assets/javascripts/issues_list/queries/user.fragment.graphql b/app/assets/javascripts/issues/list/queries/user.fragment.graphql
index 3e5bc0f7b93..3e5bc0f7b93 100644
--- a/app/assets/javascripts/issues_list/queries/user.fragment.graphql
+++ b/app/assets/javascripts/issues/list/queries/user.fragment.graphql
diff --git a/app/assets/javascripts/issues_list/utils.js b/app/assets/javascripts/issues/list/utils.js
index 99946e4e851..2919bbbfef8 100644
--- a/app/assets/javascripts/issues_list/utils.js
+++ b/app/assets/javascripts/issues/list/utils.js
@@ -36,7 +36,7 @@ import {
urlSortParams,
WEIGHT_ASC,
WEIGHT_DESC,
-} from '~/issues_list/constants';
+} from '~/issues/list/constants';
import { isPositiveInteger } from '~/lib/utils/number_utils';
import { __ } from '~/locale';
import {
@@ -72,7 +72,7 @@ export const getSortOptions = (hasIssueWeightsFeature, hasBlockedIssuesFeature)
},
{
id: 3,
- title: __('Last updated'),
+ title: __('Updated date'),
sortDirection: {
ascending: UPDATED_ASC,
descending: UPDATED_DESC,
diff --git a/app/assets/javascripts/issues/manual_ordering.js b/app/assets/javascripts/issues/manual_ordering.js
index 9613246d6a6..c78505d0610 100644
--- a/app/assets/javascripts/issues/manual_ordering.js
+++ b/app/assets/javascripts/issues/manual_ordering.js
@@ -20,7 +20,7 @@ const updateIssue = (url, issueList, { move_before_id, move_after_id }) =>
});
});
-const initManualOrdering = (draggableSelector = 'li.issue') => {
+const initManualOrdering = () => {
const issueList = document.querySelector('.manual-ordering');
if (!issueList || !(gon.current_user_id > 0)) {
@@ -37,14 +37,14 @@ const initManualOrdering = (draggableSelector = 'li.issue') => {
group: {
name: 'issues',
},
- draggable: draggableSelector,
+ draggable: 'li.issue',
onStart: () => {
sortableStart();
},
onUpdate: (event) => {
const el = event.item;
- const url = el.getAttribute('url') || el.dataset.url;
+ const url = el.getAttribute('url');
const prev = el.previousElementSibling;
const next = el.nextElementSibling;
diff --git a/app/assets/javascripts/issues/new/index.js b/app/assets/javascripts/issues/new/index.js
index 59a7cbec627..f96cacf2595 100644
--- a/app/assets/javascripts/issues/new/index.js
+++ b/app/assets/javascripts/issues/new/index.js
@@ -5,8 +5,6 @@ import TitleSuggestions from './components/title_suggestions.vue';
import TypePopover from './components/type_popover.vue';
export function initTitleSuggestions() {
- Vue.use(VueApollo);
-
const el = document.getElementById('js-suggestions');
const issueTitle = document.getElementById('issue_title');
@@ -14,6 +12,8 @@ export function initTitleSuggestions() {
return undefined;
}
+ Vue.use(VueApollo);
+
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
diff --git a/app/assets/javascripts/issues/related_merge_requests/index.js b/app/assets/javascripts/issues/related_merge_requests/index.js
index ce33cf7df1d..5045f7e1a2a 100644
--- a/app/assets/javascripts/issues/related_merge_requests/index.js
+++ b/app/assets/javascripts/issues/related_merge_requests/index.js
@@ -2,23 +2,21 @@ import Vue from 'vue';
import RelatedMergeRequests from './components/related_merge_requests.vue';
import createStore from './store';
-export default function initRelatedMergeRequests() {
- const relatedMergeRequestsElement = document.querySelector('#js-related-merge-requests');
+export function initRelatedMergeRequests() {
+ const el = document.querySelector('#js-related-merge-requests');
- if (relatedMergeRequestsElement) {
- const { endpoint, projectPath, projectNamespace } = relatedMergeRequestsElement.dataset;
-
- // eslint-disable-next-line no-new
- new Vue({
- el: relatedMergeRequestsElement,
- components: {
- RelatedMergeRequests,
- },
- store: createStore(),
- render: (createElement) =>
- createElement('related-merge-requests', {
- props: { endpoint, projectNamespace, projectPath },
- }),
- });
+ if (!el) {
+ return undefined;
}
+
+ const { endpoint, projectPath, projectNamespace } = el.dataset;
+
+ return new Vue({
+ el,
+ store: createStore(),
+ render: (createElement) =>
+ createElement(RelatedMergeRequests, {
+ props: { endpoint, projectNamespace, projectPath },
+ }),
+ });
}
diff --git a/app/assets/javascripts/issues/sentry_error_stack_trace/index.js b/app/assets/javascripts/issues/sentry_error_stack_trace/index.js
deleted file mode 100644
index 8e9ee25e7a8..00000000000
--- a/app/assets/javascripts/issues/sentry_error_stack_trace/index.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import Vue from 'vue';
-import store from '~/error_tracking/store';
-import SentryErrorStackTrace from './components/sentry_error_stack_trace.vue';
-
-export default function initSentryErrorStacktrace() {
- const sentryErrorStackTraceEl = document.querySelector('#js-sentry-error-stack-trace');
- if (sentryErrorStackTraceEl) {
- const { issueStackTracePath } = sentryErrorStackTraceEl.dataset;
- // eslint-disable-next-line no-new
- new Vue({
- el: sentryErrorStackTraceEl,
- components: {
- SentryErrorStackTrace,
- },
- store,
- render: (createElement) =>
- createElement('sentry-error-stack-trace', {
- props: { issueStackTracePath },
- }),
- });
- }
-}
diff --git a/app/assets/javascripts/issues/show.js b/app/assets/javascripts/issues/show.js
deleted file mode 100644
index e43e56d7b4e..00000000000
--- a/app/assets/javascripts/issues/show.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import loadAwardsHandler from '~/awards_handler';
-import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
-import { initIssuableHeaderWarnings, initIssuableSidebar } from '~/issuable';
-import { IssuableType } from '~/vue_shared/issuable/show/constants';
-import Issue from '~/issues/issue';
-import { initIncidentApp, initIncidentHeaderActions } from '~/issues/show/incident';
-import { initIssuableApp, initIssueHeaderActions } from '~/issues/show/issue';
-import { parseIssuableData } from '~/issues/show/utils/parse_data';
-import initNotesApp from '~/notes';
-import { store } from '~/notes/stores';
-import initRelatedMergeRequestsApp from '~/issues/related_merge_requests';
-import initSentryErrorStackTraceApp from '~/issues/sentry_error_stack_trace';
-import ZenMode from '~/zen_mode';
-
-export default function initShowIssue() {
- initNotesApp();
-
- const initialDataEl = document.getElementById('js-issuable-app');
- const { issueType, ...issuableData } = parseIssuableData(initialDataEl);
-
- switch (issueType) {
- case IssuableType.Incident:
- initIncidentApp(issuableData);
- initIncidentHeaderActions(store);
- break;
- case IssuableType.Issue:
- initIssuableApp(issuableData, store);
- initIssueHeaderActions(store);
- break;
- default:
- initIssueHeaderActions(store);
- break;
- }
-
- initIssuableHeaderWarnings(store);
- initSentryErrorStackTraceApp();
- initRelatedMergeRequestsApp();
-
- import(/* webpackChunkName: 'design_management' */ '~/design_management')
- .then((module) => module.default())
- .catch(() => {});
-
- new ZenMode(); // eslint-disable-line no-new
-
- if (issueType !== IssuableType.TestCase) {
- const awardEmojiEl = document.getElementById('js-vue-awards-block');
-
- new Issue(); // eslint-disable-line no-new
- new ShortcutsIssuable(); // eslint-disable-line no-new
- initIssuableSidebar();
- if (awardEmojiEl) {
- import('~/emoji/awards_app')
- .then((m) => m.default(awardEmojiEl))
- .catch(() => {});
- } else {
- loadAwardsHandler();
- }
- }
-}
diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue
index eeaf865a35f..0490728c6bc 100644
--- a/app/assets/javascripts/issues/show/components/app.vue
+++ b/app/assets/javascripts/issues/show/components/app.vue
@@ -6,7 +6,7 @@ import { IssuableStatus, IssuableStatusText, IssuableType } from '~/issues/const
import Poll from '~/lib/utils/poll';
import { visitUrl } from '~/lib/utils/url_utility';
import { __, sprintf } from '~/locale';
-import { IssueTypePath, IncidentTypePath, IncidentType, POLLING_DELAY } from '../constants';
+import { ISSUE_TYPE_PATH, INCIDENT_TYPE_PATH, INCIDENT_TYPE, POLLING_DELAY } from '../constants';
import eventHub from '../event_hub';
import getIssueStateQuery from '../queries/get_issue_state.query.graphql';
import Service from '../services/index';
@@ -378,15 +378,15 @@ export default {
.then((data) => {
if (
!window.location.pathname.includes(data.web_url) &&
- issueState.issueType !== IncidentType
+ issueState.issueType !== INCIDENT_TYPE
) {
visitUrl(data.web_url);
}
if (issueState.isDirty) {
const URI =
- issueState.issueType === IncidentType
- ? data.web_url.replace(IssueTypePath, IncidentTypePath)
+ issueState.issueType === INCIDENT_TYPE
+ ? data.web_url.replace(ISSUE_TYPE_PATH, INCIDENT_TYPE_PATH)
: data.web_url;
visitUrl(URI);
}
diff --git a/app/assets/javascripts/issues/show/components/fields/type.vue b/app/assets/javascripts/issues/show/components/fields/type.vue
index 9110a6924b4..75d0b9e5e76 100644
--- a/app/assets/javascripts/issues/show/components/fields/type.vue
+++ b/app/assets/javascripts/issues/show/components/fields/type.vue
@@ -2,7 +2,7 @@
import { GlFormGroup, GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui';
import { capitalize } from 'lodash';
import { __ } from '~/locale';
-import { IssuableTypes, IncidentType } from '../../constants';
+import { issuableTypes, INCIDENT_TYPE } from '../../constants';
import getIssueStateQuery from '../../queries/get_issue_state.query.graphql';
import updateIssueStateMutation from '../../queries/update_issue_state.mutation.graphql';
@@ -12,7 +12,7 @@ export const i18n = {
export default {
i18n,
- IssuableTypes,
+ issuableTypes,
components: {
GlFormGroup,
GlIcon,
@@ -45,7 +45,7 @@ export default {
return capitalize(issueType);
},
shouldShowIncident() {
- return this.issueType === IncidentType || this.canCreateIncident;
+ return this.issueType === INCIDENT_TYPE || this.canCreateIncident;
},
},
methods: {
@@ -59,7 +59,7 @@ export default {
});
},
isShown(type) {
- return type.value !== IncidentType || this.shouldShowIncident;
+ return type.value !== INCIDENT_TYPE || this.shouldShowIncident;
},
},
};
@@ -81,7 +81,7 @@ export default {
toggle-class="dropdown-menu-toggle"
>
<gl-dropdown-item
- v-for="type in $options.IssuableTypes"
+ v-for="type in $options.issuableTypes"
v-show="isShown(type)"
:key="type.value"
:is-checked="issueState.issueType === type.value"
diff --git a/app/assets/javascripts/issues/show/components/header_actions.vue b/app/assets/javascripts/issues/show/components/header_actions.vue
index 700ef92a0f3..8ba08472ea0 100644
--- a/app/assets/javascripts/issues/show/components/header_actions.vue
+++ b/app/assets/javascripts/issues/show/components/header_actions.vue
@@ -11,9 +11,8 @@ import {
import { mapActions, mapGetters, mapState } from 'vuex';
import createFlash, { FLASH_TYPES } from '~/flash';
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
-import { IssuableType } from '~/vue_shared/issuable/show/constants';
-import { IssuableStatus } from '~/issues/constants';
-import { IssueStateEvent } from '~/issues/show/constants';
+import { IssuableStatus, IssueType } from '~/issues/constants';
+import { ISSUE_STATE_EVENT_CLOSE, ISSUE_STATE_EVENT_REOPEN } from '~/issues/show/constants';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { visitUrl } from '~/lib/utils/url_utility';
import { s__, __, sprintf } from '~/locale';
@@ -83,7 +82,7 @@ export default {
default: '',
},
issueType: {
- default: IssuableType.Issue,
+ default: IssueType.Issue,
},
newIssuePath: {
default: '',
@@ -106,8 +105,8 @@ export default {
},
issueTypeText() {
const issueTypeTexts = {
- [IssuableType.Issue]: s__('HeaderAction|issue'),
- [IssuableType.Incident]: s__('HeaderAction|incident'),
+ [IssueType.Issue]: s__('HeaderAction|issue'),
+ [IssueType.Incident]: s__('HeaderAction|incident'),
};
return issueTypeTexts[this.issueType] ?? this.issueType;
@@ -163,7 +162,7 @@ export default {
input: {
iid: this.iid.toString(),
projectPath: this.projectPath,
- stateEvent: this.isClosed ? IssueStateEvent.Reopen : IssueStateEvent.Close,
+ stateEvent: this.isClosed ? ISSUE_STATE_EVENT_REOPEN : ISSUE_STATE_EVENT_CLOSE,
},
},
})
diff --git a/app/assets/javascripts/issues/sentry_error_stack_trace/components/sentry_error_stack_trace.vue b/app/assets/javascripts/issues/show/components/sentry_error_stack_trace.vue
index 1530e9a15b5..1530e9a15b5 100644
--- a/app/assets/javascripts/issues/sentry_error_stack_trace/components/sentry_error_stack_trace.vue
+++ b/app/assets/javascripts/issues/show/components/sentry_error_stack_trace.vue
diff --git a/app/assets/javascripts/issues/show/constants.js b/app/assets/javascripts/issues/show/constants.js
index 35f3bcdad70..a100aaf88ad 100644
--- a/app/assets/javascripts/issues/show/constants.js
+++ b/app/assets/javascripts/issues/show/constants.js
@@ -1,22 +1,20 @@
import { __ } from '~/locale';
-export const IssueStateEvent = {
- Close: 'CLOSE',
- Reopen: 'REOPEN',
-};
-
-export const STATUS_PAGE_PUBLISHED = __('Published on status page');
+export const INCIDENT_TYPE = 'incident';
+export const INCIDENT_TYPE_PATH = 'issues/incident';
+export const ISSUE_STATE_EVENT_CLOSE = 'CLOSE';
+export const ISSUE_STATE_EVENT_REOPEN = 'REOPEN';
+export const ISSUE_TYPE_PATH = 'issues';
export const JOIN_ZOOM_MEETING = __('Join Zoom meeting');
+export const POLLING_DELAY = 2000;
+export const STATUS_PAGE_PUBLISHED = __('Published on status page');
-export const IssuableTypes = [
+export const issuableTypes = [
{ value: 'issue', text: __('Issue'), icon: 'issue-type-issue' },
{ value: 'incident', text: __('Incident'), icon: 'issue-type-incident' },
];
-export const IssueTypePath = 'issues';
-export const IncidentTypePath = 'issues/incident';
-export const IncidentType = 'incident';
-
-export const issueState = { issueType: undefined, isDirty: false };
-
-export const POLLING_DELAY = 2000;
+export const issueState = {
+ issueType: undefined,
+ isDirty: false,
+};
diff --git a/app/assets/javascripts/issues/show/incident.js b/app/assets/javascripts/issues/show/index.js
index a260c31e1da..7f5a0e32f72 100644
--- a/app/assets/javascripts/issues/show/incident.js
+++ b/app/assets/javascripts/issues/show/index.js
@@ -1,11 +1,15 @@
import Vue from 'vue';
+import { mapGetters } from 'vuex';
+import errorTrackingStore from '~/error_tracking/store';
import { parseBoolean } from '~/lib/utils/common_utils';
-import issuableApp from './components/app.vue';
-import incidentTabs from './components/incidents/incident_tabs.vue';
-import { issueState, IncidentType } from './constants';
+import { scrollToTargetOnResize } from '~/lib/utils/resize_observer';
+import IssueApp from './components/app.vue';
+import HeaderActions from './components/header_actions.vue';
+import IncidentTabs from './components/incidents/incident_tabs.vue';
+import SentryErrorStackTrace from './components/sentry_error_stack_trace.vue';
+import { INCIDENT_TYPE, issueState } from './constants';
import apolloProvider from './graphql';
import getIssueStateQuery from './queries/get_issue_state.query.graphql';
-import HeaderActions from './components/header_actions.vue';
const bootstrapApollo = (state = {}) => {
return apolloProvider.clients.defaultClient.cache.writeQuery({
@@ -16,7 +20,7 @@ const bootstrapApollo = (state = {}) => {
});
};
-export function initIncidentApp(issuableData = {}) {
+export function initIncidentApp(issueData = {}) {
const el = document.getElementById('js-issuable-app');
if (!el) {
@@ -34,18 +38,15 @@ export function initIncidentApp(issuableData = {}) {
projectId,
slaFeatureAvailable,
uploadMetricsFeatureAvailable,
- } = issuableData;
+ } = issueData;
const fullPath = `${projectNamespace}/${projectPath}`;
return new Vue({
el,
apolloProvider,
- components: {
- issuableApp,
- },
provide: {
- issueType: IncidentType,
+ issueType: INCIDENT_TYPE,
canCreateIncident,
canUpdate,
fullPath,
@@ -55,10 +56,10 @@ export function initIncidentApp(issuableData = {}) {
uploadMetricsFeatureAvailable: parseBoolean(uploadMetricsFeatureAvailable),
},
render(createElement) {
- return createElement('issuable-app', {
+ return createElement(IssueApp, {
props: {
- ...issuableData,
- descriptionComponent: incidentTabs,
+ ...issueData,
+ descriptionComponent: IncidentTabs,
showTitleBorder: false,
},
});
@@ -66,7 +67,46 @@ export function initIncidentApp(issuableData = {}) {
});
}
-export function initIncidentHeaderActions(store) {
+export function initIssueApp(issueData, store) {
+ const el = document.getElementById('js-issuable-app');
+
+ if (!el) {
+ return undefined;
+ }
+
+ if (gon?.features?.fixCommentScroll) {
+ scrollToTargetOnResize();
+ }
+
+ bootstrapApollo({ ...issueState, issueType: el.dataset.issueType });
+
+ const { canCreateIncident, ...issueProps } = issueData;
+
+ return new Vue({
+ el,
+ apolloProvider,
+ store,
+ provide: {
+ canCreateIncident,
+ },
+ computed: {
+ ...mapGetters(['getNoteableData']),
+ },
+ render(createElement) {
+ return createElement(IssueApp, {
+ props: {
+ ...issueProps,
+ isConfidential: this.getNoteableData?.confidential,
+ isLocked: this.getNoteableData?.discussion_locked,
+ issuableStatus: this.getNoteableData?.state,
+ id: this.getNoteableData?.id,
+ },
+ });
+ },
+ });
+}
+
+export function initHeaderActions(store, type = '') {
const el = document.querySelector('.js-issue-header-actions');
if (!el) {
@@ -75,12 +115,15 @@ export function initIncidentHeaderActions(store) {
bootstrapApollo({ ...issueState, issueType: el.dataset.issueType });
+ const canCreate =
+ type === INCIDENT_TYPE ? el.dataset.canCreateIncident : el.dataset.canCreateIssue;
+
return new Vue({
el,
apolloProvider,
store,
provide: {
- canCreateIssue: parseBoolean(el.dataset.canCreateIncident),
+ canCreateIssue: parseBoolean(canCreate),
canDestroyIssue: parseBoolean(el.dataset.canDestroyIssue),
canPromoteToEpic: parseBoolean(el.dataset.canPromoteToEpic),
canReopenIssue: parseBoolean(el.dataset.canReopenIssue),
@@ -99,3 +142,20 @@ export function initIncidentHeaderActions(store) {
render: (createElement) => createElement(HeaderActions),
});
}
+
+export function initSentryErrorStackTrace() {
+ const el = document.querySelector('#js-sentry-error-stack-trace');
+
+ if (!el) {
+ return undefined;
+ }
+
+ const { issueStackTracePath } = el.dataset;
+
+ return new Vue({
+ el,
+ store: errorTrackingStore,
+ render: (createElement) =>
+ createElement(SentryErrorStackTrace, { props: { issueStackTracePath } }),
+ });
+}
diff --git a/app/assets/javascripts/issues/show/issue.js b/app/assets/javascripts/issues/show/issue.js
deleted file mode 100644
index 60e90934af8..00000000000
--- a/app/assets/javascripts/issues/show/issue.js
+++ /dev/null
@@ -1,86 +0,0 @@
-import Vue from 'vue';
-import { mapGetters } from 'vuex';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import IssuableApp from './components/app.vue';
-import HeaderActions from './components/header_actions.vue';
-import { issueState } from './constants';
-import apolloProvider from './graphql';
-import getIssueStateQuery from './queries/get_issue_state.query.graphql';
-
-const bootstrapApollo = (state = {}) => {
- return apolloProvider.clients.defaultClient.cache.writeQuery({
- query: getIssueStateQuery,
- data: {
- issueState: state,
- },
- });
-};
-
-export function initIssuableApp(issuableData, store) {
- const el = document.getElementById('js-issuable-app');
-
- if (!el) {
- return undefined;
- }
-
- bootstrapApollo({ ...issueState, issueType: el.dataset.issueType });
-
- const { canCreateIncident, ...issuableProps } = issuableData;
-
- return new Vue({
- el,
- apolloProvider,
- store,
- provide: {
- canCreateIncident,
- },
- computed: {
- ...mapGetters(['getNoteableData']),
- },
- render(createElement) {
- return createElement(IssuableApp, {
- props: {
- ...issuableProps,
- isConfidential: this.getNoteableData?.confidential,
- isLocked: this.getNoteableData?.discussion_locked,
- issuableStatus: this.getNoteableData?.state,
- id: this.getNoteableData?.id,
- },
- });
- },
- });
-}
-
-export function initIssueHeaderActions(store) {
- const el = document.querySelector('.js-issue-header-actions');
-
- if (!el) {
- return undefined;
- }
-
- bootstrapApollo({ ...issueState, issueType: el.dataset.issueType });
-
- return new Vue({
- el,
- apolloProvider,
- store,
- provide: {
- canCreateIssue: parseBoolean(el.dataset.canCreateIssue),
- canDestroyIssue: parseBoolean(el.dataset.canDestroyIssue),
- canPromoteToEpic: parseBoolean(el.dataset.canPromoteToEpic),
- canReopenIssue: parseBoolean(el.dataset.canReopenIssue),
- canReportSpam: parseBoolean(el.dataset.canReportSpam),
- canUpdateIssue: parseBoolean(el.dataset.canUpdateIssue),
- iid: el.dataset.iid,
- isIssueAuthor: parseBoolean(el.dataset.isIssueAuthor),
- issuePath: el.dataset.issuePath,
- issueType: el.dataset.issueType,
- newIssuePath: el.dataset.newIssuePath,
- projectPath: el.dataset.projectPath,
- projectId: el.dataset.projectId,
- reportAbusePath: el.dataset.reportAbusePath,
- submitAsSpamPath: el.dataset.submitAsSpamPath,
- },
- render: (createElement) => createElement(HeaderActions),
- });
-}
diff --git a/app/assets/javascripts/issues_list/components/issuable.vue b/app/assets/javascripts/issues_list/components/issuable.vue
deleted file mode 100644
index 6476d5be38c..00000000000
--- a/app/assets/javascripts/issues_list/components/issuable.vue
+++ /dev/null
@@ -1,441 +0,0 @@
-<script>
-/*
- * This is tightly coupled to projects/issues/_issue.html.haml,
- * any changes done to the haml need to be reflected here.
- */
-
-// TODO: need to move this component to graphql - https://gitlab.com/gitlab-org/gitlab/-/issues/221246
-import jiraLogo from '@gitlab/svgs/dist/illustrations/logos/jira.svg';
-import {
- GlLink,
- GlTooltipDirective as GlTooltip,
- GlSprintf,
- GlLabel,
- GlIcon,
- GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
-import { escape, isNumber } from 'lodash';
-import { isScopedLabel } from '~/lib/utils/common_utils';
-import {
- dateInWords,
- formatDate,
- getDayDifference,
- getTimeago,
- timeFor,
- newDateAsLocaleTime,
-} from '~/lib/utils/datetime_utility';
-import { convertToCamelCase } from '~/lib/utils/text_utility';
-import { mergeUrlParams, setUrlFragment, isExternal } from '~/lib/utils/url_utility';
-import { sprintf, __ } from '~/locale';
-import initUserPopovers from '~/user_popovers';
-import IssueAssignees from '~/issuable/components/issue_assignees.vue';
-
-export default {
- i18n: {
- openedAgo: __('created %{timeAgoString} by %{user}'),
- openedAgoJira: __('created %{timeAgoString} by %{user} in Jira'),
- openedAgoServiceDesk: __('created %{timeAgoString} by %{email} via %{user}'),
- },
- components: {
- IssueAssignees,
- GlLink,
- GlLabel,
- GlIcon,
- GlSprintf,
- IssueHealthStatus: () =>
- import('ee_component/related_items_tree/components/issue_health_status.vue'),
- },
- directives: {
- GlTooltip,
- SafeHtml,
- },
- inject: ['scopedLabelsAvailable'],
- props: {
- issuable: {
- type: Object,
- required: true,
- },
- isBulkEditing: {
- type: Boolean,
- required: false,
- default: false,
- },
- selected: {
- type: Boolean,
- required: false,
- default: false,
- },
- baseUrl: {
- type: String,
- required: false,
- default() {
- return window.location.href;
- },
- },
- },
- data() {
- return {
- jiraLogo,
- };
- },
- computed: {
- milestoneLink() {
- const { title } = this.issuable.milestone;
-
- return this.issuableLink({ milestone_title: title });
- },
- hasWeight() {
- return isNumber(this.issuable.weight);
- },
- dueDate() {
- return this.issuable.due_date ? newDateAsLocaleTime(this.issuable.due_date) : undefined;
- },
- dueDateWords() {
- return this.dueDate ? dateInWords(this.dueDate, true) : undefined;
- },
- isOverdue() {
- return this.dueDate ? this.dueDate < new Date() : false;
- },
- isClosed() {
- return this.issuable.state === 'closed';
- },
- isJiraIssue() {
- return this.issuable.external_tracker === 'jira';
- },
- webUrl() {
- return this.issuable.gitlab_web_url || this.issuable.web_url;
- },
- isIssuableUrlExternal() {
- return isExternal(this.webUrl);
- },
- linkTarget() {
- return this.isIssuableUrlExternal ? '_blank' : null;
- },
- issueCreatedToday() {
- return getDayDifference(new Date(this.issuable.created_at), new Date()) < 1;
- },
- labelIdsString() {
- return JSON.stringify(this.issuable.labels.map((l) => l.id));
- },
- milestoneDueDate() {
- const { due_date: dueDate } = this.issuable.milestone || {};
-
- return dueDate ? newDateAsLocaleTime(dueDate) : undefined;
- },
- milestoneTooltipText() {
- if (this.milestoneDueDate) {
- return sprintf(__('%{primary} (%{secondary})'), {
- primary: formatDate(this.milestoneDueDate, 'mmm d, yyyy'),
- secondary: timeFor(this.milestoneDueDate),
- });
- }
- return __('Milestone');
- },
- issuableAuthor() {
- return this.issuable.author;
- },
- issuableCreatedAt() {
- return getTimeago().format(this.issuable.created_at);
- },
- popoverDataAttrs() {
- const { id, username, name, avatar_url } = this.issuableAuthor;
-
- return {
- 'data-user-id': id,
- 'data-username': username,
- 'data-name': name,
- 'data-avatar-url': avatar_url,
- };
- },
- referencePath() {
- return this.issuable.references.relative;
- },
- updatedDateString() {
- return formatDate(new Date(this.issuable.updated_at), 'mmm d, yyyy h:MMtt');
- },
- updatedDateAgo() {
- // snake_case because it's the same i18n string as the HAML view
- return sprintf(__('updated %{time_ago}'), {
- time_ago: escape(getTimeago().format(this.issuable.updated_at)),
- });
- },
- issuableMeta() {
- return [
- {
- key: 'merge-requests',
- visible: this.issuable.merge_requests_count > 0,
- value: this.issuable.merge_requests_count,
- title: __('Related merge requests'),
- dataTestId: 'merge-requests',
- class: 'js-merge-requests',
- icon: 'merge-request',
- },
- {
- key: 'upvotes',
- visible: this.issuable.upvotes > 0,
- value: this.issuable.upvotes,
- title: __('Upvotes'),
- dataTestId: 'upvotes',
- class: 'js-upvotes issuable-upvotes',
- icon: 'thumb-up',
- },
- {
- key: 'downvotes',
- visible: this.issuable.downvotes > 0,
- value: this.issuable.downvotes,
- title: __('Downvotes'),
- dataTestId: 'downvotes',
- class: 'js-downvotes issuable-downvotes',
- icon: 'thumb-down',
- },
- {
- key: 'blocking-issues',
- visible: this.issuable.blocking_issues_count > 0,
- value: this.issuable.blocking_issues_count,
- title: __('Blocking issues'),
- dataTestId: 'blocking-issues',
- href: setUrlFragment(this.webUrl, 'related-issues'),
- icon: 'issue-block',
- },
- {
- key: 'comments-count',
- visible: !this.isJiraIssue,
- value: this.issuable.user_notes_count,
- title: __('Comments'),
- dataTestId: 'notes-count',
- href: setUrlFragment(this.webUrl, 'notes'),
- class: { 'no-comments': !this.issuable.user_notes_count, 'issuable-comments': true },
- icon: 'comments',
- },
- ];
- },
- healthStatus() {
- return convertToCamelCase(this.issuable.health_status);
- },
- openedMessage() {
- if (this.isJiraIssue) return this.$options.i18n.openedAgoJira;
- if (this.issuable.service_desk_reply_to) return this.$options.i18n.openedAgoServiceDesk;
- return this.$options.i18n.openedAgo;
- },
- },
- mounted() {
- // TODO: Refactor user popover to use its own component instead of
- // spawning event listeners on Vue-rendered elements.
- initUserPopovers([this.$refs.openedAgoByContainer.$el]);
- },
- methods: {
- issuableLink(params) {
- return mergeUrlParams(params, this.baseUrl);
- },
- isScoped({ name }) {
- return isScopedLabel({ title: name }) && this.scopedLabelsAvailable;
- },
- labelHref({ name }) {
- if (this.isJiraIssue) {
- return this.issuableLink({ 'labels[]': name });
- }
-
- return this.issuableLink({ 'label_name[]': name });
- },
- onSelect(ev) {
- this.$emit('select', {
- issuable: this.issuable,
- selected: ev.target.checked,
- });
- },
- issuableMetaComponent(href) {
- return href ? 'gl-link' : 'span';
- },
- },
-
- confidentialTooltipText: __('Confidential'),
-};
-</script>
-<template>
- <li
- :id="`issue_${issuable.id}`"
- class="issue"
- :class="{ today: issueCreatedToday, closed: isClosed }"
- :data-id="issuable.id"
- :data-labels="labelIdsString"
- :data-url="webUrl"
- data-qa-selector="issue_container"
- :data-qa-issue-title="issuable.title"
- >
- <div class="gl-display-flex">
- <!-- Bulk edit checkbox -->
- <div v-if="isBulkEditing" class="gl-mr-3">
- <input
- :id="`selected_issue_${issuable.id}`"
- :checked="selected"
- class="selected-issuable"
- type="checkbox"
- :data-id="issuable.id"
- @input="onSelect"
- />
- </div>
-
- <!-- Issuable info container -->
- <!-- Issuable main info -->
- <div class="gl-flex-grow-1">
- <div class="title">
- <span class="issue-title-text">
- <gl-icon
- v-if="issuable.confidential"
- v-gl-tooltip
- name="eye-slash"
- class="gl-vertical-align-text-bottom"
- :size="16"
- :title="$options.confidentialTooltipText"
- :aria-label="$options.confidentialTooltipText"
- />
- <gl-link
- :href="webUrl"
- :target="linkTarget"
- data-testid="issuable-title"
- data-qa-selector="issue_link"
- >
- {{ issuable.title }}
- <gl-icon
- v-if="isIssuableUrlExternal"
- name="external-link"
- class="gl-vertical-align-text-bottom gl-ml-2"
- />
- </gl-link>
- </span>
- <span
- v-if="issuable.has_tasks"
- class="gl-ml-2 task-status gl-display-none d-sm-inline-block"
- >{{ issuable.task_status }}</span
- >
- </div>
-
- <div class="issuable-info">
- <span class="js-ref-path gl-mr-4 mr-sm-0">
- <span
- v-if="isJiraIssue"
- v-safe-html="jiraLogo"
- class="svg-container logo-container"
- data-testid="jira-logo"
- ></span>
- {{ referencePath }}
- </span>
-
- <span data-testid="openedByMessage" class="gl-display-none d-sm-inline-block gl-mr-4">
- &middot;
- <gl-sprintf :message="openedMessage">
- <template #timeAgoString>
- <span>{{ issuableCreatedAt }}</span>
- </template>
- <template #user>
- <gl-link
- ref="openedAgoByContainer"
- v-bind="popoverDataAttrs"
- :href="issuableAuthor.web_url"
- :target="linkTarget"
- >{{ issuableAuthor.name }}</gl-link
- >
- </template>
- <template #email>
- <span>{{ issuable.service_desk_reply_to }}</span>
- </template>
- </gl-sprintf>
- </span>
-
- <gl-link
- v-if="issuable.milestone"
- v-gl-tooltip
- class="gl-display-none d-sm-inline-block gl-mr-4 js-milestone milestone"
- :href="milestoneLink"
- :title="milestoneTooltipText"
- >
- <gl-icon name="clock" class="s16 gl-vertical-align-text-bottom" />
- {{ issuable.milestone.title }}
- </gl-link>
-
- <span
- v-if="dueDate"
- v-gl-tooltip
- class="gl-display-none d-sm-inline-block gl-mr-4 js-due-date"
- :class="{ cred: isOverdue }"
- :title="__('Due date')"
- >
- <gl-icon name="calendar" />
- {{ dueDateWords }}
- </span>
-
- <span
- v-if="hasWeight"
- v-gl-tooltip
- :title="__('Weight')"
- class="gl-display-none d-sm-inline-block gl-mr-4"
- data-testid="weight"
- data-qa-selector="issuable_weight_content"
- >
- <gl-icon name="weight" class="align-text-bottom" />
- {{ issuable.weight }}
- </span>
-
- <issue-health-status
- v-if="issuable.health_status"
- :health-status="healthStatus"
- class="gl-mr-4 issuable-tag-valign"
- />
-
- <gl-label
- v-for="label in issuable.labels"
- :key="label.id"
- data-qa-selector="issuable-label"
- :target="labelHref(label)"
- :background-color="label.color"
- :description="label.description"
- :color="label.text_color"
- :title="label.name"
- :scoped="isScoped(label)"
- size="sm"
- class="gl-mr-2 issuable-tag-valign"
- >{{ label.name }}</gl-label
- >
- </div>
- </div>
-
- <!-- Issuable meta -->
- <div
- class="gl-flex-shrink-0 gl-display-flex gl-flex-direction-column align-items-end gl-justify-content-center"
- >
- <div class="controls gl-display-flex">
- <span v-if="isJiraIssue" data-testid="issuable-status">{{ issuable.status }}</span>
- <span v-else-if="isClosed" class="issuable-status">{{ __('CLOSED') }}</span>
-
- <issue-assignees
- :assignees="issuable.assignees"
- class="gl-align-items-center gl-display-flex gl-ml-3"
- :icon-size="16"
- img-css-classes="gl-mr-2!"
- :max-visible="4"
- />
-
- <template v-for="meta in issuableMeta">
- <span
- v-if="meta.visible"
- :key="meta.key"
- v-gl-tooltip
- class="gl-display-none gl-sm-display-flex gl-align-items-center gl-ml-3"
- :class="meta.class"
- :data-testid="meta.dataTestId"
- :title="meta.title"
- >
- <component :is="issuableMetaComponent(meta.href)" :href="meta.href">
- <gl-icon v-if="meta.icon" :name="meta.icon" />
- {{ meta.value }}
- </component>
- </span>
- </template>
- </div>
- <div v-gl-tooltip class="issuable-updated-at" :title="updatedDateString">
- {{ updatedDateAgo }}
- </div>
- </div>
- </div>
- </li>
-</template>
diff --git a/app/assets/javascripts/issues_list/components/issuables_list_app.vue b/app/assets/javascripts/issues_list/components/issuables_list_app.vue
deleted file mode 100644
index 71136bf0159..00000000000
--- a/app/assets/javascripts/issues_list/components/issuables_list_app.vue
+++ /dev/null
@@ -1,426 +0,0 @@
-<script>
-import {
- GlEmptyState,
- GlPagination,
- GlDeprecatedSkeletonLoading as GlSkeletonLoading,
- GlSafeHtmlDirective as SafeHtml,
-} from '@gitlab/ui';
-import { toNumber, omit } from 'lodash';
-import createFlash from '~/flash';
-import axios from '~/lib/utils/axios_utils';
-import { scrollToElement, historyPushState } from '~/lib/utils/common_utils';
-import { setUrlParams, queryToObject, getParameterByName } from '~/lib/utils/url_utility';
-import { __ } from '~/locale';
-import initManualOrdering from '~/issues/manual_ordering';
-import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
-import {
- sortOrderMap,
- availableSortOptionsJira,
- RELATIVE_POSITION,
- PAGE_SIZE,
- PAGE_SIZE_MANUAL,
- LOADING_LIST_ITEMS_LENGTH,
-} from '../constants';
-import issuableEventHub from '../eventhub';
-import { emptyStateHelper } from '../service_desk_helper';
-import Issuable from './issuable.vue';
-
-/**
- * @deprecated Use app/assets/javascripts/vue_shared/issuable/list/components/issuable_list_root.vue instead
- */
-export default {
- LOADING_LIST_ITEMS_LENGTH,
- directives: {
- SafeHtml,
- },
- components: {
- GlEmptyState,
- GlPagination,
- GlSkeletonLoading,
- Issuable,
- FilteredSearchBar,
- },
- props: {
- canBulkEdit: {
- type: Boolean,
- required: false,
- default: false,
- },
- emptyStateMeta: {
- type: Object,
- required: true,
- },
- endpoint: {
- type: String,
- required: true,
- },
- projectPath: {
- type: String,
- required: false,
- default: '',
- },
- sortKey: {
- type: String,
- required: false,
- default: '',
- },
- type: {
- type: String,
- required: false,
- default: '',
- },
- },
- data() {
- return {
- availableSortOptionsJira,
- filters: {},
- isBulkEditing: false,
- issuables: [],
- loading: false,
- page: getParameterByName('page') !== null ? toNumber(getParameterByName('page')) : 1,
- selection: {},
- totalItems: 0,
- };
- },
- computed: {
- allIssuablesSelected() {
- // WARNING: Because we are only keeping track of selected values
- // this works, we will need to rethink this if we start tracking
- // [id]: false for not selected values.
- return this.issuables.length === Object.keys(this.selection).length;
- },
- emptyState() {
- if (this.issuables.length) {
- return {}; // Empty state shouldn't be shown here
- }
-
- if (this.isServiceDesk) {
- return emptyStateHelper(this.emptyStateMeta);
- }
-
- if (this.hasFilters) {
- return {
- title: __('Sorry, your filter produced no results'),
- svgPath: this.emptyStateMeta.svgPath,
- description: __('To widen your search, change or remove filters above'),
- primaryLink: this.emptyStateMeta.createIssuePath,
- primaryText: __('New issue'),
- };
- }
-
- if (this.filters.state === 'opened') {
- return {
- title: __('There are no open issues'),
- svgPath: this.emptyStateMeta.svgPath,
- description: __('To keep this project going, create a new issue'),
- primaryLink: this.emptyStateMeta.createIssuePath,
- primaryText: __('New issue'),
- };
- } else if (this.filters.state === 'closed') {
- return {
- title: __('There are no closed issues'),
- svgPath: this.emptyStateMeta.svgPath,
- };
- }
-
- return {
- title: __('There are no issues to show'),
- svgPath: this.emptyStateMeta.svgPath,
- description: __(
- 'The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project.',
- ),
- };
- },
- hasFilters() {
- const ignored = ['utf8', 'state', 'scope', 'order_by', 'sort'];
- return Object.keys(omit(this.filters, ignored)).length > 0;
- },
- isManualOrdering() {
- return this.sortKey === RELATIVE_POSITION;
- },
- itemsPerPage() {
- return this.isManualOrdering ? PAGE_SIZE_MANUAL : PAGE_SIZE;
- },
- baseUrl() {
- return window.location.href.replace(/(\?.*)?(#.*)?$/, '');
- },
- paginationNext() {
- return this.page + 1;
- },
- paginationPrev() {
- return this.page - 1;
- },
- paginationProps() {
- const paginationProps = { value: this.page };
-
- if (this.totalItems) {
- return {
- ...paginationProps,
- perPage: this.itemsPerPage,
- totalItems: this.totalItems,
- };
- }
-
- return {
- ...paginationProps,
- prevPage: this.paginationPrev,
- nextPage: this.paginationNext,
- };
- },
- isServiceDesk() {
- return this.type === 'service_desk';
- },
- isJira() {
- return this.type === 'jira';
- },
- initialFilterValue() {
- const value = [];
- const { search } = this.getQueryObject();
-
- if (search) {
- value.push(search);
- }
- return value;
- },
- initialSortBy() {
- const { sort } = this.getQueryObject();
- return sort || 'created_desc';
- },
- },
- watch: {
- selection() {
- // We need to call nextTick here to wait for all of the boxes to be checked and rendered
- // before we query the dom in issuable_bulk_update_actions.js.
- this.$nextTick(() => {
- issuableEventHub.$emit('issuables:updateBulkEdit');
- });
- },
- issuables() {
- this.$nextTick(() => {
- initManualOrdering();
- });
- },
- },
- mounted() {
- if (this.canBulkEdit) {
- this.unsubscribeToggleBulkEdit = issuableEventHub.$on('issuables:toggleBulkEdit', (val) => {
- this.isBulkEditing = val;
- });
- }
- this.fetchIssuables();
- },
- beforeDestroy() {
- // eslint-disable-next-line @gitlab/no-global-event-off
- issuableEventHub.$off('issuables:toggleBulkEdit');
- },
- methods: {
- isSelected(issuableId) {
- return Boolean(this.selection[issuableId]);
- },
- setSelection(ids) {
- ids.forEach((id) => {
- this.select(id, true);
- });
- },
- clearSelection() {
- this.selection = {};
- },
- select(id, isSelect = true) {
- if (isSelect) {
- this.$set(this.selection, id, true);
- } else {
- this.$delete(this.selection, id);
- }
- },
- fetchIssuables(pageToFetch) {
- this.loading = true;
-
- this.clearSelection();
-
- this.setFilters();
-
- return axios
- .get(this.endpoint, {
- params: {
- ...this.filters,
-
- with_labels_details: true,
- page: pageToFetch || this.page,
- per_page: this.itemsPerPage,
- },
- })
- .then((response) => {
- this.loading = false;
- this.issuables = response.data;
- this.totalItems = Number(response.headers['x-total']);
- this.page = Number(response.headers['x-page']);
- })
- .catch(() => {
- this.loading = false;
- return createFlash({
- message: __('An error occurred while loading issues'),
- });
- });
- },
- getQueryObject() {
- return queryToObject(window.location.search, { gatherArrays: true });
- },
- onPaginate(newPage) {
- if (newPage === this.page) return;
-
- scrollToElement('#content-body');
-
- // NOTE: This allows for the params to be updated on pagination
- historyPushState(
- setUrlParams({ ...this.filters, page: newPage }, window.location.href, true),
- );
-
- this.fetchIssuables(newPage);
- },
- onSelectAll() {
- if (this.allIssuablesSelected) {
- this.selection = {};
- } else {
- this.setSelection(this.issuables.map(({ id }) => id));
- }
- },
- onSelectIssuable({ issuable, selected }) {
- if (!this.canBulkEdit) return;
-
- this.select(issuable.id, selected);
- },
- setFilters() {
- const {
- label_name: labels,
- milestone_title: milestoneTitle,
- 'not[label_name]': excludedLabels,
- 'not[milestone_title]': excludedMilestone,
- ...filters
- } = this.getQueryObject();
-
- // TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/227880
-
- if (milestoneTitle) {
- filters.milestone = milestoneTitle;
- }
- if (Array.isArray(labels)) {
- filters.labels = labels.join(',');
- }
- if (!filters.state) {
- filters.state = 'opened';
- }
-
- if (excludedLabels) {
- filters['not[labels]'] = excludedLabels;
- }
-
- if (excludedMilestone) {
- filters['not[milestone]'] = excludedMilestone;
- }
-
- Object.assign(filters, sortOrderMap[this.sortKey]);
-
- this.filters = filters;
- },
- refetchIssuables() {
- const ignored = ['utf8'];
- const params = omit(this.filters, ignored);
-
- historyPushState(setUrlParams(params, window.location.href, true, true));
- this.fetchIssuables();
- },
- handleFilter(filters) {
- const searchTokens = [];
-
- filters.forEach((filter) => {
- if (filter.type === 'filtered-search-term') {
- if (filter.value.data) {
- searchTokens.push(filter.value.data);
- }
- }
- });
-
- if (searchTokens.length) {
- this.filters.search = searchTokens.join(' ');
- }
- this.page = 1;
-
- this.refetchIssuables();
- },
- handleSort(sort) {
- this.filters.sort = sort;
- this.page = 1;
-
- this.refetchIssuables();
- },
- },
-};
-</script>
-
-<template>
- <div>
- <filtered-search-bar
- v-if="isJira"
- :namespace="projectPath"
- :search-input-placeholder="__('Search Jira issues')"
- :tokens="[]"
- :sort-options="availableSortOptionsJira"
- :initial-filter-value="initialFilterValue"
- :initial-sort-by="initialSortBy"
- class="row-content-block"
- @onFilter="handleFilter"
- @onSort="handleSort"
- />
- <ul v-if="loading" class="content-list">
- <li v-for="n in $options.LOADING_LIST_ITEMS_LENGTH" :key="n" class="issue gl-px-5! gl-py-5!">
- <gl-skeleton-loading />
- </li>
- </ul>
- <div v-else-if="issuables.length">
- <div v-if="isBulkEditing" class="issue px-3 py-3 border-bottom border-light">
- <input
- id="check-all-issues"
- type="checkbox"
- :checked="allIssuablesSelected"
- class="mr-2"
- @click="onSelectAll"
- />
- <strong>{{ __('Select all') }}</strong>
- </div>
- <ul
- class="content-list issuable-list issues-list"
- :class="{ 'manual-ordering': isManualOrdering }"
- >
- <issuable
- v-for="issuable in issuables"
- :key="issuable.id"
- class="pr-3"
- :class="{ 'user-can-drag': isManualOrdering }"
- :issuable="issuable"
- :is-bulk-editing="isBulkEditing"
- :selected="isSelected(issuable.id)"
- :base-url="baseUrl"
- @select="onSelectIssuable"
- />
- </ul>
- <div class="mt-3">
- <gl-pagination
- v-bind="paginationProps"
- class="gl-justify-content-center"
- @input="onPaginate"
- />
- </div>
- </div>
- <gl-empty-state
- v-else
- :title="emptyState.title"
- :svg-path="emptyState.svgPath"
- :primary-button-link="emptyState.primaryLink"
- :primary-button-text="emptyState.primaryText"
- >
- <template #description>
- <div v-safe-html="emptyState.description"></div>
- </template>
- </gl-empty-state>
- </div>
-</template>
diff --git a/app/assets/javascripts/issues_list/service_desk_helper.js b/app/assets/javascripts/issues_list/service_desk_helper.js
deleted file mode 100644
index 815f338f1a0..00000000000
--- a/app/assets/javascripts/issues_list/service_desk_helper.js
+++ /dev/null
@@ -1,111 +0,0 @@
-import { __, s__ } from '~/locale';
-
-/**
- * Generates empty state messages for Service Desk issues list.
- *
- * @param {emptyStateMeta} emptyStateMeta - Meta data used to generate empty state messages
- * @returns {Object} Object containing empty state messages generated using the meta data.
- */
-export function generateMessages(emptyStateMeta) {
- const {
- svgPath,
- serviceDeskHelpPage,
- serviceDeskAddress,
- editProjectPage,
- incomingEmailHelpPage,
- } = emptyStateMeta;
-
- const serviceDeskSupportedTitle = s__(
- 'ServiceDesk|Use Service Desk to connect with your users and offer customer support through email right inside GitLab',
- );
-
- const serviceDeskSupportedMessage = s__(
- 'ServiceDesk|Issues created from Service Desk emails will appear here. Each comment becomes part of the email conversation.',
- );
-
- const commonDescription = `
- <span>${serviceDeskSupportedMessage}</span>
- <a href="${serviceDeskHelpPage}">${__('Learn more.')}</a>`;
-
- return {
- serviceDeskEnabledAndCanEditProjectSettings: {
- title: serviceDeskSupportedTitle,
- svgPath,
- description: `<p>${s__('ServiceDesk|Your users can send emails to this address:')}
- <code>${serviceDeskAddress}</code>
- </p>
- ${commonDescription}`,
- },
- serviceDeskEnabledAndCannotEditProjectSettings: {
- title: serviceDeskSupportedTitle,
- svgPath,
- description: commonDescription,
- },
- serviceDeskDisabledAndCanEditProjectSettings: {
- title: serviceDeskSupportedTitle,
- svgPath,
- description: commonDescription,
- primaryLink: editProjectPage,
- primaryText: s__('ServiceDesk|Enable Service Desk'),
- },
- serviceDeskDisabledAndCannotEditProjectSettings: {
- title: serviceDeskSupportedTitle,
- svgPath,
- description: commonDescription,
- },
- serviceDeskIsNotSupported: {
- title: s__('ServiceDesk|Service Desk is not supported'),
- svgPath,
- description: s__(
- 'ServiceDesk|To enable Service Desk on this instance, an instance administrator must first set up incoming email.',
- ),
- primaryLink: incomingEmailHelpPage,
- primaryText: __('Learn more.'),
- },
- serviceDeskIsNotEnabled: {
- title: s__('ServiceDesk|Service Desk is not enabled'),
- svgPath,
- description: s__(
- 'ServiceDesk|For help setting up the Service Desk for your instance, please contact an administrator.',
- ),
- },
- };
-}
-
-/**
- * Returns the attributes used for gl-empty-state in the Service Desk issues list.
- *
- * @param {Object} emptyStateMeta - Meta data used to generate empty state messages
- * @returns {Object}
- */
-export function emptyStateHelper(emptyStateMeta) {
- const messages = generateMessages(emptyStateMeta);
-
- const { isServiceDeskSupported, canEditProjectSettings, isServiceDeskEnabled } = emptyStateMeta;
-
- if (isServiceDeskSupported) {
- if (isServiceDeskEnabled && canEditProjectSettings) {
- return messages.serviceDeskEnabledAndCanEditProjectSettings;
- }
-
- if (isServiceDeskEnabled && !canEditProjectSettings) {
- return messages.serviceDeskEnabledAndCannotEditProjectSettings;
- }
-
- // !isServiceDeskEnabled && canEditProjectSettings
- if (canEditProjectSettings) {
- return messages.serviceDeskDisabledAndCanEditProjectSettings;
- }
-
- // !isServiceDeskEnabled && !canEditProjectSettings
- return messages.serviceDeskDisabledAndCannotEditProjectSettings;
- }
-
- // !serviceDeskSupported && canEditProjectSettings
- if (canEditProjectSettings) {
- return messages.serviceDeskIsNotSupported;
- }
-
- // !serviceDeskSupported && !canEditProjectSettings
- return messages.serviceDeskIsNotEnabled;
-}
diff --git a/app/assets/javascripts/jira_import/utils/constants.js b/app/assets/javascripts/jira_import/utils/constants.js
index 178159be009..4230f85e443 100644
--- a/app/assets/javascripts/jira_import/utils/constants.js
+++ b/app/assets/javascripts/jira_import/utils/constants.js
@@ -1,5 +1,7 @@
import { __ } from '~/locale';
+export const JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY = 'jira-import-success-alert-hide-map';
+
export const debounceWait = 500;
export const dropdownLabel = __(
diff --git a/app/assets/javascripts/jira_import/utils/jira_import_utils.js b/app/assets/javascripts/jira_import/utils/jira_import_utils.js
index 4e3b5b2fbde..bd83dd4d219 100644
--- a/app/assets/javascripts/jira_import/utils/jira_import_utils.js
+++ b/app/assets/javascripts/jira_import/utils/jira_import_utils.js
@@ -1,5 +1,5 @@
import { last } from 'lodash';
-import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from '~/issues_list/constants';
+import { JIRA_IMPORT_SUCCESS_ALERT_HIDE_MAP_KEY } from './constants';
export const IMPORT_STATE = {
FAILED: 'failed',
diff --git a/app/assets/javascripts/jobs/bridge/app.vue b/app/assets/javascripts/jobs/bridge/app.vue
index 67c22712776..c639e49083b 100644
--- a/app/assets/javascripts/jobs/bridge/app.vue
+++ b/app/assets/javascripts/jobs/bridge/app.vue
@@ -1,20 +1,118 @@
<script>
+import { GlLoadingIcon } from '@gitlab/ui';
+import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { __, sprintf } from '~/locale';
+import CiHeader from '~/vue_shared/components/header_ci_component.vue';
+import getPipelineQuery from './graphql/queries/pipeline.query.graphql';
import BridgeEmptyState from './components/empty_state.vue';
import BridgeSidebar from './components/sidebar.vue';
+import { SIDEBAR_COLLAPSE_BREAKPOINTS } from './components/constants';
export default {
name: 'BridgePageApp',
components: {
BridgeEmptyState,
BridgeSidebar,
+ CiHeader,
+ GlLoadingIcon,
+ },
+ inject: ['buildId', 'projectFullPath', 'pipelineIid'],
+ apollo: {
+ pipeline: {
+ query: getPipelineQuery,
+ variables() {
+ return {
+ fullPath: this.projectFullPath,
+ iid: this.pipelineIid,
+ };
+ },
+ update(data) {
+ if (!data?.project?.pipeline) {
+ return null;
+ }
+
+ const { pipeline } = data.project;
+ const stages = pipeline?.stages.edges.map((edge) => edge.node) || [];
+ const jobs = stages.map((stage) => stage.jobs.nodes).flat();
+
+ return {
+ ...pipeline,
+ commit: {
+ ...pipeline.commit,
+ commit_path: pipeline.commit.webPath,
+ short_id: pipeline.commit.shortId,
+ },
+ id: getIdFromGraphQLId(pipeline.id),
+ jobs,
+ stages,
+ };
+ },
+ },
+ },
+ data() {
+ return {
+ isSidebarExpanded: true,
+ pipeline: {},
+ };
+ },
+ computed: {
+ bridgeJob() {
+ return (
+ this.pipeline.jobs?.filter(
+ (job) => getIdFromGraphQLId(job.id) === Number(this.buildId),
+ )[0] || {}
+ );
+ },
+ bridgeName() {
+ return sprintf(__('Job %{jobName}'), { jobName: this.bridgeJob.name });
+ },
+ isPipelineLoading() {
+ return this.$apollo.queries.pipeline.loading;
+ },
+ },
+ created() {
+ window.addEventListener('resize', this.onResize);
+ },
+ mounted() {
+ this.onResize();
+ },
+ methods: {
+ toggleSidebar() {
+ this.isSidebarExpanded = !this.isSidebarExpanded;
+ },
+ onResize() {
+ const breakpoint = bp.getBreakpointSize();
+ if (SIDEBAR_COLLAPSE_BREAKPOINTS.includes(breakpoint)) {
+ this.isSidebarExpanded = false;
+ } else if (!this.isSidebarExpanded) {
+ this.isSidebarExpanded = true;
+ }
+ },
},
};
</script>
<template>
<div>
- <!-- TODO: get job details and show CI header -->
- <!-- TODO: add downstream pipeline path -->
- <bridge-empty-state downstream-pipeline-path="#" />
- <bridge-sidebar />
+ <gl-loading-icon v-if="isPipelineLoading" size="lg" class="gl-mt-4" />
+ <div v-else>
+ <ci-header
+ class="gl-border-b-1 gl-border-b-solid gl-border-b-gray-100"
+ :status="bridgeJob.detailedStatus"
+ :time="bridgeJob.createdAt"
+ :user="pipeline.user"
+ :has-sidebar-button="true"
+ :item-name="bridgeName"
+ @clickedSidebarButton="toggleSidebar"
+ />
+ <bridge-empty-state :downstream-pipeline-path="bridgeJob.downstreamPipeline.path" />
+ <bridge-sidebar
+ v-if="isSidebarExpanded"
+ :bridge-job="bridgeJob"
+ :commit="pipeline.commit"
+ :is-sidebar-expanded="isSidebarExpanded"
+ @toggleSidebar="toggleSidebar"
+ />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/bridge/components/sidebar.vue b/app/assets/javascripts/jobs/bridge/components/sidebar.vue
index 68b767408f0..3ba07cf55d1 100644
--- a/app/assets/javascripts/jobs/bridge/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/bridge/components/sidebar.vue
@@ -1,14 +1,13 @@
<script>
import { GlButton, GlDropdown, GlDropdownItem } from '@gitlab/ui';
-import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import { __ } from '~/locale';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import { JOB_SIDEBAR } from '../../constants';
-import { SIDEBAR_COLLAPSE_BREAKPOINTS } from './constants';
+import CommitBlock from '../../components/commit_block.vue';
export default {
styles: {
- top: '75px',
width: '290px',
},
name: 'BridgeSidebar',
@@ -18,40 +17,47 @@ export default {
retryTriggerJob: __('Retry the trigger job'),
retryDownstreamPipeline: __('Retry the downstream pipeline'),
},
- borderTopClass: ['gl-border-t-solid', 'gl-border-t-1', 'gl-border-t-gray-100'],
+ sectionClass: ['gl-border-t-solid', 'gl-border-t-1', 'gl-border-t-gray-100', 'gl-py-5'],
components: {
+ CommitBlock,
GlButton,
GlDropdown,
GlDropdownItem,
TooltipOnTruncate,
},
- inject: {
- buildName: {
- type: String,
- default: '',
+ mixins: [glFeatureFlagsMixin()],
+ props: {
+ bridgeJob: {
+ type: Object,
+ required: true,
+ },
+ commit: {
+ type: Object,
+ required: true,
},
},
data() {
return {
- isSidebarExpanded: true,
+ topPosition: 0,
};
},
- created() {
- window.addEventListener('resize', this.onResize);
+ computed: {
+ rootStyle() {
+ return { ...this.$options.styles, top: `${this.topPosition}px` };
+ },
},
mounted() {
- this.onResize();
+ this.setTopPosition();
},
methods: {
- toggleSidebar() {
- this.isSidebarExpanded = !this.isSidebarExpanded;
+ onSidebarButtonClick() {
+ this.$emit('toggleSidebar');
},
- onResize() {
- const breakpoint = bp.getBreakpointSize();
- if (SIDEBAR_COLLAPSE_BREAKPOINTS.includes(breakpoint)) {
- this.isSidebarExpanded = false;
- } else if (!this.isSidebarExpanded) {
- this.isSidebarExpanded = true;
+ setTopPosition() {
+ const navbarEl = document.querySelector('.js-navbar');
+
+ if (navbarEl) {
+ this.topPosition = navbarEl.getBoundingClientRect().bottom;
}
},
},
@@ -60,19 +66,19 @@ export default {
<template>
<aside
class="gl-fixed gl-right-0 gl-px-5 gl-bg-gray-10 gl-h-full gl-border-l-solid gl-border-1 gl-border-gray-100 gl-z-index-200 gl-overflow-hidden"
- :style="this.$options.styles"
- :class="{
- 'gl-display-none': !isSidebarExpanded,
- }"
+ :style="rootStyle"
>
<div class="gl-py-5 gl-display-flex gl-align-items-center">
- <tooltip-on-truncate :title="buildName" truncate-target="child"
+ <tooltip-on-truncate :title="bridgeJob.name" truncate-target="child"
><h4 class="gl-mb-0 gl-mr-2 gl-text-truncate">
- {{ buildName }}
+ {{ bridgeJob.name }}
</h4>
</tooltip-on-truncate>
<!-- TODO: implement retry actions -->
- <div class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right">
+ <div
+ v-if="glFeatures.triggerJobRetryAction"
+ class="gl-flex-grow-1 gl-flex-shrink-0 gl-text-right"
+ >
<gl-dropdown
:text="$options.i18n.retryButton"
category="primary"
@@ -90,9 +96,10 @@ export default {
category="tertiary"
class="gl-md-display-none gl-ml-2"
icon="chevron-double-lg-right"
- @click="toggleSidebar"
+ @click="onSidebarButtonClick"
/>
</div>
- <!-- TODO: get job details and show commit block, stage dropdown, jobs list -->
+ <commit-block :commit="commit" :class="$options.sectionClass" />
+ <!-- TODO: show stage dropdown, jobs list -->
</aside>
</template>
diff --git a/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql b/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql
new file mode 100644
index 00000000000..338ca9f16c7
--- /dev/null
+++ b/app/assets/javascripts/jobs/bridge/graphql/queries/pipeline.query.graphql
@@ -0,0 +1,70 @@
+query getPipelineData($fullPath: ID!, $iid: ID!) {
+ project(fullPath: $fullPath) {
+ id
+ pipeline(iid: $iid) {
+ id
+ iid
+ path
+ sha
+ ref
+ refPath
+ commit {
+ id
+ shortId
+ title
+ webPath
+ }
+ detailedStatus {
+ id
+ icon
+ group
+ }
+ stages {
+ edges {
+ node {
+ id
+ name
+ jobs {
+ nodes {
+ id
+ createdAt
+ name
+ scheduledAt
+ startedAt
+ status
+ triggered
+ detailedStatus {
+ id
+ detailsPath
+ icon
+ group
+ text
+ tooltip
+ }
+ downstreamPipeline {
+ id
+ path
+ }
+ stage {
+ id
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ user {
+ id
+ avatarUrl
+ name
+ username
+ webPath
+ webUrl
+ status {
+ message
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/jobs/components/job_log_controllers.vue b/app/assets/javascripts/jobs/components/job_log_controllers.vue
index 97141a27a5e..8e35fd91481 100644
--- a/app/assets/javascripts/jobs/components/job_log_controllers.vue
+++ b/app/assets/javascripts/jobs/components/job_log_controllers.vue
@@ -107,6 +107,7 @@ export default {
:data-confirm="__('Are you sure you want to erase this build?')"
class="gl-ml-3"
data-testid="job-log-erase-link"
+ data-confirm-btn-variant="danger"
data-method="post"
icon="remove"
/>
diff --git a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
index 5451cd21c14..5428f657252 100644
--- a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
+++ b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue
@@ -1,5 +1,6 @@
<script>
import { mapState } from 'vuex';
+import { GlBadge } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
import { __, sprintf } from '~/locale';
@@ -10,6 +11,7 @@ export default {
name: 'JobSidebarDetailsContainer',
components: {
DetailRow,
+ GlBadge,
},
mixins: [timeagoMixin],
computed: {
@@ -100,12 +102,7 @@ export default {
<p v-if="hasTags" class="build-detail-row" data-testid="job-tags">
<span class="font-weight-bold">{{ __('Tags:') }}</span>
- <span
- v-for="(tag, i) in job.tags"
- :key="i"
- class="badge badge-pill badge-primary gl-badge sm"
- >{{ tag }}</span
- >
+ <gl-badge v-for="(tag, i) in job.tags" :key="i" variant="info">{{ tag }}</gl-badge>
</p>
</div>
</template>
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
index e078a6c2319..6e958ea1842 100644
--- a/app/assets/javascripts/jobs/index.js
+++ b/app/assets/javascripts/jobs/index.js
@@ -54,7 +54,13 @@ const initializeJobPage = (element) => {
};
const initializeBridgePage = (el) => {
- const { buildName, emptyStateIllustrationPath } = el.dataset;
+ const {
+ buildId,
+ downstreamPipelinePath,
+ emptyStateIllustrationPath,
+ pipelineIid,
+ projectFullPath,
+ } = el.dataset;
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
@@ -65,8 +71,11 @@ const initializeBridgePage = (el) => {
el,
apolloProvider,
provide: {
- buildName,
+ buildId,
+ downstreamPipelinePath,
emptyStateIllustrationPath,
+ pipelineIid,
+ projectFullPath,
},
render(h) {
return h(BridgeApp);
diff --git a/app/assets/javascripts/labels/components/delete_label_modal.vue b/app/assets/javascripts/labels/components/delete_label_modal.vue
index 1ff0938d086..2be404de1e1 100644
--- a/app/assets/javascripts/labels/components/delete_label_modal.vue
+++ b/app/assets/javascripts/labels/components/delete_label_modal.vue
@@ -56,6 +56,7 @@ export default {
</gl-sprintf>
</template>
<gl-sprintf
+ v-if="subjectName"
:message="
__(
`%{strongStart}${labelName}%{strongEnd} will be permanently deleted from ${subjectName}. This cannot be undone.`,
@@ -66,6 +67,18 @@ export default {
<strong>{{ content }}</strong>
</template>
</gl-sprintf>
+ <gl-sprintf
+ v-else
+ :message="
+ __(
+ `%{strongStart}${labelName}%{strongEnd} will be permanently deleted. This cannot be undone.`,
+ )
+ "
+ >
+ <template #strong="{ content }">
+ <strong>{{ content }}</strong>
+ </template>
+ </gl-sprintf>
<template #modal-footer>
<gl-button category="secondary" @click="closeModal">{{ __('Cancel') }}</gl-button>
<gl-button
diff --git a/app/assets/javascripts/labels/create_label_dropdown.js b/app/assets/javascripts/labels/create_label_dropdown.js
index 8c166158a44..033ca9dd3ea 100644
--- a/app/assets/javascripts/labels/create_label_dropdown.js
+++ b/app/assets/javascripts/labels/create_label_dropdown.js
@@ -16,6 +16,7 @@ export default class CreateLabelDropdown {
this.$colorPreview = $('.js-dropdown-label-color-preview', this.$el);
this.$addList = $('.js-add-list', this.$el);
this.$newLabelError = $('.js-label-error', this.$el);
+ this.$newLabelErrorContent = $('.gl-alert-content', this.$newLabelError);
this.$newLabelCreateButton = $('.js-new-label-btn', this.$el);
this.$colorSuggestions = $('.suggest-colors-dropdown a', this.$el);
@@ -119,7 +120,8 @@ export default class CreateLabelDropdown {
.join('<br/>');
}
- this.$newLabelError.html(errors).show();
+ this.$newLabelErrorContent.html(errors);
+ this.$newLabelError.show();
} else {
const addNewList = this.$addList.is(':checked');
this.$dropdownBack.trigger('click');
diff --git a/app/assets/javascripts/labels/index.js b/app/assets/javascripts/labels/index.js
index 22a9c0a89c0..e87ad8d9a06 100644
--- a/app/assets/javascripts/labels/index.js
+++ b/app/assets/javascripts/labels/index.js
@@ -26,7 +26,7 @@ export function initLabels() {
if ($('.prioritized-labels').length) {
new LabelManager(); // eslint-disable-line no-new
}
- $('.label-subscription').each((i, el) => {
+ $('.js-label-subscription').each((i, el) => {
const $el = $(el);
if ($el.find('.dropdown-group-label').length) {
diff --git a/app/assets/javascripts/lib/mermaid.js b/app/assets/javascripts/lib/mermaid.js
new file mode 100644
index 00000000000..d621c9ddf9e
--- /dev/null
+++ b/app/assets/javascripts/lib/mermaid.js
@@ -0,0 +1,61 @@
+import mermaid from 'mermaid';
+import { getParameterByName } from '~/lib/utils/url_utility';
+
+const setIframeRenderedSize = (h, w) => {
+ const { origin } = window.location;
+ window.parent.postMessage({ h, w }, origin);
+};
+
+const drawDiagram = (source) => {
+ const element = document.getElementById('app');
+ const insertSvg = (svgCode) => {
+ element.innerHTML = svgCode;
+
+ const height = parseInt(element.firstElementChild.getAttribute('height'), 10);
+ const width = parseInt(element.firstElementChild.style.maxWidth, 10);
+ setIframeRenderedSize(height, width);
+ };
+ mermaid.mermaidAPI.render('mermaid', source, insertSvg);
+};
+
+const darkModeEnabled = () => getParameterByName('darkMode') === 'true';
+
+const initMermaid = () => {
+ let theme = 'neutral';
+
+ if (darkModeEnabled()) {
+ theme = 'dark';
+ }
+
+ mermaid.initialize({
+ // mermaid core options
+ mermaid: {
+ startOnLoad: false,
+ },
+ // mermaidAPI options
+ theme,
+ flowchart: {
+ useMaxWidth: true,
+ htmlLabels: true,
+ },
+ secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'htmlLabels'],
+ securityLevel: 'strict',
+ });
+};
+
+const addListener = () => {
+ window.addEventListener(
+ 'message',
+ (event) => {
+ if (event.origin !== window.location.origin) {
+ return;
+ }
+ drawDiagram(event.data);
+ },
+ false,
+ );
+};
+
+addListener();
+initMermaid();
+export default {};
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 7235b38848c..eff00dff7a7 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -181,6 +181,7 @@ export const contentTop = () => {
},
() => getOuterHeight('.merge-request-tabs'),
() => getOuterHeight('.js-diff-files-changed'),
+ () => getOuterHeight('.issue-sticky-header.gl-fixed'),
({ desktop }) => {
const diffsTabIsActive = window.mrTabs?.currentAction === 'diffs';
let size;
@@ -746,3 +747,12 @@ export const isLoggedIn = () => Boolean(window.gon?.current_user_id);
*/
export const convertArrayOfObjectsToCamelCase = (array) =>
array.map((o) => convertObjectPropsToCamelCase(o));
+
+export const getFirstPropertyValue = (data) => {
+ if (!data) return null;
+
+ const [key] = Object.keys(data);
+ if (!key) return null;
+
+ return data[key];
+};
diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js
index a108b02bcbf..36c6545164e 100644
--- a/app/assets/javascripts/lib/utils/constants.js
+++ b/app/assets/javascripts/lib/utils/constants.js
@@ -17,7 +17,6 @@ export const BV_HIDE_MODAL = 'bv::hide::modal';
export const BV_HIDE_TOOLTIP = 'bv::hide::tooltip';
export const BV_DROPDOWN_SHOW = 'bv::dropdown::show';
export const BV_DROPDOWN_HIDE = 'bv::dropdown::hide';
-export const BV_COLLAPSE_STATE = 'bv::collapse::state';
export const DEFAULT_TH_CLASSES =
'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!';
diff --git a/app/assets/javascripts/lib/utils/resize_observer.js b/app/assets/javascripts/lib/utils/resize_observer.js
new file mode 100644
index 00000000000..e72c6fe1679
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/resize_observer.js
@@ -0,0 +1,58 @@
+import { contentTop } from './common_utils';
+
+const interactionEvents = ['mousedown', 'touchstart', 'keydown', 'wheel'];
+
+export function createResizeObserver() {
+ return new ResizeObserver((entries) => {
+ entries.forEach((entry) => {
+ entry.target.dispatchEvent(new CustomEvent(`ResizeUpdate`, { detail: { entry } }));
+ });
+ });
+}
+
+// watches for change in size of a container element (e.g. for lazy-loaded images)
+// and scroll the target element to the top of the content area
+// stop watching after any user input. So if user opens sidebar or manually
+// scrolls the page we don't hijack their scroll position
+export function scrollToTargetOnResize({
+ target = window.location.hash,
+ container = '#content-body',
+} = {}) {
+ if (!target) return null;
+
+ const ro = createResizeObserver();
+ const containerEl = document.querySelector(container);
+ let interactionListenersAdded = false;
+
+ function keepTargetAtTop() {
+ const anchorEl = document.querySelector(target);
+
+ if (!anchorEl) return;
+
+ const anchorTop = anchorEl.getBoundingClientRect().top + window.scrollY;
+ const top = anchorTop - contentTop();
+ document.documentElement.scrollTo({
+ top,
+ });
+
+ if (!interactionListenersAdded) {
+ interactionEvents.forEach((event) =>
+ // eslint-disable-next-line no-use-before-define
+ document.addEventListener(event, removeListeners),
+ );
+ interactionListenersAdded = true;
+ }
+ }
+
+ function removeListeners() {
+ interactionEvents.forEach((event) => document.removeEventListener(event, removeListeners));
+
+ ro.unobserve(containerEl);
+ containerEl.removeEventListener('ResizeUpdate', keepTargetAtTop);
+ }
+
+ containerEl.addEventListener('ResizeUpdate', keepTargetAtTop);
+
+ ro.observe(containerEl);
+ return ro;
+}
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index e221a54d9c6..376134afef0 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -101,6 +101,21 @@ function deferredInitialisation() {
initFeatureHighlight();
initCopyCodeButton();
+ const helpToggle = document.querySelector('.header-help-dropdown-toggle');
+ if (helpToggle) {
+ helpToggle.addEventListener(
+ 'click',
+ () => {
+ import(/* webpackChunkName: 'versionCheck' */ './gitlab_version_check')
+ .then(({ default: initGitlabVersionCheck }) => {
+ initGitlabVersionCheck();
+ })
+ .catch(() => {});
+ },
+ { once: true },
+ );
+ }
+
const search = document.querySelector('#search');
if (search) {
search.addEventListener(
diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue
index de733ae75df..e09d16cf680 100644
--- a/app/assets/javascripts/members/components/table/members_table.vue
+++ b/app/assets/javascripts/members/components/table/members_table.vue
@@ -6,6 +6,7 @@ import { canOverride, canRemove, canResend, canUpdate } from 'ee_else_ce/members
import { mergeUrlParams } from '~/lib/utils/url_utility';
import initUserPopovers from '~/user_popovers';
import {
+ FIELD_KEY_ACTIONS,
FIELDS,
ACTIVE_TAB_QUERY_PARAM_NAME,
TAB_QUERY_PARAM_VALUES,
@@ -63,17 +64,10 @@ export default {
return state[this.namespace].pagination;
},
}),
- filteredFields() {
+ filteredAndModifiedFields() {
return FIELDS.filter(
(field) => this.tableFields.includes(field.key) && this.showField(field),
- ).map((field) => {
- const tdClassFunction = this[field.tdClassFunction];
-
- return {
- ...field,
- ...(tdClassFunction && { tdClass: tdClassFunction }),
- };
- });
+ ).map(this.modifyFieldDefinition);
},
userIsLoggedIn() {
return this.currentUserId !== null;
@@ -100,20 +94,29 @@ export default {
);
},
showField(field) {
- if (!Object.prototype.hasOwnProperty.call(field, 'showFunction')) {
- return true;
- }
+ switch (field.key) {
+ case FIELD_KEY_ACTIONS:
+ if (!this.userIsLoggedIn) {
+ return false;
+ }
- return this[field.showFunction]();
+ return this.members.some((member) => this.hasActionButtons(member));
+ default:
+ return true;
+ }
},
- showActionsField() {
- if (!this.userIsLoggedIn) {
- return false;
+ modifyFieldDefinition(field) {
+ switch (field.key) {
+ case FIELD_KEY_ACTIONS:
+ return {
+ ...field,
+ tdClass: this.actionsFieldTdClass,
+ };
+ default:
+ return field;
}
-
- return this.members.some((member) => this.hasActionButtons(member));
},
- tdClassActions(value, key, member) {
+ actionsFieldTdClass(value, key, member) {
if (this.hasActionButtons(member)) {
return 'col-actions';
}
@@ -219,7 +222,7 @@ export default {
data-testid="members-table"
head-variant="white"
stacked="lg"
- :fields="filteredFields"
+ :fields="filteredAndModifiedFields"
:items="members"
primary-key="id"
thead-class="border-bottom"
diff --git a/app/assets/javascripts/members/constants.js b/app/assets/javascripts/members/constants.js
index f5ca881ab0d..62241eaed04 100644
--- a/app/assets/javascripts/members/constants.js
+++ b/app/assets/javascripts/members/constants.js
@@ -1,8 +1,18 @@
import { __ } from '~/locale';
+export const FIELD_KEY_ACCOUNT = 'account';
+export const FIELD_KEY_SOURCE = 'source';
+export const FIELD_KEY_GRANTED = 'granted';
+export const FIELD_KEY_INVITED = 'invited';
+export const FIELD_KEY_REQUESTED = 'requested';
+export const FIELD_KEY_MAX_ROLE = 'maxRole';
+export const FIELD_KEY_EXPIRATION = 'expiration';
+export const FIELD_KEY_LAST_SIGN_IN = 'lastSignIn';
+export const FIELD_KEY_ACTIONS = 'actions';
+
export const FIELDS = [
{
- key: 'account',
+ key: FIELD_KEY_ACCOUNT,
label: __('Account'),
sort: {
asc: 'name_asc',
@@ -10,13 +20,13 @@ export const FIELDS = [
},
},
{
- key: 'source',
+ key: FIELD_KEY_SOURCE,
label: __('Source'),
thClass: 'col-meta',
tdClass: 'col-meta',
},
{
- key: 'granted',
+ key: FIELD_KEY_GRANTED,
label: __('Access granted'),
thClass: 'col-meta',
tdClass: 'col-meta',
@@ -26,19 +36,19 @@ export const FIELDS = [
},
},
{
- key: 'invited',
+ key: FIELD_KEY_INVITED,
label: __('Invited'),
thClass: 'col-meta',
tdClass: 'col-meta',
},
{
- key: 'requested',
+ key: FIELD_KEY_REQUESTED,
label: __('Requested'),
thClass: 'col-meta',
tdClass: 'col-meta',
},
{
- key: 'maxRole',
+ key: FIELD_KEY_MAX_ROLE,
label: __('Max role'),
thClass: 'col-max-role',
tdClass: 'col-max-role',
@@ -48,13 +58,13 @@ export const FIELDS = [
},
},
{
- key: 'expiration',
+ key: FIELD_KEY_EXPIRATION,
label: __('Expiration'),
thClass: 'col-expiration',
tdClass: 'col-expiration',
},
{
- key: 'lastSignIn',
+ key: FIELD_KEY_LAST_SIGN_IN,
label: __('Last sign-in'),
sort: {
asc: 'recent_sign_in',
@@ -62,10 +72,8 @@ export const FIELDS = [
},
},
{
- key: 'actions',
+ key: FIELD_KEY_ACTIONS,
thClass: 'col-actions',
- showFunction: 'showActionsField',
- tdClassFunction: 'tdClassActions',
},
];
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue
index d988ad8d8ca..29c181f04fb 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/details_header.vue
@@ -143,6 +143,7 @@ export default {
</template>
<template #right-actions>
<gl-dropdown
+ v-if="!deleteButtonDisabled"
icon="ellipsis_v"
text="More actions"
:text-sr-only="true"
@@ -150,11 +151,7 @@ export default {
no-caret
right
>
- <gl-dropdown-item
- variant="danger"
- :disabled="deleteButtonDisabled"
- @click="$emit('delete')"
- >
+ <gl-dropdown-item variant="danger" @click="$emit('delete')">
{{ __('Delete image repository') }}
</gl-dropdown-item>
</gl-dropdown>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue
deleted file mode 100644
index a16d95a6b30..00000000000
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/empty_state.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-<script>
-import { GlEmptyState } from '@gitlab/ui';
-import {
- NO_TAGS_TITLE,
- NO_TAGS_MESSAGE,
- MISSING_OR_DELETED_IMAGE_TITLE,
- MISSING_OR_DELETED_IMAGE_MESSAGE,
-} from '../../constants/index';
-
-export default {
- components: {
- GlEmptyState,
- },
- props: {
- noContainersImage: {
- type: String,
- required: false,
- default: '',
- },
- isEmptyImage: {
- type: Boolean,
- default: false,
- required: false,
- },
- },
- computed: {
- title() {
- return this.isEmptyImage ? MISSING_OR_DELETED_IMAGE_TITLE : NO_TAGS_TITLE;
- },
- description() {
- return this.isEmptyImage ? MISSING_OR_DELETED_IMAGE_MESSAGE : NO_TAGS_MESSAGE;
- },
- },
-};
-</script>
-
-<template>
- <gl-empty-state
- :title="title"
- :svg-path="noContainersImage"
- :description="description"
- class="gl-mx-auto gl-my-0"
- />
-</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
index 2d32295b537..4fda4058711 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list.vue
@@ -1,28 +1,38 @@
<script>
+import { GlEmptyState } from '@gitlab/ui';
import createFlash from '~/flash';
import { n__ } from '~/locale';
import { joinPaths } from '~/lib/utils/url_utility';
import RegistryList from '~/packages_and_registries/shared/components/registry_list.vue';
+
+import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
+import { FILTERED_SEARCH_TERM } from '~/packages_and_registries/shared/constants';
import {
REMOVE_TAGS_BUTTON_TITLE,
TAGS_LIST_TITLE,
GRAPHQL_PAGE_SIZE,
FETCH_IMAGES_LIST_ERROR_MESSAGE,
+ NAME_SORT_FIELD,
+ NO_TAGS_TITLE,
+ NO_TAGS_MESSAGE,
+ NO_TAGS_MATCHING_FILTERS_TITLE,
+ NO_TAGS_MATCHING_FILTERS_DESCRIPTION,
} from '../../constants/index';
import getContainerRepositoryTagsQuery from '../../graphql/queries/get_container_repository_tags.query.graphql';
-import EmptyState from './empty_state.vue';
import TagsListRow from './tags_list_row.vue';
import TagsLoader from './tags_loader.vue';
export default {
name: 'TagsList',
components: {
+ GlEmptyState,
TagsListRow,
- EmptyState,
TagsLoader,
RegistryList,
+ PersistedSearch,
},
inject: ['config'],
+
props: {
id: {
type: [Number, String],
@@ -44,6 +54,7 @@ export default {
required: false,
},
},
+ searchConfig: { NAME_SORT_FIELD },
i18n: {
REMOVE_TAGS_BUTTON_TITLE,
TAGS_LIST_TITLE,
@@ -51,6 +62,9 @@ export default {
apollo: {
containerRepository: {
query: getContainerRepositoryTagsQuery,
+ skip() {
+ return !this.sort;
+ },
variables() {
return this.queryVariables;
},
@@ -62,6 +76,8 @@ export default {
data() {
return {
containerRepository: {},
+ filters: {},
+ sort: null,
};
},
computed: {
@@ -78,6 +94,8 @@ export default {
return {
id: joinPaths(this.config.gidPrefix, `${this.id}`),
first: GRAPHQL_PAGE_SIZE,
+ name: this.filters?.name,
+ sort: this.sort,
};
},
showMultiDeleteButton() {
@@ -87,7 +105,16 @@ export default {
return this.tags.length === 0;
},
isLoading() {
- return this.isImageLoading || this.$apollo.queries.containerRepository.loading;
+ return this.isImageLoading || this.$apollo.queries.containerRepository.loading || !this.sort;
+ },
+ hasFilters() {
+ return this.filters?.name;
+ },
+ emptyStateTitle() {
+ return this.hasFilters ? NO_TAGS_MATCHING_FILTERS_TITLE : NO_TAGS_TITLE;
+ },
+ emptyStateDescription() {
+ return this.hasFilters ? NO_TAGS_MATCHING_FILTERS_DESCRIPTION : NO_TAGS_MESSAGE;
},
},
methods: {
@@ -114,15 +141,47 @@ export default {
},
});
},
+ handleSearchUpdate({ sort, filters }) {
+ this.sort = sort;
+
+ const parsed = {
+ name: '',
+ };
+
+ // This takes in account the fact that we will be adding more filters types
+ // this is why is an object and not an array or a simple string
+ this.filters = filters.reduce((acc, filter) => {
+ if (filter.type === FILTERED_SEARCH_TERM) {
+ return {
+ ...acc,
+ name: `${acc.name} ${filter.value.data}`.trim(),
+ };
+ }
+ return acc;
+ }, parsed);
+ },
},
};
</script>
<template>
<div>
+ <persisted-search
+ class="gl-mb-5"
+ :sortable-fields="[$options.searchConfig.NAME_SORT_FIELD]"
+ :default-order="$options.searchConfig.NAME_SORT_FIELD.orderBy"
+ default-sort="asc"
+ @update="handleSearchUpdate"
+ />
<tags-loader v-if="isLoading" />
<template v-else>
- <empty-state v-if="hasNoTags" :no-containers-image="config.noContainersImage" />
+ <gl-empty-state
+ v-if="hasNoTags"
+ :title="emptyStateTitle"
+ :svg-path="config.noContainersImage"
+ :description="emptyStateDescription"
+ class="gl-mx-auto gl-my-0"
+ />
<template v-else>
<registry-list
:title="listTitle"
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
index 0556fd298aa..15d92ab0ef7 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/details_page/tags_list_row.vue
@@ -107,11 +107,8 @@ export default {
isInvalidTag() {
return !this.tag.digest;
},
- isCheckboxDisabled() {
- return this.isInvalidTag || this.disabled;
- },
isDeleteDisabled() {
- return this.isInvalidTag || this.disabled || !this.tag.canDelete;
+ return this.disabled || !this.tag.canDelete;
},
},
};
@@ -122,7 +119,7 @@ export default {
<template #left-action>
<gl-form-checkbox
v-if="tag.canDelete"
- :disabled="isCheckboxDisabled"
+ :disabled="disabled"
class="gl-m-0"
:checked="selected"
@change="$emit('select')"
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/common.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/common.js
index f7beec2c935..17adaec7a7d 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/common.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/common.js
@@ -2,3 +2,5 @@ import { s__, __ } from '~/locale';
export const ROOT_IMAGE_TEXT = s__('ContainerRegistry|Root image');
export const MORE_ACTIONS_TEXT = __('More actions');
+
+export const NAME_SORT_FIELD = { orderBy: 'NAME', label: __('Name') };
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js
index 19e1a75fb2f..8b8769a884d 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/details.js
@@ -116,6 +116,13 @@ export const ROOT_IMAGE_TOOLTIP = s__(
'ContainerRegistry|Image repository with no name located at the project URL.',
);
+export const NO_TAGS_MATCHING_FILTERS_TITLE = s__(
+ 'ContainerRegistry|The filter returned no results',
+);
+export const NO_TAGS_MATCHING_FILTERS_DESCRIPTION = s__(
+ 'ContainerRegistry|Please try different search criteria',
+);
+
// Parameters
export const DEFAULT_PAGE = 1;
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
index d21a154d1b8..7fa950ccfd0 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/constants/list.js
@@ -1,4 +1,5 @@
import { s__, __ } from '~/locale';
+import { NAME_SORT_FIELD } from './common';
// Translations strings
@@ -49,5 +50,5 @@ export const GRAPHQL_PAGE_SIZE = 10;
export const SORT_FIELDS = [
{ orderBy: 'UPDATED', label: __('Updated') },
{ orderBy: 'CREATED', label: __('Created') },
- { orderBy: 'NAME', label: __('Name') },
+ NAME_SORT_FIELD,
];
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
index 502382010f9..d753d33a02c 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/graphql/queries/get_container_repository_tags.query.graphql
@@ -6,11 +6,13 @@ query getContainerRepositoryTags(
$last: Int
$after: String
$before: String
+ $name: String
+ $sort: ContainerRepositoryTagSort
) {
containerRepository(id: $id) {
id
tagsCount
- tags(after: $after, before: $before, first: $first, last: $last) {
+ tags(after: $after, before: $before, first: $first, last: $last, name: $name, sort: $sort) {
nodes {
digest
location
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js
index 246a6768593..ca5bd8d6964 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/index.js
@@ -3,7 +3,8 @@ import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
import PerformancePlugin from '~/performance/vue_performance_plugin';
import Translate from '~/vue_shared/translate';
-import RegistryBreadcrumb from './components/registry_breadcrumb.vue';
+import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
+import { renderBreadcrumb } from '~/packages_and_registries/shared/utils';
import { apolloProvider } from './graphql/index';
import RegistryExplorer from './pages/index.vue';
import createRouter from './router';
@@ -84,38 +85,8 @@ export default () => {
},
});
- const attachBreadcrumb = () => {
- const breadCrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li');
- const breadCrumbEl = breadCrumbEls[breadCrumbEls.length - 1];
- const crumbs = [breadCrumbEl.querySelector('h2')];
- const nestedBreadcrumbEl = document.createElement('div');
- breadCrumbEl.replaceChild(nestedBreadcrumbEl, breadCrumbEl.querySelector('h2'));
- return new Vue({
- el: nestedBreadcrumbEl,
- router,
- apolloProvider,
- components: {
- RegistryBreadcrumb,
- },
- render(createElement) {
- // FIXME(@tnir): this is a workaround until the MR gets merged:
- // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48115
- const parentEl = breadCrumbEl.parentElement.parentElement;
- if (parentEl) {
- parentEl.classList.remove('breadcrumbs-container');
- parentEl.classList.add('gl-display-flex');
- parentEl.classList.add('w-100');
- }
- // End of FIXME(@tnir)
- return createElement('registry-breadcrumb', {
- class: breadCrumbEl.className,
- props: {
- crumbs,
- },
- });
- },
- });
+ return {
+ attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb),
+ attachMainComponent,
};
-
- return { attachBreadcrumb, attachMainComponent };
};
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue
index bc6e3091f0e..bb687ffdb89 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/pages/details.vue
@@ -1,5 +1,5 @@
<script>
-import { GlResizeObserverDirective } from '@gitlab/ui';
+import { GlResizeObserverDirective, GlEmptyState } from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
@@ -9,7 +9,6 @@ import DeleteImage from '../components/delete_image.vue';
import DeleteAlert from '../components/details_page/delete_alert.vue';
import DeleteModal from '../components/details_page/delete_modal.vue';
import DetailsHeader from '../components/details_page/details_header.vue';
-import EmptyState from '../components/details_page/empty_state.vue';
import PartialCleanupAlert from '../components/details_page/partial_cleanup_alert.vue';
import StatusAlert from '../components/details_page/status_alert.vue';
import TagsList from '../components/details_page/tags_list.vue';
@@ -26,6 +25,8 @@ import {
MISSING_OR_DELETED_IMAGE_BREADCRUMB,
ROOT_IMAGE_TEXT,
GRAPHQL_PAGE_SIZE,
+ MISSING_OR_DELETED_IMAGE_TITLE,
+ MISSING_OR_DELETED_IMAGE_MESSAGE,
} from '../constants/index';
import deleteContainerRepositoryTagsMutation from '../graphql/mutations/delete_container_repository_tags.mutation.graphql';
import getContainerRepositoryDetailsQuery from '../graphql/queries/get_container_repository_details.query.graphql';
@@ -34,13 +35,13 @@ import getContainerRepositoryTagsQuery from '../graphql/queries/get_container_re
export default {
name: 'RegistryDetailsPage',
components: {
+ GlEmptyState,
DeleteAlert,
PartialCleanupAlert,
DetailsHeader,
DeleteModal,
TagsList,
TagsLoader,
- EmptyState,
StatusAlert,
DeleteImage,
},
@@ -49,6 +50,10 @@ export default {
},
mixins: [Tracking.mixin()],
inject: ['breadCrumbState', 'config'],
+ i18n: {
+ MISSING_OR_DELETED_IMAGE_TITLE,
+ MISSING_OR_DELETED_IMAGE_MESSAGE,
+ },
apollo: {
containerRepository: {
query: getContainerRepositoryDetailsQuery,
@@ -230,6 +235,12 @@ export default {
@cancel="track('cancel_delete')"
/>
</template>
- <empty-state v-else is-empty-image :no-containers-image="config.noContainersImage" />
+ <gl-empty-state
+ v-else
+ :title="$options.i18n.MISSING_OR_DELETED_IMAGE_TITLE"
+ :description="$options.i18n.MISSING_OR_DELETED_IMAGE_MESSAGE"
+ :svg-path="config.noContainersImage"
+ class="gl-mx-auto gl-my-0"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue
index cc629ae394c..a482c29bf50 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/composer_installation.vue
@@ -6,6 +6,7 @@ import {
TRACKING_ACTION_COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND,
TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
+ COMPOSER_HELP_PATH,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
@@ -17,7 +18,7 @@ export default {
GlLink,
GlSprintf,
},
- inject: ['composerHelpPath', 'composerConfigRepositoryName', 'composerPath', 'groupListUrl'],
+ inject: ['groupListUrl'],
props: {
packageEntity: {
type: Object,
@@ -27,7 +28,7 @@ export default {
computed: {
composerRegistryInclude() {
// eslint-disable-next-line @gitlab/require-i18n-strings
- return `composer config repositories.${this.composerConfigRepositoryName} '{"type": "composer", "url": "${this.composerPath}"}'`;
+ return `composer config repositories.${this.packageEntity.composerConfigRepositoryUrl} '{"type": "composer", "url": "${this.packageEntity.composerUrl}"}'`;
},
composerPackageInclude() {
// eslint-disable-next-line @gitlab/require-i18n-strings
@@ -51,6 +52,9 @@ export default {
TRACKING_ACTION_COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
},
+ links: {
+ COMPOSER_HELP_PATH,
+ },
installOptions: [{ value: 'composer', label: s__('PackageRegistry|Show Composer commands') }],
};
</script>
@@ -79,7 +83,7 @@ export default {
<span data-testid="help-text">
<gl-sprintf :message="$options.i18n.infoLine">
<template #link="{ content }">
- <gl-link :href="composerHelpPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="$options.links.COMPOSER_HELP_PATH" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</span>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue
index 99e27c9d44a..ba0a3fcf5a1 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/conan_installation.vue
@@ -6,6 +6,7 @@ import {
TRACKING_ACTION_COPY_CONAN_COMMAND,
TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
+ CONAN_HELP_PATH,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
@@ -17,7 +18,6 @@ export default {
GlLink,
GlSprintf,
},
- inject: ['conanHelpPath', 'conanPath'],
props: {
packageEntity: {
type: Object,
@@ -31,7 +31,7 @@ export default {
},
conanSetupCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
- return `conan remote add gitlab ${this.conanPath}`;
+ return `conan remote add gitlab ${this.packageEntity.conanUrl}`;
},
},
i18n: {
@@ -44,7 +44,7 @@ export default {
TRACKING_ACTION_COPY_CONAN_SETUP_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
},
-
+ links: { CONAN_HELP_PATH },
installOptions: [{ value: 'conan', label: s__('PackageRegistry|Show Conan commands') }],
};
</script>
@@ -72,7 +72,7 @@ export default {
/>
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">
- <gl-link :href="conanHelpPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="$options.links.CONAN_HELP_PATH" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue
index 2070f0bbca0..4510c7a7322 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/maven_installation.vue
@@ -12,6 +12,7 @@ import {
TRACKING_ACTION_COPY_KOTLIN_ADD_TO_SOURCE_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
TRACKING_LABEL_MAVEN_INSTALLATION,
+ MAVEN_HELP_PATH,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
@@ -23,7 +24,6 @@ export default {
GlLink,
GlSprintf,
},
- inject: ['mavenHelpPath', 'mavenPath'],
props: {
packageEntity: {
type: Object,
@@ -36,6 +36,9 @@ export default {
};
},
computed: {
+ mavenUrl() {
+ return this.packageEntity.mavenUrl;
+ },
appGroup() {
return this.packageEntity.metadata.appGroup;
},
@@ -61,19 +64,19 @@ export default {
return `<repositories>
<repository>
<id>gitlab-maven</id>
- <url>${this.mavenPath}</url>
+ <url>${this.mavenUrl}</url>
</repository>
</repositories>
<distributionManagement>
<repository>
<id>gitlab-maven</id>
- <url>${this.mavenPath}</url>
+ <url>${this.mavenUrl}</url>
</repository>
<snapshotRepository>
<id>gitlab-maven</id>
- <url>${this.mavenPath}</url>
+ <url>${this.mavenUrl}</url>
</snapshotRepository>
</distributionManagement>`;
},
@@ -86,7 +89,7 @@ export default {
gradleGroovyAddSourceCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return `maven {
- url '${this.mavenPath}'
+ url '${this.mavenUrl}'
}`;
},
@@ -95,7 +98,7 @@ export default {
},
gradleKotlinAddSourceCommand() {
- return `maven("${this.mavenPath}")`;
+ return `maven("${this.mavenUrl}")`;
},
showMaven() {
return this.instructionType === 'maven';
@@ -126,7 +129,7 @@ export default {
TRACKING_LABEL_CODE_INSTRUCTION,
TRACKING_LABEL_MAVEN_INSTALLATION,
},
-
+ links: { MAVEN_HELP_PATH },
installOptions: [
{ value: 'maven', label: s__('PackageRegistry|Maven XML') },
{ value: 'groovy', label: s__('PackageRegistry|Gradle Groovy DSL') },
@@ -185,7 +188,7 @@ export default {
/>
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">
- <gl-link :href="mavenHelpPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="$options.links.MAVEN_HELP_PATH" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue
index 2448324549e..7479f748a56 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/npm_installation.vue
@@ -13,6 +13,7 @@ import {
YARN_PACKAGE_MANAGER,
PROJECT_PACKAGE_ENDPOINT_TYPE,
INSTANCE_PACKAGE_ENDPOINT_TYPE,
+ NPM_HELP_PATH,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
@@ -25,7 +26,7 @@ export default {
GlSprintf,
GlFormRadioGroup,
},
- inject: ['npmHelpPath', 'npmPath', 'npmProjectPath'],
+ inject: ['npmInstanceUrl'],
props: {
packageEntity: {
type: Object,
@@ -65,7 +66,9 @@ export default {
npmSetupCommand(type, endpointType) {
const scope = this.packageEntity.name.substring(0, this.packageEntity.name.indexOf('/'));
const npmPathForEndpoint =
- endpointType === INSTANCE_PACKAGE_ENDPOINT_TYPE ? this.npmPath : this.npmProjectPath;
+ endpointType === INSTANCE_PACKAGE_ENDPOINT_TYPE
+ ? this.npmInstanceUrl
+ : this.packageEntity.npmUrl;
if (type === NPM_PACKAGE_MANAGER) {
return `echo ${scope}:registry=${npmPathForEndpoint}/ >> .npmrc`;
@@ -89,6 +92,7 @@ export default {
'PackageRegistry|You may also need to setup authentication using an auth token. %{linkStart}See the documentation%{linkEnd} to find out more.',
),
},
+ links: { NPM_HELP_PATH },
installOptions: [
{ value: NPM_PACKAGE_MANAGER, label: s__('PackageRegistry|Show NPM commands') },
{ value: YARN_PACKAGE_MANAGER, label: s__('PackageRegistry|Show Yarn commands') },
@@ -150,7 +154,7 @@ export default {
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">
- <gl-link :href="npmHelpPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="$options.links.NPM_HELP_PATH" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue
index 2e9991b7be5..b2007df142c 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/nuget_installation.vue
@@ -6,6 +6,7 @@ import {
TRACKING_ACTION_COPY_NUGET_INSTALL_COMMAND,
TRACKING_ACTION_COPY_NUGET_SETUP_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
+ NUGET_HELP_PATH,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
@@ -17,7 +18,6 @@ export default {
GlLink,
GlSprintf,
},
- inject: ['nugetHelpPath', 'nugetPath'],
props: {
packageEntity: {
type: Object,
@@ -29,7 +29,7 @@ export default {
return `nuget install ${this.packageEntity.name} -Source "GitLab"`;
},
nugetSetupCommand() {
- return `nuget source Add -Name "GitLab" -Source "${this.nugetPath}" -UserName <your_username> -Password <your_token>`;
+ return `nuget source Add -Name "GitLab" -Source "${this.packageEntity.nugetUrl}" -UserName <your_username> -Password <your_token>`;
},
},
tracking: {
@@ -42,6 +42,7 @@ export default {
'PackageRegistry|For more information on the NuGet registry, %{linkStart}see the documentation%{linkEnd}.',
),
},
+ links: { NUGET_HELP_PATH },
installOptions: [{ value: 'nuget', label: s__('PackageRegistry|Show Nuget commands') }],
};
</script>
@@ -68,7 +69,7 @@ export default {
/>
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">
- <gl-link :href="nugetHelpPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="$options.links.NUGET_HELP_PATH" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
index bf7fe6fb91b..3724e371e01 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/package_files.vue
@@ -22,8 +22,12 @@ export default {
FileSha,
},
mixins: [Tracking.mixin()],
- inject: ['canDelete'],
props: {
+ canDelete: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
packageFiles: {
type: Array,
required: false,
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue
index 669adab9df6..a126d30f1ec 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/details/pypi_installation.vue
@@ -7,6 +7,7 @@ import {
TRACKING_ACTION_COPY_PIP_INSTALL_COMMAND,
TRACKING_ACTION_COPY_PYPI_SETUP_COMMAND,
TRACKING_LABEL_CODE_INSTRUCTION,
+ PYPI_HELP_PATH,
} from '~/packages_and_registries/package_registry/constants';
import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue';
@@ -18,7 +19,6 @@ export default {
GlLink,
GlSprintf,
},
- inject: ['pypiHelpPath', 'pypiPath', 'pypiSetupPath'],
props: {
packageEntity: {
type: Object,
@@ -28,11 +28,11 @@ export default {
computed: {
pypiPipCommand() {
// eslint-disable-next-line @gitlab/require-i18n-strings
- return `pip install ${this.packageEntity.name} --extra-index-url ${this.pypiPath}`;
+ return `pip install ${this.packageEntity.name} --extra-index-url ${this.packageEntity.pypiUrl}`;
},
pypiSetupCommand() {
return `[gitlab]
-repository = ${this.pypiSetupPath}
+repository = ${this.packageEntity.pypiSetupUrl}
username = __token__
password = <your personal access token>`;
},
@@ -50,6 +50,7 @@ password = <your personal access token>`;
'PackageRegistry|For more information on the PyPi registry, %{linkStart}see the documentation%{linkEnd}.',
),
},
+ links: { PYPI_HELP_PATH },
installOptions: [{ value: 'pypi', label: s__('PackageRegistry|Show PyPi commands') }],
};
</script>
@@ -86,7 +87,7 @@ password = <your personal access token>`;
/>
<gl-sprintf :message="$options.i18n.helpText">
<template #link="{ content }">
- <gl-link :href="pypiHelpPath" target="_blank">{{ content }}</gl-link>
+ <gl-link :href="$options.links.PYPI_HELP_PATH" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</div>
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
index 6fd96c0654f..6222c2e73d7 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_list_row.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButton, GlLink, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui';
+import { GlButton, GlSprintf, GlTooltipDirective, GlTruncate } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import {
@@ -18,7 +18,6 @@ export default {
name: 'PackageListRow',
components: {
GlButton,
- GlLink,
GlSprintf,
GlTruncate,
PackageTags,
@@ -42,9 +41,8 @@ export default {
packageType() {
return getPackageTypeLabel(this.packageEntity.packageType);
},
- packageLink() {
- const { project, id } = this.packageEntity;
- return `${project?.webUrl}/-/packages/${getIdFromGraphQLId(id)}`;
+ packageId() {
+ return getIdFromGraphQLId(this.packageEntity.id);
},
pipeline() {
return this.packageEntity?.pipelines?.nodes[0];
@@ -61,6 +59,9 @@ export default {
disabledRow() {
return this.packageEntity.status && this.packageEntity.status !== PACKAGE_DEFAULT_STATUS;
},
+ routerLinkEvent() {
+ return this.disabledRow ? '' : 'click';
+ },
},
i18n: {
erroredPackageText: s__('PackageRegistry|Invalid Package: failed metadata extraction'),
@@ -73,14 +74,15 @@ export default {
<list-item data-qa-selector="package_row" :disabled="disabledRow">
<template #left-primary>
<div class="gl-display-flex gl-align-items-center gl-mr-3 gl-min-w-0">
- <gl-link
- :href="packageLink"
+ <router-link
class="gl-text-body gl-min-w-0"
+ data-testid="details-link"
data-qa-selector="package_link"
- :disabled="disabledRow"
+ :event="routerLinkEvent"
+ :to="{ name: 'details', params: { id: packageId } }"
>
<gl-truncate :text="packageEntity.name" />
- </gl-link>
+ </router-link>
<gl-button
v-if="showWarningIcon"
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/constants.js b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
index ab6541e4264..c4d331fa384 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/constants.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/constants.js
@@ -74,6 +74,7 @@ export const FETCH_PACKAGE_DETAILS_ERROR_MESSAGE = s__(
);
export const DELETE_PACKAGE_SUCCESS_MESSAGE = s__('PackageRegistry|Package deleted successfully');
+export const PACKAGE_REGISTRY_TITLE = __('Package Registry');
export const PACKAGE_ERROR_STATUS = 'ERROR';
export const PACKAGE_DEFAULT_STATUS = 'DEFAULT';
@@ -142,3 +143,9 @@ export const PACKAGE_TYPES = [
export const EMPTY_LIST_HELP_URL = helpPagePath('user/packages/package_registry/index');
export const PACKAGE_HELP_URL = helpPagePath('user/packages/index');
+export const NPM_HELP_PATH = helpPagePath('user/packages/npm_registry/index');
+export const MAVEN_HELP_PATH = helpPagePath('user/packages/maven_repository/index');
+export const CONAN_HELP_PATH = helpPagePath('user/packages/conan_repository/index');
+export const NUGET_HELP_PATH = helpPagePath('user/packages/nuget_repository/index');
+export const PYPI_HELP_PATH = helpPagePath('user/packages/pypi_repository/index');
+export const COMPOSER_HELP_PATH = helpPagePath('user/packages/composer_repository/index');
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
index 08ea0938a59..c45cbe56e00 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_package_details.query.graphql
@@ -7,9 +7,19 @@ query getPackageDetails($id: ID!) {
createdAt
updatedAt
status
+ canDestroy
+ npmUrl
+ mavenUrl
+ conanUrl
+ nugetUrl
+ pypiUrl
+ pypiSetupUrl
+ composerUrl
+ composerConfigRepositoryUrl
project {
id
path
+ name
}
tags(first: 10) {
nodes {
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/index.js b/app/assets/javascripts/packages_and_registries/package_registry/index.js
index 7ec931ff9a0..6680e612985 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/index.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/index.js
@@ -2,29 +2,59 @@ import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
import PackageRegistry from '~/packages_and_registries/package_registry/pages/index.vue';
+import RegistryBreadcrumb from '~/packages_and_registries/shared/components/registry_breadcrumb.vue';
+import { renderBreadcrumb } from '~/packages_and_registries/shared/utils';
import createRouter from './router';
Vue.use(Translate);
export default () => {
const el = document.getElementById('js-vue-packages-list');
- const { endpoint, resourceId, fullPath, pageType, emptyListIllustration } = el.dataset;
- const router = createRouter(endpoint);
+ const {
+ endpoint,
+ resourceId,
+ fullPath,
+ pageType,
+ emptyListIllustration,
+ npmInstanceUrl,
+ projectListUrl,
+ groupListUrl,
+ } = el.dataset;
const isGroupPage = pageType === 'groups';
- return new Vue({
- el,
- router,
- apolloProvider,
- provide: {
- resourceId,
- fullPath,
- emptyListIllustration,
- isGroupPage,
- },
- render(createElement) {
- return createElement(PackageRegistry);
+ // This is a mini state to help the breadcrumb have the correct name in the details page
+ const breadCrumbState = Vue.observable({
+ name: '',
+ updateName(value) {
+ this.name = value;
},
});
+
+ const router = createRouter(endpoint, breadCrumbState);
+
+ const attachMainComponent = () =>
+ new Vue({
+ el,
+ router,
+ apolloProvider,
+ provide: {
+ resourceId,
+ fullPath,
+ emptyListIllustration,
+ isGroupPage,
+ npmInstanceUrl,
+ projectListUrl,
+ groupListUrl,
+ breadCrumbState,
+ },
+ render(createElement) {
+ return createElement(PackageRegistry);
+ },
+ });
+
+ return {
+ attachBreadcrumb: renderBreadcrumb(router, apolloProvider, RegistryBreadcrumb),
+ attachMainComponent,
+ };
};
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js
deleted file mode 100644
index d94bbd21035..00000000000
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/details.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import Vue from 'vue';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import PackagesApp from '~/packages_and_registries/package_registry/components/details/app.vue';
-import { apolloProvider } from '~/packages_and_registries/package_registry/graphql/index';
-import Translate from '~/vue_shared/translate';
-
-Vue.use(Translate);
-
-export default () => {
- const el = document.getElementById('js-vue-packages-detail-new');
- if (!el) {
- return null;
- }
-
- const { canDelete, ...datasetOptions } = el.dataset;
- return new Vue({
- el,
- apolloProvider,
- provide: {
- canDelete: parseBoolean(canDelete),
- ...datasetOptions,
- },
- render(createElement) {
- return createElement(PackagesApp);
- },
- });
-};
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
index d49c1be5202..162b420a784 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/details/app.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/details.vue
@@ -68,16 +68,7 @@ export default {
GlModal: GlModalDirective,
},
mixins: [Tracking.mixin()],
- inject: [
- 'packageId',
- 'projectName',
- 'canDelete',
- 'svgPath',
- 'npmPath',
- 'npmHelpPath',
- 'projectListUrl',
- 'groupListUrl',
- ],
+ inject: ['emptyListIllustration', 'projectListUrl', 'groupListUrl', 'breadCrumbState'],
trackingActions: {
DELETE_PACKAGE_TRACKING_ACTION,
REQUEST_DELETE_PACKAGE_TRACKING_ACTION,
@@ -100,7 +91,7 @@ export default {
return this.queryVariables;
},
update(data) {
- return data.package;
+ return data.package || {};
},
error(error) {
createFlash({
@@ -109,22 +100,33 @@ export default {
error,
});
},
+ result() {
+ this.breadCrumbState.updateName(
+ `${this.packageEntity?.name} v ${this.packageEntity?.version}`,
+ );
+ },
},
},
computed: {
+ projectName() {
+ return this.packageEntity.project?.name;
+ },
+ packageId() {
+ return this.$route.params.id;
+ },
queryVariables() {
return {
id: convertToGraphQLId('Packages::Package', this.packageId),
};
},
packageFiles() {
- return this.packageEntity?.packageFiles?.nodes;
+ return this.packageEntity.packageFiles?.nodes;
},
isLoading() {
return this.$apollo.queries.packageEntity.loading;
},
isValidPackage() {
- return this.isLoading || Boolean(this.packageEntity?.name);
+ return this.isLoading || Boolean(this.packageEntity.name);
},
tracking() {
return {
@@ -141,7 +143,7 @@ export default {
return this.packageEntity.packageType === PACKAGE_TYPE_NUGET;
},
showFiles() {
- return this.packageEntity?.packageType !== PACKAGE_TYPE_COMPOSER;
+ return this.packageEntity.packageType !== PACKAGE_TYPE_COMPOSER;
},
},
methods: {
@@ -235,13 +237,13 @@ export default {
v-if="!isValidPackage"
:title="s__('PackageRegistry|Unable to load package')"
:description="s__('PackageRegistry|There was a problem fetching the details for this package.')"
- :svg-path="svgPath"
+ :svg-path="emptyListIllustration"
/>
<div v-else-if="!isLoading" class="packages-app">
<package-title :package-entity="packageEntity">
<template #delete-button>
<gl-button
- v-if="canDelete"
+ v-if="packageEntity.canDestroy"
v-gl-modal="'delete-modal'"
variant="danger"
category="primary"
@@ -265,6 +267,7 @@ export default {
<package-files
v-if="showFiles"
+ :can-delete="packageEntity.canDestroy"
:package-files="packageFiles"
@download-file="track($options.trackingActions.PULL_PACKAGE)"
@delete-file="handleFileDelete"
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/router.js b/app/assets/javascripts/packages_and_registries/package_registry/router.js
index ea5b740e879..c5ef4f70dd9 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/router.js
+++ b/app/assets/javascripts/packages_and_registries/package_registry/router.js
@@ -1,10 +1,12 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import List from '~/packages_and_registries/package_registry/pages/list.vue';
+import Details from '~/packages_and_registries/package_registry/pages/details.vue';
+import { PACKAGE_REGISTRY_TITLE } from '~/packages_and_registries/package_registry/constants';
Vue.use(VueRouter);
-export default function createRouter(base) {
+export default function createRouter(base, breadCrumbState) {
const router = new VueRouter({
base,
mode: 'history',
@@ -13,9 +15,25 @@ export default function createRouter(base) {
name: 'list',
path: '/',
component: List,
+ meta: {
+ nameGenerator: () => PACKAGE_REGISTRY_TITLE,
+ root: true,
+ },
+ },
+ {
+ name: 'details',
+ path: '/:id',
+ component: Details,
+ meta: {
+ nameGenerator: () => breadCrumbState.name,
+ },
},
],
});
+ router.afterEach(() => {
+ breadCrumbState.updateName('');
+ });
+
return router;
}
diff --git a/app/assets/javascripts/packages_and_registries/shared/components/persisted_search.vue b/app/assets/javascripts/packages_and_registries/shared/components/persisted_search.vue
new file mode 100644
index 00000000000..9b2de1a1b84
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/shared/components/persisted_search.vue
@@ -0,0 +1,80 @@
+<script>
+import RegistrySearch from '~/vue_shared/components/registry/registry_search.vue';
+import UrlSync from '~/vue_shared/components/url_sync.vue';
+import { extractFilterAndSorting, getQueryParams } from '~/packages_and_registries/shared/utils';
+
+export default {
+ components: { RegistrySearch, UrlSync },
+ props: {
+ sortableFields: {
+ type: Array,
+ required: true,
+ },
+ defaultOrder: {
+ type: String,
+ required: true,
+ },
+ defaultSort: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ filters: [],
+ sorting: {
+ orderBy: this.defaultOrder,
+ sort: this.defaultSort,
+ },
+ mountRegistrySearch: false,
+ };
+ },
+ computed: {
+ parsedSorting() {
+ const cleanOrderBy = this.sorting?.orderBy.replace('_at', '');
+ return `${cleanOrderBy}_${this.sorting?.sort}`.toUpperCase();
+ },
+ },
+ mounted() {
+ const queryParams = getQueryParams(window.document.location.search);
+ const { sorting, filters } = extractFilterAndSorting(queryParams);
+ this.updateSorting(sorting);
+ this.updateFilters(filters);
+ this.mountRegistrySearch = true;
+ this.emitUpdate();
+ },
+ methods: {
+ updateFilters(newValue) {
+ this.filters = newValue;
+ },
+ updateSorting(newValue) {
+ this.sorting = { ...this.sorting, ...newValue };
+ },
+ updateSortingAndEmitUpdate(newValue) {
+ this.updateSorting(newValue);
+ this.emitUpdate();
+ },
+ emitUpdate() {
+ this.$emit('update', { sort: this.parsedSorting, filters: this.filters });
+ },
+ },
+};
+</script>
+
+<template>
+ <url-sync>
+ <template #default="{ updateQuery }">
+ <registry-search
+ v-if="mountRegistrySearch"
+ :filter="filters"
+ :sorting="sorting"
+ :tokens="$options.tokens"
+ :sortable-fields="sortableFields"
+ @sorting:changed="updateSortingAndEmitUpdate"
+ @filter:changed="updateFilters"
+ @filter:submit="emitUpdate"
+ @query:changed="updateQuery"
+ />
+ </template>
+ </url-sync>
+</template>
diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/registry_breadcrumb.vue b/app/assets/javascripts/packages_and_registries/shared/components/registry_breadcrumb.vue
index e77eda31596..a1e3c06812c 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/registry_breadcrumb.vue
+++ b/app/assets/javascripts/packages_and_registries/shared/components/registry_breadcrumb.vue
@@ -20,8 +20,11 @@ export default {
isRootRoute() {
return this.$route.name === this.rootRoute.name;
},
+ detailsRouteName() {
+ return this.detailsRoute.meta.nameGenerator();
+ },
isLoaded() {
- return this.isRootRoute || this.$store?.state.imageDetails?.name;
+ return this.isRootRoute || this.detailsRouteName;
},
allCrumbs() {
const crumbs = [
@@ -32,7 +35,7 @@ export default {
];
if (!this.isRootRoute) {
crumbs.push({
- text: this.detailsRoute.meta.nameGenerator(),
+ text: this.detailsRouteName,
href: this.detailsRoute.meta.path,
});
}
@@ -45,7 +48,9 @@ export default {
<template>
<gl-breadcrumb :key="isLoaded" :items="allCrumbs">
<template #separator>
- <gl-icon name="angle-right" :size="8" />
+ <span class="gl-mx-n5">
+ <gl-icon name="angle-right" :size="8" />
+ </span>
</template>
</gl-breadcrumb>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/shared/utils.js b/app/assets/javascripts/packages_and_registries/shared/utils.js
index cf18f655e79..7e963cd0b08 100644
--- a/app/assets/javascripts/packages_and_registries/shared/utils.js
+++ b/app/assets/javascripts/packages_and_registries/shared/utils.js
@@ -1,3 +1,4 @@
+import Vue from 'vue';
import { queryToObject } from '~/lib/utils/url_utility';
import { FILTERED_SEARCH_TERM } from './constants';
@@ -38,3 +39,37 @@ export const getCommitLink = ({ project_path: projectPath, pipeline = {} }, isGr
return `../commit/${pipeline.sha}`;
};
+
+export const renderBreadcrumb = (router, apolloProvider, RegistryBreadcrumb) => () => {
+ const breadCrumbEls = document.querySelectorAll('nav .js-breadcrumbs-list li');
+ const breadCrumbEl = breadCrumbEls[breadCrumbEls.length - 1];
+ const lastCrumb = breadCrumbEl.children[0];
+ const crumbs = [lastCrumb];
+ const nestedBreadcrumbEl = document.createElement('div');
+ breadCrumbEl.replaceChild(nestedBreadcrumbEl, lastCrumb);
+ return new Vue({
+ el: nestedBreadcrumbEl,
+ router,
+ apolloProvider,
+ components: {
+ RegistryBreadcrumb,
+ },
+ render(createElement) {
+ // FIXME(@tnir): this is a workaround until the MR gets merged:
+ // https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48115
+ const parentEl = breadCrumbEl.parentElement.parentElement;
+ if (parentEl) {
+ parentEl.classList.remove('breadcrumbs-container');
+ parentEl.classList.add('gl-display-flex');
+ parentEl.classList.add('w-100');
+ }
+ // End of FIXME(@tnir)
+ return createElement('registry-breadcrumb', {
+ class: breadCrumbEl.className,
+ props: {
+ crumbs,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/pages/admin/integrations/edit/index.js b/app/assets/javascripts/pages/admin/integrations/edit/index.js
index 8485b460261..c354ed1c142 100644
--- a/app/assets/javascripts/pages/admin/integrations/edit/index.js
+++ b/app/assets/javascripts/pages/admin/integrations/edit/index.js
@@ -1,7 +1,7 @@
import initIntegrationSettingsForm from '~/integrations/edit';
import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
-initIntegrationSettingsForm('.js-integration-settings-form');
+initIntegrationSettingsForm();
const prometheusSettingsSelector = '.js-prometheus-metrics-monitoring';
const prometheusSettingsWrapper = document.querySelector(prometheusSettingsSelector);
diff --git a/app/assets/javascripts/pages/admin/labels/edit/index.js b/app/assets/javascripts/pages/admin/labels/edit/index.js
index a3b9c43388a..a5eee2857df 100644
--- a/app/assets/javascripts/pages/admin/labels/edit/index.js
+++ b/app/assets/javascripts/pages/admin/labels/edit/index.js
@@ -1,3 +1,5 @@
import Labels from '~/labels/labels';
+import { initDeleteLabelModal } from '~/labels';
new Labels(); // eslint-disable-line no-new
+initDeleteLabelModal();
diff --git a/app/assets/javascripts/pages/admin/runners/edit/index.js b/app/assets/javascripts/pages/admin/runners/edit/index.js
new file mode 100644
index 00000000000..ddf135a2732
--- /dev/null
+++ b/app/assets/javascripts/pages/admin/runners/edit/index.js
@@ -0,0 +1,3 @@
+import { initAdminRunnerEdit } from '~/runner/admin_runner_edit';
+
+initAdminRunnerEdit();
diff --git a/app/assets/javascripts/pages/admin/runners/show/index.js b/app/assets/javascripts/pages/admin/runners/show/index.js
deleted file mode 100644
index d1853772fda..00000000000
--- a/app/assets/javascripts/pages/admin/runners/show/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import { initRunnerDetail } from '~/runner/runner_details';
-
-initRunnerDetail();
diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
index a1e7eb5d3de..cabb1b24ae6 100644
--- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js
+++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js
@@ -172,8 +172,12 @@ export default class Todos {
updateBadges(data) {
$(document).trigger('todo:toggle', data.count);
- document.querySelector('.js-todos-pending .badge').innerHTML = addDelimiter(data.count);
- document.querySelector('.js-todos-done .badge').innerHTML = addDelimiter(data.done_count);
+ document.querySelector('.js-todos-pending .js-todos-badge').innerHTML = addDelimiter(
+ data.count,
+ );
+ document.querySelector('.js-todos-done .js-todos-badge').innerHTML = addDelimiter(
+ data.done_count,
+ );
}
goToTodoUrl(e) {
diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js
index 808fcce46df..05078191e5c 100644
--- a/app/assets/javascripts/pages/explore/groups/index.js
+++ b/app/assets/javascripts/pages/explore/groups/index.js
@@ -1,6 +1,6 @@
-import GroupsList from '~/groups_list';
-import Landing from '~/landing';
-import initGroupsList from '../../../groups';
+import initGroupsList from '~/groups';
+import GroupsList from '~/groups/groups_list';
+import Landing from '~/groups/landing';
function exploreGroups() {
new GroupsList(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/groups/edit/index.js b/app/assets/javascripts/pages/groups/edit/index.js
index 604da77f60c..f6155b2ab2f 100644
--- a/app/assets/javascripts/pages/groups/edit/index.js
+++ b/app/assets/javascripts/pages/groups/edit/index.js
@@ -1,20 +1,18 @@
import { GROUP_BADGE } from '~/badges/constants';
-import initConfirmDangerModal from '~/confirm_danger_modal';
import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import initFilePickers from '~/file_pickers';
import TransferDropdown from '~/groups/transfer_dropdown';
+import setupTransferEdit from '~/groups/transfer_edit';
import groupsSelect from '~/groups_select';
import { initCascadingSettingsLockPopovers } from '~/namespaces/cascading_settings';
import mountBadgeSettings from '~/pages/shared/mount_badge_settings';
import projectSelect from '~/project_select';
import initSearchSettings from '~/search_settings';
import initSettingsPanels from '~/settings_panels';
-import setupTransferEdit from '~/transfer_edit';
import initConfirmDanger from '~/init_confirm_danger';
document.addEventListener('DOMContentLoaded', () => {
initFilePickers();
- initConfirmDangerModal();
initConfirmDanger();
initSettingsPanels();
dirtySubmitFactory(
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index 966d55e5587..725c38defc3 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,6 +1,6 @@
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
-import issuableInitBulkUpdateSidebar from '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar';
-import { mountIssuablesListApp, mountIssuesListApp } from '~/issues_list';
+import { initBulkUpdateSidebar } from '~/issuable/bulk_update_sidebar';
+import { mountIssuesListApp } from '~/issues/list';
import initManualOrdering from '~/issues/manual_ordering';
import { FILTERED_SEARCH } from '~/filtered_search/constants';
import initFilteredSearch from '~/pages/search/init_filtered_search';
@@ -13,7 +13,7 @@ if (gon.features?.vueIssuesList) {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
IssuableFilteredSearchTokenKeys.removeTokensForKeys('release');
- issuableInitBulkUpdateSidebar.init(ISSUE_BULK_UPDATE_PREFIX);
+ initBulkUpdateSidebar(ISSUE_BULK_UPDATE_PREFIX);
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
@@ -23,8 +23,4 @@ if (gon.features?.vueIssuesList) {
});
projectSelect();
initManualOrdering();
-
- if (gon.features?.vueIssuablesList) {
- mountIssuablesListApp();
- }
}
diff --git a/app/assets/javascripts/pages/groups/labels/edit/index.js b/app/assets/javascripts/pages/groups/labels/edit/index.js
index e4e377f62fc..c032321d039 100644
--- a/app/assets/javascripts/pages/groups/labels/edit/index.js
+++ b/app/assets/javascripts/pages/groups/labels/edit/index.js
@@ -1,4 +1,6 @@
import Labels from 'ee_else_ce/labels/labels';
+import { initDeleteLabelModal } from '~/labels';
// eslint-disable-next-line no-new
new Labels();
+initDeleteLabelModal();
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index cb38ee1c6e0..de28f027126 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -1,6 +1,6 @@
import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
-import issuableInitBulkUpdateSidebar from '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar';
+import { initBulkUpdateSidebar } from '~/issuable/bulk_update_sidebar';
import { FILTERED_SEARCH } from '~/filtered_search/constants';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import projectSelect from '~/project_select';
@@ -8,7 +8,7 @@ import projectSelect from '~/project_select';
const ISSUABLE_BULK_UPDATE_PREFIX = 'merge_request_';
addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys);
-issuableInitBulkUpdateSidebar.init(ISSUABLE_BULK_UPDATE_PREFIX);
+initBulkUpdateSidebar(ISSUABLE_BULK_UPDATE_PREFIX);
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
diff --git a/app/assets/javascripts/pages/groups/new/index.js b/app/assets/javascripts/pages/groups/new/index.js
index 7b0418e1ad5..702b152d25a 100644
--- a/app/assets/javascripts/pages/groups/new/index.js
+++ b/app/assets/javascripts/pages/groups/new/index.js
@@ -15,7 +15,7 @@ initFilePickers();
new Group(); // eslint-disable-line no-new
function initNewGroupCreation(el) {
- const { hasErrors } = el.dataset;
+ const { hasErrors, verificationRequired, verificationFormUrl, subscriptionsUrl } = el.dataset;
const props = {
hasErrors: parseBoolean(hasErrors),
@@ -23,6 +23,11 @@ function initNewGroupCreation(el) {
return new Vue({
el,
+ provide: {
+ verificationRequired: parseBoolean(verificationRequired),
+ verificationFormUrl,
+ subscriptionsUrl,
+ },
render(h) {
return h(NewGroupCreationApp, { props });
},
diff --git a/app/assets/javascripts/pages/groups/packages/index.js b/app/assets/javascripts/pages/groups/packages/index.js
new file mode 100644
index 00000000000..cbe08565cfa
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/packages/index.js
@@ -0,0 +1,8 @@
+import packageApp from '~/packages_and_registries/package_registry/index';
+
+const app = packageApp();
+
+if (app) {
+ app.attachBreadcrumb();
+ app.attachMainComponent();
+}
diff --git a/app/assets/javascripts/pages/groups/packages/index/index.js b/app/assets/javascripts/pages/groups/packages/index/index.js
deleted file mode 100644
index 174973a9fad..00000000000
--- a/app/assets/javascripts/pages/groups/packages/index/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import packageApp from '~/packages_and_registries/package_registry/index';
-
-packageApp();
diff --git a/app/assets/javascripts/pages/groups/settings/access_tokens/index.js b/app/assets/javascripts/pages/groups/settings/access_tokens/index.js
new file mode 100644
index 00000000000..dc1bb88bf4b
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/settings/access_tokens/index.js
@@ -0,0 +1,3 @@
+import { initExpiresAtField } from '~/access_tokens';
+
+initExpiresAtField();
diff --git a/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js b/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js
index 8485b460261..c354ed1c142 100644
--- a/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js
+++ b/app/assets/javascripts/pages/groups/settings/integrations/edit/index.js
@@ -1,7 +1,7 @@
import initIntegrationSettingsForm from '~/integrations/edit';
import PrometheusMetrics from '~/prometheus_metrics/prometheus_metrics';
-initIntegrationSettingsForm('.js-integration-settings-form');
+initIntegrationSettingsForm();
const prometheusSettingsSelector = '.js-prometheus-metrics-monitoring';
const prometheusSettingsWrapper = document.querySelector(prometheusSettingsSelector);
diff --git a/app/assets/javascripts/pages/help/index/index.js b/app/assets/javascripts/pages/help/index/index.js
index 736add8dca3..a8e67c57307 100644
--- a/app/assets/javascripts/pages/help/index/index.js
+++ b/app/assets/javascripts/pages/help/index/index.js
@@ -1,6 +1,5 @@
-import $ from 'jquery';
import docs from '~/docs/docs_bundle';
-import VersionCheckImage from '~/version_check_image';
+import initGitlabVersionCheck from '~/gitlab_version_check';
docs();
-VersionCheckImage.bindErrorEvent($('img.js-version-status-badge'));
+initGitlabVersionCheck();
diff --git a/app/assets/javascripts/pages/profiles/keys/index.js b/app/assets/javascripts/pages/profiles/keys/index.js
index 1b291d9509d..6b12604c76b 100644
--- a/app/assets/javascripts/pages/profiles/keys/index.js
+++ b/app/assets/javascripts/pages/profiles/keys/index.js
@@ -7,11 +7,13 @@ function initSshKeyValidation() {
const input = document.querySelector('.js-add-ssh-key-validation-input');
if (!input) return;
+ const supportedAlgorithms = JSON.parse(input.dataset.supportedAlgorithms);
const warning = document.querySelector('.js-add-ssh-key-validation-warning');
const originalSubmit = input.form.querySelector('.js-add-ssh-key-validation-original-submit');
const confirmSubmit = warning.querySelector('.js-add-ssh-key-validation-confirm-submit');
const addSshKeyValidation = new AddSshKeyValidation(
+ supportedAlgorithms,
input,
warning,
originalSubmit,
diff --git a/app/assets/javascripts/pages/projects/blob/show/index.js b/app/assets/javascripts/pages/projects/blob/show/index.js
index b365e039191..2fc9a111405 100644
--- a/app/assets/javascripts/pages/projects/blob/show/index.js
+++ b/app/assets/javascripts/pages/projects/blob/show/index.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import VueRouter from 'vue-router';
import TableOfContents from '~/blob/components/table_contents.vue';
import PipelineTourSuccessModal from '~/blob/pipeline_tour_success_modal.vue';
import { BlobViewer, initAuxiliaryViewer } from '~/blob/viewer/index';
@@ -12,11 +13,14 @@ import BlobContentViewer from '~/repository/components/blob_content_viewer.vue';
import '~/sourcegraph/load';
Vue.use(VueApollo);
+Vue.use(VueRouter);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
+const router = new VueRouter({ mode: 'history' });
+
const viewBlobEl = document.querySelector('#js-view-blob-app');
if (viewBlobEl) {
@@ -25,6 +29,7 @@ if (viewBlobEl) {
// eslint-disable-next-line no-new
new Vue({
el: viewBlobEl,
+ router,
apolloProvider,
provide: {
targetBranch,
@@ -41,6 +46,7 @@ if (viewBlobEl) {
});
initAuxiliaryViewer();
+ initBlob();
} else {
new BlobViewer(); // eslint-disable-line no-new
initBlob();
diff --git a/app/assets/javascripts/pages/projects/branches/index/index.js b/app/assets/javascripts/pages/projects/branches/index/index.js
index 97dc76908af..d279c4cbb08 100644
--- a/app/assets/javascripts/pages/projects/branches/index/index.js
+++ b/app/assets/javascripts/pages/projects/branches/index/index.js
@@ -1,13 +1,11 @@
import initDeprecatedRemoveRowBehavior from '~/behaviors/deprecated_remove_row_behavior';
import AjaxLoadingSpinner from '~/branches/ajax_loading_spinner';
import BranchSortDropdown from '~/branches/branch_sort_dropdown';
-import DeleteModal from '~/branches/branches_delete_modal';
import initDiverganceGraph from '~/branches/divergence_graph';
import initDeleteBranchButton from '~/branches/init_delete_branch_button';
import initDeleteBranchModal from '~/branches/init_delete_branch_modal';
AjaxLoadingSpinner.init();
-new DeleteModal(); // eslint-disable-line no-new
const { divergingCountsEndpoint, defaultBranch } = document.querySelector(
'.js-branch-list',
diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js
index 100ca5b36d9..c0eb2a8fd77 100644
--- a/app/assets/javascripts/pages/projects/edit/index.js
+++ b/app/assets/javascripts/pages/projects/edit/index.js
@@ -1,5 +1,4 @@
import { PROJECT_BADGE } from '~/badges/constants';
-import initLegacyConfirmDangerModal from '~/confirm_danger_modal';
import initConfirmDanger from '~/init_confirm_danger';
import dirtySubmitFactory from '~/dirty_submit/dirty_submit_factory';
import initFilePickers from '~/file_pickers';
@@ -15,7 +14,6 @@ import initProjectPermissionsSettings from '../shared/permissions';
import initProjectLoadingSpinner from '../shared/save_project_loader';
initFilePickers();
-initLegacyConfirmDangerModal();
initConfirmDanger();
initSettingsPanels();
initProjectDeleteButton();
diff --git a/app/assets/javascripts/pages/projects/find_file/show/index.js b/app/assets/javascripts/pages/projects/find_file/show/index.js
index a8225167c6b..f47888f0cb8 100644
--- a/app/assets/javascripts/pages/projects/find_file/show/index.js
+++ b/app/assets/javascripts/pages/projects/find_file/show/index.js
@@ -1,6 +1,6 @@
import $ from 'jquery';
import ShortcutsFindFile from '~/behaviors/shortcuts/shortcuts_find_file';
-import ProjectFindFile from '~/project_find_file';
+import ProjectFindFile from '~/projects/project_find_file';
const findElement = document.querySelector('.js-file-finder');
const projectFindFile = new ProjectFindFile($('.file-finder-holder'), {
diff --git a/app/assets/javascripts/pages/projects/imports/show/index.js b/app/assets/javascripts/pages/projects/imports/show/index.js
index 8397826f8eb..21d07e04ddc 100644
--- a/app/assets/javascripts/pages/projects/imports/show/index.js
+++ b/app/assets/javascripts/pages/projects/imports/show/index.js
@@ -1,3 +1,3 @@
-import ProjectImport from '~/project_import';
+import ProjectImport from '~/projects/project_import';
new ProjectImport(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/incidents/show/index.js b/app/assets/javascripts/pages/projects/incidents/show/index.js
index 4633eaef8f9..5a8cfcf8462 100644
--- a/app/assets/javascripts/pages/projects/incidents/show/index.js
+++ b/app/assets/javascripts/pages/projects/incidents/show/index.js
@@ -1,6 +1,6 @@
+import { initShow } from '~/issues';
import initRelatedIssues from '~/related_issues';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
-import initShow from '~/issues/show';
initShow();
initSidebarBundle();
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
index 06aba866ccf..7db34816cfe 100644
--- a/app/assets/javascripts/pages/projects/init_blob.js
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -2,8 +2,8 @@ import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
+import LineHighlighter from '~/blob/line_highlighter';
import initBlobBundle from '~/blob_edit/blob_bundle';
-import LineHighlighter from '~/line_highlighter';
export default () => {
new LineHighlighter(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/issues/edit/index.js b/app/assets/javascripts/pages/projects/issues/edit/index.js
index aa00d1f58bd..06dcd2c2d94 100644
--- a/app/assets/javascripts/pages/projects/issues/edit/index.js
+++ b/app/assets/javascripts/pages/projects/issues/edit/index.js
@@ -1,3 +1,3 @@
-import initForm from 'ee_else_ce/issues/form';
+import { initForm } from 'ee_else_ce/issues';
initForm();
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
index e937713044c..44b1d5277d1 100644
--- a/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -1,8 +1,8 @@
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import { initCsvImportExportButtons, initIssuableByEmail } from '~/issuable';
-import issuableInitBulkUpdateSidebar from '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar';
-import { mountIssuablesListApp, mountIssuesListApp, mountJiraIssuesListApp } from '~/issues_list';
+import { initBulkUpdateSidebar, initIssueStatusSelect } from '~/issuable/bulk_update_sidebar';
+import { mountIssuesListApp, mountJiraIssuesListApp } from '~/issues/list';
import initManualOrdering from '~/issues/manual_ordering';
import { FILTERED_SEARCH } from '~/filtered_search/constants';
import { ISSUABLE_INDEX } from '~/issuable/constants';
@@ -20,16 +20,13 @@ if (gon.features?.vueIssuesList) {
useDefaultState: true,
});
- issuableInitBulkUpdateSidebar.init(ISSUABLE_INDEX.ISSUE);
+ initBulkUpdateSidebar(ISSUABLE_INDEX.ISSUE);
+ initIssueStatusSelect();
new UsersSelect(); // eslint-disable-line no-new
initCsvImportExportButtons();
initIssuableByEmail();
initManualOrdering();
-
- if (gon.features?.vueIssuablesList) {
- mountIssuablesListApp();
- }
}
new ShortcutsNavigation(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/issues/new/index.js b/app/assets/javascripts/pages/projects/issues/new/index.js
index aa00d1f58bd..06dcd2c2d94 100644
--- a/app/assets/javascripts/pages/projects/issues/new/index.js
+++ b/app/assets/javascripts/pages/projects/issues/new/index.js
@@ -1,3 +1,3 @@
-import initForm from 'ee_else_ce/issues/form';
+import { initForm } from 'ee_else_ce/issues';
initForm();
diff --git a/app/assets/javascripts/pages/projects/issues/service_desk/index.js b/app/assets/javascripts/pages/projects/issues/service_desk/index.js
index 69639d17f8a..7dd128fedb9 100644
--- a/app/assets/javascripts/pages/projects/issues/service_desk/index.js
+++ b/app/assets/javascripts/pages/projects/issues/service_desk/index.js
@@ -1,8 +1,3 @@
-import { mountIssuablesListApp } from '~/issues_list';
-import { initFilteredSearchServiceDesk } from '~/issues/init_filtered_search_service_desk';
+import { initFilteredSearchServiceDesk } from '~/issues';
initFilteredSearchServiceDesk();
-
-if (gon.features?.vueIssuablesList) {
- mountIssuablesListApp();
-}
diff --git a/app/assets/javascripts/pages/projects/issues/show/index.js b/app/assets/javascripts/pages/projects/issues/show/index.js
index d0b1942f2a4..46a34c025b6 100644
--- a/app/assets/javascripts/pages/projects/issues/show/index.js
+++ b/app/assets/javascripts/pages/projects/issues/show/index.js
@@ -1,7 +1,7 @@
+import { initShow } from '~/issues';
import { store } from '~/notes/stores';
import initRelatedIssues from '~/related_issues';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
-import initShow from '~/issues/show';
initShow();
initSidebarBundle(store);
diff --git a/app/assets/javascripts/pages/projects/labels/edit/index.js b/app/assets/javascripts/pages/projects/labels/edit/index.js
index c4d7af39767..cb554e3d4da 100644
--- a/app/assets/javascripts/pages/projects/labels/edit/index.js
+++ b/app/assets/javascripts/pages/projects/labels/edit/index.js
@@ -1,3 +1,5 @@
import Labels from 'ee_else_ce/labels/labels';
+import { initDeleteLabelModal } from '~/labels';
new Labels(); // eslint-disable-line no-new
+initDeleteLabelModal();
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
index acd1731a700..e284e7b2c5e 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -2,13 +2,14 @@ import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { initCsvImportExportButtons, initIssuableByEmail } from '~/issuable';
-import issuableInitBulkUpdateSidebar from '~/issuable/bulk_update_sidebar/issuable_init_bulk_update_sidebar';
+import { initBulkUpdateSidebar, initIssueStatusSelect } from '~/issuable/bulk_update_sidebar';
import { FILTERED_SEARCH } from '~/filtered_search/constants';
import { ISSUABLE_INDEX } from '~/issuable/constants';
import initFilteredSearch from '~/pages/search/init_filtered_search';
import UsersSelect from '~/users_select';
-issuableInitBulkUpdateSidebar.init(ISSUABLE_INDEX.MERGE_REQUEST);
+initBulkUpdateSidebar(ISSUABLE_INDEX.MERGE_REQUEST);
+initIssueStatusSelect();
addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys);
IssuableFilteredSearchTokenKeys.removeTokensForKeys('iteration');
diff --git a/app/assets/javascripts/pages/projects/new/index.js b/app/assets/javascripts/pages/projects/new/index.js
index d89b4d0e0a3..5d830872ed9 100644
--- a/app/assets/javascripts/pages/projects/new/index.js
+++ b/app/assets/javascripts/pages/projects/new/index.js
@@ -1,5 +1,5 @@
import { initNewProjectCreation, initNewProjectUrlSelect } from '~/projects/new';
-import initProjectVisibilitySelector from '~/project_visibility';
+import initProjectVisibilitySelector from '~/projects/project_visibility';
import initProjectNew from '~/projects/project_new';
initProjectVisibilitySelector();
diff --git a/app/assets/javascripts/pages/projects/packages/packages/index.js b/app/assets/javascripts/pages/projects/packages/packages/index.js
new file mode 100644
index 00000000000..cbe08565cfa
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/packages/packages/index.js
@@ -0,0 +1,8 @@
+import packageApp from '~/packages_and_registries/package_registry/index';
+
+const app = packageApp();
+
+if (app) {
+ app.attachBreadcrumb();
+ app.attachMainComponent();
+}
diff --git a/app/assets/javascripts/pages/projects/packages/packages/index/index.js b/app/assets/javascripts/pages/projects/packages/packages/index/index.js
deleted file mode 100644
index 174973a9fad..00000000000
--- a/app/assets/javascripts/pages/projects/packages/packages/index/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import packageApp from '~/packages_and_registries/package_registry/index';
-
-packageApp();
diff --git a/app/assets/javascripts/pages/projects/packages/packages/show/index.js b/app/assets/javascripts/pages/projects/packages/packages/show/index.js
deleted file mode 100644
index 2dee87985cb..00000000000
--- a/app/assets/javascripts/pages/projects/packages/packages/show/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-import initPackageDetails from '~/packages_and_registries/package_registry/pages/details';
-
-initPackageDetails();
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
index e92b9b30fa4..277d2e0d30a 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
@@ -1,6 +1,6 @@
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
-const defaultTimezone = { name: 'UTC', offset: 0 };
+const defaultTimezone = { identifier: 'Etc/UTC', name: 'UTC', offset: 0 };
const defaults = {
$inputEl: null,
$dropdownEl: null,
@@ -70,7 +70,7 @@ export default class TimezoneDropdown {
setDropdownValue(timezone) {
this.$dropdownToggle.text(this.displayFormat(timezone));
- this.$input.val(timezone.name);
+ this.$input.val(timezone.identifier);
}
handleDropdownChange({ selectedObj, e }) {
diff --git a/app/assets/javascripts/pages/projects/services/edit/index.js b/app/assets/javascripts/pages/projects/services/edit/index.js
index a2b18d86240..2048d3dfc37 100644
--- a/app/assets/javascripts/pages/projects/services/edit/index.js
+++ b/app/assets/javascripts/pages/projects/services/edit/index.js
@@ -2,7 +2,7 @@ import initIntegrationSettingsForm from '~/integrations/edit';
import PrometheusAlerts from '~/prometheus_alerts';
import CustomMetrics from '~/prometheus_metrics/custom_metrics';
-initIntegrationSettingsForm('.js-integration-settings-form');
+initIntegrationSettingsForm();
const prometheusSettingsSelector = '.js-prometheus-metrics-monitoring';
const prometheusSettingsWrapper = document.querySelector(prometheusSettingsSelector);
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 384ee1f5034..d5e00f54e91 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -1,6 +1,6 @@
<script>
-import { GlIcon, GlSprintf, GlLink, GlFormCheckbox, GlToggle } from '@gitlab/ui';
-
+import { GlButton, GlIcon, GlSprintf, GlLink, GlFormCheckbox, GlToggle } from '@gitlab/ui';
+import ConfirmDanger from '~/vue_shared/components/confirm_danger/confirm_danger.vue';
import settingsMixin from 'ee_else_ce/pages/projects/shared/permissions/mixins/settings_pannel_mixin';
import { __, s__ } from '~/locale';
import {
@@ -41,16 +41,19 @@ export default {
pucWarningHelpText: s__(
'ProjectSettings|Highlight the usage of hidden unicode characters. These have innocent uses for right-to-left languages, but can also be used in potential exploits.',
),
+ confirmButtonText: __('Save changes'),
},
components: {
projectFeatureSetting,
projectSettingRow,
+ GlButton,
GlIcon,
GlSprintf,
GlLink,
GlFormCheckbox,
GlToggle,
+ ConfirmDanger,
},
mixins: [settingsMixin],
@@ -163,6 +166,15 @@ export default {
required: false,
default: '',
},
+ confirmationPhrase: {
+ type: String,
+ required: true,
+ },
+ showVisibilityConfirmModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
const defaults = {
@@ -274,6 +286,12 @@ export default {
cveIdRequestIsDisabled() {
return this.visibilityLevel !== visibilityOptions.PUBLIC;
},
+ isVisibilityReduced() {
+ return (
+ this.showVisibilityConfirmModal &&
+ this.visibilityLevel < this.currentSettings.visibilityLevel
+ );
+ },
},
watch: {
@@ -774,5 +792,23 @@ export default {
<template #help>{{ $options.i18n.pucWarningHelpText }}</template>
</gl-form-checkbox>
</project-setting-row>
+ <confirm-danger
+ v-if="isVisibilityReduced"
+ button-variant="confirm"
+ :disabled="false"
+ :phrase="confirmationPhrase"
+ :button-text="$options.i18n.confirmButtonText"
+ data-testid="project-features-save-button"
+ @confirm="$emit('confirm')"
+ />
+ <gl-button
+ v-else
+ type="submit"
+ variant="confirm"
+ data-testid="project-features-save-button"
+ data-qa-selector="visibility_features_permissions_save_button"
+ >
+ {{ $options.i18n.confirmButtonText }}
+ </gl-button>
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/index.js b/app/assets/javascripts/pages/projects/shared/permissions/index.js
index d7bae44e96e..de8b1cc400e 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/index.js
+++ b/app/assets/javascripts/pages/projects/shared/permissions/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
import settingsPanel from './components/settings_panel.vue';
export default function initProjectPermissionsSettings() {
@@ -6,8 +7,36 @@ export default function initProjectPermissionsSettings() {
const componentPropsEl = document.querySelector('.js-project-permissions-form-data');
const componentProps = JSON.parse(componentPropsEl.innerHTML);
+ const {
+ targetFormId,
+ additionalInformation,
+ confirmDangerMessage,
+ confirmButtonText,
+ showVisibilityConfirmModal,
+ htmlConfirmationMessage,
+ phrase: confirmationPhrase,
+ } = mountPoint.dataset;
+
return new Vue({
el: mountPoint,
- render: (createElement) => createElement(settingsPanel, { props: { ...componentProps } }),
+ provide: {
+ additionalInformation,
+ confirmDangerMessage,
+ confirmButtonText,
+ htmlConfirmationMessage: parseBoolean(htmlConfirmationMessage),
+ },
+ render: (createElement) =>
+ createElement(settingsPanel, {
+ props: {
+ ...componentProps,
+ confirmationPhrase,
+ showVisibilityConfirmModal: parseBoolean(showVisibilityConfirmModal),
+ },
+ on: {
+ confirm: () => {
+ if (targetFormId) document.getElementById(targetFormId)?.submit();
+ },
+ },
+ }),
});
}
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 31d69a731fe..71c6773c176 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,16 +1,16 @@
import initTree from 'ee_else_ce/repository';
import Activities from '~/activities';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
-import { BlobViewer } from '~/blob/viewer/index';
+import { BlobViewer } from '~/blob/viewer';
import { initUploadForm } from '~/blob_edit/blob_bundle';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import leaveByUrl from '~/namespaces/leave_by_url';
import initVueNotificationsDropdown from '~/notifications';
+import Star from '~/projects/star';
import { initUploadFileTrigger } from '~/projects/upload_file';
import initReadMore from '~/read_more';
import UserCallout from '~/user_callout';
-import Star from '../../../star';
initReadMore();
new Star(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/registrations/new/index.js b/app/assets/javascripts/pages/registrations/new/index.js
index ae605edeaf0..8bbe81a9ed5 100644
--- a/app/assets/javascripts/pages/registrations/new/index.js
+++ b/app/assets/javascripts/pages/registrations/new/index.js
@@ -1,3 +1,5 @@
+import { trackNewRegistrations } from '~/google_tag_manager';
+
import NoEmojiValidator from '~/emoji/no_emoji_validator';
import LengthValidator from '~/pages/sessions/new/length_validator';
import UsernameValidator from '~/pages/sessions/new/username_validator';
@@ -5,3 +7,5 @@ import UsernameValidator from '~/pages/sessions/new/username_validator';
new UsernameValidator(); // eslint-disable-line no-new
new LengthValidator(); // eslint-disable-line no-new
new NoEmojiValidator(); // eslint-disable-line no-new
+
+trackNewRegistrations();
diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
index b29e9455755..c28de88554a 100644
--- a/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
+++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_form.vue
@@ -596,7 +596,9 @@ export default {
:disabled="disableSubmitButton"
>{{ submitButtonText }}</gl-button
>
- <gl-button :href="cancelFormPath" class="float-right">{{ $options.i18n.cancel }}</gl-button>
+ <gl-button data-testid="wiki-cancel-button" :href="cancelFormPath" class="float-right">{{
+ $options.i18n.cancel
+ }}</gl-button>
</div>
</gl-form>
</template>
diff --git a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue
index 9f82d4a5395..ca78f194a82 100644
--- a/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue
+++ b/app/assets/javascripts/pipeline_editor/components/commit/commit_form.vue
@@ -142,6 +142,7 @@ export default {
class="js-no-auto-disable"
category="primary"
variant="confirm"
+ data-qa-selector="commit_changes_button"
:disabled="submitDisabled"
:loading="isSaving"
>
diff --git a/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue b/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue
index 92fa411d5af..bfbf24c6b13 100644
--- a/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue
+++ b/app/assets/javascripts/pipeline_editor/components/editor/text_editor.vue
@@ -15,12 +15,10 @@ export default {
onCiConfigUpdate(content) {
this.$emit('updateCiConfig', content);
},
- registerCiSchema() {
+ registerCiSchema({ detail: { instance } }) {
if (this.glFeatures.schemaLinting) {
- const editorInstance = this.$refs.editor.getEditor();
-
- editorInstance.use({ definition: CiSchemaExtension });
- editorInstance.registerCiSchema();
+ instance.use({ definition: CiSchemaExtension });
+ instance.registerCiSchema();
}
},
},
@@ -33,7 +31,7 @@ export default {
ref="editor"
:file-name="ciConfigPath"
v-bind="$attrs"
- @[$options.readyEvent]="registerCiSchema"
+ @[$options.readyEvent]="registerCiSchema($event)"
@input="onCiConfigUpdate"
v-on="$listeners"
/>
diff --git a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
index 16ad648afca..72b492a5877 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue
@@ -153,7 +153,9 @@ export default {
<span class="gl-font-weight-bold">
<gl-sprintf :message="$options.i18n.pipelineInfo">
<template #id="{ content }">
- <span data-testid="pipeline-id"> {{ content }}{{ pipelineId }} </span>
+ <span data-testid="pipeline-id" data-qa-selector="pipeline_id_content">
+ {{ content }}{{ pipelineId }}
+ </span>
</template>
<template #status>{{ status.text }}</template>
<template #commit>
diff --git a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
index 833d784f940..23f1592cac1 100644
--- a/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
+++ b/app/assets/javascripts/pipeline_editor/components/header/validation_segment.vue
@@ -5,6 +5,7 @@ import getAppStatus from '~/pipeline_editor/graphql/queries/client/app_status.qu
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue';
import {
EDITOR_APP_STATUS_EMPTY,
+ EDITOR_APP_STATUS_LINT_UNAVAILABLE,
EDITOR_APP_STATUS_LOADING,
EDITOR_APP_STATUS_VALID,
} from '../../constants';
@@ -17,6 +18,7 @@ export const i18n = {
loading: s__('Pipelines|Validating GitLab CI configuration…'),
invalid: s__('Pipelines|This GitLab CI configuration is invalid.'),
invalidWithReason: s__('Pipelines|This GitLab CI configuration is invalid: %{reason}.'),
+ unavailableValidation: s__('Pipelines|Configuration validation currently not available.'),
valid: s__('Pipelines|This GitLab CI configuration is valid.'),
};
@@ -29,6 +31,9 @@ export default {
TooltipOnTruncate,
},
inject: {
+ lintUnavailableHelpPagePath: {
+ default: '',
+ },
ymlHelpPagePath: {
default: '',
},
@@ -49,9 +54,15 @@ export default {
},
},
computed: {
+ helpPath() {
+ return this.isLintUnavailable ? this.lintUnavailableHelpPagePath : this.ymlHelpPagePath;
+ },
isEmpty() {
return this.appStatus === EDITOR_APP_STATUS_EMPTY;
},
+ isLintUnavailable() {
+ return this.appStatus === EDITOR_APP_STATUS_LINT_UNAVAILABLE;
+ },
isLoading() {
return this.appStatus === EDITOR_APP_STATUS_LOADING;
},
@@ -62,6 +73,8 @@ export default {
switch (this.appStatus) {
case EDITOR_APP_STATUS_EMPTY:
return 'check';
+ case EDITOR_APP_STATUS_LINT_UNAVAILABLE:
+ return 'time-out';
case EDITOR_APP_STATUS_VALID:
return 'check';
default:
@@ -74,6 +87,8 @@ export default {
switch (this.appStatus) {
case EDITOR_APP_STATUS_EMPTY:
return this.$options.i18n.empty;
+ case EDITOR_APP_STATUS_LINT_UNAVAILABLE:
+ return this.$options.i18n.unavailableValidation;
case EDITOR_APP_STATUS_VALID:
return this.$options.i18n.valid;
default:
@@ -96,10 +111,13 @@ export default {
<span v-else class="gl-display-inline-flex gl-white-space-nowrap gl-max-w-full">
<tooltip-on-truncate :title="message" class="gl-text-truncate">
- <gl-icon :name="icon" /> <span data-testid="validationMsg">{{ message }}</span>
+ <gl-icon :name="icon" />
+ <span data-qa-selector="validation_message_content" data-testid="validationMsg">
+ {{ message }}
+ </span>
</tooltip-on-truncate>
<span v-if="!isEmpty" class="gl-flex-shrink-0 gl-pl-2">
- <gl-link data-testid="learnMoreLink" :href="ymlHelpPagePath">
+ <gl-link data-testid="learnMoreLink" :href="helpPath">
{{ $options.i18n.learnMore }}
</gl-link>
</span>
diff --git a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
index 3f50a1225d8..c75b1d4bb11 100644
--- a/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
+++ b/app/assets/javascripts/pipeline_editor/components/pipeline_editor_tabs.vue
@@ -11,6 +11,7 @@ import {
EDITOR_APP_STATUS_INVALID,
EDITOR_APP_STATUS_LOADING,
EDITOR_APP_STATUS_VALID,
+ EDITOR_APP_STATUS_LINT_UNAVAILABLE,
LINT_TAB,
MERGED_TAB,
TAB_QUERY_PARAM,
@@ -106,6 +107,9 @@ export default {
isInvalid() {
return this.appStatus === EDITOR_APP_STATUS_INVALID;
},
+ isLintUnavailable() {
+ return this.appStatus === EDITOR_APP_STATUS_LINT_UNAVAILABLE;
+ },
isValid() {
return this.appStatus === EDITOR_APP_STATUS_VALID;
},
@@ -142,6 +146,7 @@ export default {
<template>
<gl-tabs
class="file-editor gl-mb-3"
+ data-qa-selector="file_editor_container"
:query-param-name="$options.query.TAB_QUERY_PARAM"
sync-active-tab-with-query-params
>
@@ -166,6 +171,7 @@ export default {
:empty-message="$options.i18n.empty.visualization"
:is-empty="isEmpty"
:is-invalid="isInvalid"
+ :is-unavailable="isLintUnavailable"
:keep-component-mounted="false"
:title="$options.i18n.tabGraph"
lazy
@@ -179,6 +185,7 @@ export default {
class="gl-mb-3"
:empty-message="$options.i18n.empty.lint"
:is-empty="isEmpty"
+ :is-unavailable="isLintUnavailable"
:title="$options.i18n.tabLint"
data-testid="lint-tab"
@click="setCurrentTab($options.tabConstants.LINT_TAB)"
@@ -192,6 +199,7 @@ export default {
:keep-component-mounted="false"
:is-empty="isEmpty"
:is-invalid="isInvalid"
+ :is-unavailable="isLintUnavailable"
:title="$options.i18n.tabMergedYaml"
lazy
data-testid="merged-tab"
diff --git a/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue b/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue
index 7c032441a04..673599da085 100644
--- a/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue
+++ b/app/assets/javascripts/pipeline_editor/components/ui/editor_tab.vue
@@ -42,6 +42,9 @@ import { __, s__ } from '~/locale';
export default {
i18n: {
invalid: __('Your CI/CD configuration syntax is invalid. View Lint tab for more details.'),
+ unavailable: __(
+ "We're experiencing difficulties and this tab content is currently unavailable.",
+ ),
},
components: {
GlAlert,
@@ -66,14 +69,14 @@ export default {
isEmpty: {
type: Boolean,
required: false,
- default: null,
+ default: false,
},
isInvalid: {
type: Boolean,
required: false,
- default: null,
+ default: false,
},
- lazy: {
+ isUnavailable: {
type: Boolean,
required: false,
default: false,
@@ -83,6 +86,11 @@ export default {
required: false,
default: true,
},
+ lazy: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -109,6 +117,9 @@ export default {
<template>
<gl-tab :lazy="isLazy" v-bind="$attrs" v-on="$listeners">
<gl-alert v-if="isEmpty" variant="tip">{{ emptyMessage }}</gl-alert>
+ <gl-alert v-else-if="isUnavailable" variant="danger" :dismissible="false">
+ {{ $options.i18n.unavailable }}</gl-alert
+ >
<gl-alert v-else-if="isInvalid" variant="danger">{{ $options.i18n.invalid }}</gl-alert>
<template v-else>
<slot v-for="slot in slots" :name="slot"></slot>
diff --git a/app/assets/javascripts/pipeline_editor/constants.js b/app/assets/javascripts/pipeline_editor/constants.js
index a2eaeeef286..bc79b0742e7 100644
--- a/app/assets/javascripts/pipeline_editor/constants.js
+++ b/app/assets/javascripts/pipeline_editor/constants.js
@@ -6,12 +6,14 @@ export const CI_CONFIG_STATUS_VALID = 'VALID';
// represent the global state of the pipeline editor app.
export const EDITOR_APP_STATUS_EMPTY = 'EMPTY';
export const EDITOR_APP_STATUS_INVALID = CI_CONFIG_STATUS_INVALID;
+export const EDITOR_APP_STATUS_LINT_UNAVAILABLE = 'LINT_DOWN';
export const EDITOR_APP_STATUS_LOADING = 'LOADING';
export const EDITOR_APP_STATUS_VALID = CI_CONFIG_STATUS_VALID;
export const EDITOR_APP_VALID_STATUSES = [
EDITOR_APP_STATUS_EMPTY,
EDITOR_APP_STATUS_INVALID,
+ EDITOR_APP_STATUS_LINT_UNAVAILABLE,
EDITOR_APP_STATUS_LOADING,
EDITOR_APP_STATUS_VALID,
];
diff --git a/app/assets/javascripts/pipeline_editor/index.js b/app/assets/javascripts/pipeline_editor/index.js
index ee93e327b76..04f91cb3d1e 100644
--- a/app/assets/javascripts/pipeline_editor/index.js
+++ b/app/assets/javascripts/pipeline_editor/index.js
@@ -37,6 +37,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
emptyStateIllustrationPath,
helpPaths,
lintHelpPagePath,
+ lintUnavailableHelpPagePath,
needsHelpPagePath,
newMergeRequestPath,
pipelinePagePath,
@@ -124,6 +125,7 @@ export const initPipelineEditor = (selector = '#js-pipeline-editor') => {
emptyStateIllustrationPath,
helpPaths,
lintHelpPagePath,
+ lintUnavailableHelpPagePath,
needsHelpPagePath,
newMergeRequestPath,
pipelinePagePath,
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
index e397054f06a..90f48195c5e 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue
@@ -12,8 +12,9 @@ import PipelineEditorMessages from './components/ui/pipeline_editor_messages.vue
import {
COMMIT_SHA_POLL_INTERVAL,
EDITOR_APP_STATUS_EMPTY,
- EDITOR_APP_VALID_STATUSES,
EDITOR_APP_STATUS_LOADING,
+ EDITOR_APP_STATUS_LINT_UNAVAILABLE,
+ EDITOR_APP_VALID_STATUSES,
LOAD_FAILURE_UNKNOWN,
STARTER_TEMPLATE_NAME,
} from './constants';
@@ -51,6 +52,7 @@ export default {
failureReasons: [],
initialCiFileContent: '',
isFetchingCommitSha: false,
+ isLintUnavailable: false,
isNewCiConfigFile: false,
lastCommittedContent: '',
shouldSkipStartScreen: false,
@@ -147,10 +149,19 @@ export default {
return { ...ciConfig, stages };
},
result({ data }) {
- this.setAppStatus(data?.ciConfig?.status);
+ if (data?.ciConfig?.status) {
+ this.setAppStatus(data.ciConfig.status);
+ if (this.isLintUnavailable) {
+ this.isLintUnavailable = false;
+ }
+ }
},
- error(err) {
- this.reportFailure(LOAD_FAILURE_UNKNOWN, [String(err)]);
+ error() {
+ // We are not using `reportFailure` here because we don't
+ // need to bring attention to the linter being down. We let
+ // the user work on their file and if they look at their
+ // lint status, they will notice that the service is down
+ this.isLintUnavailable = true;
},
watchLoading(isLoading) {
if (isLoading) {
@@ -247,6 +258,13 @@ export default {
this.setAppStatus(EDITOR_APP_STATUS_EMPTY);
}
},
+ isLintUnavailable(flag) {
+ if (flag) {
+ // We cannot set this status directly in the `error`
+ // hook otherwise we get an infinite loop caused by apollo.
+ this.setAppStatus(EDITOR_APP_STATUS_LINT_UNAVAILABLE);
+ }
+ },
},
mounted() {
this.loadTemplateFromURL();
@@ -269,14 +287,10 @@ export default {
await this.$apollo.queries.initialCiFileContent.refetch();
},
reportFailure(type, reasons = []) {
- const isCurrentFailure = this.failureType === type && this.failureReasons[0] === reasons[0];
-
- if (!isCurrentFailure) {
- this.showFailure = true;
- this.failureType = type;
- this.failureReasons = reasons;
- window.scrollTo({ top: 0, behavior: 'smooth' });
- }
+ this.showFailure = true;
+ this.failureType = type;
+ this.failureReasons = reasons;
+ window.scrollTo({ top: 0, behavior: 'smooth' });
},
reportSuccess(type) {
window.scrollTo({ top: 0, behavior: 'smooth' });
@@ -289,7 +303,10 @@ export default {
},
setAppStatus(appStatus) {
if (EDITOR_APP_VALID_STATUSES.includes(appStatus)) {
- this.$apollo.mutate({ mutation: updateAppStatus, variables: { appStatus } });
+ this.$apollo.mutate({
+ mutation: updateAppStatus,
+ variables: { appStatus },
+ });
}
},
setNewEmptyCiConfigFile() {
diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
index 8e8f31a4acc..96680080f0c 100644
--- a/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
+++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_home.vue
@@ -90,7 +90,7 @@ export default {
</script>
<template>
- <div class="gl-pr-9 gl-transition-medium gl-w-full">
+ <div class="gl-pr-10 gl-transition-medium gl-w-full">
<gl-modal
v-if="showSwitchBranchModal"
visible
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index 4db6a3c9fd8..8088858f381 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -212,7 +212,9 @@ export default {
</script>
<template>
<div class="js-pipeline-header-container">
- <gl-alert v-if="hasError" :variant="failure.variant">{{ failure.text }}</gl-alert>
+ <gl-alert v-if="hasError" :variant="failure.variant" :dismissible="false">{{
+ failure.text
+ }}</gl-alert>
<ci-header
v-if="shouldRenderContent"
:status="pipeline.detailedStatus"
diff --git a/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue b/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue
index ffac8206b58..e11073aee33 100644
--- a/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue
+++ b/app/assets/javascripts/pipelines/components/jobs/jobs_app.vue
@@ -112,7 +112,7 @@ export default {
</gl-skeleton-loader>
</div>
- <jobs-table v-else :jobs="jobs" :table-fields="$options.fields" />
+ <jobs-table v-else :jobs="jobs" :table-fields="$options.fields" data-testid="jobs-tab-table" />
<gl-intersection-observer v-if="jobsPageInfo.hasNextPage" @appear="fetchMoreJobs">
<gl-loading-icon v-if="$apollo.loading" size="md" />
diff --git a/app/assets/javascripts/pipelines/components/parsing_utils.js b/app/assets/javascripts/pipelines/components/parsing_utils.js
index fa7330ce890..cae4e11c13f 100644
--- a/app/assets/javascripts/pipelines/components/parsing_utils.js
+++ b/app/assets/javascripts/pipelines/components/parsing_utils.js
@@ -1,5 +1,6 @@
import { memoize } from 'lodash';
import { createNodeDict } from '../utils';
+import { EXPLICIT_NEEDS_PROPERTY, NEEDS_PROPERTY } from '../constants';
import { createSankey } from './dag/drawing_utils';
/*
@@ -15,12 +16,14 @@ const deduplicate = (item, itemIndex, arr) => {
return foundIdx === itemIndex;
};
-export const makeLinksFromNodes = (nodes, nodeDict) => {
+export const makeLinksFromNodes = (nodes, nodeDict, { needsKey = NEEDS_PROPERTY } = {}) => {
const constantLinkValue = 10; // all links are the same weight
return nodes
.map(({ jobs, name: groupName }) =>
- jobs.map(({ needs = [] }) =>
- needs.reduce((acc, needed) => {
+ jobs.map((job) => {
+ const needs = job[needsKey] || [];
+
+ return needs.reduce((acc, needed) => {
// It's possible that we have an optional job, which
// is being needed by another job. In that scenario,
// the needed job doesn't exist, so we don't want to
@@ -34,8 +37,8 @@ export const makeLinksFromNodes = (nodes, nodeDict) => {
}
return acc;
- }, []),
- ),
+ }, []);
+ }),
)
.flat(2);
};
@@ -76,9 +79,9 @@ export const filterByAncestors = (links, nodeDict) =>
return !allAncestors.includes(source);
});
-export const parseData = (nodes) => {
- const nodeDict = createNodeDict(nodes);
- const allLinks = makeLinksFromNodes(nodes, nodeDict);
+export const parseData = (nodes, { needsKey = NEEDS_PROPERTY } = {}) => {
+ const nodeDict = createNodeDict(nodes, { needsKey });
+ const allLinks = makeLinksFromNodes(nodes, nodeDict, { needsKey });
const filteredLinks = allLinks.filter(deduplicate);
const links = filterByAncestors(filteredLinks, nodeDict);
@@ -123,7 +126,8 @@ export const removeOrphanNodes = (sankeyfiedNodes) => {
export const listByLayers = ({ stages }) => {
const arrayOfJobs = stages.flatMap(({ groups }) => groups);
const parsedData = parseData(arrayOfJobs);
- const dataWithLayers = createSankey()(parsedData);
+ const explicitParsedData = parseData(arrayOfJobs, { needsKey: EXPLICIT_NEEDS_PROPERTY });
+ const dataWithLayers = createSankey()(explicitParsedData);
const pipelineLayers = dataWithLayers.nodes.reduce((acc, { layer, name }) => {
/* sort groups by layer */
diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue
index 64210576b29..8daf85e2b2e 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue
@@ -132,6 +132,7 @@ export default {
:ref="$options.CONTAINER_REF"
class="gl-bg-gray-10 gl-overflow-auto"
data-testid="graph-container"
+ data-qa-selector="pipeline_graph_container"
>
<links-layer
:pipeline-data="pipelineStages"
@@ -147,7 +148,10 @@ export default {
:key="`${stage.name}-${index}`"
class="gl-flex-direction-column"
>
- <div class="gl-display-flex gl-align-items-center gl-w-full gl-px-9 gl-py-4 gl-mb-5">
+ <div
+ class="gl-display-flex gl-align-items-center gl-w-full gl-px-9 gl-py-4 gl-mb-5"
+ data-qa-selector="stage_container"
+ >
<stage-name :stage-name="stage.name" />
</div>
<div :class="$options.jobWrapperClasses">
@@ -158,6 +162,7 @@ export default {
:pipeline-id="$options.PIPELINE_ID"
:is-hovered="highlightedJob === group.name"
:is-faded-out="isFadedOut(group.name)"
+ data-qa-selector="job_container"
@on-mouse-enter="setHoveredJob"
@on-mouse-leave="removeHoveredJob"
/>
diff --git a/app/assets/javascripts/pipelines/components/unwrapping_utils.js b/app/assets/javascripts/pipelines/components/unwrapping_utils.js
index 2d24beb8323..d42a11c3aba 100644
--- a/app/assets/javascripts/pipelines/components/unwrapping_utils.js
+++ b/app/assets/javascripts/pipelines/components/unwrapping_utils.js
@@ -1,4 +1,5 @@
import { reportToSentry } from '../utils';
+import { EXPLICIT_NEEDS_PROPERTY, NEEDS_PROPERTY } from '../constants';
const unwrapGroups = (stages) => {
return stages.map((stage, idx) => {
@@ -27,12 +28,16 @@ const unwrapNodesWithName = (jobArray, prop, field = 'name') => {
}
return jobArray.map((job) => {
- return { ...job, [prop]: job[prop].nodes.map((item) => item[field] || '') };
+ if (job[prop]) {
+ return { ...job, [prop]: job[prop].nodes.map((item) => item[field] || '') };
+ }
+ return job;
});
};
const unwrapJobWithNeeds = (denodedJobArray) => {
- return unwrapNodesWithName(denodedJobArray, 'needs');
+ const explicitNeedsUnwrapped = unwrapNodesWithName(denodedJobArray, EXPLICIT_NEEDS_PROPERTY);
+ return unwrapNodesWithName(explicitNeedsUnwrapped, NEEDS_PROPERTY);
};
const unwrapStagesWithNeedsAndLookup = (denodedStages) => {
diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js
index d123f7a203c..410fc7b82cd 100644
--- a/app/assets/javascripts/pipelines/constants.js
+++ b/app/assets/javascripts/pipelines/constants.js
@@ -7,6 +7,8 @@ export const ANY_TRIGGER_AUTHOR = 'Any';
export const SUPPORTED_FILTER_PARAMETERS = ['username', 'ref', 'status', 'source'];
export const FILTER_TAG_IDENTIFIER = 'tag';
export const SCHEDULE_ORIGIN = 'schedule';
+export const NEEDS_PROPERTY = 'needs';
+export const EXPLICIT_NEEDS_PROPERTY = 'previousStageJobsOrNeeds';
export const TestStatus = {
FAILED: 'failed',
diff --git a/app/assets/javascripts/pipelines/graphql/fragmentTypes.json b/app/assets/javascripts/pipelines/graphql/fragmentTypes.json
new file mode 100644
index 00000000000..4601b74b5c1
--- /dev/null
+++ b/app/assets/javascripts/pipelines/graphql/fragmentTypes.json
@@ -0,0 +1 @@
+{"__schema":{"types":[{"kind":"UNION","name":"JobNeedUnion","possibleTypes":[{"name":"CiBuildNeed"},{"name":"CiJob"}]}]}} \ No newline at end of file
diff --git a/app/assets/javascripts/pipelines/pipeline_shared_client.js b/app/assets/javascripts/pipelines/pipeline_shared_client.js
index c3be487caae..84276588d6a 100644
--- a/app/assets/javascripts/pipelines/pipeline_shared_client.js
+++ b/app/assets/javascripts/pipelines/pipeline_shared_client.js
@@ -1,10 +1,19 @@
import VueApollo from 'vue-apollo';
+import { IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql';
+import introspectionQueryResultData from './graphql/fragmentTypes.json';
+
+export const fragmentMatcher = new IntrospectionFragmentMatcher({
+ introspectionQueryResultData,
+});
export const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(
{},
{
+ cacheConfig: {
+ fragmentMatcher,
+ },
useGet: true,
},
),
diff --git a/app/assets/javascripts/pipelines/utils.js b/app/assets/javascripts/pipelines/utils.js
index e28eb74fb1b..f6e1c8b7412 100644
--- a/app/assets/javascripts/pipelines/utils.js
+++ b/app/assets/javascripts/pipelines/utils.js
@@ -1,6 +1,6 @@
import * as Sentry from '@sentry/browser';
import { pickBy } from 'lodash';
-import { SUPPORTED_FILTER_PARAMETERS } from './constants';
+import { SUPPORTED_FILTER_PARAMETERS, NEEDS_PROPERTY } from './constants';
/*
The following functions are the main engine in transforming the data as
@@ -35,11 +35,11 @@ import { SUPPORTED_FILTER_PARAMETERS } from './constants';
10 -> value (constant)
*/
-export const createNodeDict = (nodes) => {
+export const createNodeDict = (nodes, { needsKey = NEEDS_PROPERTY } = {}) => {
return nodes.reduce((acc, node) => {
const newNode = {
...node,
- needs: node.jobs.map((job) => job.needs || []).flat(),
+ needs: node.jobs.map((job) => job[needsKey] || []).flat(),
};
if (node.size > 1) {
diff --git a/app/assets/javascripts/profile/add_ssh_key_validation.js b/app/assets/javascripts/profile/add_ssh_key_validation.js
index 5c78de7ffb0..628dd159db8 100644
--- a/app/assets/javascripts/profile/add_ssh_key_validation.js
+++ b/app/assets/javascripts/profile/add_ssh_key_validation.js
@@ -1,8 +1,17 @@
export default class AddSshKeyValidation {
- constructor(inputElement, warningElement, originalSubmitElement, confirmSubmitElement) {
+ constructor(
+ supportedAlgorithms,
+ inputElement,
+ warningElement,
+ originalSubmitElement,
+ confirmSubmitElement,
+ ) {
this.inputElement = inputElement;
this.form = inputElement.form;
+ this.supportedAlgorithms = supportedAlgorithms;
+ this.publicKeyRegExp = new RegExp(`^(${this.supportedAlgorithms.join('|')})`);
+
this.warningElement = warningElement;
this.originalSubmitElement = originalSubmitElement;
@@ -23,7 +32,7 @@ export default class AddSshKeyValidation {
}
submit(event) {
- this.isValid = AddSshKeyValidation.isPublicKey(this.inputElement.value);
+ this.isValid = this.isPublicKey(this.inputElement.value);
if (this.isValid) return true;
@@ -37,7 +46,7 @@ export default class AddSshKeyValidation {
this.originalSubmitElement.classList.toggle('hide', isVisible);
}
- static isPublicKey(value) {
- return /^(ssh|ecdsa-sha2)-/.test(value);
+ isPublicKey(value) {
+ return this.publicKeyRegExp.test(value);
}
}
diff --git a/app/assets/javascripts/project_select_combo_button.js b/app/assets/javascripts/project_select_combo_button.js
index fd45d643ecc..09dbf2cee04 100644
--- a/app/assets/javascripts/project_select_combo_button.js
+++ b/app/assets/javascripts/project_select_combo_button.js
@@ -1,11 +1,12 @@
import $ from 'jquery';
+import { sprintf, __ } from '~/locale';
import AccessorUtilities from './lib/utils/accessor';
import { loadCSSFile } from './lib/utils/css_utils';
export default class ProjectSelectComboButton {
constructor(select) {
this.projectSelectInput = $(select);
- this.newItemBtn = $('.new-project-item-link');
+ this.newItemBtn = $('.js-new-project-item-link');
this.resourceType = this.newItemBtn.data('type');
this.resourceLabel = this.newItemBtn.data('label');
this.formattedText = this.deriveTextVariants();
@@ -80,9 +81,18 @@ export default class ProjectSelectComboButton {
setNewItemBtnAttributes(project) {
if (project) {
this.newItemBtn.attr('href', project.url);
- this.newItemBtn.text(`${this.formattedText.defaultTextPrefix} in ${project.name}`);
+ this.newItemBtn.text(
+ sprintf(__('New %{type} in %{project}'), {
+ type: this.resourceLabel,
+ project: project.name,
+ }),
+ );
} else {
- this.newItemBtn.text(`Select project to create ${this.formattedText.presetTextSuffix}`);
+ this.newItemBtn.text(
+ sprintf(__('Select project to create %{type}'), {
+ type: this.formattedText.presetTextSuffix,
+ }),
+ );
}
}
@@ -99,15 +109,12 @@ export default class ProjectSelectComboButton {
}
deriveTextVariants() {
- const defaultTextPrefix = this.resourceLabel;
-
// the trailing slice call depluralizes each of these strings (e.g. new-issues -> new-issue)
const localStorageItemType = `new-${this.resourceType.split('_').join('-').slice(0, -1)}`;
const presetTextSuffix = this.resourceType.split('_').join(' ').slice(0, -1);
return {
localStorageItemType, // new-issue / new-merge-request
- defaultTextPrefix, // New issue / New merge request
presetTextSuffix, // issue / merge request
};
}
diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/projects/project_find_file.js
index d295c06928f..d295c06928f 100644
--- a/app/assets/javascripts/project_find_file.js
+++ b/app/assets/javascripts/projects/project_find_file.js
diff --git a/app/assets/javascripts/project_import.js b/app/assets/javascripts/projects/project_import.js
index a51a2a2242f..27a218f1f52 100644
--- a/app/assets/javascripts/project_import.js
+++ b/app/assets/javascripts/projects/project_import.js
@@ -1,4 +1,4 @@
-import { visitUrl } from './lib/utils/url_utility';
+import { visitUrl } from '~/lib/utils/url_utility';
export default function projectImport() {
setTimeout(() => {
diff --git a/app/assets/javascripts/project_visibility.js b/app/assets/javascripts/projects/project_visibility.js
index 1b57a69d464..c962554c9f4 100644
--- a/app/assets/javascripts/project_visibility.js
+++ b/app/assets/javascripts/projects/project_visibility.js
@@ -1,4 +1,6 @@
import $ from 'jquery';
+import { escape } from 'lodash';
+import { __, sprintf } from '~/locale';
import eventHub from '~/projects/new/event_hub';
// Values are from lib/gitlab/visibility_level.rb
@@ -25,10 +27,21 @@ function setVisibilityOptions({ name, visibility, showPath, editPath }) {
if (reason) {
const optionTitle = option.querySelector('.option-title');
const optionName = optionTitle ? optionTitle.innerText.toLowerCase() : '';
- reason.innerHTML = `This project cannot be ${optionName} because the visibility of
- <a href="${showPath}">${name}</a> is ${visibility}. To make this project
- ${optionName}, you must first <a href="${editPath}">change the visibility</a>
- of the parent group.`;
+ reason.innerHTML = sprintf(
+ __(
+ 'This project cannot be %{visibilityLevel} because the visibility of %{openShowLink}%{name}%{closeShowLink} is %{visibility}. To make this project %{visibilityLevel}, you must first %{openEditLink}change the visibility%{closeEditLink} of the parent group.',
+ ),
+ {
+ visibilityLevel: optionName,
+ name: escape(name),
+ visibility,
+ openShowLink: `<a href="${showPath}">`,
+ closeShowLink: '</a>',
+ openEditLink: `<a href="${editPath}">`,
+ closeEditLink: '</a>',
+ },
+ false,
+ );
}
} else {
option.classList.remove('disabled');
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/projects/star.js
index 7cba445d9b1..578e22ca25d 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/projects/star.js
@@ -1,8 +1,8 @@
import $ from 'jquery';
-import createFlash from './flash';
-import axios from './lib/utils/axios_utils';
-import { spriteIcon } from './lib/utils/common_utils';
-import { __, s__ } from './locale';
+import createFlash from '~/flash';
+import axios from '~/lib/utils/axios_utils';
+import { spriteIcon } from '~/lib/utils/common_utils';
+import { __, s__ } from '~/locale';
export default class Star {
constructor(container = '.project-home-panel') {
diff --git a/app/assets/javascripts/related_issues/components/related_issues_list.vue b/app/assets/javascripts/related_issues/components/related_issues_list.vue
index 58138655241..8b39851405e 100644
--- a/app/assets/javascripts/related_issues/components/related_issues_list.vue
+++ b/app/assets/javascripts/related_issues/components/related_issues_list.vue
@@ -110,7 +110,7 @@ export default {
v-for="issue in relatedIssues"
:key="issue.id"
:class="{
- 'user-can-drag': canReorder,
+ 'gl-cursor-grab': canReorder,
'sortable-row': canReorder,
'card card-slim': canReorder,
}"
diff --git a/app/assets/javascripts/repository/components/blob_button_group.vue b/app/assets/javascripts/repository/components/blob_button_group.vue
index 6f540bf8ece..857795c71b0 100644
--- a/app/assets/javascripts/repository/components/blob_button_group.vue
+++ b/app/assets/javascripts/repository/components/blob_button_group.vue
@@ -1,5 +1,5 @@
<script>
-import { GlButtonGroup, GlButton, GlModalDirective } from '@gitlab/ui';
+import { GlButtonGroup, GlButton } from '@gitlab/ui';
import { uniqueId } from 'lodash';
import { sprintf, __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -20,9 +20,6 @@ export default {
DeleteBlobModal,
LockButton: () => import('ee_component/repository/components/lock_button.vue'),
},
- directives: {
- GlModal: GlModalDirective,
- },
mixins: [getRefMixin, glFeatureFlagMixin()],
inject: {
targetBranch: {
@@ -73,6 +70,10 @@ export default {
type: Boolean,
required: true,
},
+ showForkSuggestion: {
+ type: Boolean,
+ required: true,
+ },
},
computed: {
replaceModalId() {
@@ -91,6 +92,16 @@ export default {
return this.canLock ? 'lock_button' : 'disabled_lock_button';
},
},
+ methods: {
+ showModal(modalId) {
+ if (this.showForkSuggestion) {
+ this.$emit('fork');
+ return;
+ }
+
+ this.$refs[modalId].show();
+ },
+ },
};
</script>
@@ -107,14 +118,15 @@ export default {
data-testid="lock"
:data-qa-selector="lockBtnQASelector"
/>
- <gl-button v-gl-modal="replaceModalId" data-testid="replace">
+ <gl-button data-testid="replace" @click="showModal(replaceModalId)">
{{ $options.i18n.replace }}
</gl-button>
- <gl-button v-gl-modal="deleteModalId" data-testid="delete">
+ <gl-button data-testid="delete" @click="showModal(deleteModalId)">
{{ $options.i18n.delete }}
</gl-button>
</gl-button-group>
<upload-blob-modal
+ :ref="replaceModalId"
:modal-id="replaceModalId"
:modal-title="replaceModalTitle"
:commit-message="replaceModalTitle"
@@ -126,6 +138,7 @@ export default {
:primary-btn-text="$options.i18n.replacePrimaryBtnText"
/>
<delete-blob-modal
+ :ref="deleteModalId"
:modal-id="deleteModalId"
:modal-title="deleteModalTitle"
:delete-path="deletePath"
diff --git a/app/assets/javascripts/repository/components/blob_content_viewer.vue b/app/assets/javascripts/repository/components/blob_content_viewer.vue
index f3fa4526999..9368d7e6058 100644
--- a/app/assets/javascripts/repository/components/blob_content_viewer.vue
+++ b/app/assets/javascripts/repository/components/blob_content_viewer.vue
@@ -105,8 +105,10 @@ export default {
forkAndEditPath: '',
ideForkAndEditPath: '',
storedExternally: false,
+ externalStorage: '',
canModifyBlob: false,
canCurrentUserPushToBranch: false,
+ archived: false,
rawPath: '',
externalStorageUrl: '',
replacePath: '',
@@ -166,7 +168,7 @@ export default {
return pushCode && downloadCode;
},
pathLockedByUser() {
- const pathLock = this.project.pathLocks.nodes.find((node) => node.path === this.path);
+ const pathLock = this.project?.pathLocks?.nodes.find((node) => node.path === this.path);
return pathLock ? pathLock.user : null;
},
@@ -249,6 +251,7 @@ export default {
>
<template #actions>
<blob-edit
+ v-if="!blobInfo.archived"
:show-edit-button="!isBinaryFileType"
:edit-path="blobInfo.editBlobPath"
:web-ide-path="blobInfo.ideEditPath"
@@ -268,7 +271,7 @@ export default {
</gl-button>
<blob-button-group
- v-if="isLoggedIn"
+ v-if="isLoggedIn && !blobInfo.archived"
:path="path"
:name="blobInfo.name"
:replace-path="blobInfo.replacePath"
@@ -279,6 +282,8 @@ export default {
:project-path="projectPath"
:is-locked="Boolean(pathLockedByUser)"
:can-lock="canLock"
+ :show-fork-suggestion="showForkSuggestion"
+ @fork="setForkTarget('ide')"
/>
</template>
</blob-header>
@@ -289,6 +294,7 @@ export default {
/>
<blob-content
v-if="!blobViewer"
+ class="js-syntax-highlight"
:rich-viewer="legacyRichViewer"
:blob="blobInfo"
:content="legacySimpleViewer"
diff --git a/app/assets/javascripts/repository/components/blob_controls.vue b/app/assets/javascripts/repository/components/blob_controls.vue
new file mode 100644
index 00000000000..3223ed92fe2
--- /dev/null
+++ b/app/assets/javascripts/repository/components/blob_controls.vue
@@ -0,0 +1,119 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { __ } from '~/locale';
+import createFlash from '~/flash';
+import getRefMixin from '~/repository/mixins/get_ref';
+import initSourcegraph from '~/sourcegraph';
+import { updateElementsVisibility } from '../utils/dom';
+import blobControlsQuery from '../queries/blob_controls.query.graphql';
+
+export default {
+ i18n: {
+ findFile: __('Find file'),
+ blame: __('Blame'),
+ history: __('History'),
+ permalink: __('Permalink'),
+ errorMessage: __('An error occurred while loading the blob controls.'),
+ },
+ buttonClassList: 'gl-sm-w-auto gl-w-full gl-sm-mt-0 gl-mt-3',
+ components: {
+ GlButton,
+ },
+ mixins: [getRefMixin],
+ apollo: {
+ project: {
+ query: blobControlsQuery,
+ variables() {
+ return {
+ projectPath: this.projectPath,
+ filePath: this.filePath,
+ ref: this.ref,
+ };
+ },
+ skip() {
+ return !this.filePath;
+ },
+ error() {
+ createFlash({ message: this.$options.i18n.errorMessage });
+ },
+ },
+ },
+ props: {
+ projectPath: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ project: {
+ repository: {
+ blobs: {
+ nodes: [
+ {
+ findFilePath: null,
+ blamePath: null,
+ historyPath: null,
+ permalinkPath: null,
+ storedExternally: null,
+ externalStorage: null,
+ },
+ ],
+ },
+ },
+ },
+ };
+ },
+ computed: {
+ filePath() {
+ return this.$route.params.path;
+ },
+ showBlobControls() {
+ return this.filePath && this.$route.name === 'blobPathDecoded';
+ },
+ blobInfo() {
+ return this.project?.repository?.blobs?.nodes[0] || {};
+ },
+ showBlameButton() {
+ return !this.blobInfo.storedExternally && this.blobInfo.externalStorage !== 'lfs';
+ },
+ },
+ watch: {
+ showBlobControls(shouldShow) {
+ updateElementsVisibility('.tree-controls', !shouldShow);
+ },
+ blobInfo() {
+ initSourcegraph();
+ },
+ },
+};
+</script>
+
+<template>
+ <div v-if="showBlobControls">
+ <gl-button data-testid="find" :href="blobInfo.findFilePath" :class="$options.buttonClassList">
+ {{ $options.i18n.findFile }}
+ </gl-button>
+ <gl-button
+ v-if="showBlameButton"
+ data-testid="blame"
+ :href="blobInfo.blamePath"
+ :class="$options.buttonClassList"
+ >
+ {{ $options.i18n.blame }}
+ </gl-button>
+
+ <gl-button data-testid="history" :href="blobInfo.historyPath" :class="$options.buttonClassList">
+ {{ $options.i18n.history }}
+ </gl-button>
+
+ <gl-button
+ data-testid="permalink"
+ :href="blobInfo.permalinkPath"
+ :class="$options.buttonClassList"
+ class="js-data-file-blob-permalink-url"
+ >
+ {{ $options.i18n.permalink }}
+ </gl-button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/repository/components/blob_edit.vue b/app/assets/javascripts/repository/components/blob_edit.vue
index fd377ba1b81..69e2bd563c9 100644
--- a/app/assets/javascripts/repository/components/blob_edit.vue
+++ b/app/assets/javascripts/repository/components/blob_edit.vue
@@ -50,6 +50,7 @@ export default {
:web-ide-url="webIdePath"
:needs-to-fork="needsToFork"
:is-blob="true"
+ disable-fork-modal
@edit="onEdit"
/>
<div v-else>
diff --git a/app/assets/javascripts/repository/components/delete_blob_modal.vue b/app/assets/javascripts/repository/components/delete_blob_modal.vue
index 0d3dc06c2c8..f3c9aea36f1 100644
--- a/app/assets/javascripts/repository/components/delete_blob_modal.vue
+++ b/app/assets/javascripts/repository/components/delete_blob_modal.vue
@@ -146,6 +146,9 @@ export default {
/* eslint-enable dot-notation */
},
methods: {
+ show() {
+ this.$refs[this.modalId].show();
+ },
submitForm(e) {
e.preventDefault(); // Prevent modal from closing
this.form.showValidation = true;
@@ -164,6 +167,7 @@ export default {
<template>
<gl-modal
+ :ref="modalId"
v-bind="$attrs"
data-testid="modal-delete"
:modal-id="modalId"
diff --git a/app/assets/javascripts/repository/components/fork_suggestion.vue b/app/assets/javascripts/repository/components/fork_suggestion.vue
index c266bea319b..471f1dad2e3 100644
--- a/app/assets/javascripts/repository/components/fork_suggestion.vue
+++ b/app/assets/javascripts/repository/components/fork_suggestion.vue
@@ -32,6 +32,7 @@ export default {
class="gl-mr-3"
category="secondary"
variant="confirm"
+ data-method="post"
:href="forkPath"
data-testid="fork"
>
diff --git a/app/assets/javascripts/repository/components/preview/index.vue b/app/assets/javascripts/repository/components/preview/index.vue
index c6e461b10e0..dc5a031c9f3 100644
--- a/app/assets/javascripts/repository/components/preview/index.vue
+++ b/app/assets/javascripts/repository/components/preview/index.vue
@@ -47,6 +47,9 @@ export default {
}
},
},
+ safeHtmlConfig: {
+ ADD_TAGS: ['copy-code'],
+ },
};
</script>
@@ -62,7 +65,11 @@ export default {
</div>
<div class="blob-viewer" data-qa-selector="blob_viewer_content" itemprop="about">
<gl-loading-icon v-if="loading > 0" size="md" color="dark" class="my-4 mx-auto" />
- <div v-else-if="readme" ref="readme" v-safe-html="readme.html"></div>
+ <div
+ v-else-if="readme"
+ ref="readme"
+ v-safe-html:[$options.safeHtmlConfig]="readme.html"
+ ></div>
</div>
</article>
</template>
diff --git a/app/assets/javascripts/repository/components/upload_blob_modal.vue b/app/assets/javascripts/repository/components/upload_blob_modal.vue
index b56c9ce5247..7fcaf772aac 100644
--- a/app/assets/javascripts/repository/components/upload_blob_modal.vue
+++ b/app/assets/javascripts/repository/components/upload_blob_modal.vue
@@ -136,6 +136,9 @@ export default {
},
},
methods: {
+ show() {
+ this.$refs[this.modalId].show();
+ },
setFile(file) {
this.file = file;
@@ -206,6 +209,7 @@ export default {
<template>
<gl-form>
<gl-modal
+ :ref="modalId"
:modal-id="modalId"
:title="modalTitle"
:action-primary="primaryOptions"
diff --git a/app/assets/javascripts/repository/index.js b/app/assets/javascripts/repository/index.js
index 197b19387cf..120c32caefd 100644
--- a/app/assets/javascripts/repository/index.js
+++ b/app/assets/javascripts/repository/index.js
@@ -9,6 +9,7 @@ import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue';
import DirectoryDownloadLinks from './components/directory_download_links.vue';
import LastCommit from './components/last_commit.vue';
+import BlobControls from './components/blob_controls.vue';
import apolloProvider from './graphql';
import commitsQuery from './queries/commits.query.graphql';
import projectPathQuery from './queries/project_path.query.graphql';
@@ -71,8 +72,26 @@ export default function setupVueRepositoryList() {
},
});
+ const initBlobControlsApp = () =>
+ new Vue({
+ el: document.getElementById('js-blob-controls'),
+ router,
+ apolloProvider,
+ render(h) {
+ return h(BlobControls, {
+ props: {
+ projectPath,
+ },
+ });
+ },
+ });
+
initLastCommitApp();
+ if (gon.features.refactorBlobViewer) {
+ initBlobControlsApp();
+ }
+
router.afterEach(({ params: { path } }) => {
setTitle(path, ref, fullName);
});
@@ -144,7 +163,7 @@ export default function setupVueRepositoryList() {
}`,
// Ideally passing this class to `props` should work
// But it doesn't work here. :(
- class: 'btn btn-default btn-md gl-button ml-sm-0',
+ class: 'btn btn-default btn-md gl-button',
},
},
[__('History')],
diff --git a/app/assets/javascripts/repository/queries/blob_controls.query.graphql b/app/assets/javascripts/repository/queries/blob_controls.query.graphql
new file mode 100644
index 00000000000..fc1cf5f254b
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/blob_controls.query.graphql
@@ -0,0 +1,18 @@
+query getBlobControls($projectPath: ID!, $filePath: String!, $ref: String!) {
+ project(fullPath: $projectPath) {
+ id
+ repository {
+ blobs(paths: [$filePath], ref: $ref) {
+ nodes {
+ id
+ findFilePath
+ blamePath
+ historyPath
+ permalinkPath
+ storedExternally
+ externalStorage
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/repository/queries/blob_info.query.graphql b/app/assets/javascripts/repository/queries/blob_info.query.graphql
index 45d1ba80917..ae20a0f0bc4 100644
--- a/app/assets/javascripts/repository/queries/blob_info.query.graphql
+++ b/app/assets/javascripts/repository/queries/blob_info.query.graphql
@@ -1,22 +1,14 @@
+#import "ee_else_ce/repository/queries/path_locks.fragment.graphql"
+
query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
project(fullPath: $projectPath) {
- id
userPermissions {
pushCode
downloadCode
createMergeRequestIn
forkProject
}
- pathLocks {
- nodes {
- id
- path
- user {
- id
- username
- }
- }
- }
+ ...ProjectPathLocksFragment
repository {
empty
blobs(paths: [$filePath], ref: $ref) {
@@ -35,7 +27,9 @@ query getBlobInfo($projectPath: ID!, $filePath: String!, $ref: String!) {
ideForkAndEditPath
canModifyBlob
canCurrentUserPushToBranch
+ archived
storedExternally
+ externalStorage
rawPath
replacePath
pipelineEditorPath
diff --git a/app/assets/javascripts/repository/queries/path_locks.fragment.graphql b/app/assets/javascripts/repository/queries/path_locks.fragment.graphql
new file mode 100644
index 00000000000..868a513362d
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/path_locks.fragment.graphql
@@ -0,0 +1,3 @@
+fragment ProjectPathLocksFragment on Project {
+ id
+}
diff --git a/app/assets/javascripts/runner/runner_details/runner_details_app.vue b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
index 6557a7834e7..4d2ca9b0c58 100644
--- a/app/assets/javascripts/runner/runner_details/runner_details_app.vue
+++ b/app/assets/javascripts/runner/admin_runner_edit/admin_runner_edit_app.vue
@@ -1,20 +1,17 @@
<script>
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
-import { sprintf } from '~/locale';
-import RunnerTypeAlert from '../components/runner_type_alert.vue';
-import RunnerTypeBadge from '../components/runner_type_badge.vue';
+import RunnerHeader from '../components/runner_header.vue';
import RunnerUpdateForm from '../components/runner_update_form.vue';
-import { I18N_DETAILS_TITLE, I18N_FETCH_ERROR } from '../constants';
+import { I18N_FETCH_ERROR } from '../constants';
import getRunnerQuery from '../graphql/get_runner.query.graphql';
import { captureException } from '../sentry_utils';
export default {
- name: 'RunnerDetailsApp',
+ name: 'AdminRunnerEditApp',
components: {
- RunnerTypeAlert,
- RunnerTypeBadge,
+ RunnerHeader,
RunnerUpdateForm,
},
props: {
@@ -37,17 +34,12 @@ export default {
};
},
error(error) {
- createFlash({ message: I18N_FETCH_ERROR });
+ createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
},
- computed: {
- pageTitle() {
- return sprintf(I18N_DETAILS_TITLE, { runner_id: this.runnerId });
- },
- },
errorCaptured(error) {
this.reportToSentry(error);
},
@@ -60,12 +52,7 @@ export default {
</script>
<template>
<div>
- <h2 class="page-title">
- {{ pageTitle }} <runner-type-badge v-if="runner" :type="runner.runnerType" />
- </h2>
-
- <runner-type-alert v-if="runner" :type="runner.runnerType" />
-
+ <runner-header v-if="runner" :runner="runner" />
<runner-update-form :runner="runner" class="gl-my-5" />
</div>
</template>
diff --git a/app/assets/javascripts/runner/runner_details/index.js b/app/assets/javascripts/runner/admin_runner_edit/index.js
index db8f239a3c3..adb420f9963 100644
--- a/app/assets/javascripts/runner/runner_details/index.js
+++ b/app/assets/javascripts/runner/admin_runner_edit/index.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
-import RunnerDetailsApp from './runner_details_app.vue';
+import AdminRunnerEditApp from './admin_runner_edit_app.vue';
Vue.use(VueApollo);
-export const initRunnerDetail = (selector = '#js-runner-details') => {
+export const initAdminRunnerEdit = (selector = '#js-admin-runner-edit') => {
const el = document.querySelector(selector);
if (!el) {
@@ -22,7 +22,7 @@ export const initRunnerDetail = (selector = '#js-runner-details') => {
el,
apolloProvider,
render(h) {
- return h(RunnerDetailsApp, {
+ return h(AdminRunnerEditApp, {
props: {
runnerId,
},
diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
index f8220553db6..bb2bac531a7 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -1,14 +1,15 @@
<script>
import { GlBadge, GlLink } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
+import { formatNumber } from '~/locale';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerName from '../components/runner_name.vue';
-import RunnerOnlineStat from '../components/stat/runner_online_stat.vue';
+import RunnerStats from '../components/stat/runner_stats.vue';
import RunnerPagination from '../components/runner_pagination.vue';
import RunnerTypeTabs from '../components/runner_type_tabs.vue';
@@ -19,9 +20,13 @@ import {
INSTANCE_TYPE,
GROUP_TYPE,
PROJECT_TYPE,
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+ STATUS_STALE,
I18N_FETCH_ERROR,
} from '../constants';
import getRunnersQuery from '../graphql/get_runners.query.graphql';
+import getRunnersCountQuery from '../graphql/get_runners_count.query.graphql';
import {
fromUrlQueryToSearch,
fromSearchToUrl,
@@ -29,6 +34,17 @@ import {
} from '../runner_search_utils';
import { captureException } from '../sentry_utils';
+const runnersCountSmartQuery = {
+ query: getRunnersCountQuery,
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ update(data) {
+ return data?.runners?.count;
+ },
+ error(error) {
+ this.reportToSentry(error);
+ },
+};
+
export default {
name: 'AdminRunnersApp',
components: {
@@ -38,7 +54,7 @@ export default {
RunnerFilteredSearchBar,
RunnerList,
RunnerName,
- RunnerOnlineStat,
+ RunnerStats,
RunnerPagination,
RunnerTypeTabs,
},
@@ -47,26 +63,6 @@ export default {
type: String,
required: true,
},
- activeRunnersCount: {
- type: String,
- required: true,
- },
- allRunnersCount: {
- type: String,
- required: true,
- },
- instanceRunnersCount: {
- type: String,
- required: true,
- },
- groupRunnersCount: {
- type: String,
- required: true,
- },
- projectRunnersCount: {
- type: String,
- required: true,
- },
},
data() {
return {
@@ -95,16 +91,78 @@ export default {
};
},
error(error) {
- createFlash({ message: I18N_FETCH_ERROR });
+ createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
+ allRunnersCount: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return this.countVariables;
+ },
+ },
+ instanceRunnersCount: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ ...this.countVariables,
+ type: INSTANCE_TYPE,
+ };
+ },
+ },
+ groupRunnersCount: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ ...this.countVariables,
+ type: GROUP_TYPE,
+ };
+ },
+ },
+ projectRunnersCount: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ ...this.countVariables,
+ type: PROJECT_TYPE,
+ };
+ },
+ },
+ onlineRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ status: STATUS_ONLINE,
+ };
+ },
+ },
+ offlineRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ status: STATUS_OFFLINE,
+ };
+ },
+ },
+ staleRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ status: STATUS_STALE,
+ };
+ },
+ },
},
computed: {
variables() {
return fromSearchToVariables(this.search);
},
+ countVariables() {
+ // Exclude pagination variables, leave only filters variables
+ const { sort, before, last, after, first, ...countVariables } = this.variables;
+ return countVariables;
+ },
runnersLoading() {
return this.$apollo.queries.runners.loading;
},
@@ -125,7 +183,7 @@ export default {
search: {
deep: true,
handler() {
- // TODO Implement back button reponse using onpopstate
+ // TODO Implement back button response using onpopstate
updateHistory({
url: fromSearchToUrl(this.search),
title: document.title,
@@ -138,18 +196,27 @@ export default {
},
methods: {
tabCount({ runnerType }) {
+ let count;
switch (runnerType) {
case null:
- return this.allRunnersCount;
+ count = this.allRunnersCount;
+ break;
case INSTANCE_TYPE:
- return this.instanceRunnersCount;
+ count = this.instanceRunnersCount;
+ break;
case GROUP_TYPE:
- return this.groupRunnersCount;
+ count = this.groupRunnersCount;
+ break;
case PROJECT_TYPE:
- return this.projectRunnersCount;
+ count = this.projectRunnersCount;
+ break;
default:
return null;
}
+ if (typeof count === 'number') {
+ return formatNumber(count);
+ }
+ return '';
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
@@ -161,7 +228,11 @@ export default {
</script>
<template>
<div>
- <runner-online-stat class="gl-py-6 gl-px-5" :value="activeRunnersCount" />
+ <runner-stats
+ :online-runners-count="onlineRunnersTotal"
+ :offline-runners-count="offlineRunnersTotal"
+ :stale-runners-count="staleRunnersTotal"
+ />
<div
class="gl-display-flex gl-align-items-center gl-flex-direction-column-reverse gl-md-flex-direction-row gl-mt-3 gl-md-mt-0"
diff --git a/app/assets/javascripts/runner/admin_runners/index.js b/app/assets/javascripts/runner/admin_runners/index.js
index 62da6cbfa2b..3b8a8fe9cd1 100644
--- a/app/assets/javascripts/runner/admin_runners/index.js
+++ b/app/assets/javascripts/runner/admin_runners/index.js
@@ -2,6 +2,8 @@ import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { updateOutdatedUrl } from '~/runner/runner_search_utils';
import AdminRunnersApp from './admin_runners_app.vue';
Vue.use(GlToast);
@@ -14,18 +16,16 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
return null;
}
- // TODO `activeRunnersCount` should be implemented using a GraphQL API
- // https://gitlab.com/gitlab-org/gitlab/-/issues/333806
- const {
- runnerInstallHelpPage,
- registrationToken,
+ // Redirect outdated URLs
+ const updatedUrlQuery = updateOutdatedUrl();
+ if (updatedUrlQuery) {
+ visitUrl(updatedUrlQuery);
- activeRunnersCount,
- allRunnersCount,
- instanceRunnersCount,
- groupRunnersCount,
- projectRunnersCount,
- } = el.dataset;
+ // Prevent mounting the rest of the app, redirecting now.
+ return null;
+ }
+
+ const { runnerInstallHelpPage, registrationToken } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@@ -41,14 +41,6 @@ export const initAdminRunners = (selector = '#js-admin-runners') => {
return h(AdminRunnersApp, {
props: {
registrationToken,
-
- // All runner counts are returned as formatted
- // strings, we do not use `parseInt`.
- activeRunnersCount,
- allRunnersCount,
- instanceRunnersCount,
- groupRunnersCount,
- projectRunnersCount,
},
});
},
diff --git a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue
index 33f7a67aba4..0934508c87f 100644
--- a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue
+++ b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton, GlButtonGroup, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { __, s__, sprintf } from '~/locale';
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
import runnerActionsUpdateMutation from '~/runner/graphql/runner_actions_update.mutation.graphql';
@@ -69,6 +69,12 @@ export default {
runnerDeleteModalId() {
return `delete-runner-modal-${this.runnerId}`;
},
+ canUpdate() {
+ return this.runner.userPermissions?.updateRunner;
+ },
+ canDelete() {
+ return this.runner.userPermissions?.deleteRunner;
+ },
},
methods: {
async onToggleActive() {
@@ -133,7 +139,7 @@ export default {
onError(error) {
const { message } = error;
- createFlash({ message });
+ createAlert({ message });
this.reportToSentry(error);
},
@@ -156,14 +162,15 @@ export default {
See https://gitlab.com/gitlab-org/gitlab/-/issues/334802
-->
<gl-button
- v-if="runner.adminUrl"
+ v-if="canUpdate && runner.editAdminUrl"
v-gl-tooltip.hover.viewport="$options.I18N_EDIT"
- :href="runner.adminUrl"
+ :href="runner.editAdminUrl"
:aria-label="$options.I18N_EDIT"
icon="pencil"
data-testid="edit-runner"
/>
<gl-button
+ v-if="canUpdate"
v-gl-tooltip.hover.viewport="toggleActiveTitle"
:aria-label="toggleActiveTitle"
:icon="toggleActiveIcon"
@@ -172,6 +179,7 @@ export default {
@click="onToggleActive"
/>
<gl-button
+ v-if="canDelete"
v-gl-tooltip.hover.viewport="deleteTitle"
v-gl-modal="runnerDeleteModalId"
:aria-label="deleteTitle"
@@ -182,6 +190,7 @@ export default {
/>
<runner-delete-modal
+ v-if="canDelete"
:ref="runnerDeleteModalId"
:modal-id="runnerDeleteModalId"
:runner-name="runnerName"
diff --git a/app/assets/javascripts/runner/components/cells/runner_status_cell.vue b/app/assets/javascripts/runner/components/cells/runner_status_cell.vue
index 473cd7e9794..93f86ae2a2c 100644
--- a/app/assets/javascripts/runner/components/cells/runner_status_cell.vue
+++ b/app/assets/javascripts/runner/components/cells/runner_status_cell.vue
@@ -28,7 +28,15 @@ export default {
<template>
<div>
- <runner-status-badge :runner="runner" size="sm" />
- <runner-paused-badge v-if="paused" size="sm" />
+ <runner-status-badge
+ :runner="runner"
+ size="sm"
+ class="gl-display-inline-block gl-max-w-full gl-text-truncate"
+ />
+ <runner-paused-badge
+ v-if="paused"
+ size="sm"
+ class="gl-display-inline-block gl-max-w-full gl-text-truncate"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
index 3bb15bff8d8..0e259807f98 100644
--- a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
+++ b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
@@ -1,6 +1,6 @@
<script>
-import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { GlDropdownItem, GlLoadingIcon, GlModal, GlModalDirective } from '@gitlab/ui';
+import { createAlert } from '~/flash';
import { TYPE_GROUP, TYPE_PROJECT } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
@@ -10,9 +10,17 @@ import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../../constants';
export default {
name: 'RunnerRegistrationTokenReset',
+ i18n: {
+ modalTitle: __('Reset registration token'),
+ modalCopy: __('Are you sure you want to reset the registration token?'),
+ },
components: {
GlDropdownItem,
GlLoadingIcon,
+ GlModal,
+ },
+ directives: {
+ GlModal: GlModalDirective,
},
inject: {
groupId: {
@@ -22,6 +30,7 @@ export default {
default: null,
},
},
+ modalID: 'token-reset-modal',
props: {
type: {
type: String,
@@ -59,14 +68,10 @@ export default {
},
},
methods: {
+ handleModalPrimary() {
+ this.resetToken();
+ },
async resetToken() {
- // TODO Replace confirmation with gl-modal
- // See: https://gitlab.com/gitlab-org/gitlab/-/issues/333810
- // eslint-disable-next-line no-alert
- if (!window.confirm(__('Are you sure you want to reset the registration token?'))) {
- return;
- }
-
this.loading = true;
try {
const {
@@ -91,7 +96,7 @@ export default {
},
onError(error) {
const { message } = error;
- createFlash({ message });
+ createAlert({ message });
this.reportToSentry(error);
},
@@ -106,8 +111,15 @@ export default {
};
</script>
<template>
- <gl-dropdown-item @click.capture.native.stop="resetToken">
+ <gl-dropdown-item v-gl-modal="$options.modalID">
{{ __('Reset registration token') }}
+ <gl-modal
+ :modal-id="$options.modalID"
+ :title="$options.i18n.modalTitle"
+ @primary="handleModalPrimary"
+ >
+ <p>{{ $options.i18n.modalCopy }}</p>
+ </gl-modal>
<gl-loading-icon v-if="loading" inline />
</gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/runner/components/runner_header.vue b/app/assets/javascripts/runner/components/runner_header.vue
new file mode 100644
index 00000000000..09f58df7bd0
--- /dev/null
+++ b/app/assets/javascripts/runner/components/runner_header.vue
@@ -0,0 +1,52 @@
+<script>
+import { GlSprintf } from '@gitlab/ui';
+import { sprintf } from '~/locale';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { I18N_DETAILS_TITLE } from '../constants';
+import RunnerTypeBadge from './runner_type_badge.vue';
+import RunnerStatusBadge from './runner_status_badge.vue';
+
+export default {
+ components: {
+ GlSprintf,
+ TimeAgo,
+ RunnerTypeBadge,
+ RunnerStatusBadge,
+ },
+ props: {
+ runner: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ paused() {
+ return !this.runner.active;
+ },
+ heading() {
+ const id = getIdFromGraphQLId(this.runner.id);
+ return sprintf(I18N_DETAILS_TITLE, { runner_id: id });
+ },
+ },
+};
+</script>
+<template>
+ <div class="gl-py-5 gl-border-b-1 gl-border-b-solid gl-border-b-gray-100">
+ <runner-status-badge :runner="runner" />
+ <runner-type-badge v-if="runner" :type="runner.runnerType" />
+ <template v-if="runner.createdAt">
+ <gl-sprintf :message="__('%{runner} created %{timeago}')">
+ <template #runner>
+ <strong>{{ heading }}</strong>
+ </template>
+ <template #timeago>
+ <time-ago :time="runner.createdAt" />
+ </template>
+ </gl-sprintf>
+ </template>
+ <template v-else>
+ <strong>{{ heading }}</strong>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/runner/components/runner_status_badge.vue b/app/assets/javascripts/runner/components/runner_status_badge.vue
index 0823876a187..6d0445ecb7a 100644
--- a/app/assets/javascripts/runner/components/runner_status_badge.vue
+++ b/app/assets/javascripts/runner/components/runner_status_badge.vue
@@ -4,11 +4,10 @@ import { __, s__, sprintf } from '~/locale';
import { getTimeago } from '~/lib/utils/datetime_utility';
import {
I18N_ONLINE_RUNNER_TIMEAGO_DESCRIPTION,
- I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
+ I18N_NEVER_CONTACTED_RUNNER_DESCRIPTION,
I18N_OFFLINE_RUNNER_TIMEAGO_DESCRIPTION,
I18N_STALE_RUNNER_DESCRIPTION,
STATUS_ONLINE,
- STATUS_NOT_CONNECTED,
STATUS_NEVER_CONTACTED,
STATUS_OFFLINE,
STATUS_STALE,
@@ -45,12 +44,11 @@ export default {
timeAgo: this.contactedAtTimeAgo,
}),
};
- case STATUS_NOT_CONNECTED:
case STATUS_NEVER_CONTACTED:
return {
variant: 'muted',
- label: s__('Runners|not connected'),
- tooltip: I18N_NOT_CONNECTED_RUNNER_DESCRIPTION,
+ label: s__('Runners|never contacted'),
+ tooltip: I18N_NEVER_CONTACTED_RUNNER_DESCRIPTION,
};
case STATUS_OFFLINE:
return {
diff --git a/app/assets/javascripts/runner/components/runner_type_alert.vue b/app/assets/javascripts/runner/components/runner_type_alert.vue
deleted file mode 100644
index 1400875a1d6..00000000000
--- a/app/assets/javascripts/runner/components/runner_type_alert.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<script>
-import { GlAlert, GlLink } from '@gitlab/ui';
-import { helpPagePath } from '~/helpers/help_page_helper';
-import { s__ } from '~/locale';
-import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '../constants';
-
-const ALERT_DATA = {
- [INSTANCE_TYPE]: {
- message: s__(
- 'Runners|This runner is available to all groups and projects in your GitLab instance.',
- ),
- anchor: 'shared-runners',
- },
- [GROUP_TYPE]: {
- message: s__('Runners|This runner is available to all projects and subgroups in a group.'),
- anchor: 'group-runners',
- },
- [PROJECT_TYPE]: {
- message: s__('Runners|This runner is associated with one or more projects.'),
- anchor: 'specific-runners',
- },
-};
-
-export default {
- components: {
- GlAlert,
- GlLink,
- },
- props: {
- type: {
- type: String,
- required: false,
- default: null,
- validator(type) {
- return Boolean(ALERT_DATA[type]);
- },
- },
- },
- computed: {
- alert() {
- return ALERT_DATA[this.type];
- },
- helpHref() {
- return helpPagePath('ci/runners/runners_scope', { anchor: this.alert.anchor });
- },
- },
-};
-</script>
-<template>
- <gl-alert v-if="alert" variant="info" :dismissible="false">
- {{ alert.message }}
- <gl-link :href="helpHref">{{ __('Learn more.') }}</gl-link>
- </gl-alert>
-</template>
diff --git a/app/assets/javascripts/runner/components/runner_update_form.vue b/app/assets/javascripts/runner/components/runner_update_form.vue
index 9a6fc07f6dd..e3deb94236e 100644
--- a/app/assets/javascripts/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/runner/components/runner_update_form.vue
@@ -10,8 +10,8 @@ import {
import {
modelToUpdateMutationVariables,
runnerToModel,
-} from 'ee_else_ce/runner/runner_details/runner_update_form_utils';
-import createFlash, { FLASH_TYPES } from '~/flash';
+} from 'ee_else_ce/runner/runner_update_form_utils';
+import { createAlert, VARIANT_SUCCESS } from '~/flash';
import { __ } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED, PROJECT_TYPE } from '../constants';
@@ -75,14 +75,14 @@ export default {
if (errors?.length) {
// Validation errors need not be thrown
- createFlash({ message: errors[0] });
+ createAlert({ message: errors[0] });
return;
}
this.onSuccess();
} catch (error) {
const { message } = error;
- createFlash({ message });
+ createAlert({ message });
this.reportToSentry(error);
} finally {
@@ -90,7 +90,7 @@ export default {
}
},
onSuccess() {
- createFlash({ message: __('Changes saved.'), type: FLASH_TYPES.SUCCESS });
+ createAlert({ message: __('Changes saved.'), variant: VARIANT_SUCCESS });
this.model = runnerToModel(this.runner);
},
reportToSentry(error) {
diff --git a/app/assets/javascripts/runner/components/search_tokens/status_token_config.js b/app/assets/javascripts/runner/components/search_tokens/status_token_config.js
index 4b356fa47ed..79038eb8228 100644
--- a/app/assets/javascripts/runner/components/search_tokens/status_token_config.js
+++ b/app/assets/javascripts/runner/components/search_tokens/status_token_config.js
@@ -6,7 +6,7 @@ import {
STATUS_PAUSED,
STATUS_ONLINE,
STATUS_OFFLINE,
- STATUS_NOT_CONNECTED,
+ STATUS_NEVER_CONTACTED,
STATUS_STALE,
PARAM_KEY_STATUS,
} from '../../constants';
@@ -16,7 +16,7 @@ const options = [
{ value: STATUS_PAUSED, title: s__('Runners|Paused') },
{ value: STATUS_ONLINE, title: s__('Runners|Online') },
{ value: STATUS_OFFLINE, title: s__('Runners|Offline') },
- { value: STATUS_NOT_CONNECTED, title: s__('Runners|Not connected') },
+ { value: STATUS_NEVER_CONTACTED, title: s__('Runners|Never contacted') },
{ value: STATUS_STALE, title: s__('Runners|Stale') },
];
diff --git a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
index 7461308ab91..59230bb809e 100644
--- a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
+++ b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
@@ -1,6 +1,6 @@
<script>
import { GlFilteredSearchSuggestion, GlToken } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
@@ -50,7 +50,7 @@ export default {
try {
this.tags = await this.getTagsOptions(searchTerm);
} catch {
- createFlash({
+ createAlert({
message: s__('Runners|Something went wrong while fetching the tags suggestions'),
});
} finally {
diff --git a/app/assets/javascripts/runner/components/stat/runner_online_stat.vue b/app/assets/javascripts/runner/components/stat/runner_online_stat.vue
deleted file mode 100644
index b92b9badef0..00000000000
--- a/app/assets/javascripts/runner/components/stat/runner_online_stat.vue
+++ /dev/null
@@ -1,17 +0,0 @@
-<script>
-import { GlSingleStat } from '@gitlab/ui/dist/charts';
-
-export default {
- components: {
- GlSingleStat,
- },
-};
-</script>
-<template>
- <gl-single-stat
- v-bind="$attrs"
- variant="success"
- :title="s__('Runners|Online Runners')"
- :meta-text="s__('Runners|online')"
- />
-</template>
diff --git a/app/assets/javascripts/runner/components/stat/runner_stats.vue b/app/assets/javascripts/runner/components/stat/runner_stats.vue
new file mode 100644
index 00000000000..d3693ee593e
--- /dev/null
+++ b/app/assets/javascripts/runner/components/stat/runner_stats.vue
@@ -0,0 +1,49 @@
+<script>
+import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
+import RunnerStatusStat from './runner_status_stat.vue';
+
+export default {
+ components: {
+ RunnerStatusStat,
+ },
+ props: {
+ onlineRunnersCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ offlineRunnersCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ staleRunnersCount: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ },
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+ STATUS_STALE,
+};
+</script>
+<template>
+ <div class="gl-display-flex gl-py-6">
+ <runner-status-stat
+ class="gl-px-5"
+ :status="$options.STATUS_ONLINE"
+ :value="onlineRunnersCount"
+ />
+ <runner-status-stat
+ class="gl-px-5"
+ :status="$options.STATUS_OFFLINE"
+ :value="offlineRunnersCount"
+ />
+ <runner-status-stat
+ class="gl-px-5"
+ :status="$options.STATUS_STALE"
+ :value="staleRunnersCount"
+ />
+ </div>
+</template>
diff --git a/app/assets/javascripts/runner/components/stat/runner_status_stat.vue b/app/assets/javascripts/runner/components/stat/runner_status_stat.vue
new file mode 100644
index 00000000000..b77bbe15541
--- /dev/null
+++ b/app/assets/javascripts/runner/components/stat/runner_status_stat.vue
@@ -0,0 +1,65 @@
+<script>
+import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { s__, formatNumber } from '~/locale';
+import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants';
+
+export default {
+ components: {
+ GlSingleStat,
+ },
+ props: {
+ value: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ status: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ formattedValue() {
+ if (typeof this.value === 'number') {
+ return formatNumber(this.value);
+ }
+ return '-';
+ },
+ stat() {
+ switch (this.status) {
+ case STATUS_ONLINE:
+ return {
+ variant: 'success',
+ title: s__('Runners|Online runners'),
+ metaText: s__('Runners|online'),
+ };
+ case STATUS_OFFLINE:
+ return {
+ variant: 'muted',
+ title: s__('Runners|Offline runners'),
+ metaText: s__('Runners|offline'),
+ };
+ case STATUS_STALE:
+ return {
+ variant: 'warning',
+ title: s__('Runners|Stale runners'),
+ metaText: s__('Runners|stale'),
+ };
+ default:
+ return {
+ title: s__('Runners|Runners'),
+ };
+ }
+ },
+ },
+};
+</script>
+<template>
+ <gl-single-stat
+ v-if="stat"
+ :value="formattedValue"
+ :variant="stat.variant"
+ :title="stat.title"
+ :meta-text="stat.metaText"
+ />
+</template>
diff --git a/app/assets/javascripts/runner/constants.js b/app/assets/javascripts/runner/constants.js
index 355f3054917..ce8019ffaa0 100644
--- a/app/assets/javascripts/runner/constants.js
+++ b/app/assets/javascripts/runner/constants.js
@@ -18,8 +18,8 @@ export const I18N_PROJECT_RUNNER_DESCRIPTION = s__('Runners|Associated with one
export const I18N_ONLINE_RUNNER_TIMEAGO_DESCRIPTION = s__(
'Runners|Runner is online; last contact was %{timeAgo}',
);
-export const I18N_NOT_CONNECTED_RUNNER_DESCRIPTION = s__(
- 'Runners|This runner has never connected to this instance',
+export const I18N_NEVER_CONTACTED_RUNNER_DESCRIPTION = s__(
+ 'Runners|This runner has never contacted this instance',
);
export const I18N_OFFLINE_RUNNER_TIMEAGO_DESCRIPTION = s__(
'Runners|No recent contact from this runner; last contact was %{timeAgo}',
@@ -60,7 +60,6 @@ export const STATUS_ACTIVE = 'ACTIVE';
export const STATUS_PAUSED = 'PAUSED';
export const STATUS_ONLINE = 'ONLINE';
-export const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';
export const STATUS_NEVER_CONTACTED = 'NEVER_CONTACTED';
export const STATUS_OFFLINE = 'OFFLINE';
export const STATUS_STALE = 'STALE';
diff --git a/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql
index 6da9e276f74..f7bcd683718 100644
--- a/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql
+++ b/app/assets/javascripts/runner/graphql/get_group_runners.query.graphql
@@ -13,7 +13,7 @@ query getGroupRunners(
$sort: CiRunnerSort
) {
group(fullPath: $groupFullPath) {
- id
+ id # Apollo required
runners(
membership: DESCENDANTS
before: $before
diff --git a/app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql b/app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql
new file mode 100644
index 00000000000..554eb09e372
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/get_group_runners_count.query.graphql
@@ -0,0 +1,20 @@
+query getGroupRunnersCount(
+ $groupFullPath: ID!
+ $status: CiRunnerStatus
+ $type: CiRunnerType
+ $tagList: [String!]
+ $search: String
+) {
+ group(fullPath: $groupFullPath) {
+ id # Apollo required
+ runners(
+ membership: DESCENDANTS
+ status: $status
+ type: $type
+ tagList: $tagList
+ search: $search
+ ) {
+ count
+ }
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/get_runners.query.graphql b/app/assets/javascripts/runner/graphql/get_runners.query.graphql
index 51a91b9eb96..05df399fa6a 100644
--- a/app/assets/javascripts/runner/graphql/get_runners.query.graphql
+++ b/app/assets/javascripts/runner/graphql/get_runners.query.graphql
@@ -26,6 +26,7 @@ query getRunners(
nodes {
...RunnerNode
adminUrl
+ editAdminUrl
}
pageInfo {
...PageInfo
diff --git a/app/assets/javascripts/runner/graphql/get_runners_count.query.graphql b/app/assets/javascripts/runner/graphql/get_runners_count.query.graphql
new file mode 100644
index 00000000000..181a4495cae
--- /dev/null
+++ b/app/assets/javascripts/runner/graphql/get_runners_count.query.graphql
@@ -0,0 +1,10 @@
+query getRunnersCount(
+ $status: CiRunnerStatus
+ $type: CiRunnerType
+ $tagList: [String!]
+ $search: String
+) {
+ runners(status: $status, type: $type, tagList: $tagList, search: $search) {
+ count
+ }
+}
diff --git a/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql b/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
index 8c50cba7de3..8e968343b9b 100644
--- a/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/runner_details_shared.fragment.graphql
@@ -9,4 +9,6 @@ fragment RunnerDetailsShared on CiRunner {
description
maximumTimeout
tagList
+ createdAt
+ status(legacyMode: null)
}
diff --git a/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
index 169f6ffd2ea..4a771d779dc 100644
--- a/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
+++ b/app/assets/javascripts/runner/graphql/runner_node.fragment.graphql
@@ -12,4 +12,8 @@ fragment RunnerNode on CiRunner {
tagList
contactedAt
status(legacyMode: null)
+ userPermissions {
+ updateRunner
+ deleteRunner
+ }
}
diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
index a58a53a6a0d..3a7b58e3dc9 100644
--- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
@@ -1,6 +1,6 @@
<script>
import { GlLink } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
import { formatNumber, sprintf, s__ } from '~/locale';
@@ -9,7 +9,7 @@ import RegistrationDropdown from '../components/registration/registration_dropdo
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerName from '../components/runner_name.vue';
-import RunnerOnlineStat from '../components/stat/runner_online_stat.vue';
+import RunnerStats from '../components/stat/runner_stats.vue';
import RunnerPagination from '../components/runner_pagination.vue';
import RunnerTypeTabs from '../components/runner_type_tabs.vue';
@@ -19,8 +19,12 @@ import {
GROUP_FILTERED_SEARCH_NAMESPACE,
GROUP_TYPE,
GROUP_RUNNER_COUNT_LIMIT,
+ STATUS_ONLINE,
+ STATUS_OFFLINE,
+ STATUS_STALE,
} from '../constants';
import getGroupRunnersQuery from '../graphql/get_group_runners.query.graphql';
+import getGroupRunnersCountQuery from '../graphql/get_group_runners_count.query.graphql';
import {
fromUrlQueryToSearch,
fromSearchToUrl,
@@ -28,6 +32,17 @@ import {
} from '../runner_search_utils';
import { captureException } from '../sentry_utils';
+const runnersCountSmartQuery = {
+ query: getGroupRunnersCountQuery,
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ update(data) {
+ return data?.group?.runners?.count;
+ },
+ error(error) {
+ this.reportToSentry(error);
+ },
+};
+
export default {
name: 'GroupRunnersApp',
components: {
@@ -36,7 +51,7 @@ export default {
RunnerFilteredSearchBar,
RunnerList,
RunnerName,
- RunnerOnlineStat,
+ RunnerStats,
RunnerPagination,
RunnerTypeTabs,
},
@@ -84,11 +99,38 @@ export default {
};
},
error(error) {
- createFlash({ message: I18N_FETCH_ERROR });
+ createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
},
+ onlineRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ groupFullPath: this.groupFullPath,
+ status: STATUS_ONLINE,
+ };
+ },
+ },
+ offlineRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ groupFullPath: this.groupFullPath,
+ status: STATUS_OFFLINE,
+ };
+ },
+ },
+ staleRunnersTotal: {
+ ...runnersCountSmartQuery,
+ variables() {
+ return {
+ groupFullPath: this.groupFullPath,
+ status: STATUS_STALE,
+ };
+ },
+ },
},
computed: {
variables() {
@@ -147,7 +189,11 @@ export default {
<template>
<div>
- <runner-online-stat class="gl-py-6 gl-px-5" :value="groupRunnersCount" />
+ <runner-stats
+ :online-runners-count="onlineRunnersTotal"
+ :offline-runners-count="offlineRunnersTotal"
+ :stale-runners-count="staleRunnersTotal"
+ />
<div class="gl-display-flex gl-align-items-center">
<runner-type-tabs
diff --git a/app/assets/javascripts/runner/runner_search_utils.js b/app/assets/javascripts/runner/runner_search_utils.js
index b88023720e8..c80a73948b8 100644
--- a/app/assets/javascripts/runner/runner_search_utils.js
+++ b/app/assets/javascripts/runner/runner_search_utils.js
@@ -16,6 +16,7 @@ import {
PARAM_KEY_BEFORE,
DEFAULT_SORT,
RUNNER_PAGE_SIZE,
+ STATUS_NEVER_CONTACTED,
} from './constants';
/**
@@ -79,6 +80,33 @@ const getPaginationFromParams = (params) => {
};
};
+// Outdated URL parameters
+const STATUS_NOT_CONNECTED = 'NOT_CONNECTED';
+
+/**
+ * Returns an updated URL for old (or deprecated) admin runner URLs.
+ *
+ * Use for redirecting users to currently used URLs.
+ *
+ * @param {String?} URL
+ * @returns Updated URL if outdated, `null` otherwise
+ */
+export const updateOutdatedUrl = (url = window.location.href) => {
+ const urlObj = new URL(url);
+ const query = urlObj.search;
+
+ const params = queryToObject(query, { gatherArrays: true });
+
+ const runnerType = params[PARAM_KEY_STATUS]?.[0] || null;
+ if (runnerType === STATUS_NOT_CONNECTED) {
+ const updatedParams = {
+ [PARAM_KEY_STATUS]: [STATUS_NEVER_CONTACTED],
+ };
+ return setUrlParams(updatedParams, url, false, true, true);
+ }
+ return null;
+};
+
/**
* Takes a URL query and transforms it into a "search" object
* @param {String?} query
diff --git a/app/assets/javascripts/runner/runner_details/runner_update_form_utils.js b/app/assets/javascripts/runner/runner_update_form_utils.js
index 3b519fa7d71..3b519fa7d71 100644
--- a/app/assets/javascripts/runner/runner_details/runner_update_form_utils.js
+++ b/app/assets/javascripts/runner/runner_update_form_utils.js
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index 75d2b324623..d228f77f27d 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -27,6 +27,9 @@ export const i18n = {
securityConfiguration: __('Security Configuration'),
vulnerabilityManagement: s__('SecurityConfiguration|Vulnerability Management'),
securityTraining: s__('SecurityConfiguration|Security training'),
+ securityTrainingDescription: s__(
+ 'SecurityConfiguration|Enable security training to help your developers learn how to fix vulnerabilities. Developers can view security training from selected educational providers, relevant to the detected vulnerability.',
+ ),
};
export default {
@@ -160,8 +163,12 @@ export default {
</template>
</user-callout-dismisser>
- <gl-tabs content-class="gl-pt-0">
- <gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting">
+ <gl-tabs content-class="gl-pt-0" sync-active-tab-with-query-params lazy>
+ <gl-tab
+ data-testid="security-testing-tab"
+ :title="$options.i18n.securityTesting"
+ query-param-value="security-testing"
+ >
<auto-dev-ops-enabled-alert
v-if="shouldShowAutoDevopsEnabledAlert"
class="gl-mt-3"
@@ -185,9 +192,12 @@ export default {
{{ $options.i18n.description }}
</p>
<p v-if="canViewCiHistory">
- <gl-link data-testid="security-view-history-link" :href="gitlabCiHistoryPath">{{
- $options.i18n.configurationHistory
- }}</gl-link>
+ <gl-link
+ data-testid="security-view-history-link"
+ data-qa-selector="security_configuration_history_link"
+ :href="gitlabCiHistoryPath"
+ >{{ $options.i18n.configurationHistory }}</gl-link
+ >
</p>
</template>
@@ -203,7 +213,11 @@ export default {
</template>
</section-layout>
</gl-tab>
- <gl-tab data-testid="compliance-testing-tab" :title="$options.i18n.compliance">
+ <gl-tab
+ data-testid="compliance-testing-tab"
+ :title="$options.i18n.compliance"
+ query-param-value="compliance-testing"
+ >
<section-layout :heading="$options.i18n.compliance">
<template #description>
<p>
@@ -241,8 +255,14 @@ export default {
v-if="glFeatures.secureVulnerabilityTraining"
data-testid="vulnerability-management-tab"
:title="$options.i18n.vulnerabilityManagement"
+ query-param-value="vulnerability-management"
>
<section-layout :heading="$options.i18n.securityTraining">
+ <template #description>
+ <p>
+ {{ $options.i18n.securityTrainingDescription }}
+ </p>
+ </template>
<template #features>
<training-provider-list />
</template>
diff --git a/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue b/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue
index ce6a1b4888b..315f676e659 100644
--- a/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue
+++ b/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue
@@ -28,6 +28,7 @@ export default {
variant="info"
:primary-button-link="autoDevopsPath"
:primary-button-text="$options.i18n.primaryButtonText"
+ data-qa-selector="autodevops_container"
@dismiss="dismissMethod"
>
<gl-sprintf :message="$options.i18n.body">
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index dd8ba72ad1f..034dba29196 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -254,7 +254,7 @@ export const securityFeatures = [
helpPath: COVERAGE_FUZZING_HELP_PATH,
configurationHelpPath: COVERAGE_FUZZING_CONFIG_HELP_PATH,
type: REPORT_TYPE_COVERAGE_FUZZING,
- secondary: gon?.features?.corpusManagement
+ secondary: gon?.features?.corpusManagementUi
? {
type: REPORT_TYPE_CORPUS_MANAGEMENT,
name: CORPUS_MANAGEMENT_NAME,
diff --git a/app/assets/javascripts/security_configuration/components/training_provider_list.vue b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
index 509377a63e8..ca4596e16b3 100644
--- a/app/assets/javascripts/security_configuration/components/training_provider_list.vue
+++ b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
@@ -1,21 +1,39 @@
<script>
-import { GlCard, GlToggle, GlLink, GlSkeletonLoader } from '@gitlab/ui';
+import { GlAlert, GlCard, GlToggle, GlLink, GlSkeletonLoader } from '@gitlab/ui';
+import { __ } from '~/locale';
import securityTrainingProvidersQuery from '../graphql/security_training_providers.query.graphql';
+import configureSecurityTrainingProvidersMutation from '../graphql/configure_security_training_providers.mutation.graphql';
+
+const i18n = {
+ providerQueryErrorMessage: __(
+ 'Could not fetch training providers. Please refresh the page, or try again later.',
+ ),
+ configMutationErrorMessage: __(
+ 'Could not save configuration. Please refresh the page, or try again later.',
+ ),
+};
export default {
components: {
+ GlAlert,
GlCard,
GlToggle,
GlLink,
GlSkeletonLoader,
},
+ inject: ['projectPath'],
apollo: {
securityTrainingProviders: {
query: securityTrainingProvidersQuery,
+ error() {
+ this.errorMessage = this.$options.i18n.providerQueryErrorMessage;
+ },
},
},
data() {
return {
+ errorMessage: '',
+ toggleLoading: false,
securityTrainingProviders: [],
};
},
@@ -24,38 +42,92 @@ export default {
return this.$apollo.queries.securityTrainingProviders.loading;
},
},
+ methods: {
+ toggleProvider(selectedProviderId) {
+ const toggledProviders = this.securityTrainingProviders.map((provider) => ({
+ ...provider,
+ ...(provider.id === selectedProviderId && { isEnabled: !provider.isEnabled }),
+ }));
+
+ const enabledProviderIds = toggledProviders
+ .filter(({ isEnabled }) => isEnabled)
+ .map(({ id }) => id);
+
+ this.storeEnabledProviders(toggledProviders, enabledProviderIds);
+ },
+ async storeEnabledProviders(toggledProviders, enabledProviderIds) {
+ this.toggleLoading = true;
+
+ try {
+ const {
+ data: {
+ configureSecurityTrainingProviders: { errors = [] },
+ },
+ } = await this.$apollo.mutate({
+ mutation: configureSecurityTrainingProvidersMutation,
+ variables: {
+ input: {
+ enabledProviders: enabledProviderIds,
+ fullPath: this.projectPath,
+ },
+ },
+ });
+
+ if (errors.length > 0) {
+ // throwing an error here means we can handle scenarios within the `catch` block below
+ throw new Error();
+ }
+ } catch {
+ this.errorMessage = this.$options.i18n.configMutationErrorMessage;
+ } finally {
+ this.toggleLoading = false;
+ }
+ },
+ },
+ i18n,
};
</script>
<template>
- <div
- v-if="isLoading"
- class="gl-bg-white gl-py-6 gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100"
- >
- <gl-skeleton-loader :width="350" :height="44">
- <rect width="200" height="8" x="10" y="0" rx="4" />
- <rect width="300" height="8" x="10" y="15" rx="4" />
- <rect width="100" height="8" x="10" y="35" rx="4" />
- </gl-skeleton-loader>
- </div>
- <ul v-else class="gl-list-style-none gl-m-0 gl-p-0">
- <li
- v-for="{ id, isEnabled, name, description, url } in securityTrainingProviders"
- :key="id"
- class="gl-mb-6"
+ <div>
+ <gl-alert v-if="errorMessage" variant="danger" :dismissible="false" class="gl-mb-6">
+ {{ errorMessage }}
+ </gl-alert>
+ <div
+ v-if="isLoading"
+ class="gl-bg-white gl-py-6 gl-rounded-base gl-border-1 gl-border-solid gl-border-gray-100"
>
- <gl-card>
- <div class="gl-display-flex">
- <gl-toggle :value="isEnabled" :label="__('Training mode')" label-position="hidden" />
- <div class="gl-ml-5">
- <h3 class="gl-font-lg gl-m-0 gl-mb-2">{{ name }}</h3>
- <p>
- {{ description }}
- <gl-link :href="url" target="_blank">{{ __('Learn more.') }}</gl-link>
- </p>
+ <gl-skeleton-loader :width="350" :height="44">
+ <rect width="200" height="8" x="10" y="0" rx="4" />
+ <rect width="300" height="8" x="10" y="15" rx="4" />
+ <rect width="100" height="8" x="10" y="35" rx="4" />
+ </gl-skeleton-loader>
+ </div>
+ <ul v-else class="gl-list-style-none gl-m-0 gl-p-0">
+ <li
+ v-for="{ id, isEnabled, name, description, url } in securityTrainingProviders"
+ :key="id"
+ class="gl-mb-6"
+ >
+ <gl-card>
+ <div class="gl-display-flex">
+ <gl-toggle
+ :value="isEnabled"
+ :label="__('Training mode')"
+ label-position="hidden"
+ :is-loading="toggleLoading"
+ @change="toggleProvider(id)"
+ />
+ <div class="gl-ml-5">
+ <h3 class="gl-font-lg gl-m-0 gl-mb-2">{{ name }}</h3>
+ <p>
+ {{ description }}
+ <gl-link :href="url" target="_blank">{{ __('Learn more.') }}</gl-link>
+ </p>
+ </div>
</div>
- </div>
- </gl-card>
- </li>
- </ul>
+ </gl-card>
+ </li>
+ </ul>
+ </div>
</template>
diff --git a/app/assets/javascripts/security_configuration/graphql/configure_security_training_providers.mutation.graphql b/app/assets/javascripts/security_configuration/graphql/configure_security_training_providers.mutation.graphql
new file mode 100644
index 00000000000..660e0fadafb
--- /dev/null
+++ b/app/assets/javascripts/security_configuration/graphql/configure_security_training_providers.mutation.graphql
@@ -0,0 +1,9 @@
+mutation configureSecurityTrainingProviders($input: configureSecurityTrainingProvidersInput!) {
+ configureSecurityTrainingProviders(input: $input) @client {
+ errors
+ securityTrainingProviders {
+ id
+ isEnabled
+ }
+ }
+}
diff --git a/app/assets/javascripts/security_configuration/index.js b/app/assets/javascripts/security_configuration/index.js
index c86ff1a58f2..24c0585e077 100644
--- a/app/assets/javascripts/security_configuration/index.js
+++ b/app/assets/javascripts/security_configuration/index.js
@@ -2,38 +2,10 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils';
-import { __ } from '~/locale';
import SecurityConfigurationApp from './components/app.vue';
import { securityFeatures, complianceFeatures } from './components/constants';
import { augmentFeatures } from './utils';
-
-// Note: this is behind a feature flag and only a placeholder
-// until the actual GraphQL fields have been added
-// https://gitlab.com/gitlab-org/gi tlab/-/issues/346480
-export const tempResolvers = {
- Query: {
- securityTrainingProviders() {
- return [
- {
- __typename: 'SecurityTrainingProvider',
- id: 101,
- name: __('Kontra'),
- description: __('Interactive developer security education.'),
- url: 'https://application.security/',
- isEnabled: false,
- },
- {
- __typename: 'SecurityTrainingProvider',
- id: 102,
- name: __('SecureCodeWarrior'),
- description: __('Security training with guide and learning pathways.'),
- url: 'https://www.securecodewarrior.com/',
- isEnabled: true,
- },
- ];
- },
- },
-};
+import tempResolvers from './resolver';
export const initSecurityConfiguration = (el) => {
if (!el) {
diff --git a/app/assets/javascripts/security_configuration/resolver.js b/app/assets/javascripts/security_configuration/resolver.js
new file mode 100644
index 00000000000..93175d4a3d1
--- /dev/null
+++ b/app/assets/javascripts/security_configuration/resolver.js
@@ -0,0 +1,56 @@
+import produce from 'immer';
+import { __ } from '~/locale';
+import securityTrainingProvidersQuery from './graphql/security_training_providers.query.graphql';
+
+// Note: this is behind a feature flag and only a placeholder
+// until the actual GraphQL fields have been added
+// https://gitlab.com/gitlab-org/gi tlab/-/issues/346480
+export default {
+ Query: {
+ securityTrainingProviders() {
+ return [
+ {
+ __typename: 'SecurityTrainingProvider',
+ id: 101,
+ name: __('Kontra'),
+ description: __('Interactive developer security education.'),
+ url: 'https://application.security/',
+ isEnabled: false,
+ },
+ {
+ __typename: 'SecurityTrainingProvider',
+ id: 102,
+ name: __('SecureCodeWarrior'),
+ description: __('Security training with guide and learning pathways.'),
+ url: 'https://www.securecodewarrior.com/',
+ isEnabled: true,
+ },
+ ];
+ },
+ },
+
+ Mutation: {
+ configureSecurityTrainingProviders: (
+ _,
+ { input: { enabledProviders, primaryProvider } },
+ { cache },
+ ) => {
+ const sourceData = cache.readQuery({
+ query: securityTrainingProvidersQuery,
+ });
+
+ const data = produce(sourceData.securityTrainingProviders, (draftData) => {
+ /* eslint-disable no-param-reassign */
+ draftData.forEach((provider) => {
+ provider.isPrimary = provider.id === primaryProvider;
+ provider.isEnabled =
+ provider.id === primaryProvider || enabledProviders.includes(provider.id);
+ });
+ });
+ return {
+ __typename: 'configureSecurityTrainingProvidersPayload',
+ securityTrainingProviders: data,
+ };
+ },
+ },
+};
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index e41f3aa5c9d..a746642c191 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -267,6 +267,8 @@ export default {
v-if="glFeatures.improvedEmojiPicker"
dropdown-class="gl-h-full"
toggle-class="btn emoji-menu-toggle-button gl-px-4! gl-rounded-top-right-none! gl-rounded-bottom-right-none!"
+ boundary="viewport"
+ :right="false"
@click="setEmoji"
>
<template #button-content>
diff --git a/app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue b/app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue
index 6d4da104952..950647f1cb2 100644
--- a/app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue
+++ b/app/assets/javascripts/sidebar/components/crm_contacts/crm_contacts.vue
@@ -1,5 +1,5 @@
<script>
-import { GlIcon, GlPopover, GlTooltipDirective } from '@gitlab/ui';
+import { GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab/ui';
import { __, n__, sprintf } from '~/locale';
import createFlash from '~/flash';
import { convertToGraphQLId } from '~/graphql_shared/utils';
@@ -10,6 +10,7 @@ import issueCrmContactsSubscription from './queries/issue_crm_contacts.subscript
export default {
components: {
GlIcon,
+ GlLink,
GlPopover,
},
directives: {
@@ -85,9 +86,6 @@ export default {
);
},
},
- i18n: {
- help: __('Work in progress- click here to find out more'),
- },
};
</script>
@@ -97,11 +95,10 @@ export default {
<gl-icon name="users" />
<span> {{ contactCount }} </span>
</div>
- <div
- v-gl-tooltip.left.viewport="$options.i18n.help"
- class="hide-collapsed help-button float-right"
- >
- <a href="https://gitlab.com/gitlab-org/gitlab/-/issues/2256"><gl-icon name="question-o" /></a>
+ <div class="hide-collapsed help-button gl-float-right">
+ <gl-link href="https://docs.gitlab.com/ee/user/crm/" target="_blank"
+ ><gl-icon name="question-o"
+ /></gl-link>
</div>
<div class="title hide-collapsed gl-mb-2 gl-line-height-20">
{{ contactsLabel }}
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index cbe40d0bfbe..6363422259e 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -26,6 +26,7 @@ import trackShowInviteMemberLink from '~/sidebar/track_invite_members';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_vue/constants';
import LabelsSelectWidget from '~/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue';
import { LabelType } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
+import eventHub from '~/sidebar/event_hub';
import Translate from '../vue_shared/translate';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
import CopyEmailToClipboard from './components/copy_email_to_clipboard.vue';
@@ -600,6 +601,12 @@ export function mountSidebar(mediator, store) {
mountTimeTrackingComponent();
mountSeverityComponent();
+
+ if (window.gon?.features?.mrAttentionRequests) {
+ eventHub.$on('removeCurrentUserAttentionRequested', () =>
+ mediator.removeCurrentUserAttentionRequested(),
+ );
+ }
}
export { getSidebarOptions };
diff --git a/app/assets/javascripts/sidebar/sidebar_mediator.js b/app/assets/javascripts/sidebar/sidebar_mediator.js
index a49ddac8c89..25468d4a697 100644
--- a/app/assets/javascripts/sidebar/sidebar_mediator.js
+++ b/app/assets/javascripts/sidebar/sidebar_mediator.js
@@ -30,7 +30,7 @@ export default class SidebarMediator {
this.store.addAssignee(this.store.currentUser);
}
- saveAssignees(field) {
+ async saveAssignees(field) {
const selected = this.store.assignees.map((u) => u.id);
// If there are no ids, that means we have to unassign (which is id = 0)
@@ -38,10 +38,22 @@ export default class SidebarMediator {
const assignees = selected.length === 0 ? [0] : selected;
const data = { assignee_ids: assignees };
- return this.service.update(field, data);
+ try {
+ const res = await this.service.update(field, data);
+
+ this.store.overwrite('assignees', res.data.assignees);
+
+ if (res.data.reviewers) {
+ this.store.overwrite('reviewers', res.data.reviewers);
+ }
+
+ return Promise.resolve(res);
+ } catch (e) {
+ return Promise.reject(e);
+ }
}
- saveReviewers(field) {
+ async saveReviewers(field) {
const selected = this.store.reviewers.map((u) => u.id);
// If there are no ids, that means we have to unassign (which is id = 0)
@@ -49,7 +61,16 @@ export default class SidebarMediator {
const reviewers = selected.length === 0 ? [0] : selected;
const data = { reviewer_ids: reviewers };
- return this.service.update(field, data);
+ try {
+ const res = await this.service.update(field, data);
+
+ this.store.overwrite('reviewers', res.data.reviewers);
+ this.store.overwrite('assignees', res.data.assignees);
+
+ return Promise.resolve(res);
+ } catch (e) {
+ return Promise.reject();
+ }
}
requestReview({ userId, callback }) {
@@ -63,6 +84,19 @@ export default class SidebarMediator {
.catch(() => callback(userId, false));
}
+ removeCurrentUserAttentionRequested() {
+ const currentUserId = gon.current_user_id;
+
+ const currentUserReviewer = this.store.findReviewer({ id: currentUserId });
+ const currentUserAssignee = this.store.findAssignee({ id: currentUserId });
+
+ if (currentUserReviewer?.attention_requested || currentUserAssignee?.attention_requested) {
+ // Update current users attention_requested state
+ this.store.updateReviewer(currentUserId, 'attention_requested');
+ this.store.updateAssignee(currentUserId, 'attention_requested');
+ }
+ }
+
async toggleAttentionRequested(type, { user, callback }) {
try {
const isReviewer = type === 'reviewer';
@@ -82,15 +116,7 @@ export default class SidebarMediator {
const currentUserId = gon.current_user_id;
if (currentUserId !== user.id) {
- const currentUserReviewerOrAssignee = isReviewer
- ? this.store.findReviewer({ id: currentUserId })
- : this.store.findAssignee({ id: currentUserId });
-
- if (currentUserReviewerOrAssignee?.attention_requested) {
- // Update current users attention_requested state
- this.store.updateReviewer(currentUserId, 'attention_requested');
- this.store.updateAssignee(currentUserId, 'attention_requested');
- }
+ this.removeCurrentUserAttentionRequested();
}
toast(sprintf(__('Requested attention from @%{username}'), { username: user.username }));
diff --git a/app/assets/javascripts/sidebar/stores/sidebar_store.js b/app/assets/javascripts/sidebar/stores/sidebar_store.js
index 5376791469e..2caa6f4f0a0 100644
--- a/app/assets/javascripts/sidebar/stores/sidebar_store.js
+++ b/app/assets/javascripts/sidebar/stores/sidebar_store.js
@@ -98,6 +98,10 @@ export default class SidebarStore {
}
}
+ overwrite(key, newData) {
+ this[key] = newData;
+ }
+
findAssignee(findAssignee) {
return this.assignees.find(({ id }) => id === findAssignee.id);
}
diff --git a/app/assets/javascripts/tracking/index.js b/app/assets/javascripts/tracking/index.js
index 7e99ecb4f4e..d60eb37a9a2 100644
--- a/app/assets/javascripts/tracking/index.js
+++ b/app/assets/javascripts/tracking/index.js
@@ -46,7 +46,10 @@ export function initDefaultTrackers() {
// must be after enableActivityTracking
const standardContext = getStandardContext();
const experimentContexts = getAllExperimentContexts();
- window.snowplow('trackPageView', null, [standardContext, ...experimentContexts]);
+ // To not expose personal identifying information, the page title is hardcoded as `GitLab`
+ // See: https://gitlab.com/gitlab-org/gitlab/-/issues/345243
+ window.snowplow('trackPageView', 'GitLab', [standardContext, ...experimentContexts]);
+ window.snowplow('setDocumentTitle', 'GitLab');
if (window.snowplowOptions.formTracking) {
Tracking.enableFormTracking(opts.formTrackingConfig);
diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js
deleted file mode 100644
index 58bff370fa5..00000000000
--- a/app/assets/javascripts/tree.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/* eslint-disable func-names, consistent-return, one-var, class-methods-use-this */
-
-import $ from 'jquery';
-import { visitUrl } from './lib/utils/url_utility';
-
-export default class TreeView {
- constructor() {
- this.initKeyNav();
- // Code browser tree slider
- // Make the entire tree-item row clickable, but not if clicking another link (like a commit message)
- $('.tree-content-holder .tree-item').on('click', function (e) {
- const $clickedEl = $(e.target);
- const path = $('.tree-item-file-name a', this).attr('href');
- if (!$clickedEl.is('a') && !$clickedEl.is('.str-truncated')) {
- if (e.metaKey || e.which === 2) {
- e.preventDefault();
- return window.open(path, '_blank');
- }
- return visitUrl(path);
- }
- });
- // Show the "Loading commit data" for only the first element
- $('span.log_loading').first().removeClass('hide');
- }
-
- initKeyNav() {
- const li = $('tr.tree-item');
- let liSelected = null;
- return $('body').keydown((e) => {
- let next, path;
- if ($('input:focus').length > 0 && (e.which === 38 || e.which === 40)) {
- return false;
- }
- if (e.which === 40) {
- if (liSelected) {
- next = liSelected.next();
- if (next.length > 0) {
- liSelected.removeClass('selected');
- liSelected = next.addClass('selected');
- }
- } else {
- liSelected = li.eq(0).addClass('selected');
- }
- return $(liSelected).focus();
- } else if (e.which === 38) {
- if (liSelected) {
- next = liSelected.prev();
- if (next.length > 0) {
- liSelected.removeClass('selected');
- liSelected = next.addClass('selected');
- }
- } else {
- liSelected = li.last().addClass('selected');
- }
- return $(liSelected).focus();
- } else if (e.which === 13) {
- path = $('.tree-item.selected .tree-item-file-name a').attr('href');
- if (path) {
- return visitUrl(path);
- }
- }
- });
- }
-}
diff --git a/app/assets/javascripts/version_check_image.js b/app/assets/javascripts/version_check_image.js
deleted file mode 100644
index 4e00e0f11f7..00000000000
--- a/app/assets/javascripts/version_check_image.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export default class VersionCheckImage {
- static bindErrorEvent(imageElement) {
- // eslint-disable-next-line @gitlab/no-global-event-off
- imageElement.off('error').on('error', () => imageElement.hide());
- }
-}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
index 386ba2e2d77..24cefd63ce3 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/approvals/approvals.vue
@@ -3,6 +3,7 @@ import { GlButton } from '@gitlab/ui';
import createFlash from '~/flash';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import { s__ } from '~/locale';
+import sidebarEventHub from '~/sidebar/event_hub';
import eventHub from '../../event_hub';
import approvalsMixin from '../../mixins/approvals';
import MrWidgetContainer from '../mr_widget_container.vue';
@@ -172,6 +173,7 @@ export default {
this.mr.setApprovals(data);
eventHub.$emit('MRWidgetUpdateRequested');
eventHub.$emit('ApprovalUpdated');
+ sidebarEventHub.$emit('removeCurrentUserAttentionRequested');
this.$emit('updated');
})
.catch(errFn)
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue
index 33a83aef057..d878a1fa2e0 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/actions.vue
@@ -70,6 +70,7 @@ export default {
variant="confirm"
size="small"
class="gl-display-none gl-md-display-block gl-float-left"
+ data-testid="extension-actions-button"
@click="onClickAction(btn)"
>
{{ btn.text }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
index 549cf64fb08..7322958e6df 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
@@ -13,6 +13,7 @@ import * as Sentry from '@sentry/browser';
import api from '~/api';
import { sprintf, s__, __ } from '~/locale';
import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue';
+import Poll from '~/lib/utils/poll';
import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants';
import StatusIcon from './status_icon.vue';
import Actions from './actions.vue';
@@ -132,19 +133,50 @@ export default {
this.triggerRedisTracking();
},
+ initExtensionPolling() {
+ const poll = new Poll({
+ resource: {
+ fetchData: () => this.fetchCollapsedData(this.$props),
+ },
+ method: 'fetchData',
+ successCallback: (data) => {
+ if (Object.keys(data).length > 0) {
+ poll.stop();
+ this.setCollapsedData(data);
+ }
+ },
+ errorCallback: (e) => {
+ poll.stop();
+
+ this.setCollapsedError(e);
+ },
+ });
+
+ poll.makeRequest();
+ },
loadCollapsedData() {
this.loadingState = LOADING_STATES.collapsedLoading;
- this.fetchCollapsedData(this.$props)
- .then((data) => {
- this.collapsedData = data;
- this.loadingState = null;
- })
- .catch((e) => {
- this.loadingState = LOADING_STATES.collapsedError;
+ if (this.$options.enablePolling) {
+ this.initExtensionPolling();
+ } else {
+ this.fetchCollapsedData(this.$props)
+ .then((data) => {
+ this.setCollapsedData(data);
+ })
+ .catch((e) => {
+ this.setCollapsedError(e);
+ });
+ }
+ },
+ setCollapsedData(data) {
+ this.collapsedData = data;
+ this.loadingState = null;
+ },
+ setCollapsedError(e) {
+ this.loadingState = LOADING_STATES.collapsedError;
- Sentry.captureException(e);
- });
+ Sentry.captureException(e);
},
loadAllData() {
if (this.hasFullData) return;
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
index ec6e6ed2620..8438f3492b2 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js
@@ -13,6 +13,7 @@ export const registerExtension = (extension) => {
props: extension.props,
i18n: extension.i18n,
expandEvent: extension.expandEvent,
+ enablePolling: extension.enablePolling,
computed: {
...Object.keys(extension.computed).reduce(
(acc, computedKey) => ({
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
index 235a200b747..8cdaa3316ee 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_header.vue
@@ -119,6 +119,8 @@ export default {
:show-gitpod-button="mr.showGitpodButton"
:gitpod-url="mr.gitpodUrl"
:gitpod-enabled="mr.gitpodEnabled"
+ :user-preferences-gitpod-path="mr.userPreferencesGitpodPath"
+ :user-profile-enable-gitpod-path="mr.userProfileEnableGitpodPath"
:gitpod-text="$options.i18n.gitpodText"
class="gl-display-none gl-md-display-inline-block gl-mr-3"
data-placement="bottom"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index 677c50ed930..2e3a02b1712 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ciIcon from '../../vue_shared/components/ci_icon.vue';
export default {
@@ -8,6 +9,7 @@ export default {
GlButton,
GlLoadingIcon,
},
+ mixins: [glFeatureFlagMixin()],
props: {
status: {
type: String,
@@ -42,7 +44,7 @@ export default {
</div>
<gl-button
- v-if="showDisabledButton"
+ v-if="!glFeatures.restructuredMrWidget && showDisabledButton"
category="primary"
variant="success"
data-testid="disabled-merge-button"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue
index ce572f8b0bf..701ef89304c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/merge_checks_failed.vue
@@ -1,20 +1,16 @@
<script>
-import { GlButton } from '@gitlab/ui';
import { s__ } from '~/locale';
-import notesEventHub from '~/notes/event_hub';
import StatusIcon from '../mr_widget_status_icon.vue';
export default {
i18n: {
- pipelineFailed: s__(
- 'mrWidget|The pipeline for this merge request did not complete. Push a new commit to fix the failure.',
- ),
approvalNeeded: s__('mrWidget|Merge blocked: this merge request must be approved.'),
- unresolvedDiscussions: s__('mrWidget|Merge blocked: all threads must be resolved.'),
+ blockingMergeRequests: s__(
+ 'mrWidget|Merge blocked: you can only merge after the above items are resolved.',
+ ),
},
components: {
StatusIcon,
- GlButton,
},
props: {
mr: {
@@ -24,22 +20,15 @@ export default {
},
computed: {
failedText() {
- if (this.mr.isPipelineFailed) {
- return this.$options.i18n.pipelineFailed;
- } else if (this.mr.approvals && !this.mr.isApproved) {
+ if (this.mr.approvals && !this.mr.isApproved) {
return this.$options.i18n.approvalNeeded;
- } else if (this.mr.hasMergeableDiscussionsState) {
- return this.$options.i18n.unresolvedDiscussions;
+ } else if (this.mr.blockingMergeRequests?.total_count > 0) {
+ return this.$options.i18n.blockingMergeRequests;
}
return null;
},
},
- methods: {
- jumpToFirstUnresolvedDiscussion() {
- notesEventHub.$emit('jumpToFirstUnresolvedDiscussion');
- },
- },
};
</script>
@@ -48,28 +37,6 @@ export default {
<status-icon status="warning" />
<p class="media-body gl-m-0! gl-font-weight-bold gl-text-black-normal!">
{{ failedText }}
- <template v-if="failedText == $options.i18n.unresolvedDiscussions">
- <gl-button
- class="gl-ml-3"
- size="small"
- variant="confirm"
- data-testid="jumpToUnresolved"
- @click="jumpToFirstUnresolvedDiscussion"
- >
- {{ s__('mrWidget|Jump to first unresolved thread') }}
- </gl-button>
- <gl-button
- v-if="mr.createIssueToResolveDiscussionsPath"
- :href="mr.createIssueToResolveDiscussionsPath"
- class="gl-ml-3"
- size="small"
- variant="confirm"
- category="secondary"
- data-testid="resolveIssue"
- >
- {{ s__('mrWidget|Create issue to resolve all threads') }}
- </gl-button>
- </template>
</p>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
index 13b1e49f44e..071920856a8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_archived.vue
@@ -1,25 +1,22 @@
<script>
-import { GlButton } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import statusIcon from '../mr_widget_status_icon.vue';
export default {
name: 'MRWidgetArchived',
components: {
- GlButton,
statusIcon,
},
+ mixins: [glFeatureFlagMixin()],
};
</script>
<template>
<div class="mr-widget-body media">
<div class="space-children">
- <status-icon status="warning" />
- <gl-button category="secondary" variant="success" :disabled="true">
- {{ s__('mrWidget|Merge') }}
- </gl-button>
+ <status-icon status="warning" show-disabled-button />
</div>
<div class="media-body">
- <span class="bold">
+ <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
{{ s__('mrWidget|Merge unavailable: merge requests are read-only on archived projects.') }}
</span>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
index 10b93d7849f..fd42fa0421f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
@@ -12,9 +12,11 @@ export default {
</script>
<template>
<div class="mr-widget-body media">
- <status-icon :show-disabled-button="!glFeatures.restructuredMrWidget" status="loading" />
+ <status-icon :show-disabled-button="true" status="loading" />
<div class="media-body space-children">
- <span class="bold"> {{ s__('mrWidget|Checking if merge request can be merged…') }} </span>
+ <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
+ {{ s__('mrWidget|Checking if merge request can be merged…') }}
+ </span>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
index 7a002d41ac0..a2c9cfe53cc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -109,14 +109,18 @@ export default {
</gl-skeleton-loader>
</div>
<div v-else class="media-body space-children gl-display-flex gl-align-items-center">
- <span v-if="shouldBeRebased" class="bold">
+ <span
+ v-if="shouldBeRebased"
+ :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
+ class="bold"
+ >
{{
s__(`mrWidget|Merge blocked: fast-forward merge is not possible.
To merge this request, first rebase locally.`)
}}
</span>
<template v-else>
- <span class="bold">
+ <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
{{ s__('mrWidget|Merge blocked: merge conflicts must be resolved.') }}
<span v-if="!canMerge">
{{
@@ -129,6 +133,7 @@ export default {
<gl-button
v-if="showResolveButton"
:href="mr.conflictResolutionPath"
+ :size="glFeatures.restructuredMrWidget ? 'small' : 'medium'"
data-testid="resolve-conflicts-button"
>
{{ s__('mrWidget|Resolve conflicts') }}
@@ -136,6 +141,7 @@ export default {
<gl-button
v-if="canMerge"
v-gl-modal-directive="'modal-merge-info'"
+ :size="glFeatures.restructuredMrWidget ? 'small' : 'medium'"
data-testid="merge-locally-button"
>
{{ s__('mrWidget|Merge locally') }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
index f91350d4a82..5b03eda2eac 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_missing_branch.vue
@@ -74,10 +74,21 @@ export default {
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
- <span class="bold js-branch-text">
+ <span
+ :class="{
+ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget,
+ }"
+ class="bold js-branch-text"
+ >
<span class="capitalize" data-testid="missingBranchName"> {{ missingBranchName }} </span>
{{ s__('mrWidget|branch does not exist.') }} {{ missingBranchNameMessage }}
- <gl-icon v-gl-tooltip :title="message" :aria-label="message" name="question-o" />
+ <gl-icon
+ v-gl-tooltip
+ :title="message"
+ :aria-label="message"
+ name="question-o"
+ class="gl-text-blue-600 gl-cursor-pointer"
+ />
</span>
</div>
</div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
index 68ffca9cd68..34c5a2ff2c8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_pipeline_blocked.vue
@@ -1,4 +1,5 @@
<script>
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import StatusIcon from '../mr_widget_status_icon.vue';
export default {
@@ -6,13 +7,14 @@ export default {
components: {
StatusIcon,
},
+ mixins: [glFeatureFlagMixin()],
};
</script>
<template>
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
- <span class="bold">
+ <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
{{
s__(
`mrWidget|Merge blocked: pipeline must succeed. It's waiting for a manual action to continue.`,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index 01e8303f513..bb0fb410d3e 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -3,11 +3,13 @@ import { GlButton, GlSkeletonLoader } from '@gitlab/ui';
import createFlash from '~/flash';
import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import ActionsButton from '~/vue_shared/components/actions_button.vue';
import simplePoll from '../../../lib/utils/simple_poll';
import eventHub from '../../event_hub';
import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables';
import rebaseQuery from '../../queries/states/rebase.query.graphql';
import statusIcon from '../mr_widget_status_icon.vue';
+import { REBASE_BUTTON_KEY, REBASE_WITHOUT_CI_BUTTON_KEY } from '../../constants';
export default {
name: 'MRWidgetRebase',
@@ -25,8 +27,9 @@ export default {
},
components: {
statusIcon,
- GlButton,
GlSkeletonLoader,
+ ActionsButton,
+ GlButton,
},
mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin],
props: {
@@ -44,12 +47,16 @@ export default {
state: {},
isMakingRequest: false,
rebasingError: null,
+ selectedRebaseAction: REBASE_BUTTON_KEY,
};
},
computed: {
isLoading() {
return this.glFeatures.mergeRequestWidgetGraphql && this.$apollo.queries.state.loading;
},
+ showRebaseWithoutCi() {
+ return this.glFeatures?.rebaseWithoutCiUi;
+ },
rebaseInProgress() {
if (this.glFeatures.mergeRequestWidgetGraphql) {
return this.state.rebaseInProgress;
@@ -86,14 +93,36 @@ export default {
fastForwardMergeText() {
return __('Merge blocked: the source branch must be rebased onto the target branch.');
},
+ actions() {
+ return [this.rebaseAction, this.rebaseWithoutCiAction].filter((action) => action);
+ },
+ rebaseAction() {
+ return {
+ key: REBASE_BUTTON_KEY,
+ text: __('Rebase'),
+ secondaryText: __('Rebases and triggers a pipeline'),
+ attrs: {
+ 'data-qa-selector': 'mr_rebase_button',
+ },
+ handle: () => this.rebase(),
+ };
+ },
+ rebaseWithoutCiAction() {
+ return {
+ key: REBASE_WITHOUT_CI_BUTTON_KEY,
+ text: __('Rebase without CI'),
+ secondaryText: __('Performs a rebase but skips triggering a new pipeline'),
+ handle: () => this.rebase({ skipCi: true }),
+ };
+ },
},
methods: {
- rebase() {
+ rebase({ skipCi = false } = {}) {
this.isMakingRequest = true;
this.rebasingError = null;
this.service
- .rebase()
+ .rebase({ skipCi })
.then(() => {
simplePoll(this.checkRebaseStatus);
})
@@ -109,6 +138,9 @@ export default {
}
});
},
+ selectRebaseAction(key) {
+ this.selectedRebaseAction = key;
+ },
checkRebaseStatus(continuePolling, stopPolling) {
this.service
.poll()
@@ -152,12 +184,14 @@ export default {
<div class="rebase-state-find-class-convention media media-body space-children">
<span
v-if="rebaseInProgress || isMakingRequest"
+ :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
class="gl-font-weight-bold"
data-testid="rebase-message"
>{{ __('Rebase in progress') }}</span
>
<span
v-if="!rebaseInProgress && !canPushToSourceBranch"
+ :class="{ 'gl-text-body!': glFeatures.restructuredMrWidget }"
class="gl-font-weight-bold gl-ml-0!"
data-testid="rebase-message"
>{{ fastForwardMergeText }}</span
@@ -167,15 +201,26 @@ export default {
class="accept-merge-holder clearfix js-toggle-container accept-action media space-children"
>
<gl-button
+ v-if="!glFeatures.restructuredMrWidget && !showRebaseWithoutCi"
:loading="isMakingRequest"
variant="confirm"
data-qa-selector="mr_rebase_button"
+ data-testid="standard-rebase-button"
@click="rebase"
>
{{ __('Rebase') }}
</gl-button>
+ <actions-button
+ v-if="!glFeatures.restructuredMrWidget && showRebaseWithoutCi"
+ :actions="actions"
+ :selected-key="selectedRebaseAction"
+ variant="confirm"
+ category="primary"
+ @select="selectRebaseAction"
+ />
<span
v-if="!rebasingError"
+ :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
class="gl-font-weight-bold"
data-testid="rebase-message"
data-qa-selector="no_fast_forward_message_content"
@@ -186,6 +231,17 @@ export default {
<span v-else class="gl-font-weight-bold danger" data-testid="rebase-message">{{
rebasingError
}}</span>
+ <gl-button
+ v-if="glFeatures.restructuredMrWidget"
+ :loading="isMakingRequest"
+ variant="confirm"
+ size="small"
+ data-qa-selector="mr_rebase_button"
+ class="gl-ml-3!"
+ @click="rebase"
+ >
+ {{ __('Rebase') }}
+ </gl-button>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
index 2d704d3b07a..e43319d42ca 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/nothing_to_merge.vue
@@ -62,8 +62,8 @@ export default {
<gl-button
v-if="mr.newBlobPath"
:href="mr.newBlobPath"
- category="secondary"
- variant="success"
+ category="primary"
+ variant="confirm"
data-testid="createFileButton"
@click="onClickNewFile"
>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue
index b5d2f91c637..d88dad2e086 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/pipeline_failed.vue
@@ -1,6 +1,7 @@
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { s__ } from '~/locale';
import statusIcon from '../mr_widget_status_icon.vue';
@@ -11,6 +12,7 @@ export default {
GlSprintf,
statusIcon,
},
+ mixins: [glFeatureFlagMixin()],
computed: {
troubleshootingDocsPath() {
return helpPagePath('ci/troubleshooting', { anchor: 'merge-request-status-messages' });
@@ -28,7 +30,7 @@ export default {
<div class="mr-widget-body media">
<status-icon :show-disabled-button="true" status="warning" />
<div class="media-body space-children">
- <span class="bold">
+ <span :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }" class="bold">
<gl-sprintf :message="$options.i18n.failedMessage">
<template #link="{ content }">
<gl-link :href="troubleshootingDocsPath" target="_blank">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 8830128b7d6..06ce312bd4c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -529,7 +529,7 @@ export default {
<template>
<div
:class="{
- 'gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7':
+ 'gl-border-t-1 gl-border-t-solid gl-border-gray-100 gl-bg-gray-10 gl-pl-7 gl-rounded-bottom-left-base gl-rounded-bottom-right-base':
glFeatures.restructuredMrWidget,
}"
>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue
index 7eeba8d8f89..b1fbe150fcf 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/sha_mismatch.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { I18N_SHA_MISMATCH } from '../../i18n';
import statusIcon from '../mr_widget_status_icon.vue';
@@ -12,6 +13,7 @@ export default {
i18n: {
I18N_SHA_MISMATCH,
},
+ mixins: [glFeatureFlagMixin()],
props: {
mr: {
type: Object,
@@ -25,7 +27,11 @@ export default {
<div class="mr-widget-body media">
<status-icon :show-disabled-button="false" status="warning" />
<div class="media-body">
- <span class="gl-font-weight-bold" data-qa-selector="head_mismatch_content">
+ <span
+ :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
+ class="gl-font-weight-bold"
+ data-qa-selector="head_mismatch_content"
+ >
{{ $options.i18n.I18N_SHA_MISMATCH.warningMessage }}
</span>
<gl-button
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
index 69e4df0ca11..8cf6383c26a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/unresolved_discussions.vue
@@ -1,5 +1,6 @@
<script>
import { GlButton } from '@gitlab/ui';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import notesEventHub from '~/notes/event_hub';
import statusIcon from '../mr_widget_status_icon.vue';
@@ -9,6 +10,7 @@ export default {
statusIcon,
GlButton,
},
+ mixins: [glFeatureFlagMixin()],
props: {
mr: {
type: Object,
@@ -25,16 +27,24 @@ export default {
<template>
<div class="mr-widget-body media gl-flex-wrap">
- <status-icon :show-disabled-button="true" status="warning" />
+ <status-icon show-disabled-button status="warning" />
<div class="media-body">
- <span class="gl-ml-3 gl-font-weight-bold gl-display-block gl-w-100">{{
- s__('mrWidget|Merge blocked: all threads must be resolved.')
- }}</span>
+ <span
+ :class="{
+ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget,
+ 'gl-display-block': !glFeatures.restructuredMrWidget,
+ }"
+ class="gl-ml-3 gl-font-weight-bold gl-w-100"
+ >
+ {{ s__('mrWidget|Merge blocked: all threads must be resolved.') }}
+ </span>
<gl-button
data-testid="jump-to-first"
class="gl-ml-3"
size="small"
- icon="comment-next"
+ :icon="glFeatures.restructuredMrWidget ? undefined : 'comment-next'"
+ :variant="glFeatures.restructuredMrWidget && 'confirm'"
+ :category="glFeatures.restructuredMrWidget && 'secondary'"
@click="jumpToFirstUnresolvedDiscussion"
>
{{ s__('mrWidget|Jump to first unresolved thread') }}
@@ -44,7 +54,7 @@ export default {
:href="mr.createIssueToResolveDiscussionsPath"
class="js-create-issue gl-ml-3"
size="small"
- icon="issue-new"
+ :icon="glFeatures.restructuredMrWidget ? undefined : 'issue-new'"
>
{{ s__('mrWidget|Create issue to resolve all threads') }}
</gl-button>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
index ba831a33b73..e0e19094c40 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue
@@ -166,7 +166,10 @@ export default {
<status-icon :show-disabled-button="canUpdate" status="warning" />
<div class="media-body">
<div class="float-left">
- <span class="gl-font-weight-bold">
+ <span
+ :class="{ 'gl-ml-0! gl-text-body!': glFeatures.restructuredMrWidget }"
+ class="gl-font-weight-bold"
+ >
{{
__("Merge blocked: merge request must be marked as ready. It's still marked as draft.")
}}
diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js
index 2edccce7f4e..32effb91043 100644
--- a/app/assets/javascripts/vue_merge_request_widget/constants.js
+++ b/app/assets/javascripts/vue_merge_request_widget/constants.js
@@ -162,3 +162,6 @@ export const EXTENSION_SUMMARY_FAILED_CLASS = 'gl-text-red-500';
export const EXTENSION_SUMMARY_NEUTRAL_CLASS = 'gl-text-gray-700';
export { STATE_MACHINE };
+
+export const REBASE_BUTTON_KEY = 'rebase';
+export const REBASE_WITHOUT_CI_BUTTON_KEY = 'rebaseWithoutCi';
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js
new file mode 100644
index 00000000000..a564acada02
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/terraform/index.js
@@ -0,0 +1,173 @@
+import { __, n__, s__, sprintf } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+import { EXTENSION_ICONS } from '../../constants';
+
+export default {
+ name: 'WidgetTerraform',
+ enablePolling: true,
+ i18n: {
+ label: s__('Terraform|Terraform reports'),
+ loading: s__('Terraform|Loading Terraform reports...'),
+ error: s__('Terraform|Failed to load Terraform reports'),
+ reportGenerated: s__('Terraform|A Terraform report was generated in your pipelines.'),
+ namedReportGenerated: s__(
+ 'Terraform|The job %{strong_start}%{name}%{strong_end} generated a report.',
+ ),
+ reportChanges: s__(
+ 'Terraform|Reported Resource Changes: %{addNum} to add, %{changeNum} to change, %{deleteNum} to delete',
+ ),
+ reportFailed: s__('Terraform|A Terraform report failed to generate.'),
+ namedReportFailed: s__(
+ 'Terraform|The job %{strong_start}%{name}%{strong_end} failed to generate a report.',
+ ),
+ reportErrored: s__('Terraform|Generating the report caused an error.'),
+ fullLog: __('Full log'),
+ },
+ expandEvent: 'i_testing_terraform_widget_total',
+ props: ['terraformReportsPath'],
+ computed: {
+ // Extension computed props
+ statusIcon() {
+ return EXTENSION_ICONS.warning;
+ },
+ },
+ methods: {
+ // Extension methods
+ summary({ valid = [], invalid = [] }) {
+ let title;
+ let subtitle = '';
+
+ const validText = sprintf(
+ n__(
+ 'Terraform|%{strong_start}%{number}%{strong_end} Terraform report was generated in your pipelines',
+ 'Terraform|%{strong_start}%{number}%{strong_end} Terraform reports were generated in your pipelines',
+ valid.length,
+ ),
+ {
+ number: valid.length,
+ },
+ false,
+ );
+
+ const invalidText = sprintf(
+ n__(
+ 'Terraform|%{strong_start}%{number}%{strong_end} Terraform report failed to generate',
+ 'Terraform|%{strong_start}%{number}%{strong_end} Terraform reports failed to generate',
+ invalid.length,
+ ),
+ {
+ number: invalid.length,
+ },
+ false,
+ );
+
+ if (valid.length) {
+ title = validText;
+ if (invalid.length) {
+ subtitle = sprintf(`<br>%{small_start}${invalidText}%{small_end}`);
+ }
+ } else {
+ title = invalidText;
+ }
+
+ return `${title}${subtitle}`;
+ },
+ fetchCollapsedData() {
+ return Promise.resolve(this.fetchPlans().then(this.prepareReports));
+ },
+ fetchFullData() {
+ const { valid, invalid } = this.collapsedData;
+ return Promise.resolve([...valid, ...invalid]);
+ },
+ // Custom methods
+ fetchPlans() {
+ return axios
+ .get(this.terraformReportsPath)
+ .then(({ data }) => {
+ return Object.keys(data).map((key) => {
+ return data[key];
+ });
+ })
+ .catch(() => {
+ const invalidData = { tf_report_error: 'api_error' };
+ return [invalidData];
+ });
+ },
+ createReportRow(report, iconName) {
+ const addNum = Number(report.create);
+ const changeNum = Number(report.update);
+ const deleteNum = Number(report.delete);
+ const validPlanValues = addNum + changeNum + deleteNum >= 0;
+
+ const actions = [];
+
+ let title;
+ let subtitle;
+
+ if (report.job_path) {
+ const action = {
+ href: report.job_path,
+ text: this.$options.i18n.fullLog,
+ target: '_blank',
+ };
+ actions.push(action);
+ }
+
+ if (validPlanValues) {
+ if (report.job_name) {
+ title = sprintf(
+ this.$options.i18n.namedReportGenerated,
+ {
+ name: report.job_name,
+ },
+ false,
+ );
+ } else {
+ title = this.$options.i18n.reportGenerated;
+ }
+
+ subtitle = sprintf(`%{small_start}${this.$options.i18n.reportChanges}%{small_end}`, {
+ addNum,
+ changeNum,
+ deleteNum,
+ });
+ } else {
+ if (report.job_name) {
+ title = sprintf(
+ this.$options.i18n.namedReportFailed,
+ {
+ name: report.job_name,
+ },
+ false,
+ );
+ } else {
+ title = this.$options.i18n.reportFailed;
+ }
+
+ subtitle = sprintf(`%{small_start}${this.$options.i18n.reportErrored}%{small_end}`);
+ }
+
+ return {
+ text: `${title}
+ <br>
+ ${subtitle}`,
+ icon: { name: iconName },
+ actions,
+ };
+ },
+ prepareReports(reports) {
+ const valid = [];
+ const invalid = [];
+
+ reports.forEach((report) => {
+ if (report.tf_report_error) {
+ invalid.push(this.createReportRow(report, EXTENSION_ICONS.error));
+ } else {
+ valid.push(this.createReportRow(report, EXTENSION_ICONS.success));
+ }
+ });
+
+ return { valid, invalid };
+ },
+ },
+};
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index c98dc426224..83a07240403 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -1,6 +1,7 @@
<script>
import { GlSafeHtmlDirective } from '@gitlab/ui';
import { isEmpty } from 'lodash';
+import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
import MrWidgetApprovals from 'ee_else_ce/vue_merge_request_widget/components/approvals/approvals.vue';
import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_widget_service';
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
@@ -43,6 +44,7 @@ import { STATE_MACHINE, stateToComponentMap } from './constants';
import eventHub from './event_hub';
import mergeRequestQueryVariablesMixin from './mixins/merge_request_query_variables';
import getStateQuery from './queries/get_state.query.graphql';
+import terraformExtension from './extensions/terraform';
export default {
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
@@ -184,6 +186,9 @@ export default {
shouldRenderSecurityReport() {
return Boolean(this.mr.pipeline.id);
},
+ shouldRenderTerraformPlans() {
+ return Boolean(this.mr?.terraformReportsPath);
+ },
mergeError() {
let { mergeError } = this.mr;
@@ -230,6 +235,11 @@ export default {
this.initPostMergeDeploymentsPolling();
}
},
+ shouldRenderTerraformPlans(newVal) {
+ if (newVal) {
+ this.registerTerraformPlans();
+ }
+ },
},
mounted() {
MRWidgetService.fetchInitialData()
@@ -463,6 +473,11 @@ export default {
dismissSuggestPipelines() {
this.mr.isDismissedSuggestPipeline = true;
},
+ registerTerraformPlans() {
+ if (this.shouldRenderTerraformPlans && this.shouldShowExtension) {
+ registerExtension(terraformExtension);
+ }
+ },
},
};
</script>
@@ -542,7 +557,10 @@ export default {
:pipeline-path="mr.pipeline.path"
/>
- <terraform-plan v-if="mr.terraformReportsPath" :endpoint="mr.terraformReportsPath" />
+ <terraform-plan
+ v-if="mr.terraformReportsPath && !shouldShowExtension"
+ :endpoint="mr.terraformReportsPath"
+ />
<grouped-accessibility-reports-app
v-if="shouldShowAccessibilityReport"
diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
index 7dcb4881e7f..7b803b0fcbb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
+++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
@@ -55,8 +55,9 @@ export default class MRWidgetService {
return axios.get(this.endpoints.mergeActionsContentPath);
}
- rebase() {
- return axios.post(this.endpoints.rebasePath);
+ rebase({ skipCi = false } = {}) {
+ const path = `${this.endpoints.rebasePath}?skip_ci=${Boolean(skipCi)}`;
+ return axios.post(path);
}
fetchApprovals() {
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 57af869a0ba..5378dabf638 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -222,6 +222,8 @@ export default class MergeRequestStore {
this.showGitpodButton = data.show_gitpod_button;
this.gitpodUrl = data.gitpod_url;
this.gitpodEnabled = data.gitpod_enabled;
+ this.userPreferencesGitpodPath = data.user_preferences_gitpod_path;
+ this.userProfileEnableGitpodPath = data.user_profile_enable_gitpod_path;
}
setState() {
@@ -357,15 +359,13 @@ export default class MergeRequestStore {
setApprovals(data) {
this.approvals = data;
this.isApproved = data.approved || false;
+
+ this.setState();
}
+ // eslint-disable-next-line class-methods-use-this
get hasMergeChecksFailed() {
- if (!window.gon?.features?.restructuredMrWidget) return false;
-
- return (
- this.hasMergeableDiscussionsState ||
- (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed)
- );
+ return false;
}
// Because the state machine doesn't yet handle every state and transition,
diff --git a/app/assets/javascripts/vue_shared/components/actions_button.vue b/app/assets/javascripts/vue_shared/components/actions_button.vue
index bab13fe7c75..6db18afe51c 100644
--- a/app/assets/javascripts/vue_shared/components/actions_button.vue
+++ b/app/assets/javascripts/vue_shared/components/actions_button.vue
@@ -66,6 +66,7 @@ export default {
:variant="variant"
:category="category"
split
+ data-qa-selector="action_dropdown"
@click="handleClick(selectedAction, $event)"
>
<template #button-content>
@@ -79,6 +80,7 @@ export default {
:is-check-item="true"
:is-checked="action.key === selectedAction.key"
:secondary-text="action.secondaryText"
+ :data-qa-selector="`${action.key}_menu_item`"
:data-testid="`action_${action.key}`"
@click="handleItemClick(action)"
>
diff --git a/app/assets/javascripts/vue_shared/components/clipboard_button.vue b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
index 400be3ef688..f907b64608c 100644
--- a/app/assets/javascripts/vue_shared/components/clipboard_button.vue
+++ b/app/assets/javascripts/vue_shared/components/clipboard_button.vue
@@ -13,9 +13,23 @@
* />
*/
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
+import { uniqueId } from 'lodash';
+
+import { __ } from '~/locale';
+import {
+ CLIPBOARD_SUCCESS_EVENT,
+ CLIPBOARD_ERROR_EVENT,
+ I18N_ERROR_MESSAGE,
+} from '~/behaviors/copy_to_clipboard';
export default {
name: 'ClipboardButton',
+ i18n: {
+ copied: __('Copied'),
+ error: I18N_ERROR_MESSAGE,
+ },
+ CLIPBOARD_SUCCESS_EVENT,
+ CLIPBOARD_ERROR_EVENT,
directives: {
GlTooltip: GlTooltipDirective,
},
@@ -72,6 +86,13 @@ export default {
default: 'default',
},
},
+ data() {
+ return {
+ localTitle: this.title,
+ titleTimeout: null,
+ id: null,
+ };
+ },
computed: {
clipboardText() {
if (this.gfm !== null) {
@@ -79,25 +100,50 @@ export default {
}
return this.text;
},
+ tooltipDirectiveOptions() {
+ return {
+ placement: this.tooltipPlacement,
+ container: this.tooltipContainer,
+ boundary: this.tooltipBoundary,
+ };
+ },
+ },
+ created() {
+ this.id = uniqueId('clipboard-button-');
+ },
+ methods: {
+ updateTooltip(title) {
+ this.localTitle = title;
+ this.$root.$emit('bv::show::tooltip', this.id);
+
+ clearTimeout(this.titleTimeout);
+
+ this.titleTimeout = setTimeout(() => {
+ this.localTitle = this.title;
+ this.$root.$emit('bv::hide::tooltip', this.id);
+ }, 1000);
+ },
},
};
</script>
<template>
<gl-button
- v-gl-tooltip.hover.blur.viewport="{
- placement: tooltipPlacement,
- container: tooltipContainer,
- boundary: tooltipBoundary,
- }"
+ :id="id"
+ ref="copyButton"
+ v-gl-tooltip.hover.focus.click.viewport="tooltipDirectiveOptions"
:class="cssClass"
- :title="title"
+ :title="localTitle"
:data-clipboard-text="clipboardText"
+ data-clipboard-handle-tooltip="false"
:category="category"
:size="size"
icon="copy-to-clipboard"
- :aria-label="__('Copy this value')"
:variant="variant"
+ :aria-label="localTitle"
+ aria-live="polite"
+ @[$options.CLIPBOARD_SUCCESS_EVENT]="updateTooltip($options.i18n.copied)"
+ @[$options.CLIPBOARD_ERROR_EVENT]="updateTooltip($options.i18n.error)"
v-on="$listeners"
>
<slot></slot>
diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue
index f93415ced45..e12e06a2454 100644
--- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue
+++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger.vue
@@ -36,6 +36,11 @@ export default {
required: false,
default: 'confirm-danger-button',
},
+ buttonVariant: {
+ type: String,
+ required: false,
+ default: 'danger',
+ },
},
modalId: CONFIRM_DANGER_MODAL_ID,
};
@@ -45,7 +50,7 @@ export default {
<gl-button
v-gl-modal="$options.modalId"
:class="buttonClass"
- variant="danger"
+ :variant="buttonVariant"
:disabled="disabled"
:data-testid="buttonTestid"
>{{ buttonText }}</gl-button
diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js
index 18fa297da87..6629b293eb9 100644
--- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js
+++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.stories.js
@@ -11,7 +11,9 @@ const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
template: '<confirm-danger v-bind="$props" />',
provide: {
- confirmDangerMessage: 'You require more Vespene Gas',
+ additionalInformation: args.additionalInformation || null,
+ confirmDangerMessage: args.confirmDangerMessage || 'You require more Vespene Gas',
+ htmlConfirmationMessage: args.confirmDangerMessage || false,
},
});
@@ -26,3 +28,16 @@ Disabled.args = {
...Default.args,
disabled: true,
};
+
+export const AdditionalInformation = Template.bind({});
+AdditionalInformation.args = {
+ ...Default.args,
+ additionalInformation: 'This replaces the default warning information',
+};
+
+export const HtmlMessage = Template.bind({});
+HtmlMessage.args = {
+ ...Default.args,
+ confirmDangerMessage: 'You strongly require more <strong>Vespene Gas</strong>',
+ htmlConfirmationMessage: true,
+};
diff --git a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue
index 5bbe44b20b3..88890b3332d 100644
--- a/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/confirm_danger/confirm_danger_modal.vue
@@ -1,5 +1,12 @@
<script>
-import { GlAlert, GlModal, GlFormGroup, GlFormInput, GlSprintf } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlModal,
+ GlFormGroup,
+ GlFormInput,
+ GlSafeHtmlDirective as SafeHtml,
+ GlSprintf,
+} from '@gitlab/ui';
import {
CONFIRM_DANGER_MODAL_BUTTON,
CONFIRM_DANGER_MODAL_TITLE,
@@ -17,13 +24,22 @@ export default {
GlFormInput,
GlSprintf,
},
+ directives: {
+ SafeHtml,
+ },
inject: {
+ htmlConfirmationMessage: {
+ default: false,
+ },
confirmDangerMessage: {
default: '',
},
confirmButtonText: {
default: CONFIRM_DANGER_MODAL_BUTTON,
},
+ additionalInformation: {
+ default: CONFIRM_DANGER_WARNING,
+ },
},
props: {
modalId: {
@@ -81,9 +97,12 @@ export default {
:dismissible="false"
class="gl-mb-4"
>
- {{ confirmDangerMessage }}
+ <span v-if="htmlConfirmationMessage" v-safe-html="confirmDangerMessage"></span>
+ <span v-else>
+ {{ confirmDangerMessage }}
+ </span>
</gl-alert>
- <p data-testid="confirm-danger-warning">{{ $options.i18n.CONFIRM_DANGER_WARNING }}</p>
+ <p data-testid="confirm-danger-warning">{{ additionalInformation }}</p>
<p data-testid="confirm-danger-phrase">
<gl-sprintf :message="$options.i18n.CONFIRM_DANGER_PHRASE_TEXT">
<template #phrase_code>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
index 7c1828f2294..5cdf7b6a3b2 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue
@@ -332,7 +332,7 @@ export default {
v-if="showCheckbox"
class="gl-align-self-center"
:checked="checkboxChecked"
- @input="$emit('checked-input', $event)"
+ @change="$emit('checked-input', $event)"
>
<span class="gl-sr-only">{{ __('Select all') }}</span>
</gl-form-checkbox>
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
index 06478a89721..b70317b2ec4 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/tokens/author_token.vue
@@ -4,7 +4,7 @@ import { compact } from 'lodash';
import createFlash from '~/flash';
import { __ } from '~/locale';
-import { DEFAULT_LABEL_ANY } from '../constants';
+import { DEFAULT_NONE_ANY } from '../constants';
import BaseToken from './base_token.vue';
@@ -36,7 +36,7 @@ export default {
},
computed: {
defaultAuthors() {
- return this.config.defaultAuthors || [DEFAULT_LABEL_ANY];
+ return this.config.defaultAuthors || DEFAULT_NONE_ANY;
},
preloadedAuthors() {
return this.config.preloadedAuthors || [];
diff --git a/app/assets/javascripts/vue_shared/components/gitlab_version_check.vue b/app/assets/javascripts/vue_shared/components/gitlab_version_check.vue
new file mode 100644
index 00000000000..acddf16bd27
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/gitlab_version_check.vue
@@ -0,0 +1,67 @@
+<script>
+import { GlBadge } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+
+const STATUS_TYPES = {
+ SUCCESS: 'success',
+ WARNING: 'warning',
+ DANGER: 'danger',
+};
+
+export default {
+ name: 'GitlabVersionCheck',
+ components: {
+ GlBadge,
+ },
+ props: {
+ size: {
+ type: String,
+ required: false,
+ default: 'md',
+ },
+ },
+ data() {
+ return {
+ status: null,
+ };
+ },
+ computed: {
+ title() {
+ if (this.status === STATUS_TYPES.SUCCESS) {
+ return s__('VersionCheck|Up to date');
+ } else if (this.status === STATUS_TYPES.WARNING) {
+ return s__('VersionCheck|Update available');
+ } else if (this.status === STATUS_TYPES.DANGER) {
+ return s__('VersionCheck|Update ASAP');
+ }
+
+ return null;
+ },
+ },
+ created() {
+ this.checkGitlabVersion();
+ },
+ methods: {
+ checkGitlabVersion() {
+ axios
+ .get('/admin/version_check.json')
+ .then((res) => {
+ if (res.data) {
+ this.status = res.data.severity;
+ }
+ })
+ .catch(() => {
+ // Silently fail
+ this.status = null;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-badge v-if="status" class="version-check-badge" :variant="status" :size="size">{{
+ title
+ }}</gl-badge>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/line_numbers.vue b/app/assets/javascripts/vue_shared/components/line_numbers.vue
index 7e17cca3dcc..11caf3be00a 100644
--- a/app/assets/javascripts/vue_shared/components/line_numbers.vue
+++ b/app/assets/javascripts/vue_shared/components/line_numbers.vue
@@ -12,31 +12,6 @@ export default {
required: true,
},
},
- data() {
- return {
- currentlyHighlightedLine: null,
- };
- },
- mounted() {
- this.scrollToLine();
- },
- methods: {
- scrollToLine(hash = window.location.hash) {
- const lineToHighlight = hash && this.$el.querySelector(hash);
-
- if (!lineToHighlight) {
- return;
- }
-
- if (this.currentlyHighlightedLine) {
- this.currentlyHighlightedLine.classList.remove('hll');
- }
-
- lineToHighlight.classList.add('hll');
- this.currentlyHighlightedLine = lineToHighlight;
- lineToHighlight.scrollIntoView({ behavior: 'smooth', block: 'center' });
- },
- },
};
</script>
<template>
@@ -45,10 +20,9 @@ export default {
v-for="line in lines"
:id="`L${line}`"
:key="line"
- class="diff-line-num"
- :href="`#L${line}`"
+ class="diff-line-num gl-shadow-none!"
+ :to="`#LC${line}`"
:data-line-number="line"
- @click="scrollToLine(`#L${line}`)"
>
<gl-icon :size="12" name="link" />
{{ line }}
diff --git a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
index ce7cbafb97d..709d3592828 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/apply_suggestion.vue
@@ -67,7 +67,7 @@ export default {
<gl-button
class="gl-w-auto! gl-mt-3 gl-text-center! gl-hover-text-white! gl-transition-medium! float-right"
category="primary"
- variant="success"
+ variant="confirm"
data-qa-selector="commit_with_custom_message_button"
@click="onApply"
>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 86f04c78ebe..5c86c928ce3 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -2,7 +2,7 @@
import { GlIcon } from '@gitlab/ui';
import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
-import { unescape } from 'lodash';
+import { debounce, unescape } from 'lodash';
import createFlash from '~/flash';
import GLForm from '~/gl_form';
import axios from '~/lib/utils/axios_utils';
@@ -110,7 +110,7 @@ export default {
return {
markdownPreview: '',
referencedCommands: '',
- referencedUsers: '',
+ referencedUsers: [],
hasSuggestion: false,
markdownPreviewLoading: false,
previewMarkdown: false,
@@ -188,6 +188,24 @@ export default {
});
}
},
+
+ textareaValue: {
+ immediate: true,
+ handler(textareaValue, oldVal) {
+ const all = /@all([^\w._-]|$)/;
+ const hasAll = all.test(textareaValue);
+ const hadAll = all.test(oldVal);
+
+ const justAddedAll = !hadAll && hasAll;
+ const justRemovedAll = hadAll && !hasAll;
+
+ if (justAddedAll) {
+ this.debouncedFetchMarkdown();
+ } else if (justRemovedAll) {
+ this.referencedUsers = [];
+ }
+ },
+ },
},
mounted() {
// GLForm class handles all the toolbar buttons
@@ -222,9 +240,9 @@ export default {
if (this.textareaValue) {
this.markdownPreviewLoading = true;
this.markdownPreview = __('Loading…');
- axios
- .post(this.markdownPreviewPath, { text: this.textareaValue })
- .then((response) => this.renderMarkdown(response.data))
+
+ this.fetchMarkdown()
+ .then((data) => this.renderMarkdown(data))
.catch(() =>
createFlash({
message: __('Error loading markdown preview'),
@@ -239,17 +257,28 @@ export default {
this.previewMarkdown = false;
},
+ fetchMarkdown() {
+ return axios.post(this.markdownPreviewPath, { text: this.textareaValue }).then(({ data }) => {
+ const { references } = data;
+ if (references) {
+ this.referencedCommands = references.commands;
+ this.referencedUsers = references.users;
+ this.hasSuggestion = references.suggestions?.length > 0;
+ this.suggestions = references.suggestions;
+ }
+
+ return data;
+ });
+ },
+
+ debouncedFetchMarkdown: debounce(function debouncedFetchMarkdown() {
+ return this.fetchMarkdown();
+ }, 400),
+
renderMarkdown(data = {}) {
this.markdownPreviewLoading = false;
this.markdownPreview = data.body || __('Nothing to preview.');
- if (data.references) {
- this.referencedCommands = data.references.commands;
- this.referencedUsers = data.references.users;
- this.hasSuggestion = data.references.suggestions && data.references.suggestions.length;
- this.suggestions = data.references.suggestions;
- }
-
this.$nextTick()
.then(() => $(this.$refs['markdown-preview']).renderGFM())
.catch(() =>
@@ -326,18 +355,14 @@ export default {
v-html="markdownPreview /* eslint-disable-line vue/no-v-html */"
></div>
</template>
- <template v-if="previewMarkdown && !markdownPreviewLoading">
- <div
- v-if="referencedCommands"
- class="referenced-commands"
- v-html="referencedCommands /* eslint-disable-line vue/no-v-html */"
- ></div>
- <div v-if="shouldShowReferencedUsers" class="referenced-users">
- <gl-icon name="warning-solid" />
- <span
- v-html="addMultipleToDiscussionWarning /* eslint-disable-line vue/no-v-html */"
- ></span>
- </div>
- </template>
+ <div
+ v-if="referencedCommands && previewMarkdown && !markdownPreviewLoading"
+ class="referenced-commands"
+ v-html="referencedCommands /* eslint-disable-line vue/no-v-html */"
+ ></div>
+ <div v-if="shouldShowReferencedUsers" class="referenced-users">
+ <gl-icon name="warning-solid" />
+ <span v-html="addMultipleToDiscussionWarning /* eslint-disable-line vue/no-v-html */"></span>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
index 38afd56bae6..d4f50e347cb 100644
--- a/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
+++ b/app/assets/javascripts/vue_shared/components/modal_copy_button.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
-import Clipboard from 'clipboard';
+import ClipboardJS from 'clipboard';
import { uniqueId } from 'lodash';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
@@ -69,7 +69,7 @@ export default {
},
mounted() {
this.$nextTick(() => {
- this.clipboard = new Clipboard(this.$el, {
+ this.clipboard = new ClipboardJS(this.$el, {
container:
document.querySelector(`${this.modalDomId} div.modal-content`) ||
document.getElementById(this.container) ||
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
index 13a6dd43207..0fa64a29b3a 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents.vue
@@ -179,6 +179,9 @@ export default {
this.searchKey = '';
this.setFocus();
},
+ selectFirstItem() {
+ this.$refs.dropdownContentsView.selectFirstItem();
+ },
},
};
</script>
@@ -204,11 +207,13 @@ export default {
@toggleDropdownContentsCreateView="toggleDropdownContent"
@closeDropdown="$emit('closeDropdown')"
@input="debouncedSearchKeyUpdate"
+ @searchEnter="selectFirstItem"
/>
</template>
<template #default>
<component
:is="dropdownContentsView"
+ ref="dropdownContentsView"
v-model="localSelectedLabels"
:search-key="searchKey"
:allow-multiselect="allowMultiselect"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
index da626a21b14..b99083713a8 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
@@ -1,5 +1,12 @@
<script>
-import { GlTooltipDirective, GlButton, GlFormInput, GlLink, GlLoadingIcon } from '@gitlab/ui';
+import {
+ GlAlert,
+ GlTooltipDirective,
+ GlButton,
+ GlFormInput,
+ GlLink,
+ GlLoadingIcon,
+} from '@gitlab/ui';
import produce from 'immer';
import createFlash from '~/flash';
import { __ } from '~/locale';
@@ -11,6 +18,7 @@ const errorMessage = __('Error creating label.');
export default {
components: {
+ GlAlert,
GlButton,
GlFormInput,
GlLink,
@@ -42,6 +50,7 @@ export default {
labelTitle: '',
selectedColor: '',
labelCreateInProgress: false,
+ error: undefined,
};
},
computed: {
@@ -111,13 +120,14 @@ export default {
) => this.updateLabelsInCache(store, label),
});
if (labelCreate.errors.length) {
- createFlash({ message: errorMessage });
+ [this.error] = labelCreate.errors;
+ } else {
+ this.$emit('hideCreateView');
}
} catch {
createFlash({ message: errorMessage });
}
this.labelCreateInProgress = false;
- this.$emit('hideCreateView');
},
},
};
@@ -126,6 +136,9 @@ export default {
<template>
<div class="labels-select-contents-create js-labels-create">
<div class="dropdown-input">
+ <gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mb-3">
+ {{ error }}
+ </gl-alert>
<gl-form-input
v-model.trim="labelTitle"
:placeholder="__('Name new label')"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
index e9a2d7747e2..ae179ef93c7 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
@@ -84,6 +84,9 @@ export default {
showNoMatchingResultsMessage() {
return Boolean(this.searchKey) && this.visibleLabels.length === 0;
},
+ shouldHighlightFirstItem() {
+ return this.searchKey !== '' && this.visibleLabels.length > 0;
+ },
},
methods: {
isLabelSelected(label) {
@@ -128,6 +131,11 @@ export default {
onDropdownAppear() {
this.isVisible = true;
},
+ selectFirstItem() {
+ if (this.shouldHighlightFirstItem) {
+ this.handleLabelClick(this.visibleLabels[0]);
+ }
+ },
},
};
</script>
@@ -143,11 +151,13 @@ export default {
/>
<template v-else>
<gl-dropdown-item
- v-for="label in visibleLabels"
+ v-for="(label, index) in visibleLabels"
:key="label.id"
:is-checked="isLabelSelected(label)"
:is-check-centered="true"
:is-check-item="true"
+ :active="shouldHighlightFirstItem && index === 0"
+ active-class="is-focused"
data-testid="labels-list"
@click.native.capture.stop="handleLabelClick(label)"
>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue
index 7a0f20b0c83..faad69732dd 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_header.vue
@@ -83,6 +83,7 @@ export default {
data-qa-selector="dropdown_input_field"
data-testid="dropdown-input-field"
@input="$emit('input', $event)"
+ @keydown.enter="$emit('searchEnter', $event)"
/>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue
index f27f0b4e34c..caeee2df7e5 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/label_item.vue
@@ -10,7 +10,7 @@ export default {
</script>
<template>
- <div>
+ <div class="gl-display-flex gl-align-items-center">
<span
class="dropdown-label-box gl-flex-shrink-0 gl-top-0 gl-mr-3"
:style="{ 'background-color': label.color }"
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
index 3adda69b892..f53b75df4eb 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
@@ -289,6 +289,7 @@ export default {
'is-standalone': isDropdownVariantStandalone(variant),
'is-embedded': isDropdownVariantEmbedded(variant),
}"
+ data-testid="sidebar-labels"
data-qa-selector="labels_block"
>
<template v-if="isDropdownVariantSidebar(variant)">
diff --git a/app/assets/javascripts/vue_shared/components/source_editor.vue b/app/assets/javascripts/vue_shared/components/source_editor.vue
index 8a0fef36079..011cad4267c 100644
--- a/app/assets/javascripts/vue_shared/components/source_editor.vue
+++ b/app/assets/javascripts/vue_shared/components/source_editor.vue
@@ -97,7 +97,7 @@ export default {
ref="editor"
data-editor-loading
data-qa-selector="source_editor_container"
- @[$options.readyEvent]="$emit($options.readyEvent)"
+ @[$options.readyEvent]="$emit($options.readyEvent, $event)"
>
<pre class="editor-loading-content">{{ value }}</pre>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/source_viewer.vue b/app/assets/javascripts/vue_shared/components/source_viewer.vue
index 8f0d051543f..99895926653 100644
--- a/app/assets/javascripts/vue_shared/components/source_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/source_viewer.vue
@@ -1,6 +1,9 @@
<script>
import { GlSafeHtmlDirective } from '@gitlab/ui';
import LineNumbers from '~/vue_shared/components/line_numbers.vue';
+import { sanitize } from '~/lib/dompurify';
+
+const LINE_SELECT_CLASS_NAME = 'hll';
export default {
components: {
@@ -46,7 +49,15 @@ export default {
}
}
- return highlightedContent;
+ return this.wrapLines(highlightedContent);
+ },
+ },
+ watch: {
+ highlightedContent() {
+ this.$nextTick(() => this.selectLine());
+ },
+ $route() {
+ this.selectLine();
},
},
async mounted() {
@@ -73,16 +84,40 @@ export default {
return languageDefinition;
},
+ wrapLines(content) {
+ return (
+ content &&
+ content
+ .split('\n')
+ .map((line, i) => `<span id="LC${i + 1}" class="line">${line}</span>`)
+ .join('\r\n')
+ );
+ },
+ selectLine() {
+ const hash = sanitize(this.$route.hash);
+ const lineToSelect = hash && this.$el.querySelector(hash);
+
+ if (!lineToSelect) {
+ return;
+ }
+
+ if (this.$options.currentlySelectedLine) {
+ this.$options.currentlySelectedLine.classList.remove(LINE_SELECT_CLASS_NAME);
+ }
+
+ lineToSelect.classList.add(LINE_SELECT_CLASS_NAME);
+ this.$options.currentlySelectedLine = lineToSelect;
+ lineToSelect.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ },
},
userColorScheme: window.gon.user_color_scheme,
+ currentlySelectedLine: null,
};
</script>
<template>
- <div class="file-content code" :class="$options.userColorScheme">
+ <div class="file-content code js-syntax-highlight" :class="$options.userColorScheme">
<line-numbers :lines="lineNumbers" />
- <pre
- class="code gl-pl-3!"
- ><code v-safe-html="highlightedContent" class="gl-white-space-pre-wrap!"></code>
+ <pre class="code"><code v-safe-html="highlightedContent"></code>
</pre>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/web_ide_link.vue b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
index 6da2d39a95a..f02cd5c4e2e 100644
--- a/app/assets/javascripts/vue_shared/components/web_ide_link.vue
+++ b/app/assets/javascripts/vue_shared/components/web_ide_link.vue
@@ -1,6 +1,7 @@
<script>
import $ from 'jquery';
-import { __ } from '~/locale';
+import { GlModal, GlSprintf, GlLink } from '@gitlab/ui';
+import { s__, __ } from '~/locale';
import ActionsButton from '~/vue_shared/components/actions_button.vue';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
@@ -12,6 +13,19 @@ export default {
components: {
ActionsButton,
LocalStorageSync,
+ GlModal,
+ GlSprintf,
+ GlLink,
+ },
+ i18n: {
+ modal: {
+ title: __('Enable Gitpod?'),
+ content: s__(
+ 'Gitpod|To use Gitpod you must first enable the feature in the integrations section of your %{linkStart}user preferences%{linkEnd}.',
+ ),
+ actionCancelText: __('Cancel'),
+ actionPrimaryText: __('Enable Gitpod'),
+ },
},
props: {
isFork: {
@@ -49,6 +63,16 @@ export default {
required: false,
default: false,
},
+ userPreferencesGitpodPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ userProfileEnableGitpodPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
editUrl: {
type: String,
required: false,
@@ -74,10 +98,16 @@ export default {
required: false,
default: '',
},
+ disableForkModal: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
selection: KEY_WEB_IDE,
+ showEnableGitpodModal: false,
};
},
computed: {
@@ -93,8 +123,12 @@ export default {
? {
href: '#modal-confirm-fork-edit',
handle: () => {
- this.$emit('edit', 'simple');
- this.showModal('#modal-confirm-fork-edit');
+ if (this.disableForkModal) {
+ this.$emit('edit', 'simple');
+ return;
+ }
+
+ this.showJQueryModal('#modal-confirm-fork-edit');
},
}
: { href: this.editUrl };
@@ -132,8 +166,12 @@ export default {
? {
href: '#modal-confirm-fork-webide',
handle: () => {
- this.$emit('edit', 'ide');
- this.showModal('#modal-confirm-fork-webide');
+ if (this.disableForkModal) {
+ this.$emit('edit', 'ide');
+ return;
+ }
+
+ this.showJQueryModal('#modal-confirm-fork-webide');
},
}
: { href: this.webIdeUrl };
@@ -154,14 +192,23 @@ export default {
gitpodActionText() {
return this.gitpodText || __('Gitpod');
},
+ computedShowGitpodButton() {
+ return (
+ this.showGitpodButton && this.userPreferencesGitpodPath && this.userProfileEnableGitpodPath
+ );
+ },
gitpodAction() {
- if (!this.showGitpodButton) {
+ if (!this.computedShowGitpodButton) {
return null;
}
const handleOptions = this.gitpodEnabled
? { href: this.gitpodUrl }
- : { href: '#modal-enable-gitpod', handle: () => this.showModal('#modal-enable-gitpod') };
+ : {
+ handle: () => {
+ this.showModal('showEnableGitpodModal');
+ },
+ };
const secondaryText = __('Launch a ready-to-code development environment for your project.');
@@ -176,14 +223,36 @@ export default {
...handleOptions,
};
},
+ enableGitpodModalProps() {
+ return {
+ 'modal-id': 'enable-gitpod-modal',
+ size: 'sm',
+ title: this.$options.i18n.modal.title,
+ 'action-cancel': {
+ text: this.$options.i18n.modal.actionCancelText,
+ },
+ 'action-primary': {
+ text: this.$options.i18n.modal.actionPrimaryText,
+ attributes: {
+ variant: 'confirm',
+ category: 'primary',
+ href: this.userProfileEnableGitpodPath,
+ 'data-method': 'put',
+ },
+ },
+ };
+ },
},
methods: {
select(key) {
this.selection = key;
},
- showModal(id) {
+ showJQueryModal(id) {
$(id).modal('show');
},
+ showModal(dataKey) {
+ this[dataKey] = true;
+ },
},
};
</script>
@@ -202,5 +271,16 @@ export default {
:value="selection"
@input="select"
/>
+ <gl-modal
+ v-if="computedShowGitpodButton && !gitpodEnabled"
+ v-model="showEnableGitpodModal"
+ v-bind="enableGitpodModalProps"
+ >
+ <gl-sprintf :message="$options.i18n.modal.content">
+ <template #link="{ content }">
+ <gl-link :href="userPreferencesGitpodPath">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar.vue
index 5ca9e50d854..bcc889495bd 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_bulk_edit_sidebar.vue
@@ -13,6 +13,7 @@ export default {
if (layoutPageEl) {
layoutPageEl.classList.toggle('right-sidebar-expanded', value);
layoutPageEl.classList.toggle('right-sidebar-collapsed', !value);
+ layoutPageEl.classList.toggle('issuable-bulk-update-sidebar', !value);
}
},
},
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
index 0bb0e0d9fb0..af0235bfc69 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_item.vue
@@ -193,7 +193,13 @@ export default {
:title="__('This issue is hidden because its author has been banned')"
:aria-label="__('Hidden')"
/>
- <gl-link class="issue-title-text" dir="auto" :href="webUrl" v-bind="issuableTitleProps">
+ <gl-link
+ class="issue-title-text"
+ dir="auto"
+ :href="webUrl"
+ data-qa-selector="issuable_title_link"
+ v-bind="issuableTitleProps"
+ >
{{ issuable.title }}
<gl-icon v-if="isIssuableUrlExternal" name="external-link" class="gl-ml-2" />
</gl-link>
diff --git a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_tabs.vue b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_tabs.vue
index 3ff87ba3c4f..9bf54e98cc4 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/components/issuable_tabs.vue
+++ b/app/assets/javascripts/vue_shared/issuable/list/components/issuable_tabs.vue
@@ -1,5 +1,6 @@
<script>
import { GlTabs, GlTab, GlBadge } from '@gitlab/ui';
+import { formatNumber } from '~/locale';
export default {
components: {
@@ -29,6 +30,9 @@ export default {
isTabCountNumeric(tab) {
return Number.isInteger(this.tabCounts[tab.name]);
},
+ formatNumber(count) {
+ return formatNumber(count);
+ },
},
};
</script>
@@ -55,7 +59,7 @@ export default {
size="sm"
class="gl-tab-counter-badge"
>
- {{ tabCounts[tab.name] }}
+ {{ formatNumber(tabCounts[tab.name]) }}
</gl-badge>
</template>
</gl-tab>
diff --git a/app/assets/javascripts/vue_shared/issuable/list/constants.js b/app/assets/javascripts/vue_shared/issuable/list/constants.js
index 773ad0f8e93..c6dce6a51c2 100644
--- a/app/assets/javascripts/vue_shared/issuable/list/constants.js
+++ b/app/assets/javascripts/vue_shared/issuable/list/constants.js
@@ -38,7 +38,7 @@ export const AvailableSortOptions = [
},
{
id: 2,
- title: __('Last updated'),
+ title: __('Updated date'),
sortDirection: {
descending: 'updated_desc',
ascending: 'updated_asc',
diff --git a/app/assets/javascripts/vue_shared/issuable/show/constants.js b/app/assets/javascripts/vue_shared/issuable/show/constants.js
deleted file mode 100644
index 346f45c7d90..00000000000
--- a/app/assets/javascripts/vue_shared/issuable/show/constants.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export const IssuableType = {
- Issue: 'issue',
- Incident: 'incident',
- TestCase: 'test_case',
-};
diff --git a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue
index 114f60c96ee..f67e590e2ce 100644
--- a/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue
+++ b/app/assets/javascripts/vue_shared/new_namespace/new_namespace_page.vue
@@ -10,10 +10,17 @@ export default {
GlIcon,
WelcomePage,
LegacyContainer,
+ CreditCardVerification: () =>
+ import('ee_component/pages/groups/new/components/credit_card_verification.vue'),
},
directives: {
SafeHtml,
},
+ inject: {
+ verificationRequired: {
+ default: false,
+ },
+ },
props: {
title: {
type: String,
@@ -41,6 +48,7 @@ export default {
data() {
return {
activePanelName: null,
+ verificationCompleted: false,
};
},
@@ -67,6 +75,10 @@ export default {
{ text: this.activePanel.title, href: `#${this.activePanel.name}` },
];
},
+
+ shouldVerify() {
+ return this.verificationRequired && !this.verificationCompleted;
+ },
},
created() {
@@ -93,12 +105,16 @@ export default {
localStorage.setItem(this.persistenceKey, this.activePanelName);
}
},
+ onVerified() {
+ this.verificationCompleted = true;
+ },
},
};
</script>
<template>
- <welcome-page v-if="!activePanelName" :panels="panels" :title="title">
+ <credit-card-verification v-if="shouldVerify" @verified="onVerified" />
+ <welcome-page v-else-if="!activePanelName" :panels="panels" :title="title">
<template #footer>
<slot name="welcome-footer"> </slot>
</template>
diff --git a/app/assets/javascripts/work_items/components/item_title.vue b/app/assets/javascripts/work_items/components/item_title.vue
index 5e9e50a94f0..79840cc4f0f 100644
--- a/app/assets/javascripts/work_items/components/item_title.vue
+++ b/app/assets/javascripts/work_items/components/item_title.vue
@@ -2,7 +2,10 @@
import { escape } from 'lodash';
import { __ } from '~/locale';
+import { WI_TITLE_TRACK_LABEL } from '../constants';
+
export default {
+ WI_TITLE_TRACK_LABEL,
props: {
initialTitle: {
type: String,
@@ -56,6 +59,7 @@ export default {
role="textbox"
:aria-label="__('Title')"
:data-placeholder="placeholder"
+ :data-track-label="$options.WI_TITLE_TRACK_LABEL"
:contenteditable="!disabled"
class="gl-pseudo-placeholder"
@blur="handleBlur"
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index b39f68abf74..995c02a2c5b 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -1,3 +1,5 @@
export const widgetTypes = {
title: 'TITLE',
};
+
+export const WI_TITLE_TRACK_LABEL = 'item_title';
diff --git a/app/assets/javascripts/work_items/pages/work_item_root.vue b/app/assets/javascripts/work_items/pages/work_item_root.vue
index 479274baf3a..4262e169655 100644
--- a/app/assets/javascripts/work_items/pages/work_item_root.vue
+++ b/app/assets/javascripts/work_items/pages/work_item_root.vue
@@ -1,16 +1,21 @@
<script>
import { GlAlert } from '@gitlab/ui';
+import Tracking from '~/tracking';
import workItemQuery from '../graphql/work_item.query.graphql';
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
-import { widgetTypes } from '../constants';
+import { widgetTypes, WI_TITLE_TRACK_LABEL } from '../constants';
import ItemTitle from '../components/item_title.vue';
+const trackingMixin = Tracking.mixin();
+
export default {
+ titleUpdatedEvent: 'updated_title',
components: {
ItemTitle,
GlAlert,
},
+ mixins: [trackingMixin],
props: {
id: {
type: String,
@@ -34,6 +39,14 @@ export default {
},
},
computed: {
+ tracking() {
+ return {
+ category: 'workItems:show',
+ action: 'updated_title',
+ label: WI_TITLE_TRACK_LABEL,
+ property: '[type_work_item]',
+ };
+ },
titleWidgetData() {
return this.workItem?.widgets?.nodes?.find((widget) => widget.type === widgetTypes.title);
},
@@ -50,6 +63,7 @@ export default {
},
},
});
+ this.track();
} catch {
this.error = true;
}
diff --git a/app/assets/stylesheets/_page_specific_files.scss b/app/assets/stylesheets/_page_specific_files.scss
index 8f3b5b3b7cc..ff2b82d1806 100644
--- a/app/assets/stylesheets/_page_specific_files.scss
+++ b/app/assets/stylesheets/_page_specific_files.scss
@@ -28,7 +28,6 @@
@import './pages/service_desk';
@import './pages/settings';
@import './pages/settings_ci_cd';
-@import './pages/sherlock';
@import './pages/storage_quota';
@import './pages/tree';
@import './pages/users';
diff --git a/app/assets/stylesheets/components/whats_new.scss b/app/assets/stylesheets/components/whats_new.scss
index 4437b5b673d..98a7ea5792b 100644
--- a/app/assets/stylesheets/components/whats_new.scss
+++ b/app/assets/stylesheets/components/whats_new.scss
@@ -36,15 +36,15 @@
}
.with-performance-bar .whats-new-drawer {
- margin-top: $performance-bar-height + $header-height;
+ margin-top: calc(#{$performance-bar-height} + #{$header-height});
}
.with-system-header .whats-new-drawer {
- margin-top: $system-header-height + $header-height;
+ margin-top: calc(#{$system-header-height} + #{$header-height});
}
.with-performance-bar.with-system-header .whats-new-drawer {
- margin-top: $performance-bar-height + $system-header-height + $header-height;
+ margin-top: calc(#{$performance-bar-height} + #{$system-header-height} + #{$header-height});
}
.gl-badge.whats-new-item-badge {
diff --git a/app/assets/stylesheets/framework/contextual_sidebar.scss b/app/assets/stylesheets/framework/contextual_sidebar.scss
index 345c180d164..2a3ed29258a 100644
--- a/app/assets/stylesheets/framework/contextual_sidebar.scss
+++ b/app/assets/stylesheets/framework/contextual_sidebar.scss
@@ -357,7 +357,9 @@
}
> li {
- .badge.badge-pill {
+ // TODO: Remove this block once all sidebar badges use gl_badge_tag
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/350061
+ .badge.badge-pill:not(.gl-badge) {
@include gl-rounded-lg;
@include gl-py-1;
@include gl-px-3;
@@ -370,7 +372,9 @@
display: block;
}
- .badge.badge-pill {
+ // TODO: Remove this block once all sidebar badges use gl_badge_tag
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/350061
+ .badge.badge-pill:not(.gl-badge) {
@include gl-font-weight-normal;
color: $blue-700;
}
diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss
index 23dc16b7e7f..ffacac07517 100644
--- a/app/assets/stylesheets/framework/diffs.scss
+++ b/app/assets/stylesheets/framework/diffs.scss
@@ -34,7 +34,7 @@
@media (min-width: map-get($grid-breakpoints, md)) {
// The `+11` is to ensure the file header border shows when scrolled -
// the bottom of the compare-versions header and the top of the file header
- $mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height + 11;
+ $mr-file-header-top: calc(#{$mr-version-controls-height} + #{$header-height} + #{$mr-tabs-height} + 11px);
position: -webkit-sticky;
position: sticky;
@@ -42,11 +42,11 @@
z-index: 120;
.with-system-header & {
- top: $mr-file-header-top + $system-header-height;
+ top: calc(#{$mr-file-header-top} + #{$system-header-height});
}
.with-system-header.with-performance-bar & {
- top: $mr-file-header-top + $system-header-height + $performance-bar-height;
+ top: calc(#{$mr-file-header-top} + #{$system-header-height} + #{$performance-bar-height});
}
&::before {
@@ -61,22 +61,22 @@
}
.with-performance-bar & {
- top: $mr-file-header-top + $performance-bar-height;
+ top: calc(#{$mr-file-header-top} + #{$performance-bar-height});
}
&.is-commit {
- top: $header-height + $commit-stat-summary-height;
+ top: calc(#{$header-height} + #{$commit-stat-summary-height});
.with-performance-bar & {
- top: $header-height + $commit-stat-summary-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$commit-stat-summary-height} + #{$performance-bar-height});
}
}
&.is-compare {
- top: $header-height + $compare-branches-sticky-header-height;
+ top: calc(#{$header-height} + #{$compare-branches-sticky-header-height});
.with-performance-bar & {
- top: $performance-bar-height + $header-height + $compare-branches-sticky-header-height;
+ top: calc(#{$performance-bar-height} + #{$header-height} + #{$compare-branches-sticky-header-height});
}
}
}
@@ -99,17 +99,17 @@
.with-performance-bar &.conflict .file-title,
.with-performance-bar &.conflict .file-title-flex-parent {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
.with-system-header &.conflict .file-title,
.with-system-header &.conflict .file-title-flex-parent {
- top: $header-height + $system-header-height;
+ top: calc(#{$header-height} + #{$system-header-height});
}
.with-system-header.with-performance-bar &.conflict .file-title,
.with-system-header.with-performance-bar &.conflict .file-title-flex-parent {
- top: $header-height + $performance-bar-height + $system-header-height;
+ top: calc(#{$header-height} + #{$performance-bar-height} + #{$system-header-height});
}
}
@@ -825,7 +825,7 @@ table.code {
top: $header-height;
.with-performance-bar & {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
}
}
@@ -839,12 +839,12 @@ table.code {
@include media-breakpoint-up(sm) {
@include gl-sticky;
- top: $header-height + $mr-tabs-height;
+ top: calc(#{$header-height} + #{$mr-tabs-height});
@include gl-bg-white;
z-index: 200;
.with-performance-bar & {
- top: $header-height + $mr-tabs-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$mr-tabs-height} + #{$performance-bar-height});
}
&.is-stuck {
diff --git a/app/assets/stylesheets/framework/emojis.scss b/app/assets/stylesheets/framework/emojis.scss
index 1ddde3d2ed6..a31910e3090 100644
--- a/app/assets/stylesheets/framework/emojis.scss
+++ b/app/assets/stylesheets/framework/emojis.scss
@@ -43,6 +43,10 @@ gl-emoji {
border-bottom-color: transparent;
}
+.emoji-picker-category-active {
+ border-bottom-color: var(--gl-theme-accent, $theme-indigo-500);
+}
+
.emoji-picker .gl-new-dropdown-inner > :last-child {
padding-bottom: 0;
}
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index 30a1c8af414..b51daf0e4dc 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -75,6 +75,10 @@ $notification-box-shadow-color: rgba(0, 0, 0, 0.25);
.flash-action {
display: inline-block;
}
+
+ .gl-alert {
+ @include gl-my-4;
+ }
}
@include media-breakpoint-down(sm) {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 44b099fc873..68535badd78 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -150,7 +150,7 @@ $top-nav-hover-bg: var(--indigo-900-alpha-008, $indigo-900-alpha-008) !important
}
li {
- .badge.badge-pill:not(.merge-request-badge) {
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge) {
box-shadow: none;
font-weight: $gl-font-weight-bold;
}
@@ -417,7 +417,7 @@ $top-nav-hover-bg: var(--indigo-900-alpha-008, $indigo-900-alpha-008) !important
.title-container,
.navbar-nav {
- .badge.badge-pill:not(.merge-request-badge) {
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge) {
position: inherit;
font-weight: $gl-font-weight-normal;
margin-left: -6px;
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index f79dc38f2f7..fb05f8575ef 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -140,7 +140,7 @@ body {
}
.with-performance-bar .layout-page {
- margin-top: $header-height + $performance-bar-height;
+ margin-top: calc(#{$header-height} + #{$performance-bar-height});
}
.fullscreen-layout {
@@ -201,3 +201,9 @@ body {
padding-right: 0;
}
}
+
+@include media-breakpoint-up(sm) {
+ .logged-out-marketing-header-candidate {
+ --header-height: 72px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index 33f7aa4dba1..1e51bf3d974 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -276,7 +276,7 @@
top: $header-height;
.with-performance-bar & {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
}
@@ -419,6 +419,30 @@
height: $gl-font-size * $code-line-height * 0.9;
}
+@mixin email-code-block {
+ .code.language-email {
+ font-family: inherit;
+ font-size: inherit;
+
+ code {
+ white-space: pre-wrap;
+ font-family: inherit;
+
+ // Rouge `Name.Tag` and `Operator` token (email header key + ':')
+ .nt,
+ .o {
+ color: inherit;
+ font-weight: bold;
+ }
+
+ // Rouge `Name.Attribute` token (email header value)
+ .na {
+ color: inherit;
+ }
+ }
+ }
+}
+
@mixin avatar-counter($border-radius: 1em) {
background-color: $gray-darkest;
color: $white;
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index 685f1f413e6..563075b911c 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -40,9 +40,11 @@
a.active {
color: $black;
font-weight: $gl-font-weight-bold;
+ border-bottom: 2px solid var(--gl-theme-accent, $theme-indigo-500);
.badge.badge-pill {
color: $black;
+ font-weight: $gl-font-weight-bold;
}
}
@@ -126,14 +128,6 @@
input {
display: inline-block;
position: relative;
-
- &:not[type='checkbox'] {
- /* Medium devices (desktops, 992px and up) */
- @include media-breakpoint-up(md) { width: 200px; }
-
- /* Large devices (large desktops, 1200px and up) */
- @include media-breakpoint-up(lg) { width: 250px; }
- }
}
@include media-breakpoint-up(md) {
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 6c7fc25f2d9..e77971d5280 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -81,7 +81,7 @@
}
.with-performance-bar .right-sidebar.affix {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
@mixin maintain-sidebar-dimensions {
diff --git a/app/assets/stylesheets/framework/system_messages.scss b/app/assets/stylesheets/framework/system_messages.scss
index 1cb34bea069..89585fd96ae 100644
--- a/app/assets/stylesheets/framework/system_messages.scss
+++ b/app/assets/stylesheets/framework/system_messages.scss
@@ -49,11 +49,11 @@
// right sidebar eg: MR page
.nav-sidebar,
.right-sidebar {
- top: $system-header-height + $header-height;
+ top: calc(#{$system-header-height} + #{$header-height});
}
.content-wrapper-margin {
- margin-top: $system-header-height + $header-height;
+ margin-top: calc(#{$system-header-height} + #{$header-height});
}
// Performance Bar
@@ -66,14 +66,14 @@
}
.layout-page {
- margin-top: $header-height + $performance-bar-height + $system-header-height;
+ margin-top: calc(#{$header-height} + #{$performance-bar-height} + #{$system-header-height});
}
// left sidebar eg: project page
// right sidebar eg: MR page
.nav-sidebar,
.right-sidebar {
- top: $header-height + $performance-bar-height + $system-header-height;
+ top: calc(#{$header-height} + #{$performance-bar-height} + #{$system-header-height});
}
}
}
@@ -97,7 +97,7 @@
.boards-list,
.board-swimlanes {
- height: calc(100vh - #{$header-height + $breadcrumb-min-height + $performance-bar-height + $system-footer-height + $gl-padding-32});
+ height: calc(100vh - (#{$header-height} + #{$breadcrumb-min-height} + #{$performance-bar-height} + #{$system-footer-height} + #{$gl-padding-32}));
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 16ff4b81f95..51c41c46f61 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -365,10 +365,17 @@
padding: 0;
margin: 0 0 16px;
+ // Lists embedded in other lists can be "loose" or "tight"
+ // Remove bottom margin for all lists (default for tight lists)
ul,
ol {
margin-bottom: 0;
}
+
+ // Loose lists need bottom margin added back
+ p ~ ol,
+ p ~ ul {
+ margin-bottom: 16px; }
}
ul:dir(rtl),
@@ -420,12 +427,12 @@
list-style-type: none;
position: relative;
min-height: 22px;
- padding-left: 28px;
- margin-left: 0 !important;
+ padding-inline-start: 28px;
+ margin-inline-start: 0 !important;
> input.task-list-item-checkbox {
position: absolute;
- left: 8px;
+ inset-inline-start: 8px;
top: 5px;
}
}
@@ -590,6 +597,8 @@
.text-justify {
text-align: justify !important;
}
+
+ @include email-code-block;
}
/**
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 026aeeb1e8e..21add43ad3f 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -433,7 +433,7 @@ $browser-scrollbar-size: 10px;
/*
* Misc
*/
-$header-height: 40px;
+$header-height: var(--header-height, 40px);
$header-zindex: 1000;
$zindex-dropdown-menu: 300;
$suggestion-header-height: 46px;
@@ -610,6 +610,14 @@ $pagination-disabled-color: #cdcdcd;
*/
$status-icon-size: 22px;
+
+/*
+* Social Icons
+*/
+$twitter: #1d9bf0;
+$skype: #0078d7;
+$linkedin: #2867b2;
+
/*
* Award emoji
*/
@@ -667,18 +675,18 @@ $issue-boards-filter-height: 68px;
$issue-boards-filter-height-md: 110px;
$issue-boards-filter-height-sm: 299px;
$issue-boards-breadcrumbs-height-xs: 63px;
-$issue-board-list-difference-xs: $header-height + $issue-boards-breadcrumbs-height-xs;
-$issue-board-list-difference-sm: $header-height + $breadcrumb-min-height;
-$issue-board-list-difference-md: $issue-board-list-difference-sm + $issue-boards-filter-height-md;
-$issue-board-list-difference-lg: $issue-board-list-difference-sm + $issue-boards-filter-height;
+$issue-board-list-difference-xs: calc(#{$header-height} + #{$issue-boards-breadcrumbs-height-xs});
+$issue-board-list-difference-sm: calc(#{$header-height} + #{$breadcrumb-min-height});
+$issue-board-list-difference-md: calc(#{$issue-board-list-difference-sm} + #{$issue-boards-filter-height-md});
+$issue-board-list-difference-lg: calc(#{$issue-board-list-difference-sm} + #{$issue-boards-filter-height});
/*
The following heights are used in environment_logs.scss and are used for calculation of the log viewer height.
*/
$environment-logs-breadcrumbs-height: 63px;
$environment-logs-breadcrumbs-height-md: $breadcrumb-min-height;
-$environment-logs-difference-xs-up: $header-height + $environment-logs-breadcrumbs-height;
-$environment-logs-difference-md-up: $header-height + $environment-logs-breadcrumbs-height-md;
+$environment-logs-difference-xs-up: calc(#{$header-height} + #{$environment-logs-breadcrumbs-height});
+$environment-logs-difference-md-up: calc(#{$header-height} + #{$environment-logs-breadcrumbs-height-md});
/*
* Avatar
@@ -867,21 +875,12 @@ $image-comment-cursor-left-offset: 12;
$image-comment-cursor-top-offset: 12;
/*
-Add GitLab Slack Application
-*/
-$add-to-slack-popup-max-width: 400px;
-$add-to-slack-gif-max-width: 850px;
-$add-to-slack-well-max-width: 750px;
-$add-to-slack-logo-size: 100px;
-
-/*
Security & Compliance Carousel
*/
$security-and-compliance-carousel-image-carousel-width: 1000px;
$security-and-compliance-carousel-image-discover-button-width: 45%;
$security-and-compliance-carousel-image-discover-buttons-max-width: 280px;
$security-and-compliance-carousel-image-discover-footer-max-width: 500px;
-$security-and-compliance-carousel-image-discover-feedback-width: 30%;
$security-and-compliance-carousel-image-discover-text-carousel-max-width: 650px;
$security-and-compliance-carousel-image-discover-text-carousel-caption-height: 100%;
$security-and-compliance-carousel-image-discover-text-carousel-caption-max-width: 500px;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index acfda718e77..ea81863e094 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -5,7 +5,7 @@
$secondary: $gray-light;
$input-disabled-bg: $gray-light;
-$input-border-color: $gray-100;
+$input-border-color: $gray-400;
$input-color: $gl-text-color;
$input-font-size: $gl-font-size;
$font-family-sans-serif: $regular-font;
diff --git a/app/assets/stylesheets/framework/wells.scss b/app/assets/stylesheets/framework/wells.scss
index d550a1faa18..b796f04750b 100644
--- a/app/assets/stylesheets/framework/wells.scss
+++ b/app/assets/stylesheets/framework/wells.scss
@@ -65,33 +65,6 @@
display: inline;
}
- .branch-link {
- margin-bottom: 2px;
- }
-
- .limit-box {
- cursor: pointer;
- display: inline-flex;
- align-items: center;
- background-color: $red-100;
- border-radius: $border-radius-default;
- text-align: center;
-
- &:hover {
- background-color: $red-200;
- }
-
- .limit-icon {
- margin: 0 4px;
- }
-
- .limit-message {
- line-height: 16px;
- margin-right: 8px;
- font-size: 12px;
- }
- }
-
svg {
vertical-align: text-top;
}
diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss
index c0f8475323a..80052f4a4d5 100644
--- a/app/assets/stylesheets/highlight/white_base.scss
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -255,18 +255,23 @@ span.highlight_word {
.hll { background-color: $white-hll-bg; }
-.c { color: $white-c;
+.c,
+.hljs-comment { color: $white-c;
font-style: italic; }
.err { color: $white-err;
background-color: $white-err-bg; }
-.k { font-weight: $gl-font-weight-bold; }
+
+.k,
+.hljs-variable.language_,
+.hljs-built_in { font-weight: $gl-font-weight-bold; }
.o { font-weight: $gl-font-weight-bold; }
.cm { color: $white-cm;
font-style: italic; }
-.cp { color: $white-cp;
+.cp,
+.hljs-meta { color: $white-cp;
font-weight: $gl-font-weight-bold; }
.c1 { color: $white-c1;
@@ -310,20 +315,34 @@ span.highlight_word {
font-weight: $gl-font-weight-bold; }
.gt { color: $white-gt; }
.kc { font-weight: $gl-font-weight-bold; }
-.kd { font-weight: $gl-font-weight-bold; }
+
+.kd,
+.hljs-keyword { font-weight: $gl-font-weight-bold; }
.kn { font-weight: $gl-font-weight-bold; }
.kp { font-weight: $gl-font-weight-bold; }
.kr { font-weight: $gl-font-weight-bold; }
-.kt { color: $white-kt;
+.kt,
+.hljs-type { color: $white-kt;
font-weight: $gl-font-weight-bold; }
.m { color: $white-m; }
.s { color: $white-s; }
-.n { color: $white-n; }
-.na { color: $white-na; }
-.nb { color: $white-nb; }
-.nc { color: $white-nc;
+.n,
+.hljs-built_in { color: $white-n; }
+
+.na,
+.hljs-attr,
+.hljs-property,
+.hljs-title.function_ { color: $white-na; }
+
+.nb,
+.hljs-title.class_,
+.hljs-literal { color: $white-nb; }
+
+.nc,
+.hljs-title.class_,
+.hljs-built_in { color: $white-nc;
font-weight: $gl-font-weight-bold; }
.no { color: $white-no; }
.ni { color: $white-ni; }
@@ -331,7 +350,9 @@ span.highlight_word {
.ne { color: $white-ne;
font-weight: $gl-font-weight-bold; }
-.nf { color: $white-nf;
+.nf,
+.hljs-title,
+.hljs-title.function_ { color: $white-nf;
font-weight: $gl-font-weight-bold; }
.nn { color: $white-nn; }
.nt { color: $white-nt; }
@@ -340,7 +361,9 @@ span.highlight_word {
.w { color: $white-w; }
.mf { color: $white-mf; }
.mh { color: $white-mh; }
-.mi { color: $white-mi; }
+
+.mi,
+.hljs-number { color: $white-mi; }
.mo { color: $white-mo; }
.sb { color: $white-sb; }
.sc { color: $white-sc; }
@@ -351,7 +374,9 @@ span.highlight_word {
.si { color: $white-si; }
.sx { color: $white-sx; }
.sr { color: $white-sr; }
-.s1 { color: $white-s1; }
+
+.s1,
+.hljs-string { color: $white-s1; }
.ss { color: $white-ss; }
.bp { color: $white-bp; }
.vc { color: $white-vc; }
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
index d281f62c370..feb4ea77e58 100644
--- a/app/assets/stylesheets/notify.scss
+++ b/app/assets/stylesheets/notify.scss
@@ -1,3 +1,4 @@
+@import 'framework/mixins';
@import 'framework/variables';
img {
@@ -38,3 +39,14 @@ pre.commit-message {
.gl-label-text-dark {
color: $gl-text-color;
}
+
+.content {
+ .markdown-code-block pre.code {
+ padding: $gl-padding-8 $input-horizontal-padding;
+ margin: 0 0 $gl-padding-8;
+ border: 1px solid $gray-100;
+ border-radius: $border-radius-small;
+ }
+
+ @include email-code-block;
+}
diff --git a/app/assets/stylesheets/page_bundles/boards.scss b/app/assets/stylesheets/page_bundles/boards.scss
index d4c59a6ab0c..f91ca489bdf 100644
--- a/app/assets/stylesheets/page_bundles/boards.scss
+++ b/app/assets/stylesheets/page_bundles/boards.scss
@@ -1,9 +1,5 @@
@import 'mixins_and_variables_and_functions';
-.user-can-drag {
- cursor: grab;
-}
-
.is-ghost {
opacity: 0.3;
pointer-events: none;
diff --git a/app/assets/stylesheets/page_bundles/build.scss b/app/assets/stylesheets/page_bundles/build.scss
index ed62e055427..d55d6b27576 100644
--- a/app/assets/stylesheets/page_bundles/build.scss
+++ b/app/assets/stylesheets/page_bundles/build.scss
@@ -13,7 +13,7 @@
border: 1px solid var(--border-color, $border-color);
.with-performance-bar & {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
}
@@ -21,10 +21,10 @@
@include build-log-top-bar(50px);
&.has-archived-block {
- top: $header-height + 28px;
+ top: calc(#{$header-height} + 28px);
.with-performance-bar & {
- top: $header-height + $performance-bar-height + 28px;
+ top: calc(#{$header-height} + #{$performance-bar-height} + 28px);
}
}
diff --git a/app/assets/stylesheets/page_bundles/cycle_analytics.scss b/app/assets/stylesheets/page_bundles/cycle_analytics.scss
deleted file mode 100644
index 5d42ece32c9..00000000000
--- a/app/assets/stylesheets/page_bundles/cycle_analytics.scss
+++ /dev/null
@@ -1,6 +0,0 @@
-@import 'mixins_and_variables_and_functions';
-
-.cycle-analytics {
- margin: 24px auto 0;
- position: relative;
-}
diff --git a/app/assets/stylesheets/page_bundles/import.scss b/app/assets/stylesheets/page_bundles/import.scss
index 79468ce62ce..b7a4d9564fe 100644
--- a/app/assets/stylesheets/page_bundles/import.scss
+++ b/app/assets/stylesheets/page_bundles/import.scss
@@ -41,7 +41,7 @@ $import-bar-height: $gl-spacing-scale-11;
z-index: 3;
html.with-performance-bar & {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
}
diff --git a/app/assets/stylesheets/page_bundles/issues_list.scss b/app/assets/stylesheets/page_bundles/issues_list.scss
index 8a958bdf0c5..41515a98e0a 100644
--- a/app/assets/stylesheets/page_bundles/issues_list.scss
+++ b/app/assets/stylesheets/page_bundles/issues_list.scss
@@ -35,10 +35,6 @@
}
}
-.user-can-drag {
- cursor: grab;
-}
-
.is-ghost {
opacity: 0.3;
pointer-events: none;
diff --git a/app/assets/stylesheets/page_bundles/members.scss b/app/assets/stylesheets/page_bundles/members.scss
index 62dd3dcb9c0..8d2c0a8ca22 100644
--- a/app/assets/stylesheets/page_bundles/members.scss
+++ b/app/assets/stylesheets/page_bundles/members.scss
@@ -22,10 +22,6 @@
}
}
-.members-ldap {
- align-self: center;
-}
-
.card {
.card-header {
.badge.badge-pill {
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index 02113fe8b58..37ab2e2be2b 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -36,7 +36,7 @@
// If they don't match, the file tree and the diff files stick
// to the top at different heights, which is a bad-looking defect
$diff-file-header-top: 11px;
- $top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + $diff-file-header-top;
+ $top-pos: calc(#{$header-height} + #{$mr-tabs-height} + #{$mr-version-controls-height} + #{$diff-file-header-top});
position: -webkit-sticky;
position: sticky;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 8600a4059d8..cdef843c9b4 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -514,7 +514,7 @@
}
.with-performance-bar .right-sidebar {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
.sidebar-move-issue-confirmation-button {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 880231f5644..d77c8a40a79 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -256,7 +256,7 @@ ul.related-merge-requests > li gl-emoji {
}
.with-performance-bar .issue-sticky-header {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
@include media-breakpoint-up(md) {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index c7b4dd660d0..82216b8d5c5 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -68,13 +68,6 @@
color: $white;
}
-.dropdown-labels-error {
- padding: 5px 10px;
- margin-bottom: 10px;
- background-color: $red-500;
- color: $white;
-}
-
.manage-labels-list {
padding: 0;
margin-bottom: 0;
@@ -114,9 +107,8 @@
display: none;
}
-.label-subscribe-button {
- width: 105px;
- font-weight: 200;
+.label-subscription {
+ width: 109px;
}
.labels-container {
diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss
index 71ddbf175e9..fffea301a4f 100644
--- a/app/assets/stylesheets/pages/login.scss
+++ b/app/assets/stylesheets/pages/login.scss
@@ -10,6 +10,8 @@
.flash-container {
margin-bottom: $gl-padding;
+ position: relative;
+ top: 8px;
}
.brand-holder {
@@ -203,7 +205,7 @@
&.with-system-header {
.login-page-broadcast {
- margin-top: $system-header-height + $header-height;
+ margin-top: calc(#{$system-header-height} + #{$header-height});
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index a4b8e912614..10026e290e8 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -689,14 +689,14 @@ $tabs-holder-z-index: 250;
@include media-breakpoint-up(md) {
position: -webkit-sticky;
position: sticky;
- top: $header-height + $mr-tabs-height;
+ top: calc(#{$header-height} + #{$mr-tabs-height});
.with-system-header & {
- top: $header-height + $mr-tabs-height + $system-header-height;
+ top: calc(#{$header-height} + #{$mr-tabs-height} + #{$system-header-height});
}
.with-system-header.with-performance-bar & {
- top: $header-height + $mr-tabs-height + $system-header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$mr-tabs-height} + #{$system-header-height} + #{$performance-bar-height});
}
.mr-version-menus-container {
@@ -704,7 +704,7 @@ $tabs-holder-z-index: 250;
}
.with-performance-bar & {
- top: $header-height + $performance-bar-height + $mr-tabs-height;
+ top: calc(#{$header-height} + #{$performance-bar-height} + #{$mr-tabs-height});
}
}
}
@@ -717,11 +717,11 @@ $tabs-holder-z-index: 250;
border-bottom: 1px solid $border-color;
.with-system-header & {
- top: $header-height + $system-header-height;
+ top: calc(#{$header-height} + #{$system-header-height});
}
.with-system-header.with-performance-bar & {
- top: $header-height + $system-header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$system-header-height} + #{$performance-bar-height});
}
@include media-breakpoint-up(sm) {
@@ -752,7 +752,7 @@ $tabs-holder-z-index: 250;
.with-performance-bar {
.merge-request-tabs-holder,
.epic-tabs-holder {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
}
@@ -957,10 +957,10 @@ $tabs-holder-z-index: 250;
.mr-compare {
.diff-file .file-title-flex-parent {
- top: $header-height + $mr-tabs-height + 36px;
+ top: calc(#{$header-height} + #{$mr-tabs-height} + 36px);
.with-performance-bar & {
- top: $performance-bar-height + $header-height + $mr-tabs-height + 36px;
+ top: calc(#{$performance-bar-height} + #{$header-height} + #{$mr-tabs-height} + 36px);
}
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 04da75b586f..d8c3851748d 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -368,7 +368,7 @@ $system-note-svg-size: 16px;
border-radius: 0;
@media (min-width: map-get($grid-breakpoints, md)) {
- top: $mr-tabs-height + $header-height;
+ top: calc(#{$mr-tabs-height} + #{$header-height});
.with-performance-bar & {
top: 123px;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index a7ed7172f5f..f76a8030e5b 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -363,23 +363,22 @@ table.u2f-registrations {
color: $gl-text-color-secondary;
}
-.gitlab-slack-gif {
- width: 100%;
- max-width: $add-to-slack-gif-max-width;
+.gitlab-slack-body {
+ max-width: 420px;
}
-.gitlab-slack-well {
- background-color: $white;
- box-shadow: none;
- max-width: $add-to-slack-well-max-width;
+.gitlab-slack-slack-logo {
+ transform: scale(200%); // Slack logo SVG is scaled down 50% and has empty space around it
}
-.gitlab-slack-logo {
- width: $add-to-slack-logo-size;
- height: $add-to-slack-logo-size;
+.skype-icon {
+ color: $skype;
}
-.gitlab-slack-popup {
- width: 100%;
- max-width: $add-to-slack-popup-max-width;
+.linkedin-icon {
+ color: $linkedin;
+}
+
+.twitter-icon {
+ color: $twitter;
}
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 37e272cfff7..633051918a4 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -293,10 +293,8 @@
}
}
-.modal-doorkeepr-auth {
- .modal-body {
- padding: $gl-padding;
- }
+.doorkeeper-authorize {
+ max-width: px-to-rem(500px);
}
.created-deploy-token-container {
@@ -316,12 +314,6 @@
}
}
-.mirror-error-badge {
- background-color: $red-400;
- border-radius: $border-radius-default;
- color: $white;
-}
-
.push-pull-table {
margin-top: 1em;
}
diff --git a/app/assets/stylesheets/pages/sherlock.scss b/app/assets/stylesheets/pages/sherlock.scss
deleted file mode 100644
index 55b0b5295af..00000000000
--- a/app/assets/stylesheets/pages/sherlock.scss
+++ /dev/null
@@ -1,31 +0,0 @@
-table .sherlock-code {
- max-width: 700px;
-}
-
-.sherlock-code {
- pre {
- word-wrap: normal;
-
- code {
- white-space: pre;
- }
- }
-}
-
-.sherlock-line-samples-table {
- thead th,
- tbody td {
- font-size: 13px !important;
- text-align: right;
- padding: 0 10px !important;
- }
-
- .slow {
- color: $red-500;
- font-weight: $gl-font-weight-bold;
- }
-}
-
-.sherlock-file-sample pre {
- padding-top: 28px !important;
-}
diff --git a/app/assets/stylesheets/pages/users.scss b/app/assets/stylesheets/pages/users.scss
index 917d16a9c53..3dcc17df61a 100644
--- a/app/assets/stylesheets/pages/users.scss
+++ b/app/assets/stylesheets/pages/users.scss
@@ -1,7 +1,3 @@
-.user-sort-dropdown {
- margin-left: $gl-padding-8;
-}
-
.user-search-form {
position: relative;
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index f2874e67796..5024b082b99 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -129,5 +129,5 @@
}
html.with-performance-bar .nav-sidebar {
- top: $header-height + $performance-bar-height;
+ top: calc(#{$header-height} + #{$performance-bar-height});
}
diff --git a/app/assets/stylesheets/startup/startup-dark.scss b/app/assets/stylesheets/startup/startup-dark.scss
index c9ff8205142..c72de0e6f29 100644
--- a/app/assets/stylesheets/startup/startup-dark.scss
+++ b/app/assets/stylesheets/startup/startup-dark.scss
@@ -140,7 +140,7 @@ h1 {
color: #fafafa;
background-color: #333;
background-clip: padding-box;
- border: 1px solid #404040;
+ border: 1px solid #868686;
border-radius: 0.25rem;
}
@media (prefers-reduced-motion: reduce) {
@@ -314,6 +314,10 @@ h1 {
padding-left: 0.6em;
border-radius: 10rem;
}
+.badge-info {
+ color: #fff;
+ background-color: #428fdc;
+}
.bg-transparent {
background-color: transparent !important;
}
@@ -372,6 +376,24 @@ h1 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
+.gl-badge.sm {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+.gl-badge.badge-info {
+ background-color: #064787;
+ color: #9dc7f1;
+}
+a.gl-badge.badge-info.active,
+a.gl-badge.badge-info:active {
+ color: #e9f3fc;
+ background-color: #0b5cad;
+}
+a.gl-badge.badge-info:active {
+ box-shadow: inset 0 0 0 1px rgba(51, 51, 51, 0.8),
+ 0 0 0 1px rgba(51, 51, 51, 0.4), 0 0 0 4px rgba(66, 143, 220, 0.48);
+ outline: none;
+}
.gl-button .gl-badge {
top: 0;
}
@@ -704,7 +726,7 @@ input {
padding: 0 16px;
z-index: 1000;
margin-bottom: 0;
- min-height: 40px;
+ min-height: var(--header-height, 40px);
border: 0;
position: fixed;
top: 0;
@@ -729,7 +751,7 @@ input {
display: flex;
justify-content: space-between;
position: relative;
- min-height: 40px;
+ min-height: var(--header-height, 40px);
padding-left: 0;
}
.navbar-gitlab .header-content .title-container {
@@ -815,7 +837,7 @@ input {
.container-fluid
.navbar-nav
li
- .badge.badge-pill:not(.merge-request-badge) {
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge) {
box-shadow: none;
font-weight: 600;
}
@@ -898,8 +920,10 @@ input {
line-height: 18px;
margin: 4px 0 4px 2px;
}
-.title-container .badge.badge-pill:not(.merge-request-badge),
-.navbar-nav .badge.badge-pill:not(.merge-request-badge) {
+.title-container
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge),
+.navbar-nav
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge) {
position: inherit;
font-weight: 400;
margin-left: -6px;
@@ -910,17 +934,22 @@ input {
border-radius: 7px;
box-shadow: 0 1px 0 rgba(76, 78, 84, 0.2);
}
-.title-container .badge.badge-pill:not(.merge-request-badge).green-badge,
-.navbar-nav .badge.badge-pill:not(.merge-request-badge).green-badge {
+.title-container
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).green-badge,
+.navbar-nav
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).green-badge {
background-color: var(--green-400, #108548);
}
.title-container
- .badge.badge-pill:not(.merge-request-badge).merge-requests-count,
-.navbar-nav .badge.badge-pill:not(.merge-request-badge).merge-requests-count {
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).merge-requests-count,
+.navbar-nav
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).merge-requests-count {
background-color: var(--orange-400, #ab6100);
}
-.title-container .badge.badge-pill:not(.merge-request-badge).todos-count,
-.navbar-nav .badge.badge-pill:not(.merge-request-badge).todos-count {
+.title-container
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).todos-count,
+.navbar-nav
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).todos-count {
background-color: var(--blue-400, #1f75cb);
}
.title-container .canary-badge .badge,
@@ -1030,7 +1059,7 @@ input {
left: 0;
z-index: 600;
width: 220px;
- top: 40px;
+ top: var(--header-height, 40px);
background-color: #303030;
transform: translate3d(0, 0, 0);
}
@@ -1367,7 +1396,7 @@ input {
border-radius: 4px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
}
-.sidebar-top-level-items > li .badge.badge-pill {
+.sidebar-top-level-items > li .badge.badge-pill:not(.gl-badge) {
border-radius: 0.5rem;
padding-top: 0.125rem;
padding-bottom: 0.125rem;
@@ -1381,7 +1410,7 @@ input {
.sidebar-sub-level-items:not(.is-fly-out-only) {
display: block;
}
-.sidebar-top-level-items > li.active .badge.badge-pill {
+.sidebar-top-level-items > li.active .badge.badge-pill:not(.gl-badge) {
font-weight: 400;
color: #9dc7f1;
}
@@ -1766,6 +1795,9 @@ body.gl-dark {
.nav-sidebar li.active:not(.fly-out-top-item) > a:not(.has-sub-items) {
background-color: var(--indigo-900-alpha-008);
}
+body.gl-dark {
+ --gl-theme-accent: #868686;
+}
body.gl-dark .navbar-gitlab {
background-color: #fafafa;
}
@@ -1838,7 +1870,7 @@ body.gl-dark .header-search input::placeholder {
color: rgba(250, 250, 250, 0.8);
}
body.gl-dark .header-search input:active::placeholder {
- color: #fafafa;
+ color: #868686;
}
body.gl-dark .search form {
background-color: rgba(250, 250, 250, 0.2);
diff --git a/app/assets/stylesheets/startup/startup-general.scss b/app/assets/stylesheets/startup/startup-general.scss
index a57202515ad..2f79c86cdc6 100644
--- a/app/assets/stylesheets/startup/startup-general.scss
+++ b/app/assets/stylesheets/startup/startup-general.scss
@@ -121,7 +121,7 @@ h1 {
color: #303030;
background-color: #fff;
background-clip: padding-box;
- border: 1px solid #dbdbdb;
+ border: 1px solid #868686;
border-radius: 0.25rem;
}
@media (prefers-reduced-motion: reduce) {
@@ -295,6 +295,10 @@ h1 {
padding-left: 0.6em;
border-radius: 10rem;
}
+.badge-info {
+ color: #fff;
+ background-color: #1f75cb;
+}
.bg-transparent {
background-color: transparent !important;
}
@@ -353,6 +357,24 @@ h1 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
+.gl-badge.sm {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+.gl-badge.badge-info {
+ background-color: #cbe2f9;
+ color: #0b5cad;
+}
+a.gl-badge.badge-info.active,
+a.gl-badge.badge-info:active {
+ color: #033464;
+ background-color: #9dc7f1;
+}
+a.gl-badge.badge-info:active {
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8),
+ 0 0 0 1px rgba(255, 255, 255, 0.4), 0 0 0 4px rgba(31, 117, 203, 0.48);
+ outline: none;
+}
.gl-button .gl-badge {
top: 0;
}
@@ -685,7 +707,7 @@ input {
padding: 0 16px;
z-index: 1000;
margin-bottom: 0;
- min-height: 40px;
+ min-height: var(--header-height, 40px);
border: 0;
position: fixed;
top: 0;
@@ -710,7 +732,7 @@ input {
display: flex;
justify-content: space-between;
position: relative;
- min-height: 40px;
+ min-height: var(--header-height, 40px);
padding-left: 0;
}
.navbar-gitlab .header-content .title-container {
@@ -796,7 +818,7 @@ input {
.container-fluid
.navbar-nav
li
- .badge.badge-pill:not(.merge-request-badge) {
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge) {
box-shadow: none;
font-weight: 600;
}
@@ -879,8 +901,10 @@ input {
line-height: 18px;
margin: 4px 0 4px 2px;
}
-.title-container .badge.badge-pill:not(.merge-request-badge),
-.navbar-nav .badge.badge-pill:not(.merge-request-badge) {
+.title-container
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge),
+.navbar-nav
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge) {
position: inherit;
font-weight: 400;
margin-left: -6px;
@@ -891,17 +915,22 @@ input {
border-radius: 7px;
box-shadow: 0 1px 0 rgba(76, 78, 84, 0.2);
}
-.title-container .badge.badge-pill:not(.merge-request-badge).green-badge,
-.navbar-nav .badge.badge-pill:not(.merge-request-badge).green-badge {
+.title-container
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).green-badge,
+.navbar-nav
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).green-badge {
background-color: var(--green-400, #2da160);
}
.title-container
- .badge.badge-pill:not(.merge-request-badge).merge-requests-count,
-.navbar-nav .badge.badge-pill:not(.merge-request-badge).merge-requests-count {
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).merge-requests-count,
+.navbar-nav
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).merge-requests-count {
background-color: var(--orange-400, #c17d10);
}
-.title-container .badge.badge-pill:not(.merge-request-badge).todos-count,
-.navbar-nav .badge.badge-pill:not(.merge-request-badge).todos-count {
+.title-container
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).todos-count,
+.navbar-nav
+ .badge.badge-pill:not(.merge-request-badge):not(.version-check-badge).todos-count {
background-color: var(--blue-400, #428fdc);
}
.title-container .canary-badge .badge,
@@ -1011,7 +1040,7 @@ input {
left: 0;
z-index: 600;
width: 220px;
- top: 40px;
+ top: var(--header-height, 40px);
background-color: #f0f0f0;
transform: translate3d(0, 0, 0);
}
@@ -1348,7 +1377,7 @@ input {
border-radius: 4px;
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);
}
-.sidebar-top-level-items > li .badge.badge-pill {
+.sidebar-top-level-items > li .badge.badge-pill:not(.gl-badge) {
border-radius: 0.5rem;
padding-top: 0.125rem;
padding-bottom: 0.125rem;
@@ -1362,7 +1391,7 @@ input {
.sidebar-sub-level-items:not(.is-fly-out-only) {
display: block;
}
-.sidebar-top-level-items > li.active .badge.badge-pill {
+.sidebar-top-level-items > li.active .badge.badge-pill:not(.gl-badge) {
font-weight: 400;
color: #0b5cad;
}
diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss
index 3daeeb30082..3ed257caf60 100644
--- a/app/assets/stylesheets/startup/startup-signin.scss
+++ b/app/assets/stylesheets/startup/startup-signin.scss
@@ -189,7 +189,7 @@ hr {
color: #303030;
background-color: #fff;
background-clip: padding-box;
- border: 1px solid #dbdbdb;
+ border: 1px solid #868686;
border-radius: 0.25rem;
}
@media (prefers-reduced-motion: reduce) {
@@ -419,7 +419,7 @@ body.navless {
}
}
.navless-container {
- margin-top: 40px;
+ margin-top: var(--header-height, 40px);
padding-top: 32px;
}
.btn {
@@ -506,7 +506,7 @@ label.label-bold {
}
.navbar-empty {
justify-content: center;
- height: 40px;
+ height: var(--header-height, 40px);
background: #fff;
border-bottom: 1px solid #dbdbdb;
}
@@ -548,6 +548,8 @@ svg {
}
.login-page .flash-container {
margin-bottom: 16px;
+ position: relative;
+ top: 8px;
}
.login-page .brand-holder {
font-size: 18px;
@@ -770,6 +772,9 @@ svg {
.gl-mt-2 {
margin-top: 0.25rem;
}
+.gl-mt-5 {
+ margin-top: 1rem;
+}
.gl-mb-3 {
margin-bottom: 0.5rem;
}
diff --git a/app/assets/stylesheets/themes/theme_helper.scss b/app/assets/stylesheets/themes/theme_helper.scss
index e119af716a6..ec0928fc3d4 100644
--- a/app/assets/stylesheets/themes/theme_helper.scss
+++ b/app/assets/stylesheets/themes/theme_helper.scss
@@ -4,12 +4,16 @@
*/
@mixin gitlab-theme(
$search-and-nav-links,
- $active-tab-border,
+ $accent,
$border-and-box-shadow,
$sidebar-text,
$nav-svg-color,
$color-alternate
) {
+ // Set custom properties
+
+ --gl-theme-accent: #{$accent};
+
// Header
.navbar-gitlab {
@@ -163,7 +167,7 @@
&:focus,
&:active {
&::placeholder {
- color: $search-and-nav-links;
+ color: $gray-400;
}
}
}
@@ -219,22 +223,6 @@
}
}
- .nav-links li {
- &.active a,
- &.md-header-tab.active button,
- a.active {
- border-bottom: 2px solid $active-tab-border;
-
- .badge.badge-pill {
- font-weight: $gl-font-weight-bold;
- }
- }
- }
-
- .emoji-picker-category-active {
- border-bottom-color: $active-tab-border;
- }
-
.branch-header-title {
color: $border-and-box-shadow;
}
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 2623de80fe9..0e7e52129b4 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -58,6 +58,13 @@
}
}
+.gl-first-child-ml-sm-0 > a:first-child,
+.gl-first-child-ml-sm-0 > button:first-child {
+ @include media-breakpoint-up(sm) {
+ @include gl-ml-0;
+ }
+}
+
.mh-50vh { max-height: 50vh; }
.min-width-0 {
@@ -240,16 +247,6 @@ $gl-line-height-42: px-to-rem(42px);
max-width: 50%;
}
-// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1465
-.gl-popover {
- .popover-header {
- .gl-button.close {
- margin-top: -$gl-spacing-scale-3;
- margin-right: -$gl-spacing-scale-4;
- }
- }
-}
-
// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1490
.gl-w-grid-size-28 {
width: $grid-size * 28;
@@ -307,3 +304,8 @@ $gl-line-height-42: px-to-rem(42px);
width: 25%;
}
}
+
+// Will be moved to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/2600
+.gl-pr-10 {
+ padding-right: $gl-spacing-scale-10;
+}
diff --git a/app/controllers/admin/runner_projects_controller.rb b/app/controllers/admin/runner_projects_controller.rb
index fdf681de9ef..598c536d652 100644
--- a/app/controllers/admin/runner_projects_controller.rb
+++ b/app/controllers/admin/runner_projects_controller.rb
@@ -9,9 +9,9 @@ class Admin::RunnerProjectsController < Admin::ApplicationController
@runner = Ci::Runner.find(params[:runner_project][:runner_id])
if @runner.assign_to(@project, current_user)
- redirect_to admin_runner_path(@runner), notice: s_('Runners|Runner assigned to project.')
+ redirect_to edit_admin_runner_url(@runner), notice: s_('Runners|Runner assigned to project.')
else
- redirect_to admin_runner_path(@runner), alert: 'Failed adding runner to project'
+ redirect_to edit_admin_runner_url(@runner), alert: 'Failed adding runner to project'
end
end
@@ -20,7 +20,7 @@ class Admin::RunnerProjectsController < Admin::ApplicationController
runner = rp.runner
rp.destroy
- redirect_to admin_runner_path(runner), status: :found, notice: s_('Runners|Runner unassigned from project.')
+ redirect_to edit_admin_runner_url(runner), status: :found, notice: s_('Runners|Runner unassigned from project.')
end
private
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 9312651b8bf..16657612050 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -11,13 +11,21 @@ class Admin::RunnersController < Admin::ApplicationController
end
def show
+ # We will show runner details in a read-only view in
+ # future iterations. For now, this route will have a
+ # redirect until this new view is developed. See more:
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/347856
+ redirect_to edit_admin_runner_path(runner) unless Feature.enabled?(:runner_read_only_admin_view, default_enabled: :yaml)
+ end
+
+ def edit
assign_builds_and_projects
end
def update
if Ci::UpdateRunnerService.new(@runner).update(runner_params)
respond_to do |format|
- format.html { redirect_to admin_runner_path(@runner) }
+ format.html { redirect_to edit_admin_runner_path(@runner) }
end
else
assign_builds_and_projects
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index cdfb3a32f4c..b40e2affcee 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -158,7 +158,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def confirm
- if update_user { |user| user.confirm }
+ if update_user { |user| user.force_confirm }
redirect_back_or_admin_user(notice: _("Successfully confirmed"))
else
redirect_back_or_admin_user(alert: _("Error occurred. User was not confirmed"))
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index c32a7f10aa4..ee5caf63703 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -2,6 +2,7 @@
class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches]
+ before_action :check_email_search_rate_limit!, only: [:users]
feature_category :users, [:users, :user]
feature_category :projects, [:projects]
@@ -71,6 +72,12 @@ class AutocompleteController < ApplicationController
def target_branch_params
params.permit(:group_id, :project_id).select { |_, v| v.present? }
end
+
+ def check_email_search_rate_limit!
+ search_params = Gitlab::Search::Params.new(params)
+
+ check_rate_limit!(:user_email_lookup, scope: [current_user]) if search_params.email_lookup?
+ end
end
AutocompleteController.prepend_mod_with('AutocompleteController')
diff --git a/app/controllers/concerns/access_tokens_actions.rb b/app/controllers/concerns/access_tokens_actions.rb
new file mode 100644
index 00000000000..451841c43bb
--- /dev/null
+++ b/app/controllers/concerns/access_tokens_actions.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+module AccessTokensActions
+ extend ActiveSupport::Concern
+
+ included do
+ before_action -> { check_permission(:read_resource_access_tokens) }, only: [:index]
+ before_action -> { check_permission(:destroy_resource_access_tokens) }, only: [:revoke]
+ before_action -> { check_permission(:create_resource_access_tokens) }, only: [:create]
+ end
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def index
+ @resource_access_token = PersonalAccessToken.new
+ set_index_vars
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def create
+ token_response = ResourceAccessTokens::CreateService.new(current_user, resource, create_params).execute
+
+ if token_response.success?
+ @resource_access_token = token_response.payload[:access_token]
+ PersonalAccessToken.redis_store!(key_identity, @resource_access_token.token)
+
+ redirect_to resource_access_tokens_path, notice: _("Your new access token has been created.")
+ else
+ redirect_to resource_access_tokens_path, alert: _("Failed to create new access token: %{token_response_message}") % { token_response_message: token_response.message }
+ end
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def revoke
+ @resource_access_token = finder.find(params[:id])
+ revoked_response = ResourceAccessTokens::RevokeService.new(current_user, resource, @resource_access_token).execute
+
+ if revoked_response.success?
+ flash[:notice] = _("Revoked access token %{access_token_name}!") % { access_token_name: @resource_access_token.name }
+ else
+ flash[:alert] = _("Could not revoke access token %{access_token_name}.") % { access_token_name: @resource_access_token.name }
+ end
+
+ redirect_to resource_access_tokens_path
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ private
+
+ def check_permission(action)
+ render_404 unless can?(current_user, action, resource)
+ end
+
+ def create_params
+ params.require(:resource_access_token).permit(:name, :expires_at, :access_level, scopes: [])
+ end
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def set_index_vars
+ # Loading resource members so that we can fetch access level of the bot
+ # user in the resource without multiple queries.
+ resource.members.load
+
+ @scopes = Gitlab::Auth.resource_bot_scopes
+ @active_resource_access_tokens = finder(state: 'active').execute.preload_users
+ @inactive_resource_access_tokens = finder(state: 'inactive', sort: 'expires_at_asc').execute.preload_users
+ @new_resource_access_token = PersonalAccessToken.redis_getdel(key_identity)
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+
+ def finder(options = {})
+ PersonalAccessTokensFinder.new({ user: bot_users, impersonation: false }.merge(options))
+ end
+
+ def bot_users
+ resource.bots
+ end
+
+ def key_identity
+ "#{current_user.id}:#{resource.id}"
+ end
+end
diff --git a/app/controllers/concerns/check_rate_limit.rb b/app/controllers/concerns/check_rate_limit.rb
index 5ccdf843525..0eaf74fd3a9 100644
--- a/app/controllers/concerns/check_rate_limit.rb
+++ b/app/controllers/concerns/check_rate_limit.rb
@@ -8,6 +8,7 @@
# See lib/api/helpers/rate_limiter.rb for API version
module CheckRateLimit
def check_rate_limit!(key, scope:, redirect_back: false, **options)
+ return if bypass_header_set?
return unless rate_limiter.throttled?(key, scope: scope, **options)
rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
@@ -28,4 +29,8 @@ module CheckRateLimit
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
+
+ def bypass_header_set?
+ ::Gitlab::Throttle.bypass_header.present? && request.get_header(Gitlab::Throttle.bypass_header) == '1'
+ end
end
diff --git a/app/controllers/concerns/integrations/actions.rb b/app/controllers/concerns/integrations/actions.rb
index 1f788860c8f..f6e98c25b72 100644
--- a/app/controllers/concerns/integrations/actions.rb
+++ b/app/controllers/concerns/integrations/actions.rb
@@ -8,6 +8,9 @@ module Integrations::Actions
include IntegrationsHelper
before_action :integration, only: [:edit, :update, :overrides, :test]
+ before_action do
+ push_frontend_feature_flag(:vue_integration_form, current_user, default_enabled: :yaml)
+ end
urgency :low, [:test]
end
diff --git a/app/controllers/concerns/integrations/params.rb b/app/controllers/concerns/integrations/params.rb
index 201fb1dc83f..945540d1f8c 100644
--- a/app/controllers/concerns/integrations/params.rb
+++ b/app/controllers/concerns/integrations/params.rb
@@ -11,6 +11,7 @@ module Integrations
:api_key,
:api_token,
:api_url,
+ :archive_trace_events,
:bamboo_url,
:branches_to_be_notified,
:labels_to_be_notified,
diff --git a/app/controllers/concerns/sessionless_authentication.rb b/app/controllers/concerns/sessionless_authentication.rb
index c6d926c8a8d..1f17f9f4e1b 100644
--- a/app/controllers/concerns/sessionless_authentication.rb
+++ b/app/controllers/concerns/sessionless_authentication.rb
@@ -20,7 +20,7 @@ module SessionlessAuthentication
end
def sessionless_sign_in(user)
- if can?(user, :log_in) && !user.password_expired_if_applicable?
+ if user.can_log_in_with_non_expired_password?
# Notice we are passing store false, so the user is not
# actually stored in the session and a token is needed
# for every request. If you want the token to work as a
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index f48d03869a4..689ca32f6d9 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -44,6 +44,13 @@ class GraphqlController < ApplicationController
# The default feature category is overridden to read from request
feature_category :not_owned
+ # We don't know what the query is going to be, so we can't set a high urgency
+ # See https://gitlab.com/groups/gitlab-org/-/epics/5841 for the work that will
+ # allow us to specify an urgency per query.
+ # Currently, all queries have a default urgency. And this is measured in the `graphql_queries`
+ # SLI. But queries could be multiplexed, so the total duration could be longer.
+ urgency :low, [:execute]
+
def execute
result = multiplex? ? execute_multiplex : execute_query
render json: result
diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb
index ab67a007bd9..f9c875b80b2 100644
--- a/app/controllers/groups/application_controller.rb
+++ b/app/controllers/groups/application_controller.rb
@@ -37,6 +37,18 @@ class Groups::ApplicationController < ApplicationController
end
end
+ def authorize_admin_group_runners!
+ unless can?(current_user, :admin_group_runners, group)
+ render_404
+ end
+ end
+
+ def authorize_read_group_runners!
+ unless can?(current_user, :read_group_runners, group)
+ render_404
+ end
+ end
+
def authorize_create_deploy_token!
unless can?(current_user, :create_deploy_token, group)
render_404
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 3fbcb2fd7aa..6de77450a46 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -9,7 +9,6 @@ class Groups::BoardsController < Groups::ApplicationController
before_action do
push_frontend_feature_flag(:issue_boards_filtered_search, group, default_enabled: :yaml)
push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml)
- push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, group, default_enabled: :yaml)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.use { }
diff --git a/app/controllers/groups/dependency_proxy_for_containers_controller.rb b/app/controllers/groups/dependency_proxy_for_containers_controller.rb
index 171314b5f26..00839583ecc 100644
--- a/app/controllers/groups/dependency_proxy_for_containers_controller.rb
+++ b/app/controllers/groups/dependency_proxy_for_containers_controller.rb
@@ -33,17 +33,15 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
end
def blob
- return blob_via_workhorse if Feature.enabled?(:dependency_proxy_workhorse, group, default_enabled: :yaml)
-
- result = DependencyProxy::FindOrCreateBlobService
- .new(group, image, token, params[:sha]).execute
+ blob = @group.dependency_proxy_blobs.find_by_file_name(blob_file_name)
- if result[:status] == :success
- event_name = tracking_event_name(object_type: :blob, from_cache: result[:from_cache])
+ if blob.present?
+ event_name = tracking_event_name(object_type: :blob, from_cache: true)
track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
- send_upload(result[:blob].file)
+
+ send_upload(blob.file)
else
- head result[:http_status]
+ send_dependency(token_header, DependencyProxy::Registry.blob_url(image, params[:sha]), blob_file_name)
end
end
@@ -99,19 +97,6 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
private
- def blob_via_workhorse
- blob = @group.dependency_proxy_blobs.find_by_file_name(blob_file_name)
-
- if blob.present?
- event_name = tracking_event_name(object_type: :blob, from_cache: true)
- track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
-
- send_upload(blob.file)
- else
- send_dependency(token_header, DependencyProxy::Registry.blob_url(image, params[:sha]), blob_file_name)
- end
- end
-
def send_manifest(manifest, from_cache:)
response.headers[DependencyProxy::Manifest::DIGEST_HEADER] = manifest.digest
response.headers['Content-Length'] = manifest.size
@@ -160,8 +145,7 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy
end
def dependency_proxy
- @dependency_proxy ||=
- group.dependency_proxy_setting || group.create_dependency_proxy_setting
+ @dependency_proxy ||= group.dependency_proxy_setting
end
def ensure_group
diff --git a/app/controllers/groups/packages_controller.rb b/app/controllers/groups/packages_controller.rb
index 47f1816cc4c..1f3d80260ed 100644
--- a/app/controllers/groups/packages_controller.rb
+++ b/app/controllers/groups/packages_controller.rb
@@ -6,6 +6,11 @@ module Groups
feature_category :package_registry
+ # The show action renders index to allow frontend routing to work on page refresh
+ def show
+ render :index
+ end
+
private
def verify_packages_enabled!
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index 5c21c7b023c..f602d02a165 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
class Groups::RunnersController < Groups::ApplicationController
- # TODO Proper policies, such as `read_group_runners, should be implemented per
- # https://gitlab.com/gitlab-org/gitlab/-/issues/334802
- before_action :authorize_admin_group!
+ before_action :authorize_read_group_runners!, only: [:index, :show]
+ before_action :authorize_admin_group_runners!, only: [:edit, :update, :destroy, :pause, :resume]
before_action :runner_list_group_view_vue_ui_enabled, only: [:index]
before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
@@ -17,7 +16,7 @@ class Groups::RunnersController < Groups::ApplicationController
end
def runner_list_group_view_vue_ui_enabled
- return render_404 unless Feature.enabled?(:runner_list_group_view_vue_ui, group, default_enabled: :yaml)
+ render_404 unless Feature.enabled?(:runner_list_group_view_vue_ui, group, default_enabled: :yaml)
end
def show
diff --git a/app/controllers/groups/settings/access_tokens_controller.rb b/app/controllers/groups/settings/access_tokens_controller.rb
new file mode 100644
index 00000000000..b9ab2e008cc
--- /dev/null
+++ b/app/controllers/groups/settings/access_tokens_controller.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Groups
+ module Settings
+ class AccessTokensController < Groups::ApplicationController
+ include AccessTokensActions
+
+ layout 'group_settings'
+ feature_category :authentication_and_authorization
+
+ alias_method :resource, :group
+
+ def resource_access_tokens_path
+ group_settings_access_tokens_path
+ end
+ end
+ end
+end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 62336c7eede..4acbb0482f3 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -32,7 +32,6 @@ class GroupsController < Groups::ApplicationController
before_action :user_actions, only: [:show]
before_action do
- push_frontend_feature_flag(:vue_issuables_list, @group)
push_frontend_feature_flag(:vue_issues_list, @group, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, @group, default_enabled: :yaml)
end
@@ -276,7 +275,8 @@ class GroupsController < Groups::ApplicationController
:resource_access_token_creation_allowed,
:prevent_sharing_groups_outside_hierarchy,
:setup_for_company,
- :jobs_to_be_done
+ :jobs_to_be_done,
+ :crm_enabled
]
end
diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb
index 662b02010ba..fa9517c3545 100644
--- a/app/controllers/import/gitlab_controller.rb
+++ b/app/controllers/import/gitlab_controller.rb
@@ -41,7 +41,7 @@ class Import::GitlabController < Import::BaseController
override :importable_repos
def importable_repos
- client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS)
+ client.projects(starting_page: 1, page_limit: MAX_PROJECT_PAGES, per_page: PER_PAGE_PROJECTS).to_a
end
override :incompatible_repos
diff --git a/app/controllers/oauth/token_info_controller.rb b/app/controllers/oauth/token_info_controller.rb
index e37f8992d92..789356f4410 100644
--- a/app/controllers/oauth/token_info_controller.rb
+++ b/app/controllers/oauth/token_info_controller.rb
@@ -13,7 +13,7 @@ class Oauth::TokenInfoController < Doorkeeper::TokenInfoController
'expires_in_seconds' => token_json[:expires_in]
), status: :ok
else
- error = Doorkeeper::OAuth::ErrorResponse.new(name: :invalid_request)
+ error = Doorkeeper::OAuth::InvalidTokenResponse.new
response.headers.merge!(error.headers)
render json: error.body, status: error.status
end
diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb
index be2cb270a19..7a88162f469 100644
--- a/app/controllers/profiles/emails_controller.rb
+++ b/app/controllers/profiles/emails_controller.rb
@@ -52,3 +52,5 @@ class Profiles::EmailsController < Profiles::ApplicationController
@email = current_user.emails.find(params[:id])
end
end
+
+Profiles::EmailsController.prepend_mod
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index e6b80f90dca..46738651960 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -6,6 +6,9 @@ class ProfilesController < Profiles::ApplicationController
before_action :user
before_action :authorize_change_username!, only: :update_username
+ before_action only: :update_username do
+ check_rate_limit!(:profile_update_username, scope: current_user) if Feature.enabled?(:rate_limit_profile_update_username, default_enabled: :yaml)
+ end
skip_before_action :require_email, only: [:show, :update]
before_action do
push_frontend_feature_flag(:webauthn, default_enabled: :yaml)
diff --git a/app/controllers/projects/analytics/cycle_analytics/stages_controller.rb b/app/controllers/projects/analytics/cycle_analytics/stages_controller.rb
index 2f9d70fede1..7b38c069a60 100644
--- a/app/controllers/projects/analytics/cycle_analytics/stages_controller.rb
+++ b/app/controllers/projects/analytics/cycle_analytics/stages_controller.rb
@@ -11,6 +11,8 @@ class Projects::Analytics::CycleAnalytics::StagesController < Projects::Applicat
before_action :authorize_read_cycle_analytics!
before_action :only_default_value_stream_is_allowed!
+ urgency :low
+
private
override :parent
diff --git a/app/controllers/projects/analytics/cycle_analytics/summary_controller.rb b/app/controllers/projects/analytics/cycle_analytics/summary_controller.rb
index bf8742bf6e8..69327feeb02 100644
--- a/app/controllers/projects/analytics/cycle_analytics/summary_controller.rb
+++ b/app/controllers/projects/analytics/cycle_analytics/summary_controller.rb
@@ -9,6 +9,8 @@ class Projects::Analytics::CycleAnalytics::SummaryController < Projects::Applica
before_action :authorize_read_cycle_analytics!
+ urgency :low
+
def show
render json: project_level.summary
end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 81ad6243efe..adaa47b48cb 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -7,8 +7,7 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :check_issues_available!
before_action :assign_endpoint_vars
before_action do
- push_frontend_feature_flag(:swimlanes_buffered_rendering, project, default_enabled: :yaml)
- push_frontend_feature_flag(:issue_boards_filtered_search, project, default_enabled: :yaml)
+ push_frontend_feature_flag(:issue_boards_filtered_search, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
diff --git a/app/controllers/projects/google_cloud/base_controller.rb b/app/controllers/projects/google_cloud/base_controller.rb
index aff305ab7d6..f4a773a62f6 100644
--- a/app/controllers/projects/google_cloud/base_controller.rb
+++ b/app/controllers/projects/google_cloud/base_controller.rb
@@ -23,4 +23,39 @@ class Projects::GoogleCloud::BaseController < Projects::ApplicationController
def feature_flag_enabled!
access_denied! unless Feature.enabled?(:incubation_5mp_google_cloud, project)
end
+
+ def validate_gcp_token!
+ is_token_valid = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
+ .validate_token(expires_at_in_session)
+
+ return if is_token_valid
+
+ return_url = project_google_cloud_index_path(project)
+ state = generate_session_key_redirect(request.url, return_url)
+ @authorize_url = GoogleApi::CloudPlatform::Client.new(nil,
+ callback_google_api_auth_url,
+ state: state).authorize_url
+ redirect_to @authorize_url
+ end
+
+ def generate_session_key_redirect(uri, error_uri)
+ GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
+ session[key] = uri
+ session[:error_uri] = error_uri
+ end
+ end
+
+ def token_in_session
+ session[GoogleApi::CloudPlatform::Client.session_key_for_token]
+ end
+
+ def expires_at_in_session
+ session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
+ end
+
+ def handle_gcp_error(error, project)
+ Gitlab::ErrorTracking.track_exception(error, project_id: project.id)
+ @js_data = { screen: 'gcp_error', error: error.to_s }.to_json
+ render status: :unauthorized, template: 'projects/google_cloud/errors/gcp_error'
+ end
end
diff --git a/app/controllers/projects/google_cloud/deployments_controller.rb b/app/controllers/projects/google_cloud/deployments_controller.rb
new file mode 100644
index 00000000000..4e7fd73e378
--- /dev/null
+++ b/app/controllers/projects/google_cloud/deployments_controller.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class Projects::GoogleCloud::DeploymentsController < Projects::GoogleCloud::BaseController
+ before_action :validate_gcp_token!
+
+ def cloud_run
+ render json: "Placeholder"
+ end
+
+ def cloud_storage
+ render json: "Placeholder"
+ end
+end
diff --git a/app/controllers/projects/google_cloud/service_accounts_controller.rb b/app/controllers/projects/google_cloud/service_accounts_controller.rb
index a69a744154c..b5f2b658235 100644
--- a/app/controllers/projects/google_cloud/service_accounts_controller.rb
+++ b/app/controllers/projects/google_cloud/service_accounts_controller.rb
@@ -24,62 +24,16 @@ class Projects::GoogleCloud::ServiceAccountsController < Projects::GoogleCloud::
end
def create
- google_api_client = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
- service_accounts_service = GoogleCloud::ServiceAccountsService.new(project)
- gcp_project = params[:gcp_project]
- environment = params[:environment]
- generated_name = "GitLab :: #{@project.name} :: #{environment}"
- generated_desc = "GitLab generated service account for project '#{@project.name}' and environment '#{environment}'"
-
- service_account = google_api_client.create_service_account(gcp_project, generated_name, generated_desc)
- service_account_key = google_api_client.create_service_account_key(gcp_project, service_account.unique_id)
-
- service_accounts_service.add_for_project(
- environment,
- service_account.project_id,
- service_account.to_json,
- service_account_key.to_json
- )
-
- redirect_to project_google_cloud_index_path(project), notice: _('Service account generated successfully')
+ response = GoogleCloud::CreateServiceAccountsService.new(
+ project,
+ current_user,
+ google_oauth2_token: token_in_session,
+ gcp_project_id: params[:gcp_project],
+ environment_name: params[:environment]
+ ).execute
+
+ redirect_to project_google_cloud_index_path(project), notice: response.message
rescue Google::Apis::ClientError, Google::Apis::ServerError, Google::Apis::AuthorizationError => error
handle_gcp_error(error, project)
end
-
- private
-
- def validate_gcp_token!
- is_token_valid = GoogleApi::CloudPlatform::Client.new(token_in_session, nil)
- .validate_token(expires_at_in_session)
-
- return if is_token_valid
-
- return_url = project_google_cloud_index_path(project)
- state = generate_session_key_redirect(request.url, return_url)
- @authorize_url = GoogleApi::CloudPlatform::Client.new(nil,
- callback_google_api_auth_url,
- state: state).authorize_url
- redirect_to @authorize_url
- end
-
- def generate_session_key_redirect(uri, error_uri)
- GoogleApi::CloudPlatform::Client.new_session_key_for_redirect_uri do |key|
- session[key] = uri
- session[:error_uri] = error_uri
- end
- end
-
- def token_in_session
- session[GoogleApi::CloudPlatform::Client.session_key_for_token]
- end
-
- def expires_at_in_session
- session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at]
- end
-
- def handle_gcp_error(error, project)
- Gitlab::ErrorTracking.track_exception(error, project_id: project.id)
- @js_data = { screen: 'gcp_error', error: error.to_s }.to_json
- render status: :unauthorized, template: 'projects/google_cloud/errors/gcp_error'
- end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index fc67cd98d15..785fbdaa611 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -43,7 +43,6 @@ class Projects::IssuesController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:tribute_autocomplete, @project)
- push_frontend_feature_flag(:vue_issuables_list, project)
push_frontend_feature_flag(:improved_emoji_picker, project, default_enabled: :yaml)
push_frontend_feature_flag(:vue_issues_list, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
@@ -54,6 +53,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:confidential_notes, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:issue_assignees_widget, @project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:fix_comment_scroll, @project, default_enabled: :yaml)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
@@ -291,10 +291,12 @@ class Projects::IssuesController < Projects::ApplicationController
end
def issue_params
- params.require(:issue).permit(
+ all_params = params.require(:issue).permit(
*issue_params_attributes,
sentry_issue_attributes: [:sentry_issue_identifier]
)
+
+ clean_params(all_params)
end
def issue_params_attributes
@@ -348,6 +350,13 @@ class Projects::IssuesController < Projects::ApplicationController
private
+ def clean_params(all_params)
+ issue_type = all_params[:issue_type].to_s
+ all_params.delete(:issue_type) unless WorkItems::Type.allowed_types_for_issues.include?(issue_type)
+
+ all_params
+ end
+
def finder_options
options = super
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index fa7c62c34dd..bfc2fe6432d 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -19,6 +19,7 @@ class Projects::JobsController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:infinitely_collapsible_sections, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:trigger_job_retry_action, @project, default_enabled: :yaml)
end
layout 'project'
diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb
index ebba20b285a..c4f4913a620 100644
--- a/app/controllers/projects/mattermosts_controller.rb
+++ b/app/controllers/projects/mattermosts_controller.rb
@@ -20,7 +20,7 @@ class Projects::MattermostsController < Projects::ApplicationController
if result
flash[:notice] = 'This service is now configured'
- redirect_to edit_project_service_path(@project, integration)
+ redirect_to edit_project_integration_path(@project, integration)
else
flash[:alert] = message || 'Failed to configure service'
redirect_to new_project_mattermost_path(@project)
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index beb179f584b..88337242fcd 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -56,9 +56,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@diff_notes_disabled = true
- @environment = @merge_request.environments_for(current_user, latest: true).last
-
- render json: { html: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs, environment: @environment) }
+ render json: { html: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs) }
end
def diff_for_path
diff --git a/app/controllers/projects/merge_requests/diffs_controller.rb b/app/controllers/projects/merge_requests/diffs_controller.rb
index 32ca7d779d2..9bc9c19157a 100644
--- a/app/controllers/projects/merge_requests/diffs_controller.rb
+++ b/app/controllers/projects/merge_requests/diffs_controller.rb
@@ -35,13 +35,11 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
diffs = @compare.diffs_in_batch(params[:page], params[:per_page], diff_options: diff_options_hash)
unfoldable_positions = @merge_request.note_positions_for_paths(diffs.diff_file_paths, current_user).unfoldable
- environment = @merge_request.environments_for(current_user, latest: true).last
diffs.unfold_diff_files(unfoldable_positions)
diffs.write_cache
options = {
- environment: environment,
merge_request: @merge_request,
commit: commit,
diff_view: diff_view,
@@ -54,7 +52,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
# NOTE: Any variables that would affect the resulting json needs to be added to the cache_context to avoid stale cache issues.
cache_context = [
current_user&.cache_key,
- environment&.cache_key,
unfoldable_positions.map(&:to_h),
diff_view,
params[:w],
@@ -98,7 +95,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
# Deprecated: https://gitlab.com/gitlab-org/gitlab/issues/37735
def render_diffs
diffs = @compare.diffs(diff_options)
- @environment = @merge_request.environments_for(current_user, latest: true).last
diffs.unfold_diff_files(note_positions.unfoldable)
diffs.write_cache
@@ -175,7 +171,6 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def additional_attributes
{
- environment: @environment,
merge_request: @merge_request,
merge_request_diff: @merge_request_diff,
merge_request_diffs: @merge_request_diffs,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 7133233f083..f936aeb0084 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -42,17 +42,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:restructured_mr_widget, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_changes_fluid_layout, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
-
+ push_frontend_feature_flag(:refactor_mr_widgets_extensions, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:rebase_without_ci_ui, @project, default_enabled: :yaml)
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)
push_frontend_feature_flag(:diff_searching_usage_data, @project, default_enabled: :yaml)
end
- before_action do
- push_frontend_feature_flag(:show_relevant_approval_rule_approvers, @project, default_enabled: :yaml)
- end
-
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :discussions]
after_action :log_merge_request_show, only: [:show]
@@ -66,7 +63,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
feature_category :code_testing, [:test_reports, :coverage_reports]
feature_category :code_quality, [:codequality_reports, :codequality_mr_diff_reports]
- feature_category :accessibility_testing, [:accessibility_reports]
+ feature_category :code_testing, [:accessibility_reports]
feature_category :infrastructure_as_code, [:terraform_reports]
feature_category :continuous_integration, [:pipeline_status, :pipelines, :exposed_artifacts]
diff --git a/app/controllers/projects/packages/infrastructure_registry_controller.rb b/app/controllers/projects/packages/infrastructure_registry_controller.rb
index 4506a83634a..c02a0a56e03 100644
--- a/app/controllers/projects/packages/infrastructure_registry_controller.rb
+++ b/app/controllers/projects/packages/infrastructure_registry_controller.rb
@@ -9,7 +9,11 @@ module Projects
def show
@package = project.packages.find(params[:id])
- @package_files = @package.package_files.recent
+ @package_files = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ @package.installable_package_files.recent
+ else
+ @package.package_files.recent
+ end
end
end
end
diff --git a/app/controllers/projects/packages/packages_controller.rb b/app/controllers/projects/packages/packages_controller.rb
index 5de71466c10..969922266fa 100644
--- a/app/controllers/projects/packages/packages_controller.rb
+++ b/app/controllers/projects/packages/packages_controller.rb
@@ -7,8 +7,9 @@ module Projects
feature_category :package_registry
+ # The show action renders index to allow frontend routing to work on page refresh
def show
- @package = project.packages.find(params[:id])
+ render :index
end
end
end
diff --git a/app/controllers/projects/prometheus/metrics_controller.rb b/app/controllers/projects/prometheus/metrics_controller.rb
index f3a3d22244c..e61d357ce4e 100644
--- a/app/controllers/projects/prometheus/metrics_controller.rb
+++ b/app/controllers/projects/prometheus/metrics_controller.rb
@@ -66,7 +66,7 @@ module Projects
)
if @metric.persisted?
- redirect_to edit_project_service_path(project, ::Integrations::Prometheus),
+ redirect_to edit_project_integration_path(project, ::Integrations::Prometheus),
notice: _('Metric was successfully added.')
else
render 'new'
@@ -77,7 +77,7 @@ module Projects
@metric = update_metrics_service(prometheus_metric).execute
if @metric.persisted?
- redirect_to edit_project_service_path(project, ::Integrations::Prometheus),
+ redirect_to edit_project_integration_path(project, ::Integrations::Prometheus),
notice: _('Metric was successfully updated.')
else
render 'edit'
@@ -93,7 +93,7 @@ module Projects
respond_to do |format|
format.html do
- redirect_to edit_project_service_path(project, ::Integrations::Prometheus), status: :see_other
+ redirect_to edit_project_integration_path(project, ::Integrations::Prometheus), status: :see_other
end
format.json do
head :ok
diff --git a/app/controllers/projects/security/configuration_controller.rb b/app/controllers/projects/security/configuration_controller.rb
index 444f4783a19..14f765814e6 100644
--- a/app/controllers/projects/security/configuration_controller.rb
+++ b/app/controllers/projects/security/configuration_controller.rb
@@ -9,6 +9,37 @@ module Projects
def show
render_403 unless can?(current_user, :read_security_configuration, project)
+
+ respond_to do |format|
+ format.html
+ format.json do
+ render status: :ok, json: configuration.to_h
+ end
+ end
+ end
+
+ private
+
+ def configuration
+ if unify_configuration_enabled?
+ configuration_presenter
+ else
+ {}
+ end
+ end
+
+ def configuration_presenter
+ ::Projects::Security::ConfigurationPresenter.new(project,
+ **presenter_attributes,
+ current_user: current_user)
+ end
+
+ def presenter_attributes
+ {}
+ end
+
+ def unify_configuration_enabled?
+ Feature.enabled?(:unify_security_configuration, project, default_enabled: :yaml)
end
end
end
diff --git a/app/controllers/projects/service_hook_logs_controller.rb b/app/controllers/projects/service_hook_logs_controller.rb
index 88de0b7ba0d..7b037c60321 100644
--- a/app/controllers/projects/service_hook_logs_controller.rb
+++ b/app/controllers/projects/service_hook_logs_controller.rb
@@ -7,13 +7,13 @@ class Projects::ServiceHookLogsController < Projects::HookLogsController
def retry
execute_hook
- redirect_to edit_project_service_path(@project, @integration)
+ redirect_to edit_project_integration_path(@project, @integration)
end
private
def integration
- @integration ||= @project.find_or_initialize_integration(params[:service_id])
+ @integration ||= @project.find_or_initialize_integration(params[:integration_id])
end
override :hook
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index 9464826701d..9896f75c099 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -12,6 +12,9 @@ class Projects::ServicesController < Projects::ApplicationController
before_action :web_hook_logs, only: [:edit, :update]
before_action :set_deprecation_notice_for_prometheus_integration, only: [:edit, :update]
before_action :redirect_deprecated_prometheus_integration, only: [:update]
+ before_action do
+ push_frontend_feature_flag(:vue_integration_form, current_user, default_enabled: :yaml)
+ end
respond_to :html
@@ -66,7 +69,7 @@ class Projects::ServicesController < Projects::ApplicationController
private
def redirect_path
- safe_redirect_path(params[:redirect_to]).presence || edit_project_service_path(project, integration)
+ safe_redirect_path(params[:redirect_to]).presence || edit_project_integration_path(project, integration)
end
def service_test_response
@@ -119,7 +122,7 @@ class Projects::ServicesController < Projects::ApplicationController
end
def redirect_deprecated_prometheus_integration
- redirect_to edit_project_service_path(project, integration) if integration.is_a?(::Integrations::Prometheus) && Feature.enabled?(:settings_operations_prometheus_service, project)
+ redirect_to edit_project_integration_path(project, integration) if integration.is_a?(::Integrations::Prometheus) && Feature.enabled?(:settings_operations_prometheus_service, project)
end
def set_deprecation_notice_for_prometheus_integration
diff --git a/app/controllers/projects/settings/access_tokens_controller.rb b/app/controllers/projects/settings/access_tokens_controller.rb
index 1ecede4c7a2..32916831ecd 100644
--- a/app/controllers/projects/settings/access_tokens_controller.rb
+++ b/app/controllers/projects/settings/access_tokens_controller.rb
@@ -3,77 +3,15 @@
module Projects
module Settings
class AccessTokensController < Projects::ApplicationController
- include ProjectsHelper
+ include AccessTokensActions
layout 'project_settings'
- before_action -> { check_permission(:read_resource_access_tokens) }, only: [:index]
- before_action -> { check_permission(:destroy_resource_access_tokens) }, only: [:revoke]
- before_action -> { check_permission(:create_resource_access_tokens) }, only: [:create]
-
feature_category :authentication_and_authorization
- def index
- @project_access_token = PersonalAccessToken.new
- set_index_vars
- end
-
- def create
- token_response = ResourceAccessTokens::CreateService.new(current_user, @project, create_params).execute
-
- if token_response.success?
- @project_access_token = token_response.payload[:access_token]
- PersonalAccessToken.redis_store!(key_identity, @project_access_token.token)
-
- redirect_to namespace_project_settings_access_tokens_path, notice: _("Your new project access token has been created.")
- else
- redirect_to namespace_project_settings_access_tokens_path, alert: _("Failed to create new project access token: %{token_response_message}") % { token_response_message: token_response.message }
- end
- end
-
- def revoke
- @project_access_token = finder.find(params[:id])
- revoked_response = ResourceAccessTokens::RevokeService.new(current_user, @project, @project_access_token).execute
-
- if revoked_response.success?
- flash[:notice] = _("Revoked project access token %{project_access_token_name}!") % { project_access_token_name: @project_access_token.name }
- else
- flash[:alert] = _("Could not revoke project access token %{project_access_token_name}.") % { project_access_token_name: @project_access_token.name }
- end
-
- redirect_to namespace_project_settings_access_tokens_path
- end
-
- private
-
- def check_permission(action)
- render_404 unless can?(current_user, action, @project)
- end
-
- def create_params
- params.require(:project_access_token).permit(:name, :expires_at, :access_level, scopes: [])
- end
-
- def set_index_vars
- # Loading project members so that we can fetch access level of the bot
- # user in the project without multiple queries.
- @project.project_members.load
-
- @scopes = Gitlab::Auth.resource_bot_scopes
- @active_project_access_tokens = finder(state: 'active').execute.preload_users
- @inactive_project_access_tokens = finder(state: 'inactive', sort: 'expires_at_asc').execute.preload_users
- @new_project_access_token = PersonalAccessToken.redis_getdel(key_identity)
- end
-
- def finder(options = {})
- PersonalAccessTokensFinder.new({ user: bot_users, impersonation: false }.merge(options))
- end
-
- def bot_users
- @project.bots
- end
+ alias_method :resource, :project
- def key_identity
- "#{current_user.id}:#{@project.id}"
+ def resource_access_tokens_path
+ namespace_project_settings_access_tokens_path
end
end
end
diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb
index ef6c10d43cd..c71134e0547 100644
--- a/app/controllers/projects/settings/ci_cd_controller.rb
+++ b/app/controllers/projects/settings/ci_cd_controller.rb
@@ -26,9 +26,13 @@ module Projects
).to_json
end
- # @assignable_runners is using ci_owned_runners
- ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do
+ if current_user.ci_owned_runners_cross_joins_fix_enabled?
render
+ else
+ # @assignable_runners is using ci_owned_runners
+ ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do
+ render
+ end
end
end
diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb
index cc419bab687..d750bd201e2 100644
--- a/app/controllers/projects/settings/repository_controller.rb
+++ b/app/controllers/projects/settings/repository_controller.rb
@@ -81,8 +81,7 @@ module Projects
@protected_branch = @project.protected_branches.new
@protected_tag = @project.protected_tags.new
- @protected_branches_count = @protected_branches.reduce(0) { |sum, branch| sum + branch.matching(@project.repository.branches).size }
- @protected_tags_count = @protected_tags.reduce(0) { |sum, tag| sum + tag.matching(@project.repository.tags).size }
+ @protected_tags_count = @protected_tags.reduce(0) { |sum, tag| sum + tag.matching(@project.repository.tag_names).size }
load_gon_index
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index 660ebcc30d3..4f905a2d565 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -6,6 +6,7 @@ class Projects::TreeController < Projects::ApplicationController
include CreatesCommit
include ActionView::Helpers::SanitizeHelper
include RedirectsForMissingPathOnTree
+ include SourcegraphDecorator
around_action :allow_gitaly_ref_name_caching, only: [:show]
@@ -19,6 +20,9 @@ class Projects::TreeController < Projects::ApplicationController
push_frontend_feature_flag(:lazy_load_commits, @project, default_enabled: :yaml)
push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml)
push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:highlight_js, @project, default_enabled: :yaml)
+ push_licensed_feature(:file_locks) if @project.licensed_feature_available?(:file_locks)
+ push_frontend_feature_flag(:consolidated_edit_button, @project, default_enabled: :yaml)
end
feature_category :source_code_management
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 04dde5ef7b2..64abcd7cc33 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -9,6 +9,7 @@ class ProjectsController < Projects::ApplicationController
include RecordUserLastActivity
include ImportUrlParams
include FiltersEvents
+ include SourcegraphDecorator
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
@@ -39,6 +40,7 @@ class ProjectsController < Projects::ApplicationController
push_frontend_feature_flag(:increase_page_size_exponentially, @project, default_enabled: :yaml)
push_frontend_feature_flag(:new_dir_modal, @project, default_enabled: :yaml)
push_licensed_feature(:file_locks) if @project.present? && @project.licensed_feature_available?(:file_locks)
+ push_frontend_feature_flag(:consolidated_edit_button, @project, default_enabled: :yaml)
end
layout :determine_layout
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index ed3facd72c5..c1765d367d1 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -13,6 +13,13 @@ class RegistrationsController < Devise::RegistrationsController
before_action :ensure_destroy_prerequisites_met, only: [:destroy]
before_action :load_recaptcha, only: :new
before_action :set_invite_params, only: :new
+ before_action only: [:create] do
+ check_rate_limit!(:user_sign_up, scope: request.ip) if Feature.enabled?(:rate_limit_user_sign_up_endpoint, default_enabled: :yaml)
+ end
+
+ before_action only: [:new] do
+ push_frontend_feature_flag(:gitlab_gtm_datalayer, type: :ops)
+ end
feature_category :authentication_and_authorization
diff --git a/app/controllers/repositories/lfs_api_controller.rb b/app/controllers/repositories/lfs_api_controller.rb
index d93d88c9e64..2b0aa67326e 100644
--- a/app/controllers/repositories/lfs_api_controller.rb
+++ b/app/controllers/repositories/lfs_api_controller.rb
@@ -155,7 +155,7 @@ module Repositories
end
def should_auto_link?
- return false unless Feature.enabled?(:lfs_auto_link_fork_source, project)
+ return false unless Feature.enabled?(:lfs_auto_link_fork_source, project, default_enabled: :yaml)
return false unless project.forked?
# Sanity check in case for some reason the user doesn't have access to the parent
diff --git a/app/controllers/sandbox_controller.rb b/app/controllers/sandbox_controller.rb
new file mode 100644
index 00000000000..a87c2b38e60
--- /dev/null
+++ b/app/controllers/sandbox_controller.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class SandboxController < ApplicationController # rubocop:disable Gitlab/NamespacedClass
+ skip_before_action :authenticate_user!
+
+ feature_category :not_owned
+
+ def mermaid
+ render layout: false
+ end
+end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 99a6dfa811e..d58ed252a36 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -17,6 +17,7 @@ class SearchController < ApplicationController
search_term_present = params[:search].present? || params[:term].present?
search_term_present && !params[:project_id].present?
end
+ before_action :check_email_search_rate_limit!, only: [:show, :count, :autocomplete]
rescue_from ActiveRecord::QueryCanceled, with: :render_timeout
@@ -142,6 +143,7 @@ class SearchController < ApplicationController
payload[:metadata]['meta.search.filters.confidential'] = params[:confidential]
payload[:metadata]['meta.search.filters.state'] = params[:state]
payload[:metadata]['meta.search.force_search_results'] = params[:force_search_results]
+ payload[:metadata]['meta.search.project_ids'] = params[:project_ids]
if search_service.abuse_detected?
payload[:metadata]['abuse.confidence'] = Gitlab::Abuse.confidence(:certain)
@@ -198,6 +200,12 @@ class SearchController < ApplicationController
render status: :request_timeout
end
end
+
+ def check_email_search_rate_limit!
+ return unless search_service.params.email_lookup?
+
+ check_rate_limit!(:user_email_lookup, scope: [current_user])
+ end
end
SearchController.prepend_mod_with('SearchController')
diff --git a/app/controllers/sherlock/application_controller.rb b/app/controllers/sherlock/application_controller.rb
deleted file mode 100644
index c048254d348..00000000000
--- a/app/controllers/sherlock/application_controller.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-module Sherlock
- class ApplicationController < ::ApplicationController
- before_action :find_transaction
-
- def find_transaction
- if params[:transaction_id]
- @transaction = Gitlab::Sherlock.collection
- .find_transaction(params[:transaction_id])
- end
- end
- end
-end
diff --git a/app/controllers/sherlock/file_samples_controller.rb b/app/controllers/sherlock/file_samples_controller.rb
deleted file mode 100644
index 900446bb75a..00000000000
--- a/app/controllers/sherlock/file_samples_controller.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-module Sherlock
- class FileSamplesController < Sherlock::ApplicationController
- def show
- @file_sample = @transaction.find_file_sample(params[:id])
- end
- end
-end
diff --git a/app/controllers/sherlock/queries_controller.rb b/app/controllers/sherlock/queries_controller.rb
deleted file mode 100644
index 49a25c682b5..00000000000
--- a/app/controllers/sherlock/queries_controller.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-module Sherlock
- class QueriesController < Sherlock::ApplicationController
- def show
- @query = @transaction.find_query(params[:id])
- end
- end
-end
diff --git a/app/controllers/sherlock/transactions_controller.rb b/app/controllers/sherlock/transactions_controller.rb
deleted file mode 100644
index 8d1847507cc..00000000000
--- a/app/controllers/sherlock/transactions_controller.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-module Sherlock
- class TransactionsController < Sherlock::ApplicationController
- def index
- @transactions = Gitlab::Sherlock.collection.newest_first
- end
-
- def show
- @transaction = Gitlab::Sherlock.collection.find_transaction(params[:id])
-
- render_404 unless @transaction
- end
-
- def destroy_all
- Gitlab::Sherlock.collection.clear
-
- redirect_back_or_default(options: { status: :found })
- end
- end
-end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 26f56307862..8710eebf210 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -23,6 +23,9 @@ class UsersController < ApplicationController
before_action :user, except: [:exists]
before_action :authorize_read_user_profile!,
only: [:calendar, :calendar_activities, :groups, :projects, :contributed, :starred, :snippets, :followers, :following]
+ before_action only: [:exists] do
+ check_rate_limit!(:username_exists, scope: request.ip) if Feature.enabled?(:rate_limit_username_exists_endpoint, default_enabled: :yaml)
+ end
feature_category :users
diff --git a/app/events/ci/pipeline_created_event.rb b/app/events/ci/pipeline_created_event.rb
new file mode 100644
index 00000000000..8b971b63cea
--- /dev/null
+++ b/app/events/ci/pipeline_created_event.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Ci
+ class PipelineCreatedEvent < ::Gitlab::EventStore::Event
+ def schema
+ {
+ 'type' => 'object',
+ 'properties' => {
+ 'pipeline_id' => { 'type' => 'integer' }
+ }
+ }
+ end
+ end
+end
diff --git a/app/experiments/change_continuous_onboarding_link_urls_experiment.rb b/app/experiments/change_continuous_onboarding_link_urls_experiment.rb
deleted file mode 100644
index 680cb8eadd8..00000000000
--- a/app/experiments/change_continuous_onboarding_link_urls_experiment.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-class ChangeContinuousOnboardingLinkUrlsExperiment < ApplicationExperiment # rubocop:disable Gitlab/NamespacedClass
- attr_writer :namespace
-
- def track(action, **event_args)
- super(action, **event_args.merge(namespace: @namespace))
- end
-end
diff --git a/app/experiments/new_project_sast_enabled_experiment.rb b/app/experiments/new_project_sast_enabled_experiment.rb
index b7b4552f0cc..a779b8ec633 100644
--- a/app/experiments/new_project_sast_enabled_experiment.rb
+++ b/app/experiments/new_project_sast_enabled_experiment.rb
@@ -15,4 +15,7 @@ class NewProjectSastEnabledExperiment < ApplicationExperiment # rubocop:disable
def unchecked_candidate_behavior
end
+
+ def unchecked_free_indicator_behavior
+ end
end
diff --git a/app/experiments/require_verification_for_namespace_creation_experiment.rb b/app/experiments/require_verification_for_namespace_creation_experiment.rb
new file mode 100644
index 00000000000..1cadac7e7d4
--- /dev/null
+++ b/app/experiments/require_verification_for_namespace_creation_experiment.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class RequireVerificationForNamespaceCreationExperiment < ApplicationExperiment # rubocop:disable Gitlab/NamespacedClass
+ def control_behavior
+ false
+ end
+
+ def candidate_behavior
+ true
+ end
+
+ def candidate?
+ run
+ end
+
+ def record_conversion(namespace)
+ return unless should_track?
+
+ Experiment.by_name(name).record_conversion_event_for_subject(subject, namespace_id: namespace.id)
+ end
+
+ private
+
+ def subject
+ context.value[:user]
+ end
+end
diff --git a/app/experiments/templates/new_project_readme_content/readme_advanced.md.tt b/app/experiments/templates/new_project_readme_content/readme_advanced.md.tt
index 7592c7c6ab7..9b950ff49e4 100644
--- a/app/experiments/templates/new_project_readme_content/readme_advanced.md.tt
+++ b/app/experiments/templates/new_project_readme_content/readme_advanced.md.tt
@@ -47,7 +47,7 @@ Use the built-in continuous integration in GitLab.
# Editing this README
-When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](<%= redirect("https://www.makeareadme.com/") %>) for this template.
+When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com) for this template.
## Suggestions for a good README
Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information.
diff --git a/app/finders/ci/runners_finder.rb b/app/finders/ci/runners_finder.rb
index 5d597f94f72..3ebf6bd1562 100644
--- a/app/finders/ci/runners_finder.rb
+++ b/app/finders/ci/runners_finder.rb
@@ -47,15 +47,19 @@ module Ci
end
def group_runners
- raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :admin_group, @group)
+ raise Gitlab::Access::AccessDeniedError unless can?(@current_user, :read_group_runners, @group)
@runners = case @params[:membership]
when :direct
Ci::Runner.belonging_to_group(@group.id)
when :descendants, nil
- # Getting all runners from the group itself and all its descendant groups/projects
- descendant_projects = Project.for_group_and_its_subgroups(@group)
- Ci::Runner.belonging_to_group_or_project(@group.self_and_descendants, descendant_projects)
+ if ::Feature.enabled?(:ci_find_runners_by_ci_mirrors, @group, default_enabled: :yaml)
+ Ci::Runner.belonging_to_group_or_project_descendants(@group.id)
+ else
+ # Getting all runners from the group itself and all its descendant groups/projects
+ descendant_projects = Project.for_group_and_its_subgroups(@group)
+ Ci::Runner.legacy_belonging_to_group_or_project(@group.self_and_descendants, descendant_projects)
+ end
else
raise ArgumentError, 'Invalid membership filter'
end
diff --git a/app/finders/environments/environments_by_deployments_finder.rb b/app/finders/environments/environments_by_deployments_finder.rb
index 2716c80ea6e..1a0d5ff0d5e 100644
--- a/app/finders/environments/environments_by_deployments_finder.rb
+++ b/app/finders/environments/environments_by_deployments_finder.rb
@@ -14,8 +14,7 @@ module Environments
def execute
deployments =
if ref
- deployments_query = params[:with_tags] ? 'ref = :ref OR tag IS TRUE' : 'ref = :ref'
- Deployment.where(deployments_query, ref: ref.to_s)
+ Deployment.where(ref: ref.to_s)
elsif commit
Deployment.where(sha: commit.sha)
else
diff --git a/app/finders/fork_targets_finder.rb b/app/finders/fork_targets_finder.rb
index 3a79b216853..0b5dfb16572 100644
--- a/app/finders/fork_targets_finder.rb
+++ b/app/finders/fork_targets_finder.rb
@@ -8,9 +8,9 @@ class ForkTargetsFinder
# rubocop: disable CodeReuse/ActiveRecord
def execute(options = {})
- return ::Namespace.where(id: user.manageable_namespaces).sort_by_type unless options[:only_groups]
+ return ::Namespace.where(id: user.forkable_namespaces).sort_by_type unless options[:only_groups]
- ::Group.where(id: user.manageable_groups)
+ ::Group.where(id: user.manageable_groups(include_groups_with_developer_maintainer_access: true))
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index 7974710e67b..3d9b6e94cc6 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -87,13 +87,7 @@ class GroupDescendantsFinder
visible_to_user = visible_to_user.or(authorized_to_user)
end
- group_to_query = if Feature.enabled?(:linear_group_descendants_finder, current_user, default_enabled: :yaml)
- parent_group
- else
- hierarchy_for_parent
- end
-
- group_to_query.descendants.where(visible_to_user)
+ parent_group.descendants.where(visible_to_user)
# rubocop: enable CodeReuse/Finder
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -159,13 +153,7 @@ class GroupDescendantsFinder
# rubocop: disable CodeReuse/ActiveRecord
def projects_matching_filter
# rubocop: disable CodeReuse/Finder
- objects_in_hierarchy = if Feature.enabled?(:linear_group_descendants_finder, current_user, default_enabled: :yaml)
- parent_group.self_and_descendants.as_ids
- else
- hierarchy_for_parent.base_and_descendants.select(:id)
- end
-
- projects_nested_in_group = Project.where(namespace_id: objects_in_hierarchy)
+ projects_nested_in_group = Project.where(namespace_id: parent_group.self_and_descendants.as_ids)
params_with_search = params.merge(search: params[:filter])
ProjectsFinder.new(params: params_with_search,
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
index 75623d33ef5..fff17098c7b 100644
--- a/app/finders/group_members_finder.rb
+++ b/app/finders/group_members_finder.rb
@@ -1,14 +1,15 @@
# frozen_string_literal: true
class GroupMembersFinder < UnionFinder
- RELATIONS = %i(direct inherited descendants).freeze
+ RELATIONS = %i(direct inherited descendants shared_from_groups).freeze
DEFAULT_RELATIONS = %i(direct inherited).freeze
INVALID_RELATION_TYPE_ERROR_MSG = "is not a valid relation type. Valid relation types are #{RELATIONS.join(', ')}."
RELATIONS_DESCRIPTIONS = {
direct: 'Members in the group itself',
inherited: "Members in the group's ancestor groups",
- descendants: "Members in the group's subgroups"
+ descendants: "Members in the group's subgroups",
+ shared_from_groups: "Invited group's members"
}.freeze
include CreatedAtFilter
@@ -28,11 +29,7 @@ class GroupMembersFinder < UnionFinder
end
def execute(include_relations: DEFAULT_RELATIONS)
- return filter_members(group_members_list) if include_relations == [:direct]
-
groups = groups_by_relations(include_relations)
- return GroupMember.none unless groups
-
members = all_group_members(groups).distinct_on_user_with_max_access_level
filter_members(members)
@@ -45,22 +42,14 @@ class GroupMembersFinder < UnionFinder
def groups_by_relations(include_relations)
check_relation_arguments!(include_relations)
- case include_relations.sort
- when [:inherited]
- group.ancestors
- when [:descendants]
- group.descendants
- when [:direct, :inherited]
- group.self_and_ancestors
- when [:descendants, :direct]
- group.self_and_descendants
- when [:descendants, :inherited]
- find_union([group.ancestors, group.descendants], Group)
- when [:descendants, :direct, :inherited]
- group.self_and_hierarchy
- else
- nil
- end
+ related_groups = []
+
+ related_groups << Group.by_id(group.id) if include_relations&.include?(:direct)
+ related_groups << group.ancestors if include_relations&.include?(:inherited)
+ related_groups << group.descendants if include_relations&.include?(:descendants)
+ related_groups << group.shared_with_groups.public_or_visible_to_user(user) if include_relations&.include?(:shared_from_groups)
+
+ find_union(related_groups, Group)
end
def filter_members(members)
diff --git a/app/finders/groups/user_groups_finder.rb b/app/finders/groups/user_groups_finder.rb
index 5946e3a8933..f4aed413867 100644
--- a/app/finders/groups/user_groups_finder.rb
+++ b/app/finders/groups/user_groups_finder.rb
@@ -53,8 +53,7 @@ module Groups
end
def permission_scope_create_projects?
- params[:permission_scope] == :create_projects &&
- Feature.enabled?(:paginatable_namespace_drop_down_for_project_creation, current_user, default_enabled: :yaml)
+ params[:permission_scope] == :create_projects
end
end
end
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
index 7e3cdd79a4c..7cb3e7a5d7f 100644
--- a/app/finders/groups_finder.rb
+++ b/app/finders/groups_finder.rb
@@ -54,7 +54,7 @@ class GroupsFinder < UnionFinder
groups = []
if current_user
- if Feature.enabled?(:use_traversal_ids_groups_finder, default_enabled: :yaml)
+ if Feature.enabled?(:use_traversal_ids_groups_finder, current_user, default_enabled: :yaml)
groups << current_user.authorized_groups.self_and_ancestors
groups << current_user.groups.self_and_descendants
else
@@ -81,7 +81,7 @@ class GroupsFinder < UnionFinder
.groups
.where('members.access_level >= ?', params[:min_access_level])
- if Feature.enabled?(:use_traversal_ids_groups_finder, default_enabled: :yaml)
+ if Feature.enabled?(:use_traversal_ids_groups_finder, current_user, default_enabled: :yaml)
groups.self_and_descendants
else
Gitlab::ObjectHierarchy
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index 21a19aa22a1..de750b49c6a 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -27,7 +27,7 @@
# updated_after: datetime
# updated_before: datetime
# confidential: boolean
-# issue_types: array of strings (one of WorkItem::Type.base_types)
+# issue_types: array of strings (one of WorkItems::Type.base_types)
#
class IssuesFinder < IssuableFinder
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
@@ -124,13 +124,13 @@ class IssuesFinder < IssuableFinder
def by_issue_types(items)
issue_type_params = Array(params[:issue_types]).map(&:to_s)
return items if issue_type_params.blank?
- return Issue.none unless (WorkItem::Type.base_types.keys & issue_type_params).sort == issue_type_params.sort
+ return Issue.none unless (WorkItems::Type.base_types.keys & issue_type_params).sort == issue_type_params.sort
items.with_issue_type(params[:issue_types])
end
def by_negated_issue_types(items)
- issue_type_params = Array(not_params[:issue_types]).map(&:to_s) & WorkItem::Type.base_types.keys
+ issue_type_params = Array(not_params[:issue_types]).map(&:to_s) & WorkItems::Type.base_types.keys
return items if issue_type_params.blank?
items.without_issue_type(issue_type_params)
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index ba709d3bdfc..81e4ab7014d 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -140,14 +140,13 @@ class MergeRequestsFinder < IssuableFinder
# rubocop: disable CodeReuse/ActiveRecord
def by_draft(items)
- draft_param = params[:draft] || params[:wip]
+ draft_param = Gitlab::Utils.to_boolean(params.fetch(:draft) { params.fetch(:wip, nil) })
+ return items if draft_param.nil?
- if draft_param == 'yes'
+ if draft_param
items.where(wip_match(items.arel_table))
- elsif draft_param == 'no'
- items.where.not(wip_match(items.arel_table))
else
- items
+ items.where.not(wip_match(items.arel_table))
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/finders/packages/package_file_finder.rb b/app/finders/packages/package_file_finder.rb
index 792ffa0591b..55dc4be2001 100644
--- a/app/finders/packages/package_file_finder.rb
+++ b/app/finders/packages/package_file_finder.rb
@@ -19,7 +19,11 @@ class Packages::PackageFileFinder
private
def package_files
- files = package.package_files
+ files = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ package.installable_package_files
+ else
+ package.package_files
+ end
by_file_name(files)
end
diff --git a/app/finders/projects/members/effective_access_level_finder.rb b/app/finders/projects/members/effective_access_level_finder.rb
index d238679f2fb..4538fc4c855 100644
--- a/app/finders/projects/members/effective_access_level_finder.rb
+++ b/app/finders/projects/members/effective_access_level_finder.rb
@@ -27,13 +27,9 @@ module Projects
attr_reader :project
def generate_from_statement(user_ids_and_access_levels)
- "(VALUES #{generate_values_expression(user_ids_and_access_levels)}) members (user_id, access_level)"
- end
+ values_list = Arel::Nodes::ValuesList.new(user_ids_and_access_levels).to_sql
- def generate_values_expression(user_ids_and_access_levels)
- user_ids_and_access_levels.map do |user_id, access_level|
- "(#{user_id}, #{access_level})"
- end.join(",")
+ "(#{values_list}) members (user_id, access_level)"
end
def no_members?
diff --git a/app/finders/user_group_notification_settings_finder.rb b/app/finders/user_group_notification_settings_finder.rb
index c2af581dd14..c6a1a6b36d1 100644
--- a/app/finders/user_group_notification_settings_finder.rb
+++ b/app/finders/user_group_notification_settings_finder.rb
@@ -7,15 +7,7 @@ class UserGroupNotificationSettingsFinder
end
def execute
- # rubocop: disable CodeReuse/ActiveRecord
- selected_groups = Group.where(id: groups.select(:id))
- groups_with_ancestors = if Feature.enabled?(:linear_user_group_notification_settings_finder_ancestors_scopes, user, default_enabled: :yaml)
- selected_groups.self_and_ancestors
- else
- Gitlab::ObjectHierarchy.new(selected_groups).base_and_ancestors
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
+ groups_with_ancestors = groups.self_and_ancestors
@loaded_groups_with_ancestors = groups_with_ancestors.index_by(&:id)
@loaded_notification_settings = user.notification_settings_for_groups(groups_with_ancestors).preload_source_route.index_by(&:source_id)
diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb
index 596a413782e..96120d9412f 100644
--- a/app/finders/user_recent_events_finder.rb
+++ b/app/finders/user_recent_events_finder.rb
@@ -47,6 +47,24 @@ class UserRecentEventsFinder
end
# rubocop: disable CodeReuse/ActiveRecord
+ def execute_optimized_multi(users)
+ Gitlab::Pagination::Keyset::InOperatorOptimization::QueryBuilder.new(
+ scope: Event.reorder(id: :desc),
+ array_scope: User.select(:id).where(id: users),
+ # Event model has a default scope { reorder(nil) }
+ # When a relation is rordered and used as a target when merging scope,
+ # its order takes a precedence and _overwrites_ the original scope's order.
+ # Thus we have to explicitly provide `reorder` for array_mapping_scope here.
+ array_mapping_scope: -> (author_id_expression) { Event.where(Event.arel_table[:author_id].eq(author_id_expression)).reorder(id: :desc) },
+ finder_query: -> (id_expression) { Event.where(Event.arel_table[:id].eq(id_expression)) }
+ )
+ .execute
+ .limit(limit)
+ .offset(params[:offset] || 0)
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # rubocop: disable CodeReuse/ActiveRecord
def execute_multi
users = []
@target_user.each do |user|
@@ -55,7 +73,11 @@ class UserRecentEventsFinder
return Event.none if users.empty?
- event_filter.apply_filter(Event.where(author: users).limit_recent(limit, params[:offset] || 0))
+ if event_filter.filter == EventFilter::ALL
+ execute_optimized_multi(users)
+ else
+ event_filter.apply_filter(Event.where(author: users).limit_recent(limit, params[:offset] || 0))
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/graphql/mutations/clusters/agent_tokens/revoke.rb b/app/graphql/mutations/clusters/agent_tokens/revoke.rb
new file mode 100644
index 00000000000..ca570792296
--- /dev/null
+++ b/app/graphql/mutations/clusters/agent_tokens/revoke.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Clusters
+ module AgentTokens
+ class Revoke < BaseMutation
+ graphql_name 'ClusterAgentTokenRevoke'
+
+ authorize :admin_cluster
+
+ TokenID = ::Types::GlobalIDType[::Clusters::AgentToken]
+
+ argument :id, TokenID,
+ required: true,
+ description: 'Global ID of the agent token that will be revoked.'
+
+ def resolve(id:)
+ token = authorized_find!(id: id)
+ token.update(status: token.class.statuses[:revoked])
+
+ { errors: errors_on_object(token) }
+ end
+
+ private
+
+ def find_object(id:)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ id = TokenID.coerce_isolated_input(id)
+ GitlabSchema.find_by_gid(id)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/issues/set_crm_contacts.rb b/app/graphql/mutations/issues/set_crm_contacts.rb
index 4e49a45d52a..62990fc67f1 100644
--- a/app/graphql/mutations/issues/set_crm_contacts.rb
+++ b/app/graphql/mutations/issues/set_crm_contacts.rb
@@ -18,7 +18,8 @@ module Mutations
def resolve(project_path:, iid:, contact_ids:, operation_mode: Types::MutationOperationModeEnum.enum[:replace])
issue = authorized_find!(project_path: project_path, iid: iid)
project = issue.project
- raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless Feature.enabled?(:customer_relations, project.group, default_enabled: :yaml)
+
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless feature_enabled?(project)
contact_ids = contact_ids.compact.map do |contact_id|
raise Gitlab::Graphql::Errors::ArgumentError, "Contact #{contact_id} is invalid." unless contact_id.respond_to?(:model_id)
@@ -43,6 +44,12 @@ module Mutations
errors: response.errors
}
end
+
+ private
+
+ def feature_enabled?(project)
+ Feature.enabled?(:customer_relations, project.group, default_enabled: :yaml) && project.group&.crm_enabled?
+ end
end
end
end
diff --git a/app/graphql/mutations/issues/set_escalation_status.rb b/app/graphql/mutations/issues/set_escalation_status.rb
new file mode 100644
index 00000000000..6073b73277b
--- /dev/null
+++ b/app/graphql/mutations/issues/set_escalation_status.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Issues
+ class SetEscalationStatus < Base
+ graphql_name 'IssueSetEscalationStatus'
+
+ argument :status, Types::IncidentManagement::EscalationStatusEnum,
+ required: true,
+ description: 'Set the escalation status.'
+
+ def resolve(project_path:, iid:, status:)
+ issue = authorized_find!(project_path: project_path, iid: iid)
+ project = issue.project
+
+ authorize_escalation_status!(project)
+ check_feature_availability!(project, issue)
+
+ ::Issues::UpdateService.new(
+ project: project,
+ current_user: current_user,
+ params: { escalation_status: { status: status } }
+ ).execute(issue)
+
+ {
+ issue: issue,
+ errors: errors_on_object(issue)
+ }
+ end
+
+ private
+
+ def authorize_escalation_status!(project)
+ return if Ability.allowed?(current_user, :update_escalation_status, project)
+
+ raise_resource_not_available_error!
+ end
+
+ def check_feature_availability!(project, issue)
+ return if Feature.enabled?(:incident_escalations, project) && issue.supports_escalation?
+
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature unavailable for provided issue'
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/work_items/create.rb b/app/graphql/mutations/work_items/create.rb
new file mode 100644
index 00000000000..88b8cefd8d2
--- /dev/null
+++ b/app/graphql/mutations/work_items/create.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Mutations
+ module WorkItems
+ class Create < BaseMutation
+ include Mutations::SpamProtection
+ include FindsProject
+
+ graphql_name 'WorkItemCreate'
+
+ authorize :create_work_item
+
+ argument :description, GraphQL::Types::String,
+ required: false,
+ description: copy_field_description(Types::WorkItemType, :description)
+ argument :project_path, GraphQL::Types::ID,
+ required: true,
+ description: 'Full path of the project the work item is associated with.'
+ argument :title, GraphQL::Types::String,
+ required: true,
+ description: copy_field_description(Types::WorkItemType, :title)
+ argument :work_item_type_id, ::Types::GlobalIDType[::WorkItems::Type],
+ required: true,
+ description: 'Global ID of a work item type.'
+
+ field :work_item, Types::WorkItemType,
+ null: true,
+ description: 'Created work item.'
+
+ def resolve(project_path:, **attributes)
+ project = authorized_find!(project_path)
+ params = global_id_compatibility_params(attributes).merge(author_id: current_user.id)
+
+ spam_params = ::Spam::SpamParams.new_from_request(request: context[:request])
+ work_item = ::WorkItems::CreateService.new(project: project, current_user: current_user, params: params, spam_params: spam_params).execute
+
+ check_spam_action_response!(work_item)
+
+ {
+ work_item: work_item.valid? ? work_item : nil,
+ errors: errors_on_object(work_item)
+ }
+ end
+
+ private
+
+ def global_id_compatibility_params(params)
+ # TODO: remove this line when the compatibility layer is removed
+ # See: https://gitlab.com/gitlab-org/gitlab/-/issues/257883
+ params[:work_item_type_id] = ::Types::GlobalIDType[::WorkItems::Type].coerce_isolated_input(params[:work_item_type_id]) if params[:work_item_type_id]
+ params[:work_item_type_id] = params[:work_item_type_id]&.model_id
+
+ params
+ end
+ end
+ end
+end
diff --git a/app/graphql/queries/pipelines/get_pipeline_details.query.graphql b/app/graphql/queries/pipelines/get_pipeline_details.query.graphql
index dd5c9e07488..5dece2f81cc 100644
--- a/app/graphql/queries/pipelines/get_pipeline_details.query.graphql
+++ b/app/graphql/queries/pipelines/get_pipeline_details.query.graphql
@@ -1,3 +1,18 @@
+fragment CiNeeds on JobNeedUnion {
+ ...CiBuildNeedFields
+ ...CiJobNeedFields
+}
+
+fragment CiBuildNeedFields on CiBuildNeed {
+ id
+ name
+}
+
+fragment CiJobNeedFields on CiJob {
+ id
+ name
+}
+
fragment LinkedPipelineData on Pipeline {
__typename
id
@@ -91,6 +106,12 @@ query getPipelineDetails($projectPath: ID!, $iid: ID!) {
name
}
}
+ previousStageJobsOrNeeds {
+ __typename
+ nodes {
+ ...CiNeeds
+ }
+ }
status: detailedStatus {
__typename
id
diff --git a/app/graphql/resolvers/base_issues_resolver.rb b/app/graphql/resolvers/base_issues_resolver.rb
index 3983a3697cb..3e7509b4068 100644
--- a/app/graphql/resolvers/base_issues_resolver.rb
+++ b/app/graphql/resolvers/base_issues_resolver.rb
@@ -48,7 +48,8 @@ module Resolvers
labels: [:labels],
assignees: [:assignees],
timelogs: [:timelogs],
- customer_relations_contacts: { customer_relations_contacts: [:group] }
+ customer_relations_contacts: { customer_relations_contacts: [:group] },
+ escalation_status: [:incident_management_issuable_escalation_status]
}
end
diff --git a/app/graphql/resolvers/ci/config_resolver.rb b/app/graphql/resolvers/ci/config_resolver.rb
index 2d74392a84c..387185b5171 100644
--- a/app/graphql/resolvers/ci/config_resolver.rb
+++ b/app/graphql/resolvers/ci/config_resolver.rb
@@ -47,11 +47,13 @@ module Resolvers
{
status: :valid,
errors: [],
+ warnings: result.warnings,
stages: make_stages(result.jobs)
}
else
{
status: :invalid,
+ warnings: result.warnings,
errors: result.errors
}
end
diff --git a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
index 5ae19700fd5..8208fa56485 100644
--- a/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
+++ b/app/graphql/resolvers/clusters/agent_tokens_resolver.rb
@@ -9,10 +9,17 @@ module Resolvers
delegate :project, to: :agent
+ argument :status, Types::Clusters::AgentTokenStatusEnum,
+ required: false,
+ description: 'Status of the token.'
+
def resolve(**args)
return ::Clusters::AgentToken.none unless can_read_agent_tokens?
- agent.last_used_agent_tokens
+ tokens = agent.last_used_agent_tokens
+ tokens = tokens.with_status(args[:status]) if args[:status].present?
+
+ tokens
end
private
diff --git a/app/graphql/resolvers/concerns/resolves_pipelines.rb b/app/graphql/resolvers/concerns/resolves_pipelines.rb
index 1c01e5e0250..42c4c22a938 100644
--- a/app/graphql/resolvers/concerns/resolves_pipelines.rb
+++ b/app/graphql/resolvers/concerns/resolves_pipelines.rb
@@ -24,7 +24,7 @@ module ResolvesPipelines
argument :source,
GraphQL::Types::String,
required: false,
- description: "Filter pipelines by their source. Will be ignored if `dast_view_scans` feature flag is disabled."
+ description: "Filter pipelines by their source."
end
class_methods do
@@ -38,8 +38,6 @@ module ResolvesPipelines
end
def resolve_pipelines(project, params = {})
- params.delete(:source) unless Feature.enabled?(:dast_view_scans, project, default_enabled: :yaml)
-
Ci::PipelinesFinder.new(project, context[:current_user], params).execute
end
end
diff --git a/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb b/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb
index 254f1efa0a5..97cc7554ba8 100644
--- a/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb
+++ b/app/graphql/resolvers/design_management/version/designs_at_version_resolver.rb
@@ -13,13 +13,13 @@ module Resolvers
DesignID = ::Types::GlobalIDType[::DesignManagement::Design]
- argument :ids, [DesignID],
- required: false,
- description: 'Filters designs by their ID.'
argument :filenames,
[GraphQL::Types::String],
required: false,
description: 'Filters designs by their filename.'
+ argument :ids, [DesignID],
+ required: false,
+ description: 'Filters designs by their ID.'
def self.single
::Resolvers::DesignManagement::Version::DesignAtVersionResolver
diff --git a/app/graphql/resolvers/design_management/version_in_collection_resolver.rb b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb
index 80db15755d3..2682ce6b3b1 100644
--- a/app/graphql/resolvers/design_management/version_in_collection_resolver.rb
+++ b/app/graphql/resolvers/design_management/version_in_collection_resolver.rb
@@ -15,13 +15,13 @@ module Resolvers
VersionID = ::Types::GlobalIDType[::DesignManagement::Version]
- argument :sha, GraphQL::Types::String,
- required: false,
- description: "SHA256 of a specific version."
argument :id, VersionID,
as: :version_id,
required: false,
description: 'Global ID of the version.'
+ argument :sha, GraphQL::Types::String,
+ required: false,
+ description: "SHA256 of a specific version."
def resolve(version_id: nil, sha: nil)
# TODO: remove this line when the compatibility layer is removed
diff --git a/app/graphql/resolvers/group_milestones_resolver.rb b/app/graphql/resolvers/group_milestones_resolver.rb
index 44192b6f4bf..319ff9f68c4 100644
--- a/app/graphql/resolvers/group_milestones_resolver.rb
+++ b/app/graphql/resolvers/group_milestones_resolver.rb
@@ -2,12 +2,12 @@
module Resolvers
class GroupMilestonesResolver < MilestonesResolver
- argument :include_descendants, GraphQL::Types::Boolean,
- required: false,
- description: 'Include milestones from all subgroups and subprojects.'
argument :include_ancestors, GraphQL::Types::Boolean,
required: false,
description: 'Include milestones from all parent groups.'
+ argument :include_descendants, GraphQL::Types::Boolean,
+ required: false,
+ description: 'Include milestones from all subgroups and subprojects.'
type Types::MilestoneType.connection_type, null: true
diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb
index bd7f1f0774e..6dbcbe0e04d 100644
--- a/app/graphql/resolvers/merge_requests_resolver.rb
+++ b/app/graphql/resolvers/merge_requests_resolver.rb
@@ -51,6 +51,10 @@ module Resolvers
required: false,
description: 'Merge request state. If provided, all resolved merge requests will have this state.'
+ argument :draft, GraphQL::Types::Boolean,
+ required: false,
+ description: 'Limit result to draft merge requests.'
+
argument :labels, [GraphQL::Types::String],
required: false,
as: :label_name,
diff --git a/app/graphql/resolvers/users/groups_resolver.rb b/app/graphql/resolvers/users/groups_resolver.rb
index eafb56d8f4c..d8492a8fcf9 100644
--- a/app/graphql/resolvers/users/groups_resolver.rb
+++ b/app/graphql/resolvers/users/groups_resolver.rb
@@ -23,10 +23,6 @@ module Resolvers
Preloaders::GroupPolicyPreloader.new(nodes, current_user).execute
end
- def ready?(**args)
- Feature.enabled?(:paginatable_namespace_drop_down_for_project_creation, current_user, default_enabled: :yaml)
- end
-
private
def resolve_groups(**args)
diff --git a/app/graphql/resolvers/work_items/types_resolver.rb b/app/graphql/resolvers/work_items/types_resolver.rb
new file mode 100644
index 00000000000..b7a32e13423
--- /dev/null
+++ b/app/graphql/resolvers/work_items/types_resolver.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module WorkItems
+ class TypesResolver < BaseResolver
+ type Types::WorkItems::TypeType.connection_type, null: true
+
+ def resolve
+ # This will require a finder in the future when groups/projects get their work item types
+ # All groups/projects use the default types for now
+ ::WorkItems::Type.default.order_by_name_asc
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/ci/config/config_type.rb b/app/graphql/types/ci/config/config_type.rb
index 6ac21968bd4..de355c8eacf 100644
--- a/app/graphql/types/ci/config/config_type.rb
+++ b/app/graphql/types/ci/config/config_type.rb
@@ -15,6 +15,8 @@ module Types
description: 'Stages of the pipeline.'
field :status, Types::Ci::Config::StatusEnum, null: true,
description: 'Status of linting, can be either valid or invalid.'
+ field :warnings, [GraphQL::Types::String], null: true,
+ description: 'Linting warnings.'
end
end
end
diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb
index 928ca2f597d..1320b96907e 100644
--- a/app/graphql/types/ci/job_type.rb
+++ b/app/graphql/types/ci/job_type.rb
@@ -50,6 +50,8 @@ module Types
null: true,
description: 'How long the job was enqueued before starting.'
+ field :downstream_pipeline, Types::Ci::PipelineType, null: true,
+ description: 'Downstream pipeline for a bridge.'
field :previous_stage_jobs_or_needs, Types::Ci::JobNeedUnion.connection_type, null: true,
description: 'Jobs that must complete before the job runs. Returns `BuildNeed`, which is the needed jobs if the job uses the `needs` keyword, or the previous stage jobs otherwise.'
field :detailed_status, Types::Ci::DetailedStatusType, null: true,
@@ -89,6 +91,10 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, object.pipeline_id).find
end
+ def downstream_pipeline
+ object.downstream_pipeline if object.respond_to?(:downstream_pipeline)
+ end
+
def tags
object.tags.map(&:name) if object.is_a?(::Ci::Build)
end
diff --git a/app/graphql/types/ci/pipeline_message_type.rb b/app/graphql/types/ci/pipeline_message_type.rb
new file mode 100644
index 00000000000..7edea1901a1
--- /dev/null
+++ b/app/graphql/types/ci/pipeline_message_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ module Ci
+ # rubocop: disable Graphql/AuthorizeTypes
+ class PipelineMessageType < BaseObject
+ graphql_name 'PipelineMessage'
+
+ field :id, GraphQL::Types::ID, null: false,
+ description: 'ID of the pipeline message.'
+
+ field :content, GraphQL::Types::String, null: false,
+ description: 'Content of the pipeline message.'
+ end
+ end
+end
diff --git a/app/graphql/types/ci/pipeline_type.rb b/app/graphql/types/ci/pipeline_type.rb
index c8ac31bce4d..537b8e42ad1 100644
--- a/app/graphql/types/ci/pipeline_type.rb
+++ b/app/graphql/types/ci/pipeline_type.rb
@@ -18,8 +18,14 @@ module Types
field :iid, GraphQL::Types::String, null: false,
description: 'Internal ID of the pipeline.'
- field :sha, GraphQL::Types::String, null: false,
- description: "SHA of the pipeline's commit."
+ field :sha, GraphQL::Types::String, null: true,
+ method: :sha,
+ description: "SHA of the pipeline's commit." do
+ argument :format,
+ type: Types::ShaFormatEnum,
+ required: false,
+ description: 'Format of the SHA.'
+ end
field :before_sha, GraphQL::Types::String, null: true,
description: 'Base SHA of the source branch.'
@@ -162,6 +168,13 @@ module Types
field :ref, GraphQL::Types::String, null: true,
description: 'Reference to the branch from which the pipeline was triggered.'
+ field :ref_path, GraphQL::Types::String, null: true,
+ description: 'Reference path to the branch from which the pipeline was triggered.',
+ method: :source_ref_path
+
+ field :warning_messages, [Types::Ci::PipelineMessageType], null: true,
+ description: 'Pipeline warning messages.'
+
def detailed_status
object.detailed_status(current_user)
end
@@ -189,6 +202,12 @@ module Types
end.take # rubocop: disable CodeReuse/ActiveRecord
end
+ def sha(format: Types::ShaFormatEnum.enum[:long])
+ return pipeline.short_sha if format == Types::ShaFormatEnum.enum[:short]
+
+ pipeline.sha
+ end
+
alias_method :pipeline, :object
end
end
diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb
index d37cca0927f..4fe65734911 100644
--- a/app/graphql/types/ci/runner_type.rb
+++ b/app/graphql/types/ci/runner_type.rb
@@ -4,6 +4,7 @@ module Types
module Ci
class RunnerType < BaseObject
edge_type_class(RunnerWebUrlEdge)
+ connection_type_class(Types::CountableConnectionType)
graphql_name 'CiRunner'
authorize :read_runner
present_using ::Ci::RunnerPresenter
@@ -18,8 +19,10 @@ module Types
description: 'ID of the runner.'
field :description, GraphQL::Types::String, null: true,
description: 'Description of the runner.'
+ field :created_at, Types::TimeType, null: true,
+ description: 'Timestamp of creation of this runner.'
field :contacted_at, Types::TimeType, null: true,
- description: 'Last contact from the runner.',
+ description: 'Timestamp of last contact from this runner.',
method: :contacted_at
field :maximum_timeout, GraphQL::Types::Int, null: true,
description: 'Maximum timeout (in seconds) for jobs processed by the runner.'
@@ -54,6 +57,12 @@ module Types
description: "Number of jobs processed by the runner (limited to #{JOB_COUNT_LIMIT}, plus one to indicate that more items exist)."
field :admin_url, GraphQL::Types::String, null: true,
description: 'Admin URL of the runner. Only available for administrators.'
+ field :edit_admin_url, GraphQL::Types::String, null: true,
+ description: 'Admin form URL of the runner. Only available for administrators.'
+ field :executor_name, GraphQL::Types::String, null: true,
+ description: 'Executor last advertised by the runner.',
+ method: :executor_name,
+ feature_flag: :graphql_ci_runner_executor
def job_count
# We limit to 1 above the JOB_COUNT_LIMIT to indicate that more items exist after JOB_COUNT_LIMIT
@@ -64,6 +73,10 @@ module Types
Gitlab::Routing.url_helpers.admin_runner_url(runner) if can_admin_runners?
end
+ def edit_admin_url
+ Gitlab::Routing.url_helpers.edit_admin_runner_url(runner) if can_admin_runners?
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def project_count
BatchLoader::GraphQL.for(runner.id).batch(key: :runner_project_count) do |ids, loader, args|
diff --git a/app/graphql/types/clusters/agent_token_status_enum.rb b/app/graphql/types/clusters/agent_token_status_enum.rb
new file mode 100644
index 00000000000..c00a64d21c1
--- /dev/null
+++ b/app/graphql/types/clusters/agent_token_status_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ module Clusters
+ class AgentTokenStatusEnum < BaseEnum
+ graphql_name 'AgentTokenStatus'
+ description 'Agent token statuses'
+
+ ::Clusters::AgentToken.statuses.keys.each do |status|
+ value status.upcase, value: status, description: "#{status.titleize} agent token."
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/clusters/agent_token_type.rb b/app/graphql/types/clusters/agent_token_type.rb
index 94c5fc46a5d..96fdb5f05c8 100644
--- a/app/graphql/types/clusters/agent_token_type.rb
+++ b/app/graphql/types/clusters/agent_token_type.rb
@@ -44,6 +44,11 @@ module Types
null: true,
description: 'Name given to the token.'
+ field :status,
+ Types::Clusters::AgentTokenStatusEnum,
+ null: true,
+ description: 'Current status of the token.'
+
def cluster_agent
Gitlab::Graphql::Loaders::BatchModelLoader.new(::Clusters::Agent, object.agent_id).find
end
diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb
index 7d141bd6daa..2584e15ff0b 100644
--- a/app/graphql/types/commit_type.rb
+++ b/app/graphql/types/commit_type.rb
@@ -39,6 +39,8 @@ module Types
description: 'Rendered HTML of the commit signature.'
field :author_name, type: GraphQL::Types::String, null: true,
description: 'Commit authors name.'
+ field :author_email, type: GraphQL::Types::String, null: true,
+ description: "Commit author's email."
field :author_gravatar, type: GraphQL::Types::String, null: true,
description: 'Commit authors gravatar.'
diff --git a/app/graphql/types/deprecated_mutations.rb b/app/graphql/types/deprecated_mutations.rb
index 49bad56b6f9..70d5fc31cd1 100644
--- a/app/graphql/types/deprecated_mutations.rb
+++ b/app/graphql/types/deprecated_mutations.rb
@@ -5,7 +5,8 @@ module Types
extend ActiveSupport::Concern
prepended do
- # placeholder for any FOSS mutations to be deprecated
+ mount_mutation Mutations::Clusters::AgentTokens::Delete,
+ deprecated: { reason: 'Tokens must be revoked with ClusterAgentTokenRevoke', milestone: '14.7' }
end
end
end
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 4a20d84f2ab..e02650fd285 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -210,6 +210,11 @@ module Types
null: true,
description: "Find contacts of this group."
+ field :work_item_types, Types::WorkItems::TypeType.connection_type,
+ resolver: Resolvers::WorkItems::TypesResolver,
+ description: 'Work item types available to the group.',
+ feature_flag: :work_items
+
def avatar_url
object.avatar_url(only_path: false)
end
diff --git a/app/graphql/types/incident_management/escalation_status_enum.rb b/app/graphql/types/incident_management/escalation_status_enum.rb
new file mode 100644
index 00000000000..bc462f03148
--- /dev/null
+++ b/app/graphql/types/incident_management/escalation_status_enum.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ module IncidentManagement
+ class EscalationStatusEnum < BaseEnum
+ graphql_name 'IssueEscalationStatus'
+ description 'Issue escalation status values'
+
+ ::IncidentManagement::IssuableEscalationStatus.status_names.each do |status|
+ value status.to_s.upcase, value: status, description: "#{::IncidentManagement::IssuableEscalationStatus::STATUS_DESCRIPTIONS[status]}."
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb
index 498569f11ca..46fe91feae4 100644
--- a/app/graphql/types/issue_type.rb
+++ b/app/graphql/types/issue_type.rb
@@ -140,6 +140,9 @@ module Types
field :customer_relations_contacts, Types::CustomerRelations::ContactType.connection_type, null: true,
description: 'Customer relations contacts of the issue.'
+ field :escalation_status, Types::IncidentManagement::EscalationStatusEnum, null: true,
+ description: 'Escalation status of the issue.'
+
def author
Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
end
@@ -167,6 +170,12 @@ module Types
def hidden?
object.hidden? if Feature.enabled?(:ban_user_feature_flag)
end
+
+ def escalation_status
+ return unless Feature.enabled?(:incident_escalations, object.project) && object.supports_escalation?
+
+ object.escalation_status&.status_name
+ end
end
end
diff --git a/app/graphql/types/issue_type_enum.rb b/app/graphql/types/issue_type_enum.rb
index 0cfba6bbbd0..b18c8b90e96 100644
--- a/app/graphql/types/issue_type_enum.rb
+++ b/app/graphql/types/issue_type_enum.rb
@@ -5,7 +5,7 @@ module Types
graphql_name 'IssueType'
description 'Issue type'
- ::WorkItem::Type.allowed_types_for_issues.each do |issue_type|
+ ::WorkItems::Type.allowed_types_for_issues.each do |issue_type|
value issue_type.upcase, value: issue_type, description: "#{issue_type.titleize} issue type"
end
end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 0672ec6f0f8..ea05671c79c 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -189,6 +189,8 @@ module Types
description: 'Indicates if the merge request has CI.'
field :mergeable, GraphQL::Types::Boolean, null: false, method: :mergeable?, calls_gitaly: true,
description: 'Indicates if the merge request is mergeable.'
+ field :commits, Types::CommitType.connection_type, null: true,
+ calls_gitaly: true, description: 'Merge request commits.'
field :commits_without_merge_commits, Types::CommitType.connection_type, null: true,
calls_gitaly: true, description: 'Merge request commits excluding merge commits.'
field :security_auto_fix, GraphQL::Types::Boolean, null: true,
@@ -196,7 +198,7 @@ module Types
field :auto_merge_strategy, GraphQL::Types::String, null: true,
description: 'Selected auto merge strategy.'
field :merge_user, Types::UserType, null: true,
- description: 'User who merged this merge request.'
+ description: 'User who merged this merge request or set it to merge when pipeline succeeds.'
field :timelogs, Types::TimelogType.connection_type, null: false,
description: 'Timelogs on the merge request.'
@@ -249,16 +251,28 @@ module Types
!!object.discussion_locked
end
+ def default_merge_commit_message
+ object.default_merge_commit_message(include_description: false, user: current_user)
+ end
+
def default_merge_commit_message_with_description
object.default_merge_commit_message(include_description: true)
end
+ def default_squash_commit_message
+ object.default_squash_commit_message(user: current_user)
+ end
+
def available_auto_merge_strategies
AutoMergeService.new(object.project, current_user).available_strategies(object)
end
+ def commits
+ object.commits.commits
+ end
+
def commits_without_merge_commits
- object.recent_commits.without_merge_commits
+ object.commits.without_merge_commits
end
def security_auto_fix
@@ -268,6 +282,10 @@ module Types
def reviewers
object.reviewers
end
+
+ def merge_user
+ object.metrics&.merged_by || object.merge_user
+ end
end
end
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index e8a952e9c61..c350f4dd922 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -35,7 +35,7 @@ module Types
mount_mutation Mutations::Clusters::Agents::Create
mount_mutation Mutations::Clusters::Agents::Delete
mount_mutation Mutations::Clusters::AgentTokens::Create
- mount_mutation Mutations::Clusters::AgentTokens::Delete
+ mount_mutation Mutations::Clusters::AgentTokens::Revoke
mount_mutation Mutations::Commits::Create, calls_gitaly: true
mount_mutation Mutations::CustomEmoji::Create, feature_flag: :custom_emoji
mount_mutation Mutations::CustomEmoji::Destroy, feature_flag: :custom_emoji
@@ -55,6 +55,7 @@ module Types
mount_mutation Mutations::Issues::SetDueDate
mount_mutation Mutations::Issues::SetSeverity
mount_mutation Mutations::Issues::SetSubscription
+ mount_mutation Mutations::Issues::SetEscalationStatus
mount_mutation Mutations::Issues::Update
mount_mutation Mutations::Issues::Move
mount_mutation Mutations::Labels::Create
@@ -123,6 +124,7 @@ module Types
mount_mutation Mutations::Packages::Destroy
mount_mutation Mutations::Packages::DestroyFile
mount_mutation Mutations::Echo
+ mount_mutation Mutations::WorkItems::Create, feature_flag: :work_items
end
end
diff --git a/app/graphql/types/packages/package_details_type.rb b/app/graphql/types/packages/package_details_type.rb
index 5ac80860fe2..1d2cf9649d8 100644
--- a/app/graphql/types/packages/package_details_type.rb
+++ b/app/graphql/types/packages/package_details_type.rb
@@ -3,6 +3,8 @@
module Types
module Packages
class PackageDetailsType < PackageType
+ include ::PackagesHelper
+
graphql_name 'PackageDetailsType'
description 'Represents a package details in the Package Registry. Note that this type is in beta and susceptible to changes'
authorize :read_package
@@ -21,9 +23,58 @@ module Types
description: 'Pipelines that built the package.',
deprecated: { reason: 'Due to scalability concerns, this field is going to be removed', milestone: '14.6' }
+ field :composer_config_repository_url, GraphQL::Types::String, null: true, description: 'Url of the Composer setup endpoint.'
+ field :composer_url, GraphQL::Types::String, null: true, description: 'Url of the Composer endpoint.'
+ field :conan_url, GraphQL::Types::String, null: true, description: 'Url of the Conan project endpoint.'
+ field :maven_url, GraphQL::Types::String, null: true, description: 'Url of the Maven project endpoint.'
+ field :npm_url, GraphQL::Types::String, null: true, description: 'Url of the NPM project endpoint.'
+ field :nuget_url, GraphQL::Types::String, null: true, description: 'Url of the Nuget project endpoint.'
+ field :pypi_setup_url, GraphQL::Types::String, null: true, description: 'Url of the PyPi project setup endpoint.'
+ field :pypi_url, GraphQL::Types::String, null: true, description: 'Url of the PyPi project endpoint.'
+
def versions
object.versions
end
+
+ def package_files
+ if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ object.installable_package_files
+ else
+ object.package_files
+ end
+ end
+
+ def composer_config_repository_url
+ composer_config_repository_name(object.project.group&.id)
+ end
+
+ def composer_url
+ composer_registry_url(object.project.group&.id)
+ end
+
+ def conan_url
+ package_registry_project_url(object.project.id, :conan)
+ end
+
+ def maven_url
+ package_registry_project_url(object.project.id, :maven)
+ end
+
+ def npm_url
+ package_registry_project_url(object.project.id, :npm)
+ end
+
+ def nuget_url
+ nuget_package_registry_url(object.project.id)
+ end
+
+ def pypi_setup_url
+ package_registry_project_url(object.project.id, :pypi)
+ end
+
+ def pypi_url
+ pypi_registry_url(object.project.id)
+ end
end
end
end
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 3d2ee47a499..f4067552f55 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -27,7 +27,6 @@ module Types
field :description, GraphQL::Types::String, null: true,
description: 'Short description of the project.'
- markdown_field :description_html, null: true
field :tag_list, GraphQL::Types::String, null: true,
deprecated: { reason: 'Use `topics`', milestone: '13.12' },
@@ -75,21 +74,6 @@ module Types
field :avatar_url, GraphQL::Types::String, null: true, calls_gitaly: true,
description: 'URL to avatar image file of the project.'
- {
- issues: "Issues are",
- merge_requests: "Merge Requests are",
- wiki: 'Wikis are',
- snippets: 'Snippets are',
- container_registry: 'Container Registry is'
- }.each do |feature, name_string|
- field "#{feature}_enabled", GraphQL::Types::Boolean, null: true,
- description: "Indicates if #{name_string} enabled for the current user"
-
- define_method "#{feature}_enabled" do
- object.feature_available?(feature, context[:current_user])
- end
- end
-
field :jobs_enabled, GraphQL::Types::Boolean, null: true,
description: 'Indicates if CI/CD pipeline jobs are enabled for the current user.'
@@ -391,6 +375,17 @@ module Types
null: true,
description: 'Template used to create squash commit message in merge requests.'
+ field :labels,
+ Types::LabelType.connection_type,
+ null: true,
+ description: 'Labels available on this project.',
+ resolver: Resolvers::LabelsResolver
+
+ field :work_item_types, Types::WorkItems::TypeType.connection_type,
+ resolver: Resolvers::WorkItems::TypesResolver,
+ description: 'Work item types available to the project.',
+ feature_flag: :work_items
+
def label(title:)
BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args|
LabelsFinder
@@ -400,11 +395,22 @@ module Types
end
end
- field :labels,
- Types::LabelType.connection_type,
- null: true,
- description: 'Labels available on this project.',
- resolver: Resolvers::LabelsResolver
+ {
+ issues: "Issues are",
+ merge_requests: "Merge Requests are",
+ wiki: 'Wikis are',
+ snippets: 'Snippets are',
+ container_registry: 'Container Registry is'
+ }.each do |feature, name_string|
+ field "#{feature}_enabled", GraphQL::Types::Boolean, null: true,
+ description: "Indicates if #{name_string} enabled for the current user"
+
+ define_method "#{feature}_enabled" do
+ object.feature_available?(feature, context[:current_user])
+ end
+ end
+
+ markdown_field :description_html, null: true
def avatar_url
object.avatar_url(only_path: false)
diff --git a/app/graphql/types/projects/topic_type.rb b/app/graphql/types/projects/topic_type.rb
index 79ab69e794b..c579f2f2b9d 100644
--- a/app/graphql/types/projects/topic_type.rb
+++ b/app/graphql/types/projects/topic_type.rb
@@ -14,11 +14,12 @@ module Types
field :description, GraphQL::Types::String, null: true,
description: 'Description of the topic.'
- markdown_field :description_html, null: true
field :avatar_url, GraphQL::Types::String, null: true,
description: 'URL to avatar image file of the topic.'
+ markdown_field :description_html, null: true
+
def avatar_url
object.avatar_url(only_path: false)
end
diff --git a/app/graphql/types/release_type.rb b/app/graphql/types/release_type.rb
index fcc9ec49252..fbc3779ea9b 100644
--- a/app/graphql/types/release_type.rb
+++ b/app/graphql/types/release_type.rb
@@ -20,7 +20,6 @@ module Types
authorize: :download_code
field :description, GraphQL::Types::String, null: true,
description: 'Description (also known as "release notes") of the release.'
- markdown_field :description_html, null: true
field :name, GraphQL::Types::String, null: true,
description: 'Name of the release.'
field :created_at, Types::TimeType, null: true,
@@ -42,14 +41,16 @@ module Types
field :author, Types::UserType, null: true,
description: 'User that created the release.'
- def author
- Gitlab::Graphql::Loaders::BatchModelLoader.new(User, release.author_id).find
- end
-
field :commit, Types::CommitType, null: true,
complexity: 10, calls_gitaly: true,
description: 'Commit associated with the release.'
+ markdown_field :description_html, null: true
+
+ def author
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(User, release.author_id).find
+ end
+
def commit
return if release.sha.nil?
diff --git a/app/graphql/types/repository/blob_type.rb b/app/graphql/types/repository/blob_type.rb
index 3265c14bdca..28339093172 100644
--- a/app/graphql/types/repository/blob_type.rb
+++ b/app/graphql/types/repository/blob_type.rb
@@ -56,6 +56,9 @@ module Types
field :stored_externally, GraphQL::Types::Boolean, null: true, method: :stored_externally?,
description: "Whether the blob's content is stored externally (for instance, in LFS)."
+ field :external_storage, GraphQL::Types::String, null: true, method: :external_storage,
+ description: "External storage being used, if enabled (for instance, 'LFS')."
+
field :edit_blob_path, GraphQL::Types::String, null: true,
description: 'Web path to edit the blob in the old-style editor.'
@@ -71,6 +74,19 @@ module Types
field :pipeline_editor_path, GraphQL::Types::String, null: true,
description: 'Web path to edit .gitlab-ci.yml file.'
+ field :find_file_path, GraphQL::Types::String, null: true,
+ description: 'Web path to find file.'
+
+ field :blame_path, GraphQL::Types::String, null: true,
+ description: 'Web path to blob blame page.'
+
+ field :history_path, GraphQL::Types::String, null: true,
+ description: 'Web path to blob history page.'
+
+ field :permalink_path, GraphQL::Types::String, null: true,
+ description: 'Web path to blob permalink.',
+ calls_gitaly: true
+
field :code_owners, [Types::UserType], null: true,
description: 'List of code owners for the blob.',
calls_gitaly: true
@@ -98,6 +114,9 @@ module Types
field :can_current_user_push_to_branch, GraphQL::Types::Boolean, null: true, method: :can_current_user_push_to_branch?,
description: 'Whether the current user can push to the branch.'
+ field :archived, GraphQL::Types::Boolean, null: true, method: :archived?,
+ description: 'Whether the current project is archived.'
+
def raw_text_blob
object.data unless object.binary?
end
diff --git a/app/graphql/types/sha_format_enum.rb b/app/graphql/types/sha_format_enum.rb
new file mode 100644
index 00000000000..2e0e5bb85f3
--- /dev/null
+++ b/app/graphql/types/sha_format_enum.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Types
+ class ShaFormatEnum < BaseEnum
+ graphql_name 'ShaFormat'
+ description 'How to format SHA strings.'
+
+ FORMATS_DESCRIPTION = {
+ short: 'Abbreviated format. Short SHAs are typically eight characters long.',
+ long: 'Unabbreviated format.'
+ }.freeze
+
+ FORMATS_DESCRIPTION.each do |format, description|
+ value format.to_s.upcase,
+ description: description,
+ value: format.to_s
+ end
+ end
+end
diff --git a/app/graphql/types/user_interface.rb b/app/graphql/types/user_interface.rb
index 7cc201b6df4..6bb4cb29cdd 100644
--- a/app/graphql/types/user_interface.rb
+++ b/app/graphql/types/user_interface.rb
@@ -64,8 +64,7 @@ module Types
description: 'Group memberships of the user.'
field :groups,
resolver: Resolvers::Users::GroupsResolver,
- description: 'Groups where the user has access. Will always return `null` if ' \
- '`paginatable_namespace_drop_down_for_project_creation` feature flag is disabled.'
+ description: 'Groups where the user has access.'
field :group_count,
resolver: Resolvers::Users::GroupCountResolver,
description: 'Group count for the user.'
diff --git a/app/graphql/types/work_item_type.rb b/app/graphql/types/work_item_type.rb
new file mode 100644
index 00000000000..486c1e52987
--- /dev/null
+++ b/app/graphql/types/work_item_type.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Types
+ class WorkItemType < BaseObject
+ graphql_name 'WorkItem'
+
+ authorize :read_issue
+
+ field :description, GraphQL::Types::String, null: true,
+ description: 'Description of the work item.'
+ field :id, Types::GlobalIDType[::WorkItem], null: false,
+ description: 'Global ID of the work item.'
+ field :iid, GraphQL::Types::ID, null: false,
+ description: 'Internal ID of the work item.'
+ field :title, GraphQL::Types::String, null: false,
+ description: 'Title of the work item.'
+ field :work_item_type, Types::WorkItems::TypeType, null: false,
+ description: 'Type assigned to the work item.'
+
+ markdown_field :title_html, null: true
+ markdown_field :description_html, null: true
+ end
+end
diff --git a/app/graphql/types/work_items/type_type.rb b/app/graphql/types/work_items/type_type.rb
new file mode 100644
index 00000000000..f31bd7ee9ba
--- /dev/null
+++ b/app/graphql/types/work_items/type_type.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module Types
+ module WorkItems
+ class TypeType < BaseObject
+ graphql_name 'WorkItemType'
+
+ authorize :read_work_item_type
+
+ field :icon_name, GraphQL::Types::String, null: true,
+ description: 'Icon name of the work item type.'
+ field :id, Types::GlobalIDType[::WorkItems::Type], null: false,
+ description: 'Global ID of the work item type.'
+ field :name, GraphQL::Types::String, null: false,
+ description: 'Name of the work item type.'
+ end
+ end
+end
diff --git a/app/helpers/admin/background_migrations_helper.rb b/app/helpers/admin/background_migrations_helper.rb
index 698d81cc8a2..6516ea27b2c 100644
--- a/app/helpers/admin/background_migrations_helper.rb
+++ b/app/helpers/admin/background_migrations_helper.rb
@@ -2,15 +2,15 @@
module Admin
module BackgroundMigrationsHelper
- def batched_migration_status_badge_class_name(migration)
- class_names = {
- 'active' => 'badge-info',
- 'paused' => 'badge-warning',
- 'failed' => 'badge-danger',
- 'finished' => 'badge-success'
+ def batched_migration_status_badge_variant(migration)
+ variants = {
+ 'active' => :info,
+ 'paused' => :warning,
+ 'failed' => :danger,
+ 'finished' => :success
}
- class_names[migration.status]
+ variants[migration.status]
end
# The extra logic here is needed because total_tuple_count is just
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 02a87979f40..e88d1832480 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -285,6 +285,7 @@ module ApplicationHelper
class_names << 'environment-logs-page' if current_controller?(:logs)
class_names << 'with-performance-bar' if performance_bar_enabled?
class_names << system_message_class
+ class_names << marketing_header_experiment_class
class_names
end
@@ -420,6 +421,18 @@ module ApplicationHelper
def appearance
::Appearance.current
end
+
+ def marketing_header_experiment_class
+ return if current_user
+
+ experiment(:logged_out_marketing_header, actor: nil) do |e|
+ html_class = 'logged-out-marketing-header-candidate'
+ e.candidate { html_class }
+ e.try(:trial_focused) { html_class }
+ e.control {}
+ e.run
+ end
+ end
end
ApplicationHelper.prepend_mod
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index b8ee71daeee..7541247b19f 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -144,36 +144,39 @@ module ApplicationSettingsHelper
end
def external_authorization_description
- _("If enabled, access to projects will be validated on an external service"\
+ s_("ExternalAuthorization|Access to projects is validated on an external service"\
" using their classification label.")
end
def external_authorization_timeout_help_text
- _("Time in seconds GitLab will wait for a response from the external "\
- "service. When the service does not respond in time, access will be "\
- "denied.")
+ s_("ExternalAuthorization|Period GitLab waits for a response from the external "\
+ "service. If there is no response, access is denied. Default: 0.5 seconds.")
end
def external_authorization_url_help_text
- _("When leaving the URL blank, classification labels can still be "\
- "specified without disabling cross project features or performing "\
- "external authorization checks.")
+ s_("ExternalAuthorization|URL to which the projects make authorization requests. If the URL is blank, cross-project "\
+ "features are available and can still specify classification "\
+ "labels for projects.")
end
def external_authorization_client_certificate_help_text
- _("The X509 Certificate to use when mutual TLS is required to communicate "\
- "with the external authorization service. If left blank, the server "\
- "certificate is still validated when accessing over HTTPS.")
+ s_("ExternalAuthorization|Certificate used to authenticate with the external authorization service. "\
+ "If blank, the server certificate is validated when accessing over HTTPS.")
end
def external_authorization_client_key_help_text
- _("The private key to use when a client certificate is provided. This value "\
- "is encrypted at rest.")
+ s_("ExternalAuthorization|Private key of client authentication certificate. "\
+ "Encrypted when stored.")
end
def external_authorization_client_pass_help_text
- _("The passphrase required to decrypt the private key. This is optional "\
- "and the value is encrypted at rest.")
+ s_("ExternalAuthorization|Passphrase required to decrypt the private key. "\
+ "Encrypted when stored.")
+ end
+
+ def external_authorization_client_url_help_text
+ s_("ExternalAuthorization|Classification label to use when requesting authorization if no specific "\
+ " label is defined on the project.")
end
def sidekiq_job_limiter_mode_help_text
@@ -401,6 +404,12 @@ module ApplicationSettingsHelper
:rate_limiting_response_text,
:container_registry_expiration_policies_worker_capacity,
:container_registry_cleanup_tags_service_max_list_size,
+ :container_registry_import_max_tags_count,
+ :container_registry_import_max_retries,
+ :container_registry_import_start_max_retries,
+ :container_registry_import_max_step_duration,
+ :container_registry_import_target_plan,
+ :container_registry_import_created_before,
:keep_latest_artifact,
:whats_new_variant,
:user_deactivation_emails_enabled,
@@ -411,7 +420,8 @@ module ApplicationSettingsHelper
:sidekiq_job_limiter_mode,
:sidekiq_job_limiter_compression_threshold_bytes,
:sidekiq_job_limiter_limit_bytes,
- :suggest_pipeline_enabled
+ :suggest_pipeline_enabled,
+ :user_email_lookup_limit
].tap do |settings|
settings << :deactivate_dormant_users unless Gitlab.com?
end
@@ -486,6 +496,10 @@ module ApplicationSettingsHelper
def pending_user_count
User.blocked_pending_approval.count
end
+
+ def registration_features_can_be_prompted?
+ !Gitlab::CurrentSettings.usage_ping_enabled?
+ end
end
ApplicationSettingsHelper.prepend_mod_with('ApplicationSettingsHelper')
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index c1a74382d46..fb2fa547447 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -3,6 +3,7 @@
module AuthHelper
PROVIDERS_WITH_ICONS = %w(
atlassian_oauth2
+ auth0
authentiq
azure_activedirectory_v2
azure_oauth2
@@ -12,8 +13,10 @@ module AuthHelper
github
gitlab
google_oauth2
+ jwt
openid_connect
salesforce
+ shibboleth
twitter
).freeze
LDAP_PROVIDER = /\Aldap/.freeze
@@ -177,15 +180,13 @@ module AuthHelper
def google_tag_manager_enabled?
return false unless Gitlab.dev_env_or_com?
- has_config_key = if Feature.enabled?(:gtm_nonce, type: :ops)
- extra_config.has_key?('google_tag_manager_nonce_id') &&
- extra_config.google_tag_manager_nonce_id.present?
- else
- extra_config.has_key?('google_tag_manager_id') &&
- extra_config.google_tag_manager_id.present?
- end
-
- has_config_key && !current_user
+ if Feature.enabled?(:gtm_nonce, type: :ops)
+ extra_config.has_key?('google_tag_manager_nonce_id') &&
+ extra_config.google_tag_manager_nonce_id.present?
+ else
+ extra_config.has_key?('google_tag_manager_id') &&
+ extra_config.google_tag_manager_id.present?
+ end
end
def google_tag_manager_id
diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb
index eb30ffc0093..4ec95dc8bd7 100644
--- a/app/helpers/button_helper.rb
+++ b/app/helpers/button_helper.rb
@@ -50,7 +50,7 @@ module ButtonHelper
data: data,
type: :button,
title: title,
- aria: { label: title },
+ aria: { label: title, live: 'polite' },
itemprop: item_prop
}
diff --git a/app/helpers/ci/jobs_helper.rb b/app/helpers/ci/jobs_helper.rb
index c7f40decae8..c0dca66bac8 100644
--- a/app/helpers/ci/jobs_helper.rb
+++ b/app/helpers/ci/jobs_helper.rb
@@ -19,10 +19,12 @@ module Ci
}
end
- def bridge_data(build)
+ def bridge_data(build, project)
{
- "build_name" => build.name,
- "empty-state-illustration-path" => image_path('illustrations/job-trigger-md.svg')
+ "build_id" => build.id,
+ "empty-state-illustration-path" => image_path('illustrations/job-trigger-md.svg'),
+ "pipeline_iid" => build.pipeline.iid,
+ "project_full_path" => project.full_path
}
end
diff --git a/app/helpers/ci/pipeline_editor_helper.rb b/app/helpers/ci/pipeline_editor_helper.rb
index 9bbc326a750..bb7226da74e 100644
--- a/app/helpers/ci/pipeline_editor_helper.rb
+++ b/app/helpers/ci/pipeline_editor_helper.rb
@@ -20,6 +20,7 @@ module Ci
"empty-state-illustration-path" => image_path('illustrations/empty-state/empty-dag-md.svg'),
"initial-branch-name" => initial_branch,
"lint-help-page-path" => help_page_path('ci/lint', anchor: 'validate-basic-logic-and-syntax'),
+ "lint-unavailable-help-page-path" => help_page_path('ci/pipeline_editor/index', anchor: 'configuration-validation-currently-not-available'),
"needs-help-page-path" => help_page_path('ci/yaml/index', anchor: 'needs'),
"new-merge-request-path" => namespace_project_new_merge_request_path,
"pipeline_etag" => latest_commit ? graphql_etag_pipeline_sha_path(commit_sha) : '',
diff --git a/app/helpers/ci/runners_helper.rb b/app/helpers/ci/runners_helper.rb
index 8f219656b71..f84b42209da 100644
--- a/app/helpers/ci/runners_helper.rb
+++ b/app/helpers/ci/runners_helper.rb
@@ -24,7 +24,7 @@ module Ci
span_class = 'gl-text-gray-600'
end
when :not_connected, :never_contacted
- title = s_("Runners|New runner, has not connected yet")
+ title = s_("Runners|New runner, has not contacted yet")
icon = 'warning-solid'
when :offline
title = s_("Runners|Runner is offline, last contact was %{runner_contact} ago") % { runner_contact: time_ago_in_words(runner.contacted_at) }
@@ -65,14 +65,7 @@ module Ci
# Runner install help page is external, located at
# https://gitlab.com/gitlab-org/gitlab-runner
runner_install_help_page: 'https://docs.gitlab.com/runner/install/',
- registration_token: Gitlab::CurrentSettings.runners_registration_token,
-
- # All runner counts are returned as formatted strings
- active_runners_count: Ci::Runner.online.count.to_s,
- all_runners_count: limited_counter_with_delimiter(Ci::Runner),
- instance_runners_count: limited_counter_with_delimiter(Ci::Runner.instance_type),
- group_runners_count: limited_counter_with_delimiter(Ci::Runner.group_type),
- project_runners_count: limited_counter_with_delimiter(Ci::Runner.project_type)
+ registration_token: Gitlab::CurrentSettings.runners_registration_token
}
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index ee5f4bb364a..43e727ac483 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -27,10 +27,12 @@ module CommitsHelper
end
def commit_to_html(commit, ref, project)
- render 'projects/commits/commit.html',
- commit: commit,
- ref: ref,
- project: project
+ render partial: 'projects/commits/commit', formats: :html,
+ locals: {
+ commit: commit,
+ ref: ref,
+ project: project
+ }
end
# Breadcrumb links for a Project and, if applicable, a tree path
@@ -73,9 +75,7 @@ module CommitsHelper
# Returns a link formatted as a commit branch link
def commit_branch_link(url, text)
- link_to(url, class: 'badge badge-gray ref-name branch-link') do
- sprite_icon('branch', size: 12, css_class: 'fork-svg') + "#{text}"
- end
+ gl_badge_tag(text, { variant: :info, icon: 'branch' }, { href: url, class: 'gl-font-monospace gl-mb-1' })
end
# Returns the sorted alphabetically links to branches, separated by a comma
@@ -87,9 +87,7 @@ module CommitsHelper
# Returns a link formatted as a commit tag link
def commit_tag_link(url, text)
- link_to(url, class: 'badge badge-gray ref-name') do
- sprite_icon('tag', size: 12, css_class: 'gl-mr-2 vertical-align-middle') + "#{text}"
- end
+ gl_badge_tag(text, { variant: :info, icon: 'tag' }, { href: url, class: 'gl-font-monospace' })
end
# Returns the sorted links to tags, separated by a comma
diff --git a/app/helpers/custom_metrics_helper.rb b/app/helpers/custom_metrics_helper.rb
index 9fbfe377c61..5442120008a 100644
--- a/app/helpers/custom_metrics_helper.rb
+++ b/app/helpers/custom_metrics_helper.rb
@@ -5,7 +5,7 @@ module CustomMetricsHelper
{
'custom-metrics-path' => url_for([project, metric]),
'metric-persisted' => metric.persisted?.to_s,
- 'edit-project-service-path' => edit_project_service_path(project, ::Integrations::Prometheus),
+ 'edit-project-service-path' => edit_project_integration_path(project, ::Integrations::Prometheus),
'validate-query-path' => validate_query_project_prometheus_metrics_path(project),
'title' => metric.title.to_s,
'query' => metric.query.to_s,
diff --git a/app/helpers/environment_helper.rb b/app/helpers/environment_helper.rb
index f57bb600527..1f0bf46097d 100644
--- a/app/helpers/environment_helper.rb
+++ b/app/helpers/environment_helper.rb
@@ -54,6 +54,8 @@ module EnvironmentHelper
s_('Deployment|canceled')
when 'skipped'
s_('Deployment|skipped')
+ when 'blocked'
+ s_('Deployment|blocked')
end
klass = "ci-status ci-#{status.dasherize}"
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index cde45e7bc0f..1894aba7dc0 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -59,7 +59,7 @@ module EnvironmentsHelper
return {} unless project
{
- 'settings_path' => edit_project_service_path(project, 'prometheus'),
+ 'settings_path' => edit_project_integration_path(project, 'prometheus'),
'clusters_path' => project_clusters_path(project),
'dashboards_endpoint' => project_performance_monitoring_dashboards_path(project, format: :json),
'default_branch' => project.default_branch,
diff --git a/app/helpers/groups/crm_settings_helper.rb b/app/helpers/groups/crm_settings_helper.rb
new file mode 100644
index 00000000000..ab47ec40b13
--- /dev/null
+++ b/app/helpers/groups/crm_settings_helper.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Groups
+ module CrmSettingsHelper
+ def crm_feature_flag_enabled?(group)
+ Feature.enabled?(:customer_relations, group)
+ end
+ end
+end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 9ba7d004d6c..7296560a450 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -134,6 +134,16 @@ module GroupsHelper
@group_projects_sort || @sort || params[:sort] || sort_value_recently_created
end
+ def verification_for_group_creation_data
+ # overridden in EE
+ {}
+ end
+
+ def require_verification_for_group_creation_enabled?
+ # overridden in EE
+ false
+ end
+
private
def group_title_link(group, hidable: false, show_avatar: false, for_dropdown: false)
diff --git a/app/helpers/hooks_helper.rb b/app/helpers/hooks_helper.rb
index c1dfd2b2cda..1e50033e0e0 100644
--- a/app/helpers/hooks_helper.rb
+++ b/app/helpers/hooks_helper.rb
@@ -39,7 +39,7 @@ module HooksHelper
def hook_log_path(hook, hook_log)
case hook
- when ProjectHook
+ when ProjectHook, ServiceHook
hook_log.present.details_path
when SystemHook
admin_hook_hook_log_path(hook, hook_log)
diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb
index c5e767c6f64..230f80e20a5 100644
--- a/app/helpers/integrations_helper.rb
+++ b/app/helpers/integrations_helper.rb
@@ -29,7 +29,7 @@ module IntegrationsHelper
def scoped_integration_path(integration, project: nil, group: nil)
if project.present?
- project_service_path(project, integration)
+ project_integration_path(project, integration)
elsif group.present?
group_settings_integration_path(group, integration)
else
@@ -39,7 +39,7 @@ module IntegrationsHelper
def scoped_edit_integration_path(integration, project: nil, group: nil)
if project.present?
- edit_project_service_path(project, integration)
+ edit_project_integration_path(project, integration)
elsif group.present?
edit_group_settings_integration_path(group, integration)
else
@@ -53,7 +53,7 @@ module IntegrationsHelper
def scoped_test_integration_path(integration, project: nil, group: nil)
if project.present?
- test_project_service_path(project, integration)
+ test_project_integration_path(project, integration)
elsif group.present?
test_group_settings_integration_path(group, integration)
else
@@ -90,7 +90,9 @@ module IntegrationsHelper
cancel_path: scoped_integrations_path(project: project, group: group),
can_test: integration.testable?.to_s,
test_path: scoped_test_integration_path(integration, project: project, group: group),
- reset_path: scoped_reset_integration_path(integration, group: group)
+ reset_path: scoped_reset_integration_path(integration, group: group),
+ form_path: scoped_integration_path(integration, project: project, group: group),
+ redirect_to: request.referer
}
if integration.is_a?(Integrations::Jira)
@@ -101,8 +103,9 @@ module IntegrationsHelper
form_data
end
- def integration_overrides_data(integration)
+ def integration_overrides_data(integration, project: nil, group: nil)
{
+ edit_path: scoped_edit_integration_path(integration, project: project, group: group),
overrides_path: scoped_overrides_integration_path(integration, format: :json)
}
end
@@ -225,6 +228,10 @@ module IntegrationsHelper
name: integration.to_param
}
end
+
+ def vue_integration_form_enabled?
+ Feature.enabled?(:vue_integration_form, current_user, default_enabled: :yaml)
+ end
end
IntegrationsHelper.prepend_mod_with('IntegrationsHelper')
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index cddf740a0e6..5aa2aca37f3 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -7,7 +7,7 @@ module IssuesHelper
classes = ["issue"]
classes << "closed" if issue.closed?
classes << "today" if issue.new?
- classes << "user-can-drag" if @sort == 'relative_position'
+ classes << "gl-cursor-grab" if @sort == 'relative_position'
classes.join(' ')
end
@@ -51,7 +51,7 @@ module IssuesHelper
end
def work_item_type_icon(issue_type)
- if WorkItem::Type.base_types.include?(issue_type)
+ if WorkItems::Type.base_types.include?(issue_type)
"issue-type-#{issue_type.to_s.dasherize}"
else
'issue-type-issue'
@@ -168,21 +168,6 @@ module IssuesHelper
issue.moved_from.project.service_desk_enabled? && !issue.project.service_desk_enabled?
end
- def use_startup_call?
- request.query_parameters.empty? && @sort == 'created_date'
- end
-
- def startup_call_params
- {
- state: 'opened',
- with_labels_details: 'true',
- page: 1,
- per_page: 20,
- order_by: 'created_at',
- sort: 'desc'
- }
- end
-
def issue_header_actions_data(project, issuable, current_user)
new_issuable_params = { issue: { description: _('Related to #%{issue_id}.') % { issue_id: issuable.iid } + "\n\n" } }
if issuable.incident?
diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb
index 7f8f6d77ff4..6330b8fc829 100644
--- a/app/helpers/learn_gitlab_helper.rb
+++ b/app/helpers/learn_gitlab_helper.rb
@@ -27,8 +27,12 @@ module LearnGitlabHelper
urls_to_use = nil
- experiment(:change_continuous_onboarding_link_urls) do |e|
- e.namespace = project.namespace
+ experiment(
+ :change_continuous_onboarding_link_urls,
+ namespace: project.namespace,
+ actor: current_user,
+ sticky_to: project.namespace
+ ) do |e|
e.use { urls_to_use = action_urls }
e.try { urls_to_use = new_action_urls(project) }
end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index 6acec417a75..64b58d28fc9 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -49,13 +49,6 @@ module NamespacesHelper
end
end
- def namespaces_options_with_developer_maintainer_access(options = {})
- selected = options.delete(:selected) || :current_user
- options[:groups] = current_user.manageable_groups_with_routes(include_groups_with_developer_maintainer_access: true)
-
- namespaces_options(selected, **options)
- end
-
def cascading_namespace_settings_popover_data(attribute, group, settings_path_helper)
locked_by_ancestor = group.namespace_settings.public_send("#{attribute}_locked_by_ancestor?") # rubocop:disable GitlabSecurity/PublicSend
diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb
index ecef2d38e54..24102a90a3b 100644
--- a/app/helpers/nav/top_nav_helper.rb
+++ b/app/helpers/nav/top_nav_helper.rb
@@ -123,7 +123,7 @@ module Nav
if dashboard_nav_link?(:milestones)
builder.add_primary_menu_item_with_shortcut(
id: 'milestones',
- title: 'Milestones',
+ title: _('Milestones'),
href: dashboard_milestones_path,
active: active_nav_link?(controller: 'dashboard/milestones'),
icon: 'clock',
@@ -144,7 +144,7 @@ module Nav
if dashboard_nav_link?(:activity)
builder.add_primary_menu_item_with_shortcut(
id: 'activity',
- title: 'Activity',
+ title: _('Activity'),
href: activity_dashboard_path,
active: active_nav_link?(path: 'dashboard#activity'),
icon: 'history',
@@ -189,15 +189,6 @@ module Nav
end
end
# rubocop: enable Cop/UserAdmin
-
- if Gitlab::Sherlock.enabled?
- builder.add_secondary_menu_item(
- id: 'sherlock',
- title: _('Sherlock Transactions'),
- icon: 'admin',
- href: sherlock_transactions_path
- )
- end
end
def projects_menu_item_attrs
@@ -212,7 +203,7 @@ module Nav
def groups_menu_item_attrs
{
id: 'groups',
- title: 'Groups',
+ title: _('Groups'),
icon: 'group',
shortcut_class: 'dashboard-shortcuts-groups'
}
diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb
index 9a6630ec3cb..1c4d294baa7 100644
--- a/app/helpers/nav_helper.rb
+++ b/app/helpers/nav_helper.rb
@@ -46,10 +46,6 @@ module NavHelper
class_names
end
- def has_extra_nav_icons?
- Gitlab::Sherlock.enabled? || current_user.admin?
- end
-
def page_has_markdown?
current_path?('merge_requests#show') ||
current_path?('projects/merge_requests/conflicts#show') ||
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 2dadaa0be0a..1afdb7a0ab9 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -48,7 +48,7 @@ module NotesHelper
data[:note_type] = LegacyDiffNote.name
else
data[:note_type] = DiffNote.name
- data[:position] = position.to_json
+ data[:position] = Gitlab::Json.dump(position)
end
data
diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb
index 66f80e7eeb8..402a363349f 100644
--- a/app/helpers/packages_helper.rb
+++ b/app/helpers/packages_helper.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module PackagesHelper
+ include ::API::Helpers::RelatedResourcesHelpers
+
def package_sort_path(options = {})
"#{request.path}?#{options.to_param}"
end
@@ -53,30 +55,4 @@ module PackagesHelper
project.container_expiration_policy.nil? &&
project.container_repositories.exists?
end
-
- def package_details_data(project, package)
- {
- package_id: package.id,
- can_delete: can?(current_user, :destroy_package, project).to_s,
- svg_path: image_path('illustrations/no-packages.svg'),
- npm_path: package_registry_instance_url(:npm),
- npm_project_path: package_registry_project_url(project.id, :npm),
- npm_help_path: help_page_path('user/packages/npm_registry/index'),
- maven_path: package_registry_project_url(project.id, :maven),
- maven_help_path: help_page_path('user/packages/maven_repository/index'),
- conan_path: package_registry_project_url(project.id, :conan),
- conan_help_path: help_page_path('user/packages/conan_repository/index'),
- nuget_path: nuget_package_registry_url(project.id),
- nuget_help_path: help_page_path('user/packages/nuget_repository/index'),
- pypi_path: pypi_registry_url(project.id),
- pypi_setup_path: package_registry_project_url(project.id, :pypi),
- pypi_help_path: help_page_path('user/packages/pypi_repository/index'),
- composer_path: composer_registry_url(project&.group&.id),
- composer_help_path: help_page_path('user/packages/composer_repository/index'),
- project_name: project.name,
- project_list_url: project_packages_path(project),
- group_list_url: project.group ? group_packages_path(project.group) : '',
- composer_config_repository_name: composer_config_repository_name(project.group&.id)
- }
- end
end
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 2729951d685..fb74a52fcda 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -125,7 +125,7 @@ module PageLayoutHelper
end
def fluid_layout
- current_user && current_user.layout == "fluid"
+ @force_fluid_layout == true || (current_user && current_user.layout == "fluid")
end
def blank_container(enabled = false)
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 76f2ee0981b..17450e5b26b 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -4,8 +4,8 @@
module PreferencesHelper
def layout_choices
[
- ['Fixed', :fixed],
- ['Fluid', :fluid]
+ [s_('Layout|Fixed'), :fixed],
+ [s_('Layout|Fluid'), :fluid]
]
end
@@ -40,9 +40,9 @@ module PreferencesHelper
def project_view_choices
[
- ['Files and Readme (default)', :files],
- ['Activity', :activity],
- ['Readme', :readme]
+ [s_('ProjectView|Files and Readme (default)'), :files],
+ [s_('ProjectView|Activity'), :activity],
+ [s_('ProjectView|Readme'), :readme]
]
end
diff --git a/app/helpers/projects/cluster_agents_helper.rb b/app/helpers/projects/cluster_agents_helper.rb
index aeeab250c7a..e3027759d65 100644
--- a/app/helpers/projects/cluster_agents_helper.rb
+++ b/app/helpers/projects/cluster_agents_helper.rb
@@ -3,9 +3,10 @@
module Projects::ClusterAgentsHelper
def js_cluster_agent_details_data(agent_name, project)
{
+ activity_empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg'),
agent_name: agent_name,
- project_path: project.full_path,
- activity_empty_state_image: image_path('illustrations/empty-state/empty-state-agents.svg')
+ empty_state_svg_path: image_path('illustrations/operations-dashboard_empty.svg'),
+ project_path: project.full_path
}
end
end
diff --git a/app/helpers/projects/issues/service_desk_helper.rb b/app/helpers/projects/issues/service_desk_helper.rb
deleted file mode 100644
index 0f87e5ed837..00000000000
--- a/app/helpers/projects/issues/service_desk_helper.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-module Projects::Issues::ServiceDeskHelper
- def service_desk_meta(project)
- empty_state_meta = {
- is_service_desk_supported: Gitlab::ServiceDesk.supported?,
- is_service_desk_enabled: project.service_desk_enabled?,
- can_edit_project_settings: can?(current_user, :admin_project, project)
- }
-
- if Gitlab::ServiceDesk.supported?
- empty_state_meta.merge(supported_meta(project))
- else
- empty_state_meta.merge(unsupported_meta(project))
- end
- end
-
- private
-
- def supported_meta(project)
- {
- service_desk_address: project.service_desk_address,
- service_desk_help_page: help_page_path('user/project/service_desk'),
- edit_project_page: edit_project_path(project),
- svg_path: image_path('illustrations/service_desk_empty.svg')
- }
- end
-
- def unsupported_meta(project)
- {
- incoming_email_help_page: help_page_path('administration/incoming_email', anchor: 'set-it-up'),
- svg_path: image_path('illustrations/service-desk-setup.svg')
- }
- end
-end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 827d2cb7164..084c962d34c 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -660,6 +660,31 @@ module ProjectsHelper
project.unlink_forks_upon_visibility_decrease_enabled? && project.visibility_level > Gitlab::VisibilityLevel::PRIVATE && project.forks_count > 0
end
+ def confirm_reduce_visibility_message(project)
+ strong_start = "<strong>".html_safe
+ strong_end = "</strong>".html_safe
+ message = _("You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end}.")
+
+ if project.group
+ message = _("You're about to reduce the visibility of the project %{strong_start}%{project_name}%{strong_end} in %{strong_start}%{group_name}%{strong_end}.")
+ end
+
+ html_escape(message) % { strong_start: strong_start, strong_end: strong_end, project_name: project.name, group_name: project.group ? project.group.name : nil }
+ end
+
+ def visibility_confirm_modal_data(project, target_form_id = nil)
+ {
+ target_form_id: target_form_id,
+ button_testid: 'reduce-project-visibility-button',
+ confirm_button_text: _('Reduce project visibility'),
+ confirm_danger_message: confirm_reduce_visibility_message(project),
+ phrase: project.full_path,
+ additional_information: _('Note: current forks will keep their visibility level.'),
+ html_confirmation_message: true.to_s,
+ show_visibility_confirm_modal: show_visibility_confirm_modal?(project).to_s
+ }
+ end
+
def build_project_breadcrumb_link(project)
project_name = simple_sanitize(project.name)
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index aedb7df9e4f..6efede8d565 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -141,7 +141,7 @@ module SearchHelper
}
},
{
- title: _('Last updated'),
+ title: _('Updated date'),
sortable: true,
sortParam: {
asc: 'updated_asc',
@@ -336,7 +336,7 @@ module SearchHelper
link_to search_path(search_params) do
concat label
concat ' '
- concat content_tag(:span, count, class: ['badge badge-pill gl-badge badge-muted sm', badge_class], data: badge_data)
+ concat gl_badge_tag(count, { size: :sm }, { class: badge_class, data: badge_data })
end
end
end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index e9ef9146529..ca90d1e13c1 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -26,11 +26,8 @@ module SnippetsHelper
return unless attrs = snippet_badge_attributes(snippet)
icon_name, text = attrs
- tag.span(class: %w[badge badge-gray]) do
- concat(sprite_icon(icon_name, size: 14, css_class: 'gl-vertical-align-middle'))
- concat(' ')
- concat(text)
- end
+
+ gl_badge_tag(text, icon: icon_name)
end
def snippet_badge_attributes(snippet)
diff --git a/app/helpers/sorting_titles_values_helper.rb b/app/helpers/sorting_titles_values_helper.rb
index 75ba6e8a153..4dfa7689110 100644
--- a/app/helpers/sorting_titles_values_helper.rb
+++ b/app/helpers/sorting_titles_values_helper.rb
@@ -59,7 +59,7 @@ module SortingTitlesValuesHelper
end
def sort_title_latest_activity
- s_('SortOptions|Last updated')
+ _('Updated date')
end
def sort_title_milestone
@@ -127,7 +127,7 @@ module SortingTitlesValuesHelper
end
def sort_title_recently_updated
- s_('SortOptions|Last updated')
+ _('Updated date')
end
def sort_title_start_date_later
diff --git a/app/helpers/ssh_keys_helper.rb b/app/helpers/ssh_keys_helper.rb
index f5a9bea482b..17a9fd6146d 100644
--- a/app/helpers/ssh_keys_helper.rb
+++ b/app/helpers/ssh_keys_helper.rb
@@ -18,4 +18,14 @@ module SshKeysHelper
container: 'body'
}
end
+
+ def ssh_key_allowed_algorithms
+ allowed_algorithms = Gitlab::CurrentSettings.allowed_key_types.flat_map do |ssh_key_type_name|
+ Gitlab::SSHPublicKey.supported_algorithms_for_name(ssh_key_type_name)
+ end
+
+ quoted_allowed_algorithms = allowed_algorithms.map { |name| "'#{name}'" }
+
+ Gitlab::Utils.to_exclusive_sentence(quoted_allowed_algorithms)
+ end
end
diff --git a/app/helpers/tracking_helper.rb b/app/helpers/tracking_helper.rb
index 3f53bd535b2..1beb88548c5 100644
--- a/app/helpers/tracking_helper.rb
+++ b/app/helpers/tracking_helper.rb
@@ -13,6 +13,10 @@ module TrackingHelper
}
end
+ def tracking_attrs_data(label, action, property)
+ tracking_attrs(label, action, property).fetch(:data, {})
+ end
+
private
def tracking_enabled?
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index d089b540282..4437f309a9c 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -191,7 +191,10 @@ module TreeHelper
web_ide_url: web_ide_url,
edit_url: edit_url(options),
- gitpod_url: gitpod_url
+
+ gitpod_url: gitpod_url,
+ user_preferences_gitpod_path: profile_preferences_path(anchor: 'user_gitpod_enabled'),
+ user_profile_enable_gitpod_path: profile_path(user: { gitpod_enabled: true })
}
end
diff --git a/app/helpers/version_check_helper.rb b/app/helpers/version_check_helper.rb
index 7875b9e4a28..48548ae9e6a 100644
--- a/app/helpers/version_check_helper.rb
+++ b/app/helpers/version_check_helper.rb
@@ -1,12 +1,11 @@
# frozen_string_literal: true
module VersionCheckHelper
- def version_status_badge
- return unless Rails.env.production?
- return unless Gitlab::CurrentSettings.version_check_enabled
- return if User.single_user&.requires_usage_stats_consent?
+ def show_version_check?
+ return false unless Gitlab::CurrentSettings.version_check_enabled
+ return false if User.single_user&.requires_usage_stats_consent?
- image_tag VersionCheck.image_url, class: 'js-version-status-badge'
+ current_user&.can_read_all_resources?
end
def link_to_version
diff --git a/app/helpers/webpack_helper.rb b/app/helpers/webpack_helper.rb
index 0d27e07f172..64900714327 100644
--- a/app/helpers/webpack_helper.rb
+++ b/app/helpers/webpack_helper.rb
@@ -25,6 +25,10 @@ module WebpackHelper
else
preload_link_tag(path, options)
end
+ rescue Gitlab::Webpack::Manifest::AssetMissingError
+ # In development/test, incremental compilation may be enabled, meaning not
+ # all chunks may be available/split out
+ raise unless Gitlab.dev_or_test_env?
end
def webpack_controller_bundle_tags
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 13b24e099c9..8e30eeee73f 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -246,7 +246,7 @@ class NotifyPreview < ActionMailer::Preview
def cleanup
email = nil
- ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
+ ApplicationRecord.transaction do
email = yield
raise ActiveRecord::Rollback
end
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index 0094d98fb73..9f634e70ff4 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -21,7 +21,6 @@
#
class ActiveSession
include ActiveModel::Model
- include ::Gitlab::Redis::SessionsStoreHelper
SESSION_BATCH_SIZE = 200
ALLOWED_NUMBER_OF_ACTIVE_SESSIONS = 100
@@ -66,7 +65,7 @@ class ActiveSession
end
def self.set(user, request)
- redis_store_class.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
session_private_id = request.session.id.private_id
client = DeviceDetector.new(request.user_agent)
timestamp = Time.current
@@ -107,7 +106,7 @@ class ActiveSession
end
def self.list(user)
- redis_store_class.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
cleaned_up_lookup_entries(redis, user).map do |raw_session|
load_raw_session(raw_session)
end
@@ -115,7 +114,7 @@ class ActiveSession
end
def self.cleanup(user)
- redis_store_class.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
clean_up_old_sessions(redis, user)
cleaned_up_lookup_entries(redis, user)
end
@@ -138,7 +137,7 @@ class ActiveSession
def self.destroy_session(user, session_id)
return unless session_id
- redis_store_class.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
destroy_sessions(redis, user, [session_id].compact)
end
end
@@ -147,7 +146,7 @@ class ActiveSession
sessions = not_impersonated(user)
sessions.reject! { |session| session.current?(current_rack_session) } if current_rack_session
- redis_store_class.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
session_ids = sessions.flat_map(&:ids)
destroy_sessions(redis, user, session_ids) if session_ids.any?
end
@@ -182,7 +181,7 @@ class ActiveSession
#
# Returns an array of strings
def self.session_ids_for_user(user_id)
- redis_store_class.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
redis.smembers(lookup_key_name(user_id))
end
end
@@ -195,7 +194,7 @@ class ActiveSession
def self.sessions_from_ids(session_ids)
return [] if session_ids.empty?
- redis_store_class.with do |redis|
+ Gitlab::Redis::Sessions.with do |redis|
session_keys = rack_session_keys(session_ids)
session_keys.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch|
diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb
index f40d0cd2fa4..a53fa39c58f 100644
--- a/app/models/alert_management/alert.rb
+++ b/app/models/alert_management/alert.rb
@@ -78,7 +78,6 @@ module AlertManagement
scope :for_environment, -> (environment) { where(environment: environment) }
scope :for_assignee_username, -> (assignee_username) { joins(:assignees).merge(User.by_username(assignee_username)) }
scope :search, -> (query) { fuzzy_search(query, [:title, :description, :monitoring_tool, :service]) }
- scope :open, -> { with_status(open_statuses) }
scope :not_resolved, -> { without_status(:resolved) }
scope :with_prometheus_alert, -> { includes(:prometheus_alert) }
scope :with_threat_monitoring_alerts, -> { where(domain: :threat_monitoring ) }
@@ -143,18 +142,6 @@ module AlertManagement
reference.to_i > 0 && reference.to_i <= Gitlab::Database::MAX_INT_VALUE
end
- def self.open_statuses
- [:triggered, :acknowledged]
- end
-
- def self.open_status?(status)
- open_statuses.include?(status)
- end
-
- def open?
- self.class.open_status?(status_name)
- end
-
def prometheus?
monitoring_tool == Gitlab::AlertManagement::Payload::MONITORING_TOOLS[:prometheus]
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 65472615f42..b69c0199c70 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -9,6 +9,7 @@ class ApplicationSetting < ApplicationRecord
include Sanitizable
ignore_columns %i[elasticsearch_shards elasticsearch_replicas], remove_with: '14.4', remove_after: '2021-09-22'
+ ignore_columns %i[static_objects_external_storage_auth_token], remove_with: '14.9', remove_after: '2022-03-22'
INSTANCE_REVIEW_MIN_USERS = 50
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
@@ -21,7 +22,7 @@ class ApplicationSetting < ApplicationRecord
add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required }
add_authentication_token_field :health_check_access_token
- add_authentication_token_field :static_objects_external_storage_auth_token, encrypted: :optional
+ add_authentication_token_field :static_objects_external_storage_auth_token, encrypted: :required
belongs_to :self_monitoring_project, class_name: "Project", foreign_key: 'instance_administration_project_id'
belongs_to :push_rule
@@ -77,6 +78,10 @@ class ApplicationSetting < ApplicationRecord
chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds
+ chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
+ chronic_duration_attr :group_runner_token_expiration_interval_human_readable, :group_runner_token_expiration_interval
+ chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval
+
validates :grafana_url,
system_hook_url: {
blocked_message: "is blocked: %{exception_message}. " + GRAFANA_URL_ERROR_MESSAGE
@@ -352,18 +357,28 @@ class ApplicationSetting < ApplicationRecord
validates :hashed_storage_enabled, inclusion: { in: [true], message: _("Hashed storage can't be disabled anymore for new projects") }
validates :container_registry_delete_tags_service_timeout,
+ :container_registry_cleanup_tags_service_max_list_size,
+ :container_registry_expiration_policies_worker_capacity,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
- validates :container_registry_cleanup_tags_service_max_list_size,
+ validates :container_registry_import_max_tags_count,
+ :container_registry_import_max_retries,
+ :container_registry_import_start_max_retries,
+ :container_registry_import_max_step_duration,
+ allow_nil: false,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
- validates :container_registry_expiration_policies_worker_capacity,
- numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+ validates :container_registry_import_target_plan, presence: true
+ validates :container_registry_import_created_before, presence: true
validates :dependency_proxy_ttl_group_policy_worker_capacity,
allow_nil: false,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+ validates :packages_cleanup_package_file_worker_capacity,
+ allow_nil: false,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+
validates :invisible_captcha_enabled,
inclusion: { in: [true, false], message: _('must be a boolean value') }
@@ -500,6 +515,9 @@ class ApplicationSetting < ApplicationRecord
validates :notes_create_limit,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+ validates :user_email_lookup_limit,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+
validates :notes_create_limit_allowlist,
length: { maximum: 100, message: N_('is too long (maximum is 100 entries)') },
allow_nil: false
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 5e20aac3b92..25198178f69 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -14,7 +14,7 @@ module ApplicationSettingImplementation
# Setting a key restriction to `-1` means that all keys of this type are
# forbidden.
FORBIDDEN_KEY_VALUE = KeyRestrictionValidator::FORBIDDEN
- SUPPORTED_KEY_TYPES = %i[rsa dsa ecdsa ed25519].freeze
+ SUPPORTED_KEY_TYPES = Gitlab::SSHPublicKey.supported_types
VALID_RUNNER_REGISTRAR_TYPES = %w(project group).freeze
DEFAULT_PROTECTED_PATHS = [
@@ -217,12 +217,19 @@ module ApplicationSettingImplementation
wiki_page_max_content_bytes: 50.megabytes,
container_registry_delete_tags_service_timeout: 250,
container_registry_expiration_policies_worker_capacity: 0,
+ container_registry_import_max_tags_count: 100,
+ container_registry_import_max_retries: 3,
+ container_registry_import_start_max_retries: 50,
+ container_registry_import_max_step_duration: 5.minutes,
+ container_registry_import_target_plan: 'free',
+ container_registry_import_created_before: '2022-01-23 00:00:00',
kroki_enabled: false,
kroki_url: nil,
kroki_formats: { blockdiag: false, bpmn: false, excalidraw: false },
rate_limiting_response_text: nil,
whats_new_variant: 0,
- user_deactivation_emails_enabled: true
+ user_deactivation_emails_enabled: true,
+ user_email_lookup_limit: 60
}
end
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 1a8bd05c42c..35c4e08730e 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class AuditEvent < ApplicationRecord
+ include AfterCommitQueue
include CreatedAtFilterable
include BulkInsertSafe
include EachBatch
diff --git a/app/models/bulk_imports/file_transfer/project_config.rb b/app/models/bulk_imports/file_transfer/project_config.rb
index fdfb0dd0186..38884df9fcf 100644
--- a/app/models/bulk_imports/file_transfer/project_config.rb
+++ b/app/models/bulk_imports/file_transfer/project_config.rb
@@ -8,6 +8,8 @@ module BulkImports
group_members
).freeze
+ LFS_OBJECTS_RELATION = 'lfs_objects'
+
def import_export_yaml
::Gitlab::ImportExport.config_file
end
@@ -15,6 +17,10 @@ module BulkImports
def skipped_relations
SKIPPED_RELATIONS
end
+
+ def file_relations
+ [UPLOADS_RELATION, LFS_OBJECTS_RELATION]
+ end
end
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 428e440afba..c4d1a2c740b 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -268,6 +268,10 @@ module Ci
!build.any_unmet_prerequisites? # If false is returned, it stops the transition
end
+ before_transition on: :enqueue do |build|
+ !build.waiting_for_deployment_approval? # If false is returned, it stops the transition
+ end
+
after_transition created: :scheduled do |build|
build.run_after_commit do
Ci::BuildScheduleWorker.perform_at(build.scheduled_at, build.id)
@@ -393,6 +397,10 @@ module Ci
auto_retry.allowed?
end
+ def auto_retry_expected?
+ failed? && auto_retry_allowed?
+ end
+
def detailed_status(current_user)
Gitlab::Ci::Status::Build::Factory
.new(self.present, current_user)
@@ -416,6 +424,10 @@ module Ci
true
end
+ def save_tags
+ super unless Thread.current['ci_bulk_insert_tags']
+ end
+
def archived?
return true if degenerated?
@@ -424,7 +436,11 @@ module Ci
end
def playable?
- action? && !archived? && (manual? || scheduled? || retryable?)
+ action? && !archived? && (manual? || scheduled? || retryable?) && !waiting_for_deployment_approval?
+ end
+
+ def waiting_for_deployment_approval?
+ manual? && starts_environment? && deployment&.blocked?
end
def schedulable?
@@ -751,9 +767,7 @@ module Ci
def any_runners_available?
cache_for_available_runners do
- ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339937') do
- project.active_runners.exists?
- end
+ project.active_runners.exists?
end
end
@@ -1021,7 +1035,15 @@ module Ci
transaction do
update_columns(status: :failed, failure_reason: :data_integrity_failure)
all_queuing_entries.delete_all
+ all_runtime_metadata.delete_all
end
+
+ Gitlab::AppLogger.info(
+ message: 'Build doomed',
+ class: self.class.name,
+ build_id: id,
+ pipeline_id: pipeline_id,
+ project_id: project_id)
end
def degradation_threshold
@@ -1051,10 +1073,7 @@ module Ci
end
def drop_with_exit_code!(failure_reason, exit_code)
- transaction do
- conditionally_allow_failure!(exit_code)
- drop!(failure_reason)
- end
+ drop!(::Gitlab::Ci::Build::Status::Reason.new(self, failure_reason, exit_code))
end
def exit_codes_defined?
@@ -1065,6 +1084,10 @@ module Ci
::Ci::PendingBuild.upsert_from_build!(self)
end
+ def create_runtime_metadata!
+ ::Ci::RunningBuild.upsert_shared_runner_build!(self)
+ end
+
##
# We can have only one queuing entry or running build tracking entry,
# because there is a unique index on `build_id` in each table, but we need
@@ -1093,6 +1116,13 @@ module Ci
end
end
+ def allowed_to_fail_with_code?(exit_code)
+ options
+ .dig(:allow_failure_criteria, :exit_codes)
+ .to_a
+ .include?(exit_code)
+ end
+
protected
def run_status_commit_hooks!
@@ -1174,27 +1204,15 @@ module Ci
break variables unless Feature.enabled?(:ci_job_jwt, project, default_enabled: true)
jwt = Gitlab::Ci::Jwt.for_build(self)
+ jwt_v2 = Gitlab::Ci::JwtV2.for_build(self)
variables.append(key: 'CI_JOB_JWT', value: jwt, public: false, masked: true)
+ variables.append(key: 'CI_JOB_JWT_V1', value: jwt, public: false, masked: true)
+ variables.append(key: 'CI_JOB_JWT_V2', value: jwt_v2, public: false, masked: true)
rescue OpenSSL::PKey::RSAError, Gitlab::Ci::Jwt::NoSigningKeyError => e
Gitlab::ErrorTracking.track_exception(e)
end
end
- def conditionally_allow_failure!(exit_code)
- return unless exit_code
-
- if allowed_to_fail_with_code?(exit_code)
- update_columns(allow_failure: true)
- end
- end
-
- def allowed_to_fail_with_code?(exit_code)
- options
- .dig(:allow_failure_criteria, :exit_codes)
- .to_a
- .include?(exit_code)
- end
-
def cache_for_online_runners(&block)
Rails.cache.fetch(
['has-online-runners', id],
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index e6dd62fab34..3426c4d5248 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -181,9 +181,7 @@ module Ci
end
scope :erasable, -> do
- types = self.file_types.reject { |file_type| NON_ERASABLE_FILE_TYPES.include?(file_type) }.values
-
- where(file_type: types)
+ where(file_type: self.erasable_file_types)
end
scope :downloadable, -> { where(file_type: DOWNLOADABLE_TYPES) }
@@ -263,6 +261,10 @@ module Ci
[file_type]
end
+ def self.erasable_file_types
+ self.file_types.keys - NON_ERASABLE_FILE_TYPES
+ end
+
def self.total_size
self.sum(:size)
end
@@ -271,10 +273,6 @@ module Ci
self.where(project: project).sum(:size)
end
- def self.distinct_job_ids
- distinct.pluck(:job_id)
- end
-
##
# FastDestroyAll concerns
# rubocop: disable CodeReuse/ServiceClass
@@ -350,9 +348,7 @@ module Ci
def store_after_commit?
strong_memoize(:store_after_commit) do
- trace? &&
- JobArtifactUploader.direct_upload_enabled? &&
- Feature.enabled?(:ci_store_trace_outside_transaction, project, default_enabled: :yaml)
+ trace? && JobArtifactUploader.direct_upload_enabled?
end
end
diff --git a/app/models/ci/namespace_mirror.rb b/app/models/ci/namespace_mirror.rb
index 8a4be3139e8..ce3faf3546b 100644
--- a/app/models/ci/namespace_mirror.rb
+++ b/app/models/ci/namespace_mirror.rb
@@ -10,6 +10,12 @@ module Ci
where('traversal_ids @> ARRAY[?]::int[]', id)
end
+ scope :contains_any_of_namespaces, -> (ids) do
+ where('traversal_ids && ARRAY[?]::int[]', ids)
+ end
+
+ scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) }
+
class << self
def sync!(event)
namespace = event.namespace
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index a90bd739741..00d331df4c3 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -69,6 +69,7 @@ module Ci
has_many :builds, foreign_key: :commit_id, inverse_of: :pipeline
has_many :generic_commit_statuses, foreign_key: :commit_id, inverse_of: :pipeline, class_name: 'GenericCommitStatus'
has_many :job_artifacts, through: :builds
+ has_many :build_trace_chunks, class_name: 'Ci::BuildTraceChunk', through: :builds, source: :trace_chunks
has_many :trigger_requests, dependent: :destroy, foreign_key: :commit_id # rubocop:disable Cop/ActiveRecordDependent
has_many :variables, class_name: 'Ci::PipelineVariable'
has_many :deployments, through: :builds
@@ -130,6 +131,7 @@ module Ci
after_create :keep_around_commits, unless: :importing?
use_fast_destroy :job_artifacts
+ use_fast_destroy :build_trace_chunks
# We use `Enums::Ci::Pipeline.sources` here so that EE can more easily extend
# this `Hash` with new values.
@@ -242,11 +244,7 @@ module Ci
::JiraConnect::SyncBuildsWorker.perform_async(pipeline.id, seq_id)
end
- if Feature.enabled?(:expire_job_and_pipeline_cache_synchronously, pipeline.project, default_enabled: :yaml)
- Ci::ExpirePipelineCacheService.new.execute(pipeline) # rubocop: disable CodeReuse/ServiceClass
- else
- ExpirePipelineCacheWorker.perform_async(pipeline.id)
- end
+ Ci::ExpirePipelineCacheService.new.execute(pipeline) # rubocop: disable CodeReuse/ServiceClass
end
end
@@ -466,6 +464,14 @@ module Ci
statuses.count(:id)
end
+ def tags_count
+ ActsAsTaggableOn::Tagging.where(taggable: builds).count
+ end
+
+ def distinct_tags_count
+ ActsAsTaggableOn::Tagging.where(taggable: builds).count('distinct(tag_id)')
+ end
+
def stages_names
statuses.order(:stage_idx).distinct
.pluck(:stage, :stage_idx).map(&:first)
@@ -1284,6 +1290,12 @@ module Ci
end
end
+ def use_variables_builder_definitions?
+ strong_memoize(:use_variables_builder_definitions) do
+ ::Feature.enabled?(:ci_use_variables_builder_definitions, project, default_enabled: :yaml)
+ end
+ end
+
private
def add_message(severity, content)
diff --git a/app/models/ci/project_mirror.rb b/app/models/ci/project_mirror.rb
index d6aaa3f50c1..9000d1791a6 100644
--- a/app/models/ci/project_mirror.rb
+++ b/app/models/ci/project_mirror.rb
@@ -6,6 +6,9 @@ module Ci
class ProjectMirror < ApplicationRecord
belongs_to :project
+ scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) }
+ scope :by_project_id, -> (project_id) { where(project_id: project_id) }
+
class << self
def sync!(event)
upsert({ project_id: event.project_id, namespace_id: event.project.namespace_id },
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index a80fd02080f..809c245d2b9 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -95,7 +95,33 @@ module Ci
joins(:runner_projects).where(ci_runner_projects: { project_id: project_id })
}
- scope :belonging_to_group, -> (group_id, include_ancestors: false) {
+ scope :belonging_to_group, -> (group_id) {
+ joins(:runner_namespaces)
+ .where(ci_runner_namespaces: { namespace_id: group_id })
+ }
+
+ scope :belonging_to_group_or_project_descendants, -> (group_id) {
+ group_ids = Ci::NamespaceMirror.contains_namespace(group_id).select(:namespace_id)
+ project_ids = Ci::ProjectMirror.by_namespace_id(group_ids).select(:project_id)
+
+ group_runners = joins(:runner_namespaces).where(ci_runner_namespaces: { namespace_id: group_ids })
+ project_runners = joins(:runner_projects).where(ci_runner_projects: { project_id: project_ids })
+
+ union_sql = ::Gitlab::SQL::Union.new([group_runners, project_runners]).to_sql
+
+ from("(#{union_sql}) #{table_name}")
+ }
+
+ scope :belonging_to_group_and_ancestors, -> (group_id) {
+ group_self_and_ancestors_ids = ::Group.find_by(id: group_id)&.self_and_ancestor_ids
+
+ joins(:runner_namespaces)
+ .where(ci_runner_namespaces: { namespace_id: group_self_and_ancestors_ids })
+ }
+
+ # deprecated
+ # split this into: belonging_to_group & belonging_to_group_and_ancestors
+ scope :legacy_belonging_to_group, -> (group_id, include_ancestors: false) {
groups = ::Group.where(id: group_id)
groups = groups.self_and_ancestors if include_ancestors
@@ -104,7 +130,8 @@ module Ci
.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336433')
}
- scope :belonging_to_group_or_project, -> (group_id, project_id) {
+ # deprecated
+ scope :legacy_belonging_to_group_or_project, -> (group_id, project_id) {
groups = ::Group.where(id: group_id)
group_runners = joins(:runner_namespaces).where(ci_runner_namespaces: { namespace_id: groups })
@@ -117,11 +144,11 @@ module Ci
}
scope :belonging_to_parent_group_of_project, -> (project_id) {
+ raise ArgumentError, "only 1 project_id allowed for performance reasons" unless project_id.is_a?(Integer)
+
project_groups = ::Group.joins(:projects).where(projects: { id: project_id })
- joins(:groups)
- .where(namespaces: { id: project_groups.self_and_ancestors.as_ids })
- .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336433')
+ belonging_to_group(project_groups.self_and_ancestors.pluck(:id))
}
scope :owned_or_instance_wide, -> (project_id) do
@@ -132,7 +159,7 @@ module Ci
instance_type
],
remove_duplicates: false
- ).allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336433')
+ )
end
scope :assignable_for, ->(project) do
@@ -183,6 +210,8 @@ module Ci
validates :config, json_schema: { filename: 'ci_runner_config' }
+ validates :maintainer_note, length: { maximum: 255 }
+
# Searches for runners matching the given query.
#
# This method uses ILIKE on PostgreSQL for the description field and performs a full match on tokens.
@@ -233,18 +262,16 @@ module Ci
Arel.sql("(#{arel_tag_names_array.to_sql})")
]
- ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339621') do
- group(*unique_params).pluck('array_agg(ci_runners.id)', *unique_params).map do |values|
- Gitlab::Ci::Matching::RunnerMatcher.new({
- runner_ids: values[0],
- runner_type: values[1],
- public_projects_minutes_cost_factor: values[2],
- private_projects_minutes_cost_factor: values[3],
- run_untagged: values[4],
- access_level: values[5],
- tag_list: values[6]
- })
- end
+ group(*unique_params).pluck('array_agg(ci_runners.id)', *unique_params).map do |values|
+ Gitlab::Ci::Matching::RunnerMatcher.new({
+ runner_ids: values[0],
+ runner_type: values[1],
+ public_projects_minutes_cost_factor: values[2],
+ private_projects_minutes_cost_factor: values[3],
+ run_untagged: values[4],
+ access_level: values[5],
+ tag_list: values[6]
+ })
end
end
@@ -441,6 +468,7 @@ module Ci
private
EXECUTOR_NAME_TO_TYPES = {
+ 'unknown' => :unknown,
'custom' => :custom,
'shell' => :shell,
'docker' => :docker,
@@ -454,6 +482,8 @@ module Ci
'kubernetes' => :kubernetes
}.freeze
+ EXECUTOR_TYPE_TO_NAMES = EXECUTOR_NAME_TO_TYPES.invert.freeze
+
def cleanup_runner_queue
Gitlab::Redis::SharedState.with do |redis|
redis.del(runner_queue_key)
diff --git a/app/models/ci/secure_file.rb b/app/models/ci/secure_file.rb
new file mode 100644
index 00000000000..56f632b6232
--- /dev/null
+++ b/app/models/ci/secure_file.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Ci
+ class SecureFile < Ci::ApplicationRecord
+ include FileStoreMounter
+
+ FILE_SIZE_LIMIT = 5.megabytes.freeze
+ CHECKSUM_ALGORITHM = 'sha256'
+
+ belongs_to :project, optional: false
+
+ validates :file, presence: true, file_size: { maximum: FILE_SIZE_LIMIT }
+ validates :checksum, :file_store, :name, :permissions, :project_id, presence: true
+
+ before_validation :assign_checksum
+
+ enum permissions: { read_only: 0, read_write: 1, execute: 2 }
+
+ default_value_for(:file_store) { Ci::SecureFileUploader.default_store }
+
+ mount_file_store_uploader Ci::SecureFileUploader
+
+ def checksum_algorithm
+ CHECKSUM_ALGORITHM
+ end
+
+ private
+
+ def assign_checksum
+ self.checksum = file.checksum if file.present? && file_changed?
+ end
+ end
+end
diff --git a/app/models/clusters/agent.rb b/app/models/clusters/agent.rb
index 98490a13351..79fc2b58237 100644
--- a/app/models/clusters/agent.rb
+++ b/app/models/clusters/agent.rb
@@ -5,6 +5,7 @@ module Clusters
self.table_name = 'cluster_agents'
INACTIVE_AFTER = 1.hour.freeze
+ ACTIVITY_EVENT_LIMIT = 200
belongs_to :created_by_user, class_name: 'User', optional: true
belongs_to :project, class_name: '::Project' # Otherwise, it will load ::Clusters::Project
@@ -36,8 +37,15 @@ module Clusters
requested_project == project
end
- def active?
- agent_tokens.where("last_used_at > ?", INACTIVE_AFTER.ago).exists?
+ def connected?
+ agent_tokens.active.where("last_used_at > ?", INACTIVE_AFTER.ago).exists?
+ end
+
+ def activity_event_deletion_cutoff
+ # Order is defined by the association
+ activity_events
+ .offset(ACTIVITY_EVENT_LIMIT - 1)
+ .pick(:recorded_at)
end
end
end
diff --git a/app/models/clusters/agent_token.rb b/app/models/clusters/agent_token.rb
index 87dba50cd69..691d628524f 100644
--- a/app/models/clusters/agent_token.rb
+++ b/app/models/clusters/agent_token.rb
@@ -10,9 +10,6 @@ module Clusters
self.table_name = 'cluster_agent_tokens'
- # The `UPDATE_USED_COLUMN_EVERY` defines how often the token DB entry can be updated
- UPDATE_USED_COLUMN_EVERY = (40.minutes..55.minutes).freeze
-
belongs_to :agent, class_name: 'Clusters::Agent', optional: false
belongs_to :created_by_user, class_name: 'User', optional: true
@@ -22,40 +19,11 @@ module Clusters
validates :name, presence: true, length: { maximum: 255 }
scope :order_last_used_at_desc, -> { order(::Gitlab::Database.nulls_last_order('last_used_at', 'DESC')) }
+ scope :with_status, -> (status) { where(status: status) }
- def track_usage
- track_values = { last_used_at: Time.current.utc }
-
- cache_attributes(track_values)
-
- if can_update_track_values?
- log_activity_event!(track_values[:last_used_at]) unless agent.active?
-
- # Use update_column so updated_at is skipped
- update_columns(track_values)
- end
- end
-
- private
-
- def can_update_track_values?
- # Use a random threshold to prevent beating DB updates.
- last_used_at_max_age = Random.rand(UPDATE_USED_COLUMN_EVERY)
-
- real_last_used_at = read_attribute(:last_used_at)
-
- # Handle too many updates from high token traffic
- real_last_used_at.nil? ||
- (Time.current - real_last_used_at) >= last_used_at_max_age
- end
-
- def log_activity_event!(recorded_at)
- agent.activity_events.create!(
- kind: :agent_connected,
- level: :info,
- recorded_at: recorded_at,
- agent_token: self
- )
- end
+ enum status: {
+ active: 0,
+ revoked: 1
+ }
end
end
diff --git a/app/models/clusters/agents/activity_event.rb b/app/models/clusters/agents/activity_event.rb
index 5d9c885c923..ec2bbfde339 100644
--- a/app/models/clusters/agents/activity_event.rb
+++ b/app/models/clusters/agents/activity_event.rb
@@ -3,6 +3,7 @@
module Clusters
module Agents
class ActivityEvent < ApplicationRecord
+ include EachBatch
include NullifyIfBlank
self.table_name = 'agent_activity_events'
@@ -12,6 +13,7 @@ module Clusters
belongs_to :agent_token, class_name: 'Clusters::AgentToken'
scope :in_timeline_order, -> { order(recorded_at: :desc, id: :desc) }
+ scope :recorded_before, -> (cutoff) { where('recorded_at < ?', cutoff) }
validates :recorded_at, :kind, :level, presence: true
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index b57a24dead0..33cd5de3518 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.35.0'
+ VERSION = '0.36.0'
self.table_name = 'clusters_applications_runners'
@@ -41,7 +41,7 @@ module Clusters
end
def prepare_uninstall
- runner&.update!(active: false)
+ # No op, see https://gitlab.com/gitlab-org/gitlab/-/issues/350180.
end
def post_uninstall
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index d6a2f62ca9b..21e2e21e9b3 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -170,8 +170,11 @@ class CommitStatus < Ci::ApplicationRecord
end
before_transition any => :failed do |commit_status, transition|
- failure_reason = transition.args.first
- commit_status.failure_reason = CommitStatus.failure_reasons[failure_reason]
+ reason = ::Gitlab::Ci::Build::Status::Reason
+ .fabricate(commit_status, transition.args.first)
+
+ commit_status.failure_reason = reason.failure_reason_enum
+ commit_status.allow_failure = true if reason.force_allow_failure?
end
before_transition [:skipped, :manual] => :created do |commit_status, transition|
@@ -191,11 +194,7 @@ class CommitStatus < Ci::ApplicationRecord
commit_status.run_after_commit do
PipelineProcessWorker.perform_async(pipeline_id) unless transition_options[:skip_pipeline_processing]
- if Feature.enabled?(:expire_job_and_pipeline_cache_synchronously, project, default_enabled: :yaml)
- expire_etag_cache!
- else
- ExpireJobCacheWorker.perform_async(id)
- end
+ expire_etag_cache!
end
end
@@ -221,8 +220,8 @@ class CommitStatus < Ci::ApplicationRecord
false
end
- def self.bulk_insert_tags!(statuses, tag_list_by_build)
- Gitlab::Ci::Tags::BulkInsert.new(statuses, tag_list_by_build).insert!
+ def self.bulk_insert_tags!(statuses)
+ Gitlab::Ci::Tags::BulkInsert.new(statuses).insert!
end
def locking_enabled?
diff --git a/app/models/concerns/ci/contextable.rb b/app/models/concerns/ci/contextable.rb
index 12ddbc2cc40..ed3b422251f 100644
--- a/app/models/concerns/ci/contextable.rb
+++ b/app/models/concerns/ci/contextable.rb
@@ -13,6 +13,8 @@ module Ci
track_duration do
variables = pipeline.variables_builder.scoped_variables(self, environment: environment, dependencies: dependencies)
+ next variables if pipeline.use_variables_builder_definitions?
+
variables.concat(project.predefined_variables)
variables.concat(pipeline.predefined_variables)
variables.concat(runner.predefined_variables) if runnable? && runner
@@ -60,49 +62,27 @@ module Ci
end
def user_variables
- Gitlab::Ci::Variables::Collection.new.tap do |variables|
- break variables if user.blank?
-
- variables.append(key: 'GITLAB_USER_ID', value: user.id.to_s)
- variables.append(key: 'GITLAB_USER_EMAIL', value: user.email)
- variables.append(key: 'GITLAB_USER_LOGIN', value: user.username)
- variables.append(key: 'GITLAB_USER_NAME', value: user.name)
- end
+ pipeline.variables_builder.user_variables(user)
end
def kubernetes_variables
- ::Gitlab::Ci::Variables::Collection.new.tap do |collection|
- # Should get merged with the cluster kubeconfig in deployment_variables, see
- # https://gitlab.com/gitlab-org/gitlab/-/issues/335089
- template = ::Ci::GenerateKubeconfigService.new(self).execute
-
- if template.valid?
- collection.append(key: 'KUBECONFIG', value: template.to_yaml, public: false, file: true)
- end
- end
+ pipeline.variables_builder.kubernetes_variables(self)
end
def deployment_variables(environment:)
- return [] unless environment
-
- project.deployment_variables(
- environment: environment,
- kubernetes_namespace: expanded_kubernetes_namespace
- )
+ pipeline.variables_builder.deployment_variables(job: self, environment: environment)
end
def secret_instance_variables
- project.ci_instance_variables_for(ref: git_ref)
+ pipeline.variables_builder.secret_instance_variables(ref: git_ref)
end
def secret_group_variables(environment: expanded_environment_name)
- return [] unless project.group
-
- project.group.ci_variables_for(git_ref, project, environment: environment)
+ pipeline.variables_builder.secret_group_variables(environment: environment, ref: git_ref)
end
def secret_project_variables(environment: expanded_environment_name)
- project.ci_variables_for(ref: git_ref, environment: environment)
+ pipeline.variables_builder.secret_project_variables(environment: environment, ref: git_ref)
end
end
end
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index 611b27c722b..aa9669ee208 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -18,13 +18,19 @@ module Ci
delegate :timeout, to: :metadata, prefix: true, allow_nil: true
delegate :interruptible, to: :metadata, prefix: false, allow_nil: true
- delegate :has_exposed_artifacts?, to: :metadata, prefix: false, allow_nil: true
delegate :environment_auto_stop_in, to: :metadata, prefix: false, allow_nil: true
delegate :set_cancel_gracefully, to: :metadata, prefix: false, allow_nil: false
- delegate :cancel_gracefully?, to: :metadata, prefix: false, allow_nil: false
before_create :ensure_metadata
end
+ def has_exposed_artifacts?
+ !!metadata&.has_exposed_artifacts?
+ end
+
+ def cancel_gracefully?
+ !!metadata&.cancel_gracefully?
+ end
+
def ensure_metadata
metadata || build_metadata(project: project)
end
diff --git a/app/models/concerns/forced_email_confirmation.rb b/app/models/concerns/forced_email_confirmation.rb
new file mode 100644
index 00000000000..649400184e5
--- /dev/null
+++ b/app/models/concerns/forced_email_confirmation.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module ForcedEmailConfirmation
+ extend ActiveSupport::Concern
+
+ included do
+ attr_accessor :skip_confirmation_period_expiry_check
+ end
+
+ def force_confirm(args = {})
+ self.skip_confirmation_period_expiry_check = true
+ confirm(args)
+ ensure
+ self.skip_confirmation_period_expiry_check = nil
+ end
+
+ protected
+
+ # Override, from Devise::Models::Confirmable
+ # Link: https://github.com/heartcombo/devise/blob/main/lib/devise/models/confirmable.rb
+ def confirmation_period_expired?
+ return false if skip_confirmation_period_expiry_check
+
+ super
+ end
+end
diff --git a/app/models/concerns/has_wiki.rb b/app/models/concerns/has_wiki.rb
index df7bbe4dc08..89bcabafb84 100644
--- a/app/models/concerns/has_wiki.rb
+++ b/app/models/concerns/has_wiki.rb
@@ -17,7 +17,7 @@ module HasWiki
def wiki
strong_memoize(:wiki) do
- Wiki.for_container(self, self.default_owner)
+ Wiki.for_container(self, self.first_owner)
end
end
diff --git a/app/models/concerns/import_state/sidekiq_job_tracker.rb b/app/models/concerns/import_state/sidekiq_job_tracker.rb
index 340bf4279bc..b7d0ed0f51b 100644
--- a/app/models/concerns/import_state/sidekiq_job_tracker.rb
+++ b/app/models/concerns/import_state/sidekiq_job_tracker.rb
@@ -15,7 +15,7 @@ module ImportState
def refresh_jid_expiration
return unless jid
- Gitlab::SidekiqStatus.set(jid, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION, value: 2)
+ Gitlab::SidekiqStatus.set(jid, Gitlab::Import::StuckImportJob::IMPORT_JOBS_EXPIRATION)
end
def self.jid_by(project_id:, status:)
diff --git a/app/models/concerns/incident_management/escalatable.rb b/app/models/concerns/incident_management/escalatable.rb
index 81eef50603a..a9e4a066e0e 100644
--- a/app/models/concerns/incident_management/escalatable.rb
+++ b/app/models/concerns/incident_management/escalatable.rb
@@ -27,6 +27,8 @@ module IncidentManagement
ignored: 'No action will be taken'
}.freeze
+ OPEN_STATUSES = [:triggered, :acknowledged].freeze
+
included do
validates :status, presence: true
@@ -34,6 +36,7 @@ module IncidentManagement
# Descending sort order sorts statuses: Triggered > Acknowledged > Resolved > Ignored
# https://gitlab.com/gitlab-org/gitlab/-/issues/221242#what-is-the-expected-correct-behavior
scope :order_status, -> (sort_order) { order(status: sort_order == :asc ? :desc : :asc) }
+ scope :open, -> { with_status(OPEN_STATUSES) }
state_machine :status, initial: :triggered do
state :triggered, value: STATUSES[:triggered]
@@ -89,6 +92,10 @@ module IncidentManagement
@status_names ||= state_machine_statuses.keys
end
+ def open_status?(status)
+ OPEN_STATUSES.include?(status)
+ end
+
private
def state_machine_statuses
@@ -99,6 +106,10 @@ module IncidentManagement
def status_event_for(status)
self.class.state_machines[:status].events.transitions_for(self, to: status.to_s.to_sym).first&.event
end
+
+ def open?
+ self.class.open_status?(status_name)
+ end
end
end
end
diff --git a/app/models/concerns/packages/debian/distribution.rb b/app/models/concerns/packages/debian/distribution.rb
index ff52769fce8..2d46889ce6a 100644
--- a/app/models/concerns/packages/debian/distribution.rb
+++ b/app/models/concerns/packages/debian/distribution.rb
@@ -96,6 +96,15 @@ module Packages
architectures.pluck(:name).sort
end
+ def package_files
+ if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ ::Packages::PackageFile.installable
+ .for_package_ids(packages.select(:id))
+ else
+ ::Packages::PackageFile.for_package_ids(packages.select(:id))
+ end
+ end
+
private
def unique_codename_and_suite
diff --git a/app/models/concerns/packages/destructible.rb b/app/models/concerns/packages/destructible.rb
new file mode 100644
index 00000000000..a3b7d8580c1
--- /dev/null
+++ b/app/models/concerns/packages/destructible.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Packages
+ module Destructible
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def next_pending_destruction(order_by: nil)
+ set = pending_destruction.limit(1).lock('FOR UPDATE SKIP LOCKED')
+ set = set.order(order_by) if order_by
+ set.take
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/packages/installable.rb b/app/models/concerns/packages/installable.rb
new file mode 100644
index 00000000000..e9303e55412
--- /dev/null
+++ b/app/models/concerns/packages/installable.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Packages
+ # This module requires a status column.
+ # It also requires a constant INSTALLABLE_STATUSES. This should be
+ # an array that defines which values of the status column are
+ # considered as installable.
+ module Installable
+ extend ActiveSupport::Concern
+
+ included do
+ scope :with_status, ->(status) { where(status: status) }
+ scope :installable, -> { with_status(const_get(:INSTALLABLE_STATUSES, false)) }
+ end
+ end
+end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 1663aa6c886..20743ebcb52 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -64,8 +64,6 @@ module Participable
#
# Returns an Array of User instances.
def visible_participants(user)
- return participants(user) unless Feature.enabled?(:verify_participants_access, project, default_enabled: :yaml)
-
filter_by_ability(raw_participants(user, verify_access: true))
end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index f382b3624ed..2cf95ac0dae 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -190,5 +190,10 @@ module Routable
route || build_route(source: self)
route.path = build_full_path
route.name = build_full_name
+ route.namespace = if is_a?(Namespace)
+ self
+ elsif is_a?(Project)
+ self.project_namespace
+ end
end
end
diff --git a/app/models/concerns/runner_token_expiration_interval.rb b/app/models/concerns/runner_token_expiration_interval.rb
new file mode 100644
index 00000000000..f84e69e7b7d
--- /dev/null
+++ b/app/models/concerns/runner_token_expiration_interval.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module RunnerTokenExpirationInterval
+ extend ActiveSupport::Concern
+
+ def enforced_runner_token_expiration_interval_human_readable
+ interval = enforced_runner_token_expiration_interval
+ ChronicDuration.output(interval, format: :short) if interval
+ end
+
+ def effective_runner_token_expiration_interval
+ [
+ enforced_runner_token_expiration_interval,
+ runner_token_expiration_interval&.seconds
+ ].compact.min
+ end
+
+ def effective_runner_token_expiration_interval_human_readable
+ interval = effective_runner_token_expiration_interval
+ ChronicDuration.output(interval, format: :short) if interval
+ end
+end
diff --git a/app/models/concerns/ttl_expirable.rb b/app/models/concerns/ttl_expirable.rb
index 6d89521255c..1c2147beedd 100644
--- a/app/models/concerns/ttl_expirable.rb
+++ b/app/models/concerns/ttl_expirable.rb
@@ -7,16 +7,10 @@ module TtlExpirable
validates :status, presence: true
default_value_for :read_at, Time.zone.now
- enum status: { default: 0, expired: 1, processing: 2, error: 3 }
+ enum status: { default: 0, pending_destruction: 1, processing: 2, error: 3 }
scope :read_before, ->(number_of_days) { where("read_at <= ?", Time.zone.now - number_of_days.days) }
scope :active, -> { where(status: :default) }
-
- scope :lock_next_by, ->(sort) do
- order(sort)
- .limit(1)
- .lock('FOR UPDATE SKIP LOCKED')
- end
end
def read!
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index c914819f79d..b03d946fc47 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -13,9 +13,15 @@ class ContainerRepository < ApplicationRecord
validates :name, length: { minimum: 0, allow_nil: false }
validates :name, uniqueness: { scope: :project_id }
+ validates :migration_state, presence: true
+
+ validates :migration_retries_count, presence: true,
+ numericality: { greater_than_or_equal_to: 0 },
+ allow_nil: false
enum status: { delete_scheduled: 0, delete_failed: 1 }
enum expiration_policy_cleanup_status: { cleanup_unscheduled: 0, cleanup_scheduled: 1, cleanup_unfinished: 2, cleanup_ongoing: 3 }
+ enum migration_skipped_reason: { not_in_plan: 0, too_many_retries: 1, too_many_tags: 2, root_namespace_in_deny_list: 3 }
delegate :client, to: :registry
diff --git a/app/models/customer_relations/contact.rb b/app/models/customer_relations/contact.rb
index d8669f1f4c2..168f1c48a6c 100644
--- a/app/models/customer_relations/contact.rb
+++ b/app/models/customer_relations/contact.rb
@@ -24,11 +24,12 @@ class CustomerRelations::Contact < ApplicationRecord
validates :email, length: { maximum: 255 }
validates :description, length: { maximum: 1024 }
validate :validate_email_format
+ validate :unique_email_for_group_hierarchy
- def self.find_ids_by_emails(group_id, emails)
+ def self.find_ids_by_emails(group, emails)
raise ArgumentError, "Cannot lookup more than #{MAX_PLUCK} emails" if emails.length > MAX_PLUCK
- where(group_id: group_id, email: emails)
+ where(group_id: group.self_and_ancestor_ids, email: emails)
.pluck(:id)
end
@@ -39,4 +40,14 @@ class CustomerRelations::Contact < ApplicationRecord
self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email)
end
+
+ def unique_email_for_group_hierarchy
+ return unless group
+ return unless email
+
+ duplicate_email_exists = CustomerRelations::Contact
+ .where(group_id: group.self_and_hierarchy.pluck(:id), email: email)
+ .where.not(id: id).exists?
+ self.errors.add(:email, _('contact with same email already exists in group hierarchy')) if duplicate_email_exists
+ end
end
diff --git a/app/models/customer_relations/issue_contact.rb b/app/models/customer_relations/issue_contact.rb
index 78f662b6a58..89dac6bad22 100644
--- a/app/models/customer_relations/issue_contact.rb
+++ b/app/models/customer_relations/issue_contact.rb
@@ -6,7 +6,7 @@ class CustomerRelations::IssueContact < ApplicationRecord
belongs_to :issue, optional: false, inverse_of: :customer_relations_contacts
belongs_to :contact, optional: false, inverse_of: :issue_contacts
- validate :contact_belongs_to_issue_group
+ validate :contact_belongs_to_issue_group_or_ancestor
def self.find_contact_ids_by_emails(issue_id, emails)
raise ArgumentError, "Cannot lookup more than #{MAX_PLUCK} emails" if emails.length > MAX_PLUCK
@@ -18,11 +18,11 @@ class CustomerRelations::IssueContact < ApplicationRecord
private
- def contact_belongs_to_issue_group
+ def contact_belongs_to_issue_group_or_ancestor
return unless contact&.group_id
return unless issue&.project&.namespace_id
- return if contact.group_id == issue.project.namespace_id
+ return if issue.project.group&.self_and_ancestor_ids&.include?(contact.group_id)
- errors.add(:base, _('The contact does not belong to the same group as the issue'))
+ errors.add(:base, _('The contact does not belong to the issue group or an ancestor'))
end
end
diff --git a/app/models/dependency_proxy/blob.rb b/app/models/dependency_proxy/blob.rb
index bd5c022e692..e4018ab4770 100644
--- a/app/models/dependency_proxy/blob.rb
+++ b/app/models/dependency_proxy/blob.rb
@@ -3,6 +3,7 @@
class DependencyProxy::Blob < ApplicationRecord
include FileStoreMounter
include TtlExpirable
+ include Packages::Destructible
include EachBatch
belongs_to :group
diff --git a/app/models/dependency_proxy/manifest.rb b/app/models/dependency_proxy/manifest.rb
index 64f484942ef..fe887c99e81 100644
--- a/app/models/dependency_proxy/manifest.rb
+++ b/app/models/dependency_proxy/manifest.rb
@@ -3,6 +3,7 @@
class DependencyProxy::Manifest < ApplicationRecord
include FileStoreMounter
include TtlExpirable
+ include Packages::Destructible
include EachBatch
belongs_to :group
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 4c60ce57f49..2f04d99f9f6 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -8,6 +8,7 @@ class Deployment < ApplicationRecord
include Importable
include Gitlab::Utils::StrongMemoize
include FastDestroyAll
+ include FromUnion
StatusUpdateError = Class.new(StandardError)
StatusSyncError = Class.new(StandardError)
@@ -69,6 +70,10 @@ class Deployment < ApplicationRecord
transition created: :blocked
end
+ event :unblock do
+ transition blocked: :created
+ end
+
event :succeed do
transition any - [:success] => :success
end
@@ -107,10 +112,7 @@ class Deployment < ApplicationRecord
deployment.run_after_commit do
Deployments::UpdateEnvironmentWorker.perform_async(id)
Deployments::LinkMergeRequestWorker.perform_async(id)
-
- if ::Feature.enabled?(:deployments_archive, deployment.project, default_enabled: :yaml)
- Deployments::ArchiveInProjectWorker.perform_async(deployment.project_id)
- end
+ Deployments::ArchiveInProjectWorker.perform_async(deployment.project_id)
end
end
diff --git a/app/models/email.rb b/app/models/email.rb
index 676e79406e9..3896dfd5d22 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -6,8 +6,8 @@ class Email < ApplicationRecord
belongs_to :user, optional: false
- validates :email, presence: true, uniqueness: true
- validate :validate_email_format
+ validates :email, presence: true, uniqueness: true, devise_email: true
+
validate :unique_email, if: ->(email) { email.email_changed? }
scope :confirmed, -> { where.not(confirmed_at: nil) }
@@ -19,6 +19,7 @@ class Email < ApplicationRecord
# This module adds async behaviour to Devise emails
# and should be added after Devise modules are initialized.
include AsyncDeviseEmail
+ include ForcedEmailConfirmation
self.reconfirmable = false # currently email can't be changed, no need to reconfirm
@@ -32,10 +33,6 @@ class Email < ApplicationRecord
self.errors.add(:email, 'has already been taken') if primary_email_of_another_user?
end
- def validate_email_format
- self.errors.add(:email, I18n.t(:invalid, scope: 'valid_email.validations.email')) unless ValidateEmail.valid?(self.email)
- end
-
# once email is confirmed, update the gpg signatures
def update_invalid_gpg_signatures
user.update_invalid_gpg_signatures if confirmed?
diff --git a/app/models/experiment.rb b/app/models/experiment.rb
index cd0814c476a..2300ec2996d 100644
--- a/app/models/experiment.rb
+++ b/app/models/experiment.rb
@@ -7,7 +7,7 @@ class Experiment < ApplicationRecord
validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
def self.add_user(name, group_type, user, context = {})
- find_or_create_by!(name: name).record_user_and_group(user, group_type, context)
+ by_name(name).record_user_and_group(user, group_type, context)
end
def self.add_group(name, variant:, group:)
@@ -15,11 +15,15 @@ class Experiment < ApplicationRecord
end
def self.add_subject(name, variant:, subject:)
- find_or_create_by!(name: name).record_subject_and_variant!(subject, variant)
+ by_name(name).record_subject_and_variant!(subject, variant)
end
def self.record_conversion_event(name, user, context = {})
- find_or_create_by!(name: name).record_conversion_event_for_user(user, context)
+ by_name(name).record_conversion_event_for_user(user, context)
+ end
+
+ def self.by_name(name)
+ find_or_create_by!(name: name)
end
# Create or update the recorded experiment_user row for the user in this experiment.
@@ -41,6 +45,16 @@ class Experiment < ApplicationRecord
experiment_user.update!(converted_at: Time.current, context: merged_context(experiment_user, context))
end
+ def record_conversion_event_for_subject(subject, context = {})
+ raise 'Incompatible subject provided!' unless ExperimentSubject.valid_subject?(subject)
+
+ attr_name = subject.class.table_name.singularize.to_sym
+ experiment_subject = experiment_subjects.find_by(attr_name => subject)
+ return unless experiment_subject
+
+ experiment_subject.update!(converted_at: Time.current, context: merged_context(experiment_subject, context))
+ end
+
def record_subject_and_variant!(subject, variant)
raise 'Incompatible subject provided!' unless ExperimentSubject.valid_subject?(subject)
@@ -57,7 +71,7 @@ class Experiment < ApplicationRecord
private
- def merged_context(experiment_user, new_context)
- experiment_user.context.deep_merge(new_context.deep_stringify_keys)
+ def merged_context(experiment_subject, new_context)
+ experiment_subject.context.deep_merge(new_context.deep_stringify_keys)
end
end
diff --git a/app/models/external_pull_request.rb b/app/models/external_pull_request.rb
index 3fc166203e7..4654f7e2341 100644
--- a/app/models/external_pull_request.rb
+++ b/app/models/external_pull_request.rb
@@ -11,7 +11,7 @@
# When the mirror is updated and changes are pushed to branches we check
# if there are open pull requests for the source and target branch.
# If so, we create pipelines for external pull requests.
-class ExternalPullRequest < ApplicationRecord
+class ExternalPullRequest < Ci::ApplicationRecord
include Gitlab::Utils::StrongMemoize
include ShaAttribute
@@ -40,6 +40,9 @@ class ExternalPullRequest < ApplicationRecord
scope :by_source_branch, ->(branch) { where(source_branch: branch) }
scope :by_source_repository, -> (repository) { where(source_repository: repository) }
+ # Needed to override Ci::ApplicationRecord as this does not have ci_ table prefix
+ self.table_name = 'external_pull_requests'
+
def self.create_or_update_from_params(params)
find_params = params.slice(:project_id, :source_branch, :target_branch)
diff --git a/app/models/group.rb b/app/models/group.rb
index f51782785f9..53da70f47e5 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -17,6 +17,8 @@ class Group < Namespace
include GroupAPICompatibility
include EachBatch
include BulkMemberAccessLoad
+ include ChronicDurationAttribute
+ include RunnerTokenExpirationInterval
def self.sti_name
'Group'
@@ -91,6 +93,11 @@ class Group < Namespace
has_many :group_callouts, class_name: 'Users::GroupCallout', foreign_key: :group_id
delegate :prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap, :setup_for_company, :jobs_to_be_done, to: :namespace_settings
+ delegate :runner_token_expiration_interval, :runner_token_expiration_interval=, :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
+ delegate :subgroup_runner_token_expiration_interval, :subgroup_runner_token_expiration_interval=, :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
+ delegate :project_runner_token_expiration_interval, :project_runner_token_expiration_interval=, :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval_human_readable=, to: :namespace_settings, allow_nil: true
+
+ has_one :crm_settings, class_name: 'Group::CrmSettings', inverse_of: :group
accepts_nested_attributes_for :variables, allow_destroy: true
@@ -121,6 +128,8 @@ class Group < Namespace
scope :by_id, ->(groups) { where(id: groups) }
+ scope :by_ids_or_paths, -> (ids, paths) { by_id(ids).or(where(path: paths)) }
+
scope :for_authorized_group_members, -> (user_ids) do
joins(:group_members)
.where(members: { user_id: user_ids })
@@ -212,6 +221,10 @@ class Group < Namespace
Set.new(group_ids)
end
+ def get_ids_by_ids_or_paths(ids, paths)
+ by_ids_or_paths(ids, paths).pluck(:id)
+ end
+
private
def public_to_user_arel(user)
@@ -619,7 +632,7 @@ class Group < Namespace
end
end
- def group_member(user)
+ def member(user)
if group_members.loaded?
group_members.find { |gm| gm.user_id == user.id }
else
@@ -631,6 +644,10 @@ class Group < Namespace
GroupMember.where(source_id: self_and_ancestors_ids, user_id: user.id).order(:access_level).last
end
+ def bots
+ users.project_bot
+ end
+
def related_group_ids
[id,
*ancestors.pluck(:id),
@@ -713,8 +730,8 @@ class Group < Namespace
end
end
- def default_owner
- owners.first || parent&.default_owner || owner
+ def first_owner
+ owners.first || parent&.first_owner || owner
end
def default_branch_name
@@ -764,6 +781,29 @@ class Group < Namespace
super || build_dependency_proxy_image_ttl_policy
end
+ def dependency_proxy_setting
+ super || build_dependency_proxy_setting
+ end
+
+ def crm_enabled?
+ crm_settings&.enabled?
+ end
+
+ def shared_with_group_links_visible_to_user(user)
+ shared_with_group_links.preload_shared_with_groups.filter { |link| Ability.allowed?(user, :read_group, link.shared_with_group) }
+ end
+
+ def enforced_runner_token_expiration_interval
+ all_parent_groups = Gitlab::ObjectHierarchy.new(Group.where(id: id)).ancestors
+ all_group_settings = NamespaceSetting.where(namespace_id: all_parent_groups)
+ group_interval = all_group_settings.where.not(subgroup_runner_token_expiration_interval: nil).minimum(:subgroup_runner_token_expiration_interval)&.seconds
+
+ [
+ Gitlab::CurrentSettings.group_runner_token_expiration_interval&.seconds,
+ group_interval
+ ].compact.min
+ end
+
private
def max_member_access(user_ids)
diff --git a/app/models/group/crm_settings.rb b/app/models/group/crm_settings.rb
new file mode 100644
index 00000000000..30fbe6ae07f
--- /dev/null
+++ b/app/models/group/crm_settings.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class Group::CrmSettings < ApplicationRecord
+ self.primary_key = :group_id
+ self.table_name = 'group_crm_settings'
+
+ belongs_to :group, -> { where(type: Group.sti_name) }, foreign_key: 'group_id'
+
+ validates :group, presence: true
+end
diff --git a/app/models/group_group_link.rb b/app/models/group_group_link.rb
index fdc54ba33ab..c4c3fc390e1 100644
--- a/app/models/group_group_link.rb
+++ b/app/models/group_group_link.rb
@@ -14,7 +14,7 @@ class GroupGroupLink < ApplicationRecord
presence: true
scope :non_guests, -> { where('group_access > ?', Gitlab::Access::GUEST) }
- scope :public_or_visible_to_user, ->(group, user) { where(shared_group: group, shared_with_group: Group.public_or_visible_to_user(user)) } # rubocop:disable Cop/GroupPublicOrVisibleToUser
+ scope :preload_shared_with_groups, -> { preload(:shared_with_group) }
def self.access_options
Gitlab::Access.options_with_owner
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 16b95d2a2b9..9f45160d3a8 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -41,6 +41,11 @@ class ProjectHook < WebHook
super.merge(project: project)
end
+ override :parent
+ def parent
+ project
+ end
+
private
override :web_hooks_disable_failed?
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index 1a466b333a5..80e167b350b 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -2,6 +2,7 @@
class ServiceHook < WebHook
include Presentable
+ extend ::Gitlab::Utils::Override
belongs_to :integration, foreign_key: :service_id
validates :integration, presence: true
@@ -9,4 +10,7 @@ class ServiceHook < WebHook
def execute(data, hook_name = 'service_hook')
super(data, hook_name)
end
+
+ override :parent
+ delegate :parent, to: :integration
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index e8a55abfc8f..3320c13e87b 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -122,6 +122,11 @@ class WebHook < ApplicationRecord
nil
end
+ # Returns the associated Project or Group for the WebHook if one exists.
+ # Overridden by inheriting classes.
+ def parent
+ end
+
# Custom attributes to be included in the worker context.
def application_context
{ related_class: type }
diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb
index bbddc18103a..dc025e576ed 100644
--- a/app/models/instance_configuration.rb
+++ b/app/models/instance_configuration.rb
@@ -117,7 +117,8 @@ class InstanceConfiguration
group_export: application_setting_limit_per_minute(:group_export_limit),
group_export_download: application_setting_limit_per_minute(:group_download_export_limit),
group_import: application_setting_limit_per_minute(:group_import_limit),
- raw_blob: application_setting_limit_per_minute(:raw_blob_request_limit)
+ raw_blob: application_setting_limit_per_minute(:raw_blob_request_limit),
+ user_email_lookup: application_setting_limit_per_minute(:user_email_lookup_limit)
}
end
diff --git a/app/models/integration.rb b/app/models/integration.rb
index 29d96650a81..89b34932e20 100644
--- a/app/models/integration.rb
+++ b/app/models/integration.rb
@@ -92,6 +92,7 @@ class Integration < ApplicationRecord
scope :note_hooks, -> { where(note_events: true, active: true) }
scope :confidential_note_hooks, -> { where(confidential_note_events: true, active: true) }
scope :job_hooks, -> { where(job_events: true, active: true) }
+ scope :archive_trace_hooks, -> { where(archive_trace_events: true, active: true) }
scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) }
scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) }
scope :deployment_hooks, -> { where(deployment_events: true, active: true) }
diff --git a/app/models/integrations/base_chat_notification.rb b/app/models/integrations/base_chat_notification.rb
index ca72de47d30..d0d54a92021 100644
--- a/app/models/integrations/base_chat_notification.rb
+++ b/app/models/integrations/base_chat_notification.rb
@@ -204,7 +204,7 @@ module Integrations
when "wiki_page"
Integrations::ChatMessage::WikiPageMessage.new(data)
when "deployment"
- Integrations::ChatMessage::DeploymentMessage.new(data)
+ Integrations::ChatMessage::DeploymentMessage.new(data) if notify_for_ref?(data)
end
end
@@ -241,7 +241,11 @@ module Integrations
def notify_for_ref?(data)
return true if data[:object_kind] == 'tag_push'
- return true if data.dig(:object_attributes, :tag)
+ return true if data[:object_kind] == 'deployment' && !Feature.enabled?(:chat_notification_deployment_protected_branch_filter, project)
+
+ ref = data[:ref] || data.dig(:object_attributes, :ref)
+ return true if ref.blank? # No need to check protected branches when there is no ref
+ return true if Gitlab::Git.tag_ref?(ref) # Skip protected branch check because it doesn't support tags
notify_for_branch?(data)
end
diff --git a/app/models/integrations/datadog.rb b/app/models/integrations/datadog.rb
index 72e0ca22ac2..b86f0aaa7ef 100644
--- a/app/models/integrations/datadog.rb
+++ b/app/models/integrations/datadog.rb
@@ -2,7 +2,6 @@
module Integrations
class Datadog < Integration
- include ActionView::Helpers::UrlHelper
include HasWebHook
extend Gitlab::Utils::Override
@@ -34,12 +33,21 @@ module Integrations
SUPPORTED_EVENTS
end
+ def supported_events
+ events = super
+
+ return events + ['archive_trace'] if Feature.enabled?(:datadog_integration_logs_collection, parent)
+
+ events
+ end
+
def self.default_test_event
'pipeline'
end
def configurable_events
[] # do not allow to opt out of required hooks
+ # archive_trace is opt-in but we handle it with a more detailed field below
end
def title
@@ -51,7 +59,11 @@ module Integrations
end
def help
- docs_link = link_to s_('DatadogIntegration|How do I set up this integration?'), Rails.application.routes.url_helpers.help_page_url('integration/datadog'), target: '_blank', rel: 'noopener noreferrer'
+ docs_link = ActionController::Base.helpers.link_to(
+ s_('DatadogIntegration|How do I set up this integration?'),
+ Rails.application.routes.url_helpers.help_page_url('integration/datadog'),
+ target: '_blank', rel: 'noopener noreferrer'
+ )
s_('DatadogIntegration|Send CI/CD pipeline information to Datadog to monitor for job failures and troubleshoot performance issues. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
end
@@ -60,7 +72,7 @@ module Integrations
end
def fields
- [
+ f = [
{
type: 'text',
name: 'datadog_site',
@@ -93,7 +105,21 @@ module Integrations
linkClose: '</a>'.html_safe
},
required: true
- },
+ }
+ ]
+
+ if Feature.enabled?(:datadog_integration_logs_collection, parent)
+ f.append({
+ type: 'checkbox',
+ name: 'archive_trace_events',
+ title: s_('Logs'),
+ checkbox_label: s_('Enable logs collection'),
+ help: s_('When enabled, job logs are collected by Datadog and displayed along with pipeline execution traces.'),
+ required: false
+ })
+ end
+
+ f += [
{
type: 'text',
name: 'datadog_service',
@@ -116,6 +142,8 @@ module Integrations
}
}
]
+
+ f
end
override :hook_url
@@ -136,8 +164,7 @@ module Integrations
object_kind = 'job' if object_kind == 'build'
return unless supported_events.include?(object_kind)
- data = data.with_retried_builds if data.respond_to?(:with_retried_builds)
-
+ data = hook_data(data, object_kind)
execute_web_hook!(data, "#{object_kind} hook")
end
@@ -158,5 +185,13 @@ module Integrations
# US3 needs to keep a prefix but other datacenters cannot have the listed "app" prefix
datadog_site.delete_prefix("app.")
end
+
+ def hook_data(data, object_kind)
+ if object_kind == 'pipeline' && data.respond_to?(:with_retried_builds)
+ return data.with_retried_builds
+ end
+
+ data
+ end
end
end
diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb
index d46299de1be..816f5cbe177 100644
--- a/app/models/integrations/jira.rb
+++ b/app/models/integrations/jira.rb
@@ -303,11 +303,7 @@ module Integrations
private
def branch_name(commit)
- if Feature.enabled?(:jira_use_first_ref_by_oid, project, default_enabled: :yaml)
- commit.first_ref_by_oid(project.repository)
- else
- commit.ref_names(project.repository).first
- end
+ commit.first_ref_by_oid(project.repository)
end
def server_info
diff --git a/app/models/internal_id.rb b/app/models/internal_id.rb
index 10d24ab50b2..b502d5e354d 100644
--- a/app/models/internal_id.rb
+++ b/app/models/internal_id.rb
@@ -57,7 +57,7 @@ class InternalId < ApplicationRecord
self.internal_id_transactions_total.increment(
operation: operation,
usage: usage.to_s,
- in_transaction: ActiveRecord::Base.connection.transaction_open?.to_s # rubocop: disable Database/MultipleDatabases
+ in_transaction: InternalId.connection.transaction_open?.to_s
)
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 537e16e5cc3..4f2773f4147 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -48,7 +48,7 @@ class Issue < ApplicationRecord
belongs_to :duplicated_to, class_name: 'Issue'
belongs_to :closed_by, class_name: 'User'
belongs_to :iteration, foreign_key: 'sprint_id'
- belongs_to :work_item_type, class_name: 'WorkItem::Type', inverse_of: :work_items
+ belongs_to :work_item_type, class_name: 'WorkItems::Type', inverse_of: :work_items
belongs_to :moved_to, class_name: 'Issue'
has_one :moved_from, class_name: 'Issue', foreign_key: :moved_to_id
@@ -85,13 +85,16 @@ class Issue < ApplicationRecord
has_many :issue_customer_relations_contacts, class_name: 'CustomerRelations::IssueContact', inverse_of: :issue
has_many :customer_relations_contacts, through: :issue_customer_relations_contacts, source: :contact, class_name: 'CustomerRelations::Contact', inverse_of: :issues
+ alias_attribute :escalation_status, :incident_management_issuable_escalation_status
+
accepts_nested_attributes_for :issuable_severity, update_only: true
accepts_nested_attributes_for :sentry_issue
+ accepts_nested_attributes_for :incident_management_issuable_escalation_status, update_only: true
validates :project, presence: true
validates :issue_type, presence: true
- enum issue_type: WorkItem::Type.base_types
+ enum issue_type: WorkItems::Type.base_types
alias_method :issuing_parent, :project
@@ -230,8 +233,6 @@ class Issue < ApplicationRecord
end
def next_object_by_relative_position(ignoring: nil, order: :asc)
- return super unless Feature.enabled?(:optimized_issue_neighbor_queries, project, default_enabled: :yaml)
-
array_mapping_scope = -> (id_expression) do
relation = Issue.where(Issue.arel_table[:project_id].eq(id_expression))
@@ -600,6 +601,11 @@ class Issue < ApplicationRecord
author&.banned?
end
+ # Necessary until all issues are backfilled and we add a NOT NULL constraint on the DB
+ def work_item_type
+ super || WorkItems::Type.default_by_type(issue_type)
+ end
+
private
def spammable_attribute_changed?
diff --git a/app/models/key.rb b/app/models/key.rb
index a478434538c..933c939fdf5 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -22,7 +22,7 @@ class Key < ApplicationRecord
validates :key,
presence: true,
length: { maximum: 5000 },
- format: { with: /\A(ssh|ecdsa)-.*\Z/ }
+ format: { with: /\A(#{Gitlab::SSHPublicKey.supported_algorithms.join('|')})/ }
validates :fingerprint,
uniqueness: true,
diff --git a/app/models/label.rb b/app/models/label.rb
index a46d6bc5c0f..0ebbb5b9bd3 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -260,7 +260,7 @@ class Label < ApplicationRecord
attributes
end
- def present(attributes)
+ def present(attributes = {})
super(**attributes.merge(presenter_class: ::LabelPresenter))
end
diff --git a/app/models/loose_foreign_keys/deleted_record.rb b/app/models/loose_foreign_keys/deleted_record.rb
index 0fbdd2d8a5b..db82d5bbf29 100644
--- a/app/models/loose_foreign_keys/deleted_record.rb
+++ b/app/models/loose_foreign_keys/deleted_record.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class LooseForeignKeys::DeletedRecord < ApplicationRecord
+class LooseForeignKeys::DeletedRecord < Gitlab::Database::SharedModel
PARTITION_DURATION = 1.day
include PartitionedTable
diff --git a/app/models/loose_foreign_keys/modification_tracker.rb b/app/models/loose_foreign_keys/modification_tracker.rb
index 6eb04608cd9..72a596d2114 100644
--- a/app/models/loose_foreign_keys/modification_tracker.rb
+++ b/app/models/loose_foreign_keys/modification_tracker.rb
@@ -4,7 +4,7 @@ module LooseForeignKeys
class ModificationTracker
MAX_DELETES = 100_000
MAX_UPDATES = 50_000
- MAX_RUNTIME = 3.minutes
+ MAX_RUNTIME = 30.seconds # must be less than the scheduling frequency of the LooseForeignKeys::CleanupWorker cron worker
delegate :monotonic_time, to: :'Gitlab::Metrics::System'
diff --git a/app/models/member.rb b/app/models/member.rb
index 90fb281abf4..6c0503dca3f 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -18,11 +18,15 @@ class Member < ApplicationRecord
AVATAR_SIZE = 40
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
+ STATE_ACTIVE = 0
+ STATE_AWAITING = 1
+
attr_accessor :raw_invite_token
belongs_to :created_by, class_name: "User"
belongs_to :user
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
+ belongs_to :member_namespace, inverse_of: :namespace_members, foreign_key: 'member_namespace_id', class_name: 'Namespace'
has_one :member_task
delegate :name, :username, :email, :last_activity_on, to: :user, prefix: true
@@ -231,14 +235,7 @@ class Member < ApplicationRecord
end
def left_join_users
- users = User.arel_table
- members = Member.arel_table
-
- member_users = members.join(users, Arel::Nodes::OuterJoin)
- .on(members[:user_id].eq(users[:id]))
- .join_sources
-
- joins(member_users)
+ left_outer_joins(:user)
end
def access_for_user_ids(user_ids)
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 1ad4cb6d368..a8a4fbedc41 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -18,7 +18,7 @@ class GroupMember < Member
default_scope { where(source_type: SOURCE_TYPE) } # rubocop:disable Cop/DefaultScope
- scope :of_groups, ->(groups) { where(source_id: groups.select(:id)) }
+ scope :of_groups, ->(groups) { where(source_id: groups&.select(:id)) }
scope :of_ldap_type, -> { where(ldap: true) }
scope :count_users_by_group_id, -> { group(:source_id).count }
scope :with_user, -> (user) { where(user: user) }
diff --git a/app/models/members/project_namespace_member.rb b/app/models/members/project_namespace_member.rb
new file mode 100644
index 00000000000..0e0c52ee3ca
--- /dev/null
+++ b/app/models/members/project_namespace_member.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+# TODO: https://gitlab.com/groups/gitlab-org/-/epics/7054
+# This file is a part of the Consolidate Group and Project member management epic,
+# and will be developed further as we progress through that epic.
+class ProjectNamespaceMember < ProjectMember # rubocop:disable Gitlab/NamespacedClass
+end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index f88aee38d67..cf36e72a565 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1315,9 +1315,9 @@ class MergeRequest < ApplicationRecord
self.target_project.repository.branch_exists?(self.target_branch)
end
- def default_merge_commit_message(include_description: false)
+ def default_merge_commit_message(include_description: false, user: nil)
if self.target_project.merge_commit_template.present? && !include_description
- return ::Gitlab::MergeRequests::CommitMessageGenerator.new(merge_request: self).merge_message
+ return ::Gitlab::MergeRequests::CommitMessageGenerator.new(merge_request: self, current_user: user).merge_message
end
closes_issues_references = visible_closing_issues_for.map do |issue|
@@ -1339,9 +1339,9 @@ class MergeRequest < ApplicationRecord
message.join("\n\n")
end
- def default_squash_commit_message
+ def default_squash_commit_message(user: nil)
if self.target_project.squash_commit_template.present?
- return ::Gitlab::MergeRequests::CommitMessageGenerator.new(merge_request: self).squash_message
+ return ::Gitlab::MergeRequests::CommitMessageGenerator.new(merge_request: self, current_user: user).squash_message
end
title
@@ -1395,20 +1395,6 @@ class MergeRequest < ApplicationRecord
actual_head_pipeline.success?
end
- def environments_for(current_user, latest: false)
- return [] unless diff_head_commit
-
- envs = Environments::EnvironmentsByDeploymentsFinder.new(target_project, current_user,
- ref: target_branch, commit: diff_head_commit, with_tags: true, find_latest: latest).execute
-
- if source_project
- envs.concat Environments::EnvironmentsByDeploymentsFinder.new(source_project, current_user,
- ref: source_branch, commit: diff_head_commit, find_latest: latest).execute
- end
-
- envs.uniq
- end
-
##
# This method is for looking for active environments which created via pipelines for merge requests.
# Since deployments run on a merge request ref (e.g. `refs/merge-requests/:iid/head`),
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 4b1cf2fa217..0dc20e0016c 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -43,6 +43,8 @@ class Namespace < ApplicationRecord
has_many :projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :project_statistics
has_one :namespace_settings, inverse_of: :namespace, class_name: 'NamespaceSetting', autosave: true
+ has_one :namespace_route, foreign_key: :namespace_id, autosave: false, inverse_of: :namespace, class_name: 'Route'
+ has_many :namespace_members, foreign_key: :member_namespace_id, inverse_of: :member_namespace, class_name: 'Member'
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
@@ -299,6 +301,10 @@ class Namespace < ApplicationRecord
user_namespace?
end
+ def first_owner
+ owner
+ end
+
def find_fork_of(project)
return unless project.fork_network
diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb
index 170b29e9e21..ef917c8a22e 100644
--- a/app/models/namespace_setting.rb
+++ b/app/models/namespace_setting.rb
@@ -3,6 +3,7 @@
class NamespaceSetting < ApplicationRecord
include CascadingNamespaceSettingAttribute
include Sanitizable
+ include ChronicDurationAttribute
cascading_attr :delayed_project_removal
@@ -12,17 +13,19 @@ class NamespaceSetting < ApplicationRecord
validate :allow_mfa_for_group
validate :allow_resource_access_token_creation_for_group
- before_save :set_prevent_sharing_groups_outside_hierarchy, if: -> { user_cap_enabled? }
- after_save :disable_project_sharing!, if: -> { user_cap_enabled? }
-
before_validation :normalize_default_branch_name
enum jobs_to_be_done: { basics: 0, move_repository: 1, code_storage: 2, exploring: 3, ci: 4, other: 5 }, _suffix: true
+ chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
+ chronic_duration_attr :subgroup_runner_token_expiration_interval_human_readable, :subgroup_runner_token_expiration_interval
+ chronic_duration_attr :project_runner_token_expiration_interval_human_readable, :project_runner_token_expiration_interval
+
NAMESPACE_SETTINGS_PARAMS = [:default_branch_name, :delayed_project_removal,
:lock_delayed_project_removal, :resource_access_token_creation_allowed,
:prevent_sharing_groups_outside_hierarchy, :new_user_signups_cap,
- :setup_for_company, :jobs_to_be_done].freeze
+ :setup_for_company, :jobs_to_be_done, :runner_token_expiration_interval,
+ :subgroup_runner_token_expiration_interval, :project_runner_token_expiration_interval].freeze
self.primary_key = :namespace_id
@@ -59,18 +62,6 @@ class NamespaceSetting < ApplicationRecord
errors.add(:resource_access_token_creation_allowed, _('is not allowed since the group is not top-level group.'))
end
end
-
- def set_prevent_sharing_groups_outside_hierarchy
- self.prevent_sharing_groups_outside_hierarchy = true
- end
-
- def disable_project_sharing!
- namespace.update_attribute(:share_with_group_lock, true)
- end
-
- def user_cap_enabled?
- new_user_signups_cap.present? && namespace.root?
- end
end
NamespaceSetting.prepend_mod_with('NamespaceSetting')
diff --git a/app/models/namespaces/traversal/linear.rb b/app/models/namespaces/traversal/linear.rb
index 5a5f2a5d063..757a0e40eb3 100644
--- a/app/models/namespaces/traversal/linear.rb
+++ b/app/models/namespaces/traversal/linear.rb
@@ -57,6 +57,13 @@ module Namespaces
traversal_ids.present?
end
+ def use_traversal_ids_for_self_and_hierarchy?
+ return false unless use_traversal_ids?
+ return false unless Feature.enabled?(:use_traversal_ids_for_self_and_hierarchy, root_ancestor, default_enabled: :yaml)
+
+ traversal_ids.present?
+ end
+
def use_traversal_ids_for_ancestors?
return false unless use_traversal_ids?
return false unless Feature.enabled?(:use_traversal_ids_for_ancestors, root_ancestor, default_enabled: :yaml)
@@ -107,6 +114,12 @@ module Namespaces
self_and_descendants.where.not(id: id)
end
+ def self_and_hierarchy
+ return super unless use_traversal_ids_for_self_and_hierarchy?
+
+ self_and_descendants.or(ancestors)
+ end
+
def ancestors(hierarchy_order: nil)
return super unless use_traversal_ids_for_ancestors?
diff --git a/app/models/namespaces/traversal/linear_scopes.rb b/app/models/namespaces/traversal/linear_scopes.rb
index 0dfb7320461..9f0f49e729c 100644
--- a/app/models/namespaces/traversal/linear_scopes.rb
+++ b/app/models/namespaces/traversal/linear_scopes.rb
@@ -22,19 +22,28 @@ module Namespaces
unscoped.where(id: root_ids)
end
- def self_and_ancestors(include_self: true, hierarchy_order: nil)
+ def self_and_ancestors(include_self: true, upto: nil, hierarchy_order: nil)
return super unless use_traversal_ids_for_ancestor_scopes?
+ ancestors_cte, base_cte = ancestor_ctes
+ namespaces = Arel::Table.new(:namespaces)
+
records = unscoped
- .where(id: select('unnest(traversal_ids)'))
+ .with(base_cte.to_arel, ancestors_cte.to_arel)
+ .distinct
+ .from([ancestors_cte.table, namespaces])
+ .where(namespaces[:id].eq(ancestors_cte.table[:ancestor_id]))
.order_by_depth(hierarchy_order)
- .normal_select
- if include_self
- records
- else
- records.where.not(id: all.as_ids)
+ unless include_self
+ records = records.where(ancestors_cte.table[:base_id].not_eq(ancestors_cte.table[:ancestor_id]))
+ end
+
+ if upto
+ records = records.where.not(id: unscoped.where(id: upto).select('unnest(traversal_ids)'))
end
+
+ records
end
def self_and_ancestor_ids(include_self: true)
@@ -150,6 +159,20 @@ module Namespaces
records.where('namespaces.id <> base.id')
end
end
+
+ def ancestor_ctes
+ base_scope = all.select('namespaces.id', 'namespaces.traversal_ids')
+ base_cte = Gitlab::SQL::CTE.new(:base_ancestors_cte, base_scope)
+
+ # We have to alias id with 'AS' to avoid ambiguous column references by calling methods.
+ ancestors_scope = unscoped
+ .unscope(where: [:type])
+ .select('id as base_id', 'unnest(traversal_ids) as ancestor_id')
+ .from(base_cte.table)
+ ancestors_cte = Gitlab::SQL::CTE.new(:ancestors_cte, ancestors_scope)
+
+ [ancestors_cte, base_cte]
+ end
end
end
end
diff --git a/app/models/namespaces/traversal/recursive_scopes.rb b/app/models/namespaces/traversal/recursive_scopes.rb
index 925d9b8bb0c..583c53f8221 100644
--- a/app/models/namespaces/traversal/recursive_scopes.rb
+++ b/app/models/namespaces/traversal/recursive_scopes.rb
@@ -17,8 +17,8 @@ module Namespaces
.where(namespaces: { parent_id: nil })
end
- def self_and_ancestors(include_self: true, hierarchy_order: nil)
- records = Gitlab::ObjectHierarchy.new(all).base_and_ancestors(hierarchy_order: hierarchy_order)
+ def self_and_ancestors(include_self: true, upto: nil, hierarchy_order: nil)
+ records = Gitlab::ObjectHierarchy.new(all).base_and_ancestors(upto: upto, hierarchy_order: hierarchy_order)
if include_self
records
diff --git a/app/models/onboarding_progress.rb b/app/models/onboarding_progress.rb
index c12309d1852..58b7848f7e2 100644
--- a/app/models/onboarding_progress.rb
+++ b/app/models/onboarding_progress.rb
@@ -20,7 +20,14 @@ class OnboardingProgress < ApplicationRecord
:issue_created,
:issue_auto_closed,
:repository_imported,
- :repository_mirrored
+ :repository_mirrored,
+ :secure_dependency_scanning_run,
+ :secure_container_scanning_run,
+ :secure_dast_run,
+ :secure_secret_detection_run,
+ :secure_coverage_fuzzing_run,
+ :secure_api_fuzzing_run,
+ :secure_cluster_image_scanning_run
].freeze
scope :incomplete_actions, -> (actions) do
@@ -52,12 +59,19 @@ class OnboardingProgress < ApplicationRecord
where(namespace: namespace).any?
end
- def register(namespace, action)
- return unless root_namespace?(namespace) && ACTIONS.include?(action)
+ def register(namespace, actions)
+ actions = Array(actions)
+ return unless root_namespace?(namespace) && actions.difference(ACTIONS).empty?
- action_column = column_name(action)
- onboarding_progress = find_by(namespace: namespace, action_column => nil)
- onboarding_progress&.update!(action_column => Time.current)
+ onboarding_progress = find_by(namespace: namespace)
+ return unless onboarding_progress
+
+ now = Time.current
+ nil_actions = actions.select { |action| onboarding_progress[column_name(action)].nil? }
+ return if nil_actions.empty?
+
+ updates = nil_actions.inject({}) { |sum, action| sum.merge!({ column_name(action) => now }) }
+ onboarding_progress.update!(updates)
end
def completed?(namespace, action)
diff --git a/app/models/packages/debian/group_distribution.rb b/app/models/packages/debian/group_distribution.rb
index 50c1ec9f163..01938f4a2ec 100644
--- a/app/models/packages/debian/group_distribution.rb
+++ b/app/models/packages/debian/group_distribution.rb
@@ -12,8 +12,4 @@ class Packages::Debian::GroupDistribution < ApplicationRecord
.for_projects(group.all_projects.public_only)
.with_debian_codename(codename)
end
-
- def package_files
- ::Packages::PackageFile.for_package_ids(packages.select(:id))
- end
end
diff --git a/app/models/packages/debian/project_distribution.rb b/app/models/packages/debian/project_distribution.rb
index 5ac60d789b3..73777e3b9d8 100644
--- a/app/models/packages/debian/project_distribution.rb
+++ b/app/models/packages/debian/project_distribution.rb
@@ -9,5 +9,4 @@ class Packages::Debian::ProjectDistribution < ApplicationRecord
has_many :publications, class_name: 'Packages::Debian::Publication', inverse_of: :distribution, foreign_key: :distribution_id
has_many :packages, class_name: 'Packages::Package', through: :publications
- has_many :package_files, class_name: 'Packages::PackageFile', through: :packages
end
diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb
index 962a1057a22..52dd0aba43b 100644
--- a/app/models/packages/package.rb
+++ b/app/models/packages/package.rb
@@ -5,9 +5,10 @@ class Packages::Package < ApplicationRecord
include Gitlab::SQL::Pattern
include UsageStatistics
include Gitlab::Utils::StrongMemoize
+ include Packages::Installable
DISPLAYABLE_STATUSES = [:default, :error].freeze
- INSTALLABLE_STATUSES = [:default].freeze
+ INSTALLABLE_STATUSES = [:default, :hidden].freeze
enum package_type: {
maven: 1,
@@ -31,6 +32,9 @@ class Packages::Package < ApplicationRecord
# package_files must be destroyed by ruby code in order to properly remove carrierwave uploads and update project statistics
has_many :package_files, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
+ # TODO: put the installable default scope on the :package_files association once the dependent: :destroy is removed
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/349191
+ has_many :installable_package_files, -> { installable }, class_name: 'Packages::PackageFile', inverse_of: :package
has_many :dependency_links, inverse_of: :package, class_name: 'Packages::DependencyLink'
has_many :tags, inverse_of: :package, class_name: 'Packages::Tag'
has_one :conan_metadatum, inverse_of: :package, class_name: 'Packages::Conan::Metadatum'
@@ -100,9 +104,7 @@ class Packages::Package < ApplicationRecord
scope :without_version_like, -> (version) { where.not(arel_table[:version].matches(version)) }
scope :with_package_type, ->(package_type) { where(package_type: package_type) }
scope :without_package_type, ->(package_type) { where.not(package_type: package_type) }
- scope :with_status, ->(status) { where(status: status) }
scope :displayable, -> { with_status(DISPLAYABLE_STATUSES) }
- scope :installable, -> { with_status(INSTALLABLE_STATUSES) }
scope :including_project_route, -> { includes(project: { namespace: :route }) }
scope :including_tags, -> { includes(:tags) }
scope :including_dependency_links, -> { includes(dependency_links: :dependency) }
@@ -131,7 +133,7 @@ class Packages::Package < ApplicationRecord
scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) }
scope :has_version, -> { where.not(version: nil) }
- scope :preload_files, -> { preload(:package_files) }
+ scope :preload_files, -> { Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml) ? preload(:installable_package_files) : preload(:package_files) }
scope :preload_pipelines, -> { preload(pipelines: :user) }
scope :last_of_each_version, -> { where(id: all.select('MAX(id) AS id').group(:version)) }
scope :limit_recent, ->(limit) { order_created_desc.limit(limit) }
diff --git a/app/models/packages/package_file.rb b/app/models/packages/package_file.rb
index 87c9f56cc41..072ff4a3a5f 100644
--- a/app/models/packages/package_file.rb
+++ b/app/models/packages/package_file.rb
@@ -2,12 +2,18 @@
class Packages::PackageFile < ApplicationRecord
include UpdateProjectStatistics
include FileStoreMounter
+ include Packages::Installable
+ include Packages::Destructible
+
+ INSTALLABLE_STATUSES = [:default].freeze
delegate :project, :project_id, to: :package
delegate :conan_file_type, to: :conan_file_metadatum
delegate :file_type, :dsc?, :component, :architecture, :fields, to: :debian_file_metadatum, prefix: :debian
delegate :channel, :metadata, to: :helm_file_metadatum, prefix: :helm
+ enum status: { default: 0, pending_destruction: 1, processing: 2, error: 3 }
+
belongs_to :package
# used to move the linked file within object storage
@@ -48,9 +54,12 @@ class Packages::PackageFile < ApplicationRecord
end
scope :for_helm_with_channel, ->(project, channel) do
- joins(:package).merge(project.packages.helm.installable)
- .joins(:helm_file_metadatum)
- .where(packages_helm_file_metadata: { channel: channel })
+ result = joins(:package)
+ .merge(project.packages.helm.installable)
+ .joins(:helm_file_metadatum)
+ .where(packages_helm_file_metadata: { channel: channel })
+ result = result.installable if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ result
end
scope :with_conan_file_type, ->(file_type) do
@@ -94,14 +103,19 @@ class Packages::PackageFile < ApplicationRecord
skip_callback :commit, :after, :remove_previously_stored_file, if: :execute_move_in_object_storage?
after_commit :move_in_object_storage, if: :execute_move_in_object_storage?
- # Returns the most recent package files for *each* of the given packages.
+ # Returns the most recent installable package file for *each* of the given packages.
# The order is not guaranteed.
def self.most_recent_for(packages, extra_join: nil, extra_where: nil)
cte_name = :packages_cte
cte = Gitlab::SQL::CTE.new(cte_name, packages.select(:id))
- package_files = ::Packages::PackageFile.limit_recent(1)
- .where(arel_table[:package_id].eq(Arel.sql("#{cte_name}.id")))
+ package_files = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ ::Packages::PackageFile.installable.limit_recent(1)
+ .where(arel_table[:package_id].eq(Arel.sql("#{cte_name}.id")))
+ else
+ ::Packages::PackageFile.limit_recent(1)
+ .where(arel_table[:package_id].eq(Arel.sql("#{cte_name}.id")))
+ end
package_files = package_files.joins(extra_join) if extra_join
package_files = package_files.where(extra_where) if extra_where
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index 0c5a155d48a..c21027455b1 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -46,9 +46,6 @@ class PagesDomain < ApplicationRecord
algorithm: 'aes-256-cbc'
after_initialize :set_verification_code
- after_create :update_daemon
- after_update :update_daemon, if: :saved_change_to_pages_config?
- after_destroy :update_daemon
scope :for_project, ->(project) { where(project: project) }
@@ -233,32 +230,6 @@ class PagesDomain < ApplicationRecord
self.verification_code = SecureRandom.hex(16)
end
- # rubocop: disable CodeReuse/ServiceClass
- def update_daemon
- return if usage_serverless?
- return unless pages_deployed?
-
- run_after_commit { PagesUpdateConfigurationWorker.perform_async(project_id) }
- end
- # rubocop: enable CodeReuse/ServiceClass
-
- def saved_change_to_pages_config?
- saved_change_to_project_id? ||
- saved_change_to_domain? ||
- saved_change_to_certificate? ||
- saved_change_to_key? ||
- became_enabled? ||
- became_disabled?
- end
-
- def became_enabled?
- enabled_until.present? && !enabled_until_before_last_save.present?
- end
-
- def became_disabled?
- !enabled_until.present? && enabled_until_before_last_save.present?
- end
-
def validate_matching_key
unless has_matching_key?
self.errors.add(:key, "doesn't match the certificate")
diff --git a/app/models/preloaders/environments/deployment_preloader.rb b/app/models/preloaders/environments/deployment_preloader.rb
new file mode 100644
index 00000000000..fcf892698bb
--- /dev/null
+++ b/app/models/preloaders/environments/deployment_preloader.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Preloaders
+ module Environments
+ # This class is to batch-load deployments of multiple environments.
+ # The deployments to batch-load are fetched using UNION of N selects in a single query instead of default scoping with `IN (environment_id1, environment_id2 ...)`.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/345672#note_761852224 for more details.
+ class DeploymentPreloader
+ attr_reader :environments
+
+ def initialize(environments)
+ @environments = environments
+ end
+
+ def execute_with_union(association_name, association_attributes)
+ load_deployment_association(association_name, association_attributes)
+ end
+
+ private
+
+ def load_deployment_association(association_name, association_attributes)
+ return unless environments.present?
+
+ union_arg = environments.inject([]) do |result, environment|
+ result << environment.association(association_name).scope
+ end
+
+ union_sql = Deployment.from_union(union_arg).to_sql
+
+ deployments = Deployment
+ .from("(#{union_sql}) #{::Deployment.table_name}")
+ .preload(association_attributes)
+
+ deployments_by_environment_id = deployments.index_by(&:environment_id)
+
+ environments.each do |environment|
+ environment.association(association_name).target = deployments_by_environment_id[environment.id]
+ environment.association(association_name).loaded!
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/project.rb b/app/models/project.rb
index a751e8adeb0..f2b3db684ae 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -37,6 +37,7 @@ class Project < ApplicationRecord
include EachBatch
include GitlabRoutingHelper
include BulkMemberAccessLoad
+ include RunnerTokenExpirationInterval
extend Gitlab::Cache::RequestCache
extend Gitlab::Utils::Override
@@ -340,6 +341,7 @@ class Project < ApplicationRecord
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
has_many :variables, class_name: 'Ci::Variable'
has_many :triggers, class_name: 'Ci::Trigger'
+ has_many :secure_files, class_name: 'Ci::SecureFile'
has_many :environments
has_many :environments_for_dashboard, -> { from(with_rank.unfoldered.available, :environments).where('rank <= 3') }, class_name: 'Environment'
has_many :deployments
@@ -436,6 +438,7 @@ class Project < ApplicationRecord
prefix: :import, to: :import_state, allow_nil: true
delegate :squash_always?, :squash_never?, :squash_enabled_by_default?, :squash_readonly?, to: :project_setting
delegate :squash_option, :squash_option=, to: :project_setting
+ delegate :mr_default_target_self, :mr_default_target_self=, to: :project_setting
delegate :previous_default_branch, :previous_default_branch=, to: :project_setting
delegate :no_import?, to: :import_state, allow_nil: true
delegate :name, to: :owner, allow_nil: true, prefix: true
@@ -452,7 +455,8 @@ class Project < ApplicationRecord
delegate :job_token_scope_enabled, :job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :keep_latest_artifact, :keep_latest_artifact=, to: :ci_cd_settings, allow_nil: true
delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, to: :ci_cd_settings, allow_nil: true
- delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
+ delegate :runner_token_expiration_interval, :runner_token_expiration_interval=, :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval_human_readable=, to: :ci_cd_settings, allow_nil: true
+ delegate :actual_limits, :actual_plan_name, :actual_plan, to: :namespace, allow_nil: true
delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?,
:allow_merge_on_skipped_pipeline=, :has_confluence?, :has_shimo?,
to: :project_setting
@@ -983,7 +987,7 @@ class Project < ApplicationRecord
end
def context_commits_enabled?
- Feature.enabled?(:context_commits, default_enabled: true)
+ Feature.enabled?(:context_commits, self, default_enabled: :yaml)
end
# LFS and hashed repository storage are required for using Design Management.
@@ -1512,11 +1516,11 @@ class Project < ApplicationRecord
group || namespace.try(:owner)
end
- def default_owner
+ def first_owner
obj = owner
- if obj.respond_to?(:default_owner)
- obj.default_owner
+ if obj.respond_to?(:first_owner)
+ obj.first_owner
else
obj
end
@@ -1660,7 +1664,7 @@ class Project < ApplicationRecord
attrs
end
- def project_member(user)
+ def member(user)
if project_members.loaded?
project_members.find { |member| member.user_id == user.id }
else
@@ -1773,17 +1777,12 @@ class Project < ApplicationRecord
def all_runners
Ci::Runner.from_union([runners, group_runners, shared_runners])
- .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339937')
end
def all_available_runners
Ci::Runner.from_union([runners, group_runners, available_shared_runners])
- .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339937')
end
- # Once issue 339937 is fixed, please search for all mentioned of
- # https://gitlab.com/gitlab-org/gitlab/-/issues/339937,
- # and remove the allow_cross_joins_across_databases.
def active_runners
strong_memoize(:active_runners) do
all_available_runners.active
@@ -1791,9 +1790,7 @@ class Project < ApplicationRecord
end
def any_online_runners?(&block)
- ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339937') do
- online_runners_with_tags.any?(&block)
- end
+ online_runners_with_tags.any?(&block)
end
def valid_runners_token?(token)
@@ -2702,6 +2699,10 @@ class Project < ApplicationRecord
ci_cd_settings.keep_latest_artifact?
end
+ def runner_token_expiration_interval
+ ci_cd_settings&.runner_token_expiration_interval
+ end
+
def group_runners_enabled?
return false unless ci_cd_settings
@@ -2733,6 +2734,17 @@ class Project < ApplicationRecord
end
end
+ def enforced_runner_token_expiration_interval
+ all_parent_groups = Gitlab::ObjectHierarchy.new(Group.where(id: group)).base_and_ancestors
+ all_group_settings = NamespaceSetting.where(namespace_id: all_parent_groups)
+ group_interval = all_group_settings.where.not(project_runner_token_expiration_interval: nil).minimum(:project_runner_token_expiration_interval)&.seconds
+
+ [
+ Gitlab::CurrentSettings.project_runner_token_expiration_interval&.seconds,
+ group_interval
+ ].compact.min
+ end
+
private
# overridden in EE
diff --git a/app/models/project_ci_cd_setting.rb b/app/models/project_ci_cd_setting.rb
index c0c2ea42d46..28a493cae33 100644
--- a/app/models/project_ci_cd_setting.rb
+++ b/app/models/project_ci_cd_setting.rb
@@ -1,9 +1,11 @@
# frozen_string_literal: true
class ProjectCiCdSetting < ApplicationRecord
+ include ChronicDurationAttribute
+
belongs_to :project, inverse_of: :ci_cd_settings
- DEFAULT_GIT_DEPTH = 50
+ DEFAULT_GIT_DEPTH = 20
before_create :set_default_git_depth
@@ -17,6 +19,8 @@ class ProjectCiCdSetting < ApplicationRecord
default_value_for :forward_deployment_enabled, true
+ chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
+
def forward_deployment_enabled?
super && ::Feature.enabled?(:forward_deployment_enabled, project, default_enabled: true)
end
diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb
index fc834286876..4e37174e604 100644
--- a/app/models/project_setting.rb
+++ b/app/models/project_setting.rb
@@ -22,6 +22,16 @@ class ProjectSetting < ApplicationRecord
def squash_readonly?
%w[always never].include?(squash_option)
end
+
+ validate :validates_mr_default_target_self
+
+ private
+
+ def validates_mr_default_target_self
+ if mr_default_target_self_changed? && !project.forked?
+ errors.add :mr_default_target_self, _('This setting is allowed for forked projects only')
+ end
+ end
end
ProjectSetting.prepend_mod
diff --git a/app/models/protectable_dropdown.rb b/app/models/protectable_dropdown.rb
index e1336be9528..855876f2ec9 100644
--- a/app/models/protectable_dropdown.rb
+++ b/app/models/protectable_dropdown.rb
@@ -2,6 +2,10 @@
class ProtectableDropdown
REF_TYPES = %i[branches tags].freeze
+ REF_NAME_METHODS = {
+ branches: :branch_names,
+ tags: :tag_names
+ }.freeze
def initialize(project, ref_type)
raise ArgumentError, "invalid ref type `#{ref_type}`" unless ref_type.in?(REF_TYPES)
@@ -23,12 +27,12 @@ class ProtectableDropdown
private
- def refs
- @project.repository.public_send(@ref_type) # rubocop:disable GitlabSecurity/PublicSend
+ def ref_names
+ @project.repository.public_send(ref_name_method) # rubocop:disable GitlabSecurity/PublicSend
end
- def ref_names
- refs.map(&:name)
+ def ref_name_method
+ REF_NAME_METHODS[@ref_type]
end
def protections
diff --git a/app/models/ref_matcher.rb b/app/models/ref_matcher.rb
index fa7d2c0f06c..46f4ce0ecc7 100644
--- a/app/models/ref_matcher.rb
+++ b/app/models/ref_matcher.rb
@@ -5,10 +5,10 @@ class RefMatcher
@ref_name_or_pattern = ref_name_or_pattern
end
- # Returns all branches/tags (among the given list of refs [`Gitlab::Git::Branch`])
+ # Returns all branches/tags (among the given list of refs [`Gitlab::Git::Branch`] or their names [`String`])
# that match the current protected ref.
def matching(refs)
- refs.select { |ref| matches?(ref.name) }
+ refs.select { |ref| ref.is_a?(String) ? matches?(ref) : matches?(ref.name) }
end
# Checks if the protected ref matches the given ref name.
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 645cc9773bd..be8e530c650 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -191,7 +191,7 @@ class Repository
end
def find_tag(name)
- if @tags.blank? && Feature.enabled?(:find_tag_via_gitaly, project, default_enabled: :yaml)
+ if @tags.blank?
raw_repository.find_tag(name)
else
tags.find { |tag| tag.name == name }
diff --git a/app/models/route.rb b/app/models/route.rb
index fcc8459d6e5..12b2d5c5bb2 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -5,6 +5,7 @@ class Route < ApplicationRecord
include Gitlab::SQL::Pattern
belongs_to :source, polymorphic: true, inverse_of: :route # rubocop:disable Cop/PolymorphicAssociations
+ belongs_to :namespace, inverse_of: :namespace_route
validates :source, presence: true
validates :path,
diff --git a/app/models/user.rb b/app/models/user.rb
index a39da30220a..a587723053f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -48,7 +48,7 @@ class User < ApplicationRecord
add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) }
add_authentication_token_field :feed_token
- add_authentication_token_field :static_object_token
+ add_authentication_token_field :static_object_token, encrypted: :optional
default_value_for :admin, false
default_value_for(:external) { Gitlab::CurrentSettings.user_default_external }
@@ -81,6 +81,7 @@ class User < ApplicationRecord
# This module adds async behaviour to Devise emails
# and should be added after Devise modules are initialized.
include AsyncDeviseEmail
+ include ForcedEmailConfirmation
MINIMUM_INACTIVE_DAYS = 90
@@ -250,7 +251,7 @@ class User < ApplicationRecord
validate :notification_email_verified, if: :notification_email_changed?
validate :public_email_verified, if: :public_email_changed?
validate :commit_email_verified, if: :commit_email_changed?
- validate :signup_email_valid?, on: :create, if: ->(user) { !user.created_by_id }
+ validate :email_allowed_by_restrictions?, if: ->(user) { user.new_record? ? !user.created_by_id : user.email_changed? }
validate :check_username_format, if: :username_changed?
validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids,
@@ -330,6 +331,7 @@ class User < ApplicationRecord
delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true
delegate :pronunciation, :pronunciation=, to: :user_detail, allow_nil: true
delegate :registration_objective, :registration_objective=, to: :user_detail, allow_nil: true
+ delegate :requires_credit_card_verification, :requires_credit_card_verification=, to: :user_detail, allow_nil: true
accepts_nested_attributes_for :user_preference, update_only: true
accepts_nested_attributes_for :user_detail, update_only: true
@@ -465,7 +467,7 @@ class User < ApplicationRecord
scope :dormant, -> { with_state(:active).human_or_service_user.where('last_activity_on <= ?', MINIMUM_INACTIVE_DAYS.day.ago.to_date) }
scope :with_no_activity, -> { with_state(:active).human_or_service_user.where(last_activity_on: nil) }
scope :by_provider_and_extern_uid, ->(provider, extern_uid) { joins(:identities).merge(Identity.with_extern_uid(provider, extern_uid)) }
- scope :get_ids_by_username, -> (username) { where(username: username).pluck(:id) }
+ scope :by_ids_or_usernames, -> (ids, usernames) { where(username: usernames).or(where(id: ids)) }
strip_attributes! :name
@@ -536,27 +538,15 @@ class User < ApplicationRecord
end
def self.with_two_factor
- with_u2f_registrations = <<-SQL
- EXISTS (
- SELECT *
- FROM u2f_registrations AS u2f
- WHERE u2f.user_id = users.id
- ) OR users.otp_required_for_login = ?
- OR
- EXISTS (
- SELECT *
- FROM webauthn_registrations AS webauthn
- WHERE webauthn.user_id = users.id
- )
- SQL
-
- where(with_u2f_registrations, true)
+ where(otp_required_for_login: true)
+ .or(where_exists(U2fRegistration.where(U2fRegistration.arel_table[:user_id].eq(arel_table[:id]))))
+ .or(where_exists(WebauthnRegistration.where(WebauthnRegistration.arel_table[:user_id].eq(arel_table[:id]))))
end
def self.without_two_factor
- joins("LEFT OUTER JOIN u2f_registrations AS u2f ON u2f.user_id = users.id
- LEFT OUTER JOIN webauthn_registrations AS webauthn ON webauthn.user_id = users.id")
- .where("u2f.id IS NULL AND webauthn.id IS NULL AND users.otp_required_for_login = ?", false)
+ where
+ .missing(:u2f_registrations, :webauthn_registrations)
+ .where(otp_required_for_login: false)
end
#
@@ -720,13 +710,19 @@ class User < ApplicationRecord
.take(1) # at most 1 record as there is a unique constraint
where(
- fuzzy_arel_match(:name, query)
- .or(fuzzy_arel_match(:username, query))
+ fuzzy_arel_match(:name, query, use_minimum_char_limit: user_search_minimum_char_limit)
+ .or(fuzzy_arel_match(:username, query, use_minimum_char_limit: user_search_minimum_char_limit))
.or(arel_table[:email].eq(query))
.or(arel_table[:id].eq(matched_by_email_user_id))
)
end
+ # This method is overridden in JiHu.
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/348509
+ def user_search_minimum_char_limit
+ true
+ end
+
def by_login(login)
return unless login
@@ -841,6 +837,10 @@ class User < ApplicationRecord
def single_user
User.non_internal.first if single_user?
end
+
+ def get_ids_by_ids_or_usernames(ids, usernames)
+ by_ids_or_usernames(ids, usernames).pluck(:id)
+ end
end
#
@@ -1337,7 +1337,7 @@ class User < ApplicationRecord
def can_leave_project?(project)
project.namespace != namespace &&
- project.project_member(self)
+ project.member(self)
end
def full_website_url
@@ -1536,8 +1536,8 @@ class User < ApplicationRecord
end
end
- def manageable_namespaces
- @manageable_namespaces ||= [namespace] + manageable_groups
+ def forkable_namespaces
+ @forkable_namespaces ||= [namespace] + manageable_groups(include_groups_with_developer_maintainer_access: true)
end
def manageable_groups(include_groups_with_developer_maintainer_access: false)
@@ -1606,23 +1606,32 @@ class User < ApplicationRecord
def ci_owned_runners
@ci_owned_runners ||= begin
- project_runners = Ci::RunnerProject
- .where(project: authorized_projects(Gitlab::Access::MAINTAINER))
- .joins(:runner)
- .select('ci_runners.*')
-
- group_runners = Ci::RunnerNamespace
- .where(namespace_id: owned_groups.self_and_descendant_ids)
- .joins(:runner)
- .select('ci_runners.*')
-
- Ci::Runner.from_union([project_runners, group_runners]).allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436')
+ if ci_owned_runners_cross_joins_fix_enabled?
+ Ci::Runner
+ .from_union([ci_owned_project_runners_from_project_members,
+ ci_owned_project_runners_from_group_members,
+ ci_owned_group_runners])
+ else
+ Ci::Runner
+ .from_union([ci_legacy_owned_project_runners, ci_legacy_owned_group_runners])
+ .allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436')
+ end
end
end
def owns_runner?(runner)
- ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do
+ if ci_owned_runners_cross_joins_fix_enabled?
ci_owned_runners.exists?(runner.id)
+ else
+ ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/336436') do
+ ci_owned_runners.exists?(runner.id)
+ end
+ end
+ end
+
+ def ci_owned_runners_cross_joins_fix_enabled?
+ strong_memoize(:ci_owned_runners_cross_joins_fix_enabled) do
+ Feature.enabled?(:ci_owned_runners_cross_joins_fix, self, default_enabled: :yaml)
end
end
@@ -1902,6 +1911,10 @@ class User < ApplicationRecord
true
end
+ def can_log_in_with_non_expired_password?
+ can?(:log_in) && !password_expired_if_applicable?
+ end
+
def can_be_deactivated?
active? && no_recent_activity? && !internal?
end
@@ -1980,18 +1993,22 @@ class User < ApplicationRecord
ci_job_token_scope.present?
end
- # override from Devise::Confirmable
+ # override from Devise::Models::Confirmable
#
# Add the primary email to user.emails (or confirm it if it was already
# present) when the primary email is confirmed.
- def confirm(*args)
- saved = super(*args)
+ def confirm(args = {})
+ saved = super(args)
return false unless saved
email_to_confirm = self.emails.find_by(email: self.email)
if email_to_confirm.present?
- email_to_confirm.confirm(*args)
+ if skip_confirmation_period_expiry_check
+ email_to_confirm.force_confirm(args)
+ else
+ email_to_confirm.confirm(args)
+ end
else
add_primary_email_to_emails!
end
@@ -2142,14 +2159,14 @@ class User < ApplicationRecord
end
end
- def signup_email_valid?
+ def email_allowed_by_restrictions?
error = validate_admin_signup_restrictions(email)
errors.add(:email, error) if error
end
def signup_email_invalid_message
- _('is not allowed for sign-up.')
+ self.new_record? ? _('is not allowed for sign-up.') : _('is not allowed.')
end
def check_username_format
@@ -2192,6 +2209,50 @@ class User < ApplicationRecord
::Gitlab::Auth::Ldap::Access.allowed?(self)
end
+
+ def ci_legacy_owned_project_runners
+ Ci::RunnerProject
+ .select('ci_runners.*')
+ .joins(:runner)
+ .where(project: authorized_projects(Gitlab::Access::MAINTAINER))
+ end
+
+ def ci_legacy_owned_group_runners
+ Ci::RunnerNamespace
+ .select('ci_runners.*')
+ .joins(:runner)
+ .where(namespace_id: owned_groups.self_and_descendant_ids)
+ end
+
+ def ci_owned_project_runners_from_project_members
+ Ci::RunnerProject
+ .select('ci_runners.*')
+ .joins(:runner)
+ .where(project: project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id))
+ end
+
+ def ci_owned_project_runners_from_group_members
+ Ci::RunnerProject
+ .select('ci_runners.*')
+ .joins(:runner)
+ .joins('JOIN ci_project_mirrors ON ci_project_mirrors.project_id = ci_runner_projects.project_id')
+ .joins('JOIN ci_namespace_mirrors ON ci_namespace_mirrors.namespace_id = ci_project_mirrors.namespace_id')
+ .merge(ci_namespace_mirrors_for_group_members(Gitlab::Access::MAINTAINER))
+ end
+
+ def ci_owned_group_runners
+ Ci::RunnerNamespace
+ .select('ci_runners.*')
+ .joins(:runner)
+ .joins('JOIN ci_namespace_mirrors ON ci_namespace_mirrors.namespace_id = ci_runner_namespaces.namespace_id')
+ .merge(ci_namespace_mirrors_for_group_members(Gitlab::Access::OWNER))
+ end
+
+ def ci_namespace_mirrors_for_group_members(level)
+ Ci::NamespaceMirror.contains_any_of_namespaces(
+ group_members.where('access_level >= ?', level).pluck(:source_id)
+ )
+ end
end
User.prepend_mod_with('User')
diff --git a/app/models/users/callout.rb b/app/models/users/callout.rb
index 9ce0beed3b3..8394192c5ae 100644
--- a/app/models/users/callout.rb
+++ b/app/models/users/callout.rb
@@ -40,7 +40,8 @@ module Users
profile_personal_access_token_expiry: 37, # EE-only
terraform_notification_dismissed: 38,
security_newsletter_callout: 39,
- verification_reminder: 40 # EE-only
+ verification_reminder: 40, # EE-only
+ ci_deprecation_warning_for_types_keyword: 41
}
validates :feature_name,
diff --git a/app/models/work_item.rb b/app/models/work_item.rb
new file mode 100644
index 00000000000..02f52f04c85
--- /dev/null
+++ b/app/models/work_item.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class WorkItem < Issue
+ self.table_name = 'issues'
+ self.inheritance_column = :_type_disabled
+end
diff --git a/app/models/work_item/type.rb b/app/models/work_item/type.rb
deleted file mode 100644
index 3acb9c0011c..00000000000
--- a/app/models/work_item/type.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-# Note: initial thinking behind `icon_name` is for it to do triple duty:
-# 1. one of our svg icon names, such as `external-link` or a new one `bug`
-# 2. if it's an absolute url, then url to a user uploaded icon/image
-# 3. an emoji, with the format of `:smile:`
-class WorkItem::Type < ApplicationRecord
- self.table_name = 'work_item_types'
-
- include CacheMarkdownField
-
- # Base types need to exist on the DB on app startup
- # This constant is used by the DB seeder
- BASE_TYPES = {
- issue: { name: 'Issue', icon_name: 'issue-type-issue', enum_value: 0 },
- incident: { name: 'Incident', icon_name: 'issue-type-incident', enum_value: 1 },
- test_case: { name: 'Test Case', icon_name: 'issue-type-test-case', enum_value: 2 }, ## EE-only
- requirement: { name: 'Requirement', icon_name: 'issue-type-requirements', enum_value: 3 }, ## EE-only
- task: { name: 'Task', icon_name: 'issue-type-task', enum_value: 4 }
- }.freeze
-
- cache_markdown_field :description, pipeline: :single_line
-
- enum base_type: BASE_TYPES.transform_values { |value| value[:enum_value] }
-
- belongs_to :namespace, optional: true
- has_many :work_items, class_name: 'Issue', foreign_key: :work_item_type_id, inverse_of: :work_item_type
-
- before_validation :strip_whitespace
-
- # TODO: review validation rules
- # https://gitlab.com/gitlab-org/gitlab/-/issues/336919
- validates :name, presence: true
- validates :name, uniqueness: { case_sensitive: false, scope: [:namespace_id] }
- validates :name, length: { maximum: 255 }
- validates :icon_name, length: { maximum: 255 }
-
- def self.default_by_type(type)
- find_by(namespace_id: nil, base_type: type)
- end
-
- def self.default_issue_type
- default_by_type(:issue)
- end
-
- def self.allowed_types_for_issues
- base_types.keys.excluding('task')
- end
-
- private
-
- def strip_whitespace
- name&.strip!
- end
-end
diff --git a/app/models/work_items/type.rb b/app/models/work_items/type.rb
new file mode 100644
index 00000000000..494c4f5abe4
--- /dev/null
+++ b/app/models/work_items/type.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+# Note: initial thinking behind `icon_name` is for it to do triple duty:
+# 1. one of our svg icon names, such as `external-link` or a new one `bug`
+# 2. if it's an absolute url, then url to a user uploaded icon/image
+# 3. an emoji, with the format of `:smile:`
+module WorkItems
+ class Type < ApplicationRecord
+ self.table_name = 'work_item_types'
+
+ include CacheMarkdownField
+
+ # Base types need to exist on the DB on app startup
+ # This constant is used by the DB seeder
+ BASE_TYPES = {
+ issue: { name: 'Issue', icon_name: 'issue-type-issue', enum_value: 0 },
+ incident: { name: 'Incident', icon_name: 'issue-type-incident', enum_value: 1 },
+ test_case: { name: 'Test Case', icon_name: 'issue-type-test-case', enum_value: 2 }, ## EE-only
+ requirement: { name: 'Requirement', icon_name: 'issue-type-requirements', enum_value: 3 }, ## EE-only
+ task: { name: 'Task', icon_name: 'issue-type-task', enum_value: 4 }
+ }.freeze
+
+ cache_markdown_field :description, pipeline: :single_line
+
+ enum base_type: BASE_TYPES.transform_values { |value| value[:enum_value] }
+
+ belongs_to :namespace, optional: true
+ has_many :work_items, class_name: 'Issue', foreign_key: :work_item_type_id, inverse_of: :work_item_type
+
+ before_validation :strip_whitespace
+
+ # TODO: review validation rules
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/336919
+ validates :name, presence: true
+ validates :name, uniqueness: { case_sensitive: false, scope: [:namespace_id] }
+ validates :name, length: { maximum: 255 }
+ validates :icon_name, length: { maximum: 255 }
+
+ scope :default, -> { where(namespace: nil) }
+ scope :order_by_name_asc, -> { order('LOWER(name)') }
+
+ def self.default_by_type(type)
+ find_by(namespace_id: nil, base_type: type)
+ end
+
+ def self.default_issue_type
+ default_by_type(:issue)
+ end
+
+ def self.allowed_types_for_issues
+ base_types.keys.excluding('task')
+ end
+
+ def default?
+ namespace.blank?
+ end
+
+ private
+
+ def strip_whitespace
+ name&.strip!
+ end
+ end
+end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index c3b4b163cb4..2a2ddf29899 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -9,7 +9,7 @@ class GlobalPolicy < BasePolicy
with_options scope: :user, score: 0
condition(:access_locked) { @user&.access_locked? }
- condition(:can_create_fork, scope: :user) { @user && @user.manageable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } }
+ condition(:can_create_fork, scope: :user) { @user && @user.forkable_namespaces.any? { |namespace| @user.can?(:create_projects, namespace) } }
condition(:required_terms_not_accepted, scope: :user, score: 0) do
@user&.required_terms_not_accepted?
diff --git a/app/policies/group_member_policy.rb b/app/policies/group_member_policy.rb
index f7a7286aba7..a394b63fc8e 100644
--- a/app/policies/group_member_policy.rb
+++ b/app/policies/group_member_policy.rb
@@ -5,6 +5,7 @@ class GroupMemberPolicy < BasePolicy
with_scope :subject
condition(:last_owner) { @subject.group.member_last_owner?(@subject) || @subject.group.member_last_blocked_owner?(@subject) }
+ condition(:project_bot) { @subject.user&.project_bot? && @subject.group.member?(@subject.user) }
desc "Membership is users' own"
with_score 0
@@ -20,11 +21,13 @@ class GroupMemberPolicy < BasePolicy
prevent :destroy_group_member
end
- rule { can?(:admin_group_member) }.policy do
+ rule { ~project_bot & can?(:admin_group_member) }.policy do
enable :update_group_member
enable :destroy_group_member
end
+ rule { project_bot & can?(:admin_group_member) }.enable :destroy_project_bot_member
+
rule { is_target_user }.policy do
enable :destroy_group_member
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 5c4990ffd9b..fee47fe0ae9 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -23,6 +23,9 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
condition(:parent_share_with_group_locked, scope: :subject) { @subject.parent&.share_with_group_lock? }
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
+ desc "User is a project bot"
+ condition(:project_bot) { user.project_bot? && access_level >= GroupMember::GUEST }
+
condition(:has_projects) do
group_projects_for(user: @user, group: @subject).any?
end
@@ -75,7 +78,7 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
with_scope :subject
condition(:has_project_with_service_desk_enabled) { @subject.has_project_with_service_desk_enabled? }
- condition(:crm_enabled, score: 0, scope: :subject) { Feature.enabled?(:customer_relations, @subject) }
+ condition(:crm_enabled, score: 0, scope: :subject) { Feature.enabled?(:customer_relations, @subject) && @subject.crm_enabled? }
with_scope :subject
condition(:group_runner_registration_allowed, score: 0, scope: :subject) do
@@ -120,8 +123,6 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
enable :read_group_member
enable :read_custom_emoji
enable :read_counts
- enable :read_crm_organization
- enable :read_crm_contact
end
rule { ~public_group & ~has_access }.prevent :read_counts
@@ -156,13 +157,14 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
enable :read_prometheus
enable :read_package
enable :read_package_settings
+ enable :read_crm_organization
+ enable :read_crm_contact
end
rule { maintainer }.policy do
enable :destroy_package
enable :create_projects
enable :admin_pipeline
- enable :admin_group_runners
enable :admin_build
enable :read_cluster
enable :add_cluster
@@ -180,6 +182,10 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
enable :admin_group_member
enable :change_visibility_level
+ enable :read_group_runners
+ enable :admin_group_runners
+ enable :register_group_runners
+
enable :set_note_created_at
enable :set_emails_disabled
enable :change_prevent_sharing_groups_outside_hierarchy
@@ -205,10 +211,6 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
enable :read_nested_project_resources
end
- rule { can?(:admin_group_runners) }.policy do
- enable :register_group_runners
- end
-
rule { owner }.enable :create_subgroup
rule { maintainer & maintainer_can_create_group }.enable :create_subgroup
@@ -250,6 +252,8 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
enable :admin_dependency_proxy
end
+ rule { project_bot }.enable :project_bot_access
+
rule { can?(:admin_group) & resource_access_token_feature_available }.policy do
enable :read_resource_access_tokens
enable :destroy_resource_access_tokens
@@ -260,6 +264,10 @@ class GroupPolicy < Namespaces::GroupProjectNamespaceSharedPolicy
enable :create_resource_access_tokens
end
+ rule { can?(:project_bot_access) }.policy do
+ prevent :create_resource_access_tokens
+ end
+
rule { support_bot & has_project_with_service_desk_enabled }.policy do
enable :read_label
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index b3aa49a00ae..55f43cd9f7b 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -258,6 +258,11 @@ class ProjectPolicy < BasePolicy
rule { can?(:reporter_access) & can?(:create_issue) }.enable :create_incident
+ rule { can?(:guest_access) & can?(:create_issue) }.policy do
+ enable :create_task
+ enable :create_work_item
+ end
+
# These abilities are not allowed to admins that are not members of the project,
# that's why they are defined separately.
rule { guest & can?(:download_code) }.enable :build_download_code
@@ -399,6 +404,7 @@ class ProjectPolicy < BasePolicy
enable :destroy_feature_flag
enable :admin_feature_flag
enable :admin_feature_flags_user_lists
+ enable :update_escalation_status
end
rule { can?(:developer_access) & user_confirmed? }.policy do
diff --git a/app/policies/work_items/type_policy.rb b/app/policies/work_items/type_policy.rb
new file mode 100644
index 00000000000..c9b3321146a
--- /dev/null
+++ b/app/policies/work_items/type_policy.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module WorkItems
+ class TypePolicy < BasePolicy
+ condition(:is_default_type) { @subject.default? }
+
+ rule { is_default_type }.enable :read_work_item_type
+ end
+end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index 3bd92ebc942..2577fcaf303 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -63,6 +63,22 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
project_ci_pipeline_editor_path(project, branch_name: blob.commit_id) if can_collaborate_with_project?(project) && blob.path == project.ci_config_path_or_default
end
+ def find_file_path
+ url_helpers.project_find_file_path(project, ref_qualified_path)
+ end
+
+ def blame_path
+ url_helpers.project_blame_path(project, ref_qualified_path)
+ end
+
+ def history_path
+ url_helpers.project_commits_path(project, ref_qualified_path)
+ end
+
+ def permalink_path
+ url_helpers.project_blob_path(project, File.join(project.repository.commit.sha, blob.path))
+ end
+
# Will be overridden in EE
def code_owners
[]
@@ -86,6 +102,10 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
user_access(project).can_push_to_branch?(blob.commit_id)
end
+ def archived?
+ project.archived
+ end
+
def ide_edit_path
super(project, blob.commit_id, blob.path)
end
diff --git a/app/presenters/ci/runner_presenter.rb b/app/presenters/ci/runner_presenter.rb
index ad889d444f8..ffd826fab64 100644
--- a/app/presenters/ci/runner_presenter.rb
+++ b/app/presenters/ci/runner_presenter.rb
@@ -11,5 +11,9 @@ module Ci
delegator_override :locked
alias_method :locked, :locked?
+
+ def executor_name
+ Ci::Runner::EXECUTOR_TYPE_TO_NAMES[executor_type&.to_sym]
+ end
end
end
diff --git a/app/presenters/label_presenter.rb b/app/presenters/label_presenter.rb
index fafade2828f..8d604f9a0f6 100644
--- a/app/presenters/label_presenter.rb
+++ b/app/presenters/label_presenter.rb
@@ -2,7 +2,7 @@
class LabelPresenter < Gitlab::View::Presenter::Delegated
presents ::Label, as: :label
- delegate :name, :full_name, to: :label_subject, prefix: :subject
+ delegate :name, :full_name, to: :label_subject, prefix: :subject, allow_nil: true
delegator_override :subject # TODO: Fix `Gitlab::View::Presenter::Delegated#subject` not to override `Label#subject`.
@@ -10,6 +10,7 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated
case label
when GroupLabel then edit_group_label_path(label.group, label)
when ProjectLabel then edit_project_label_path(label.project, label)
+ else edit_admin_label_path(label)
end
end
@@ -17,6 +18,7 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated
case label
when GroupLabel then group_label_path(label.group, label)
when ProjectLabel then project_label_path(label.project, label)
+ else admin_label_path(label)
end
end
@@ -43,7 +45,7 @@ class LabelPresenter < Gitlab::View::Presenter::Delegated
end
def label_subject
- @label_subject ||= label.subject
+ @label_subject ||= label.subject if label.respond_to?(:subject)
end
private
diff --git a/app/presenters/packages/conan/package_presenter.rb b/app/presenters/packages/conan/package_presenter.rb
index df770777ad1..57636922676 100644
--- a/app/presenters/packages/conan/package_presenter.rb
+++ b/app/presenters/packages/conan/package_presenter.rb
@@ -80,7 +80,13 @@ module Packages
def package_files
return unless @package
- @package_files ||= @package.package_files.preload_conan_file_metadata
+ strong_memoize(:package_files) do
+ if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ @package.installable_package_files.preload_conan_file_metadata
+ else
+ @package.package_files.preload_conan_file_metadata
+ end
+ end
end
def matching_reference?(package_file)
diff --git a/app/presenters/packages/detail/package_presenter.rb b/app/presenters/packages/detail/package_presenter.rb
index 59e50b96ab2..c257edcadfb 100644
--- a/app/presenters/packages/detail/package_presenter.rb
+++ b/app/presenters/packages/detail/package_presenter.rb
@@ -15,7 +15,7 @@ module Packages
id: @package.id,
created_at: @package.created_at,
name: name,
- package_files: @package.package_files.map { |pf| build_package_file_view(pf) },
+ package_files: package_file_views,
package_type: @package.package_type,
status: @package.status,
project_id: @package.project_id,
@@ -38,6 +38,16 @@ module Packages
private
+ def package_file_views
+ package_files = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ @package.installable_package_files
+ else
+ @package.package_files
+ end
+
+ package_files.map { |pf| build_package_file_view(pf) }
+ end
+
def build_package_file_view(package_file)
file_view = {
created_at: package_file.created_at,
diff --git a/app/presenters/packages/npm/package_presenter.rb b/app/presenters/packages/npm/package_presenter.rb
index c30dfa6196b..1f94187204f 100644
--- a/app/presenters/packages/npm/package_presenter.rb
+++ b/app/presenters/packages/npm/package_presenter.rb
@@ -26,7 +26,11 @@ module Packages
.preload_npm_metadatum
batched_packages.each do |package|
- package_file = package.package_files.last
+ package_file = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ package.installable_package_files.last
+ else
+ package.package_files.last
+ end
next unless package_file
diff --git a/app/presenters/packages/nuget/presenter_helpers.rb b/app/presenters/packages/nuget/presenter_helpers.rb
index 09bf1e009a6..cd3e123033c 100644
--- a/app/presenters/packages/nuget/presenter_helpers.rb
+++ b/app/presenters/packages/nuget/presenter_helpers.rb
@@ -27,12 +27,19 @@ module Packages
end
def archive_url_for(package)
+ package_files = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ package.installable_package_files
+ else
+ package.package_files
+ end
+
+ package_filename = package_files.with_format(NUGET_PACKAGE_FORMAT).last&.file_name
path = api_v4_projects_packages_nuget_download_package_name_package_version_package_filename_path(
{
id: package.project_id,
package_name: package.name,
package_version: package.version,
- package_filename: package.package_files.with_format(NUGET_PACKAGE_FORMAT).last&.file_name
+ package_filename: package_filename
},
true
)
diff --git a/app/presenters/packages/pypi/package_presenter.rb b/app/presenters/packages/pypi/package_presenter.rb
index 7997c1b9b79..33854e4d2fc 100644
--- a/app/presenters/packages/pypi/package_presenter.rb
+++ b/app/presenters/packages/pypi/package_presenter.rb
@@ -36,7 +36,13 @@ module Packages
refs = []
@packages.map do |package|
- package.package_files.each do |file|
+ package_files = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ package.installable_package_files
+ else
+ package.package_files
+ end
+
+ package_files.each do |file|
url = build_pypi_package_path(file)
refs << package_link(url, package.pypi_metadatum.required_python, file.file_name)
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index ec66f9bdd4f..64cd54953e2 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -364,7 +364,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
if clusters.empty?
AnchorData.new(false,
statistic_icon + _('Add Kubernetes cluster'),
- new_project_cluster_path(project))
+ project_clusters_path(project))
else
cluster_link = clusters.count == 1 ? project_cluster_path(project, clusters.first) : project_clusters_path(project)
diff --git a/app/presenters/service_hook_presenter.rb b/app/presenters/service_hook_presenter.rb
index 91911eb3dff..f2a06358918 100644
--- a/app/presenters/service_hook_presenter.rb
+++ b/app/presenters/service_hook_presenter.rb
@@ -4,10 +4,10 @@ class ServiceHookPresenter < Gitlab::View::Presenter::Delegated
presents ::ServiceHook, as: :service_hook
def logs_details_path(log)
- project_service_hook_log_path(integration.project, integration, log)
+ project_integration_hook_log_path(integration.project, integration, log)
end
def logs_retry_path(log)
- retry_project_service_hook_log_path(integration.project, integration, log)
+ retry_project_integration_hook_log_path(integration.project, integration, log)
end
end
diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb
index 99663c8d5eb..169df94ad7b 100644
--- a/app/serializers/analytics_build_entity.rb
+++ b/app/serializers/analytics_build_entity.rb
@@ -9,6 +9,13 @@ class AnalyticsBuildEntity < Grape::Entity
expose :ref, as: :branch
expose :short_sha
expose :author, using: UserEntity
+ expose :project_path do |build|
+ build.project.path
+ end
+
+ expose :namespace_full_path do |build|
+ build.project.namespace.full_path
+ end
expose :started_at, as: :date do |build|
interval_in_words(build[:started_at])
diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb
index 307ce14a921..a0d6d120a48 100644
--- a/app/serializers/analytics_issue_entity.rb
+++ b/app/serializers/analytics_issue_entity.rb
@@ -6,6 +6,13 @@ class AnalyticsIssueEntity < Grape::Entity
expose :title
expose :author, using: UserEntity
+ expose :project_path do |object|
+ object[:project_path]
+ end
+
+ expose :namespace_full_path do |object|
+ object[:namespace_path]
+ end
expose :iid do |object|
object[:iid].to_s
diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb
index 2fb1ad52135..11445f93609 100644
--- a/app/serializers/environment_serializer.rb
+++ b/app/serializers/environment_serializer.rb
@@ -52,7 +52,17 @@ class EnvironmentSerializer < BaseSerializer
end
def batch_load(resource)
- resource = resource.preload(environment_associations)
+ if ::Feature.enabled?(:custom_preloader_for_deployments, default_enabled: :yaml)
+ resource = resource.preload(environment_associations.except(:last_deployment, :upcoming_deployment))
+
+ Preloaders::Environments::DeploymentPreloader.new(resource)
+ .execute_with_union(:last_deployment, deployment_associations)
+
+ Preloaders::Environments::DeploymentPreloader.new(resource)
+ .execute_with_union(:upcoming_deployment, deployment_associations)
+ else
+ resource = resource.preload(environment_associations)
+ end
resource.all.to_a.tap do |environments|
environments.each do |environment|
diff --git a/app/serializers/group_child_entity.rb b/app/serializers/group_child_entity.rb
index 0ca6e7b40d9..c469dbdd6b8 100644
--- a/app/serializers/group_child_entity.rb
+++ b/app/serializers/group_child_entity.rb
@@ -33,6 +33,8 @@ class GroupChildEntity < Grape::Entity
end
# Project only attributes
+ expose :last_activity_at, if: lambda { |instance| project? }
+
expose :star_count, :archived,
if: lambda { |_instance, _options| project? }
diff --git a/app/serializers/merge_request_poll_cached_widget_entity.rb b/app/serializers/merge_request_poll_cached_widget_entity.rb
index 8b0f3c8eb74..5bf02c93c99 100644
--- a/app/serializers/merge_request_poll_cached_widget_entity.rb
+++ b/app/serializers/merge_request_poll_cached_widget_entity.rb
@@ -17,7 +17,6 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity
expose :target_project_id
expose :squash
expose :rebase_in_progress?, as: :rebase_in_progress
- expose :default_squash_commit_message
expose :commits_count
expose :merge_ongoing?, as: :merge_ongoing
expose :work_in_progress?, as: :work_in_progress
@@ -27,6 +26,10 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity
expose :source_branch_exists?, as: :source_branch_exists
expose :branch_missing?, as: :branch_missing
+ expose :default_squash_commit_message do |merge_request|
+ merge_request.default_squash_commit_message(user: request.current_user)
+ end
+
expose :commits_without_merge_commits, using: MergeRequestWidgetCommitEntity do |merge_request|
merge_request.recent_commits.without_merge_commits
end
diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb
index 074bd2d18d7..f68477e82c9 100644
--- a/app/serializers/merge_request_poll_widget_entity.rb
+++ b/app/serializers/merge_request_poll_widget_entity.rb
@@ -19,7 +19,9 @@ class MergeRequestPollWidgetEntity < Grape::Entity
# User entities
expose :merge_user, using: UserEntity
- expose :default_merge_commit_message
+ expose :default_merge_commit_message do |merge_request, options|
+ merge_request.default_merge_commit_message(include_description: false, user: current_user)
+ end
expose :mergeable do |merge_request, options|
next merge_request.mergeable? if Feature.disabled?(:check_mergeability_async_in_widget, merge_request.project, default_enabled: :yaml)
diff --git a/app/serializers/merge_request_sidebar_basic_entity.rb b/app/serializers/merge_request_sidebar_basic_entity.rb
index 3c911bbe4c8..a0832655563 100644
--- a/app/serializers/merge_request_sidebar_basic_entity.rb
+++ b/app/serializers/merge_request_sidebar_basic_entity.rb
@@ -5,5 +5,9 @@ class MergeRequestSidebarBasicEntity < IssuableSidebarBasicEntity
expose :can_merge do |merge_request|
merge_request.can_be_merged_by?(current_user)
end
+
+ expose :can_update_merge_request do |merge_request|
+ current_user.can?(:update_merge_request, merge_request)
+ end
end
end
diff --git a/app/services/alert_management/alerts/update_service.rb b/app/services/alert_management/alerts/update_service.rb
index 089715a42fb..7a9bcf2a52d 100644
--- a/app/services/alert_management/alerts/update_service.rb
+++ b/app/services/alert_management/alerts/update_service.rb
@@ -2,7 +2,7 @@
module AlertManagement
module Alerts
- class UpdateService
+ class UpdateService < ::BaseProjectService
include Gitlab::Utils::StrongMemoize
# @param alert [AlertManagement::Alert]
@@ -10,10 +10,10 @@ module AlertManagement
# @param params [Hash] Attributes of the alert
def initialize(alert, current_user, params)
@alert = alert
- @current_user = current_user
- @params = params
@param_errors = []
@status = params.delete(:status)
+
+ super(project: alert.project, current_user: current_user, params: params)
end
def execute
@@ -36,7 +36,7 @@ module AlertManagement
private
- attr_reader :alert, :current_user, :params, :param_errors, :status
+ attr_reader :alert, :param_errors, :status
def allowed?
current_user&.can?(:update_alert_management_alert, alert)
@@ -109,7 +109,7 @@ module AlertManagement
end
def add_assignee_system_note(old_assignees)
- SystemNoteService.change_issuable_assignees(alert, alert.project, current_user, old_assignees)
+ SystemNoteService.change_issuable_assignees(alert, project, current_user, old_assignees)
end
# ------ Status-related behavior -------
@@ -129,6 +129,7 @@ module AlertManagement
def handle_status_change
add_status_change_system_note
resolve_todos if alert.resolved?
+ sync_to_incident if should_sync_to_incident?
end
def add_status_change_system_note
@@ -139,6 +140,22 @@ module AlertManagement
todo_service.resolve_todos_for_target(alert, current_user)
end
+ def sync_to_incident
+ ::Issues::UpdateService.new(
+ project: project,
+ current_user: current_user,
+ params: { escalation_status: { status: status } }
+ ).execute(alert.issue)
+ end
+
+ def should_sync_to_incident?
+ Feature.enabled?(:incident_escalations, project) &&
+ alert.issue &&
+ alert.issue.supports_escalation? &&
+ alert.issue.escalation_status &&
+ alert.issue.escalation_status.status != alert.status
+ end
+
def filter_duplicate
# Only need to check if changing to an open status
return unless params[:status_event] && AlertManagement::Alert.open_status?(status)
@@ -154,7 +171,7 @@ module AlertManagement
def open_alerts
strong_memoize(:open_alerts) do
- AlertManagement::Alert.for_fingerprint(alert.project, alert.fingerprint).open
+ AlertManagement::Alert.for_fingerprint(project, alert.fingerprint).open
end
end
@@ -166,7 +183,7 @@ module AlertManagement
def open_alert_url_params
open_alert = open_alerts.first
- alert_path = Gitlab::Routing.url_helpers.details_project_alert_management_path(alert.project, open_alert)
+ alert_path = Gitlab::Routing.url_helpers.details_project_alert_management_path(project, open_alert)
{
link_start: '<a href="%{url}">'.html_safe % { url: alert_path },
diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb
index 1426bf25a00..f2b1d89161c 100644
--- a/app/services/audit_event_service.rb
+++ b/app/services/audit_event_service.rb
@@ -141,7 +141,7 @@ class AuditEventService
event.save! if should_save_database?(@save_type)
stream_event_to_external_destinations(event) if should_save_stream?(@save_type)
rescue StandardError => e
- Gitlab::ErrorTracking.track_exception(e, audit_event_type: event.class.to_s)
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, audit_event_type: event.class.to_s)
end
end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index ea4723c9e28..a92a2c8aef5 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -124,7 +124,8 @@ module Auth
type: type,
name: path.to_s,
actions: authorized_actions,
- migration_eligible: self.class.migration_eligible(project: requested_project)
+ migration_eligible: self.class.migration_eligible(project: requested_project),
+ cdn_redirect: cdn_redirect
}.compact
end
@@ -145,6 +146,16 @@ module Auth
# we'll remove them manually from this deny list, and their new repositories will become eligible.
Feature.disabled?(:container_registry_migration_phase1_deny, project.root_ancestor) &&
Feature.enabled?(:container_registry_migration_phase1_allow, project)
+ rescue ContainerRegistry::Path::InvalidRegistryPathError => ex
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(ex, **Gitlab::ApplicationContext.current)
+ false
+ end
+
+ # This is used to determine whether blob download requests using a given JWT token should be redirected to Google
+ # Cloud CDN or not. The intent is to enable a percentage of time rollout for this new feature on the Container
+ # Registry side. See https://gitlab.com/gitlab-org/gitlab/-/issues/349417 for more details.
+ def cdn_redirect
+ Feature.enabled?(:container_registry_cdn_redirect) || nil
end
##
diff --git a/app/services/auto_merge/base_service.rb b/app/services/auto_merge/base_service.rb
index da80211f9bb..4ed4368d3b7 100644
--- a/app/services/auto_merge/base_service.rb
+++ b/app/services/auto_merge/base_service.rb
@@ -6,7 +6,7 @@ module AutoMerge
include MergeRequests::AssignsMergeParams
def execute(merge_request)
- ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
+ ApplicationRecord.transaction do
register_auto_merge_parameters!(merge_request)
yield if block_given?
end
@@ -29,7 +29,7 @@ module AutoMerge
end
def cancel(merge_request)
- ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
+ ApplicationRecord.transaction do
clear_auto_merge_parameters!(merge_request)
yield if block_given?
end
@@ -41,7 +41,7 @@ module AutoMerge
end
def abort(merge_request, reason)
- ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
+ ApplicationRecord.transaction do
clear_auto_merge_parameters!(merge_request)
yield if block_given?
end
diff --git a/app/services/bulk_imports/archive_extraction_service.rb b/app/services/bulk_imports/archive_extraction_service.rb
index 9fc828b8e34..caa40d98a76 100644
--- a/app/services/bulk_imports/archive_extraction_service.rb
+++ b/app/services/bulk_imports/archive_extraction_service.rb
@@ -28,8 +28,8 @@ module BulkImports
end
def execute
- validate_filepath
validate_tmpdir
+ validate_filepath
validate_symlink
extract_archive
@@ -46,7 +46,7 @@ module BulkImports
end
def validate_tmpdir
- raise(BulkImports::Error, 'Invalid target directory') unless File.expand_path(tmpdir).start_with?(Dir.tmpdir)
+ Gitlab::Utils.check_allowed_absolute_path!(tmpdir, [Dir.tmpdir])
end
def validate_symlink
diff --git a/app/services/bulk_imports/file_decompression_service.rb b/app/services/bulk_imports/file_decompression_service.rb
index fe9017377ec..b76746b199f 100644
--- a/app/services/bulk_imports/file_decompression_service.rb
+++ b/app/services/bulk_imports/file_decompression_service.rb
@@ -1,21 +1,26 @@
# frozen_string_literal: true
+# File Decompression Service allows gzipped files decompression into tmp directory.
+#
+# @param tmpdir [String] Temp directory to store downloaded file to. Must be located under `Dir.tmpdir`.
+# @param filename [String] Name of the file to decompress.
module BulkImports
class FileDecompressionService
include Gitlab::ImportExport::CommandLineUtil
ServiceError = Class.new(StandardError)
- def initialize(dir:, filename:)
- @dir = dir
+ def initialize(tmpdir:, filename:)
+ @tmpdir = tmpdir
@filename = filename
- @filepath = File.join(@dir, @filename)
+ @filepath = File.join(@tmpdir, @filename)
@decompressed_filename = File.basename(@filename, '.gz')
- @decompressed_filepath = File.join(@dir, @decompressed_filename)
+ @decompressed_filepath = File.join(@tmpdir, @decompressed_filename)
end
def execute
- validate_dir
+ validate_tmpdir
+ validate_filepath
validate_decompressed_file_size if Feature.enabled?(:validate_import_decompressed_archive_size, default_enabled: :yaml)
validate_symlink(filepath)
@@ -33,10 +38,14 @@ module BulkImports
private
- attr_reader :dir, :filename, :filepath, :decompressed_filename, :decompressed_filepath
+ attr_reader :tmpdir, :filename, :filepath, :decompressed_filename, :decompressed_filepath
- def validate_dir
- raise(ServiceError, 'Invalid target directory') unless dir.start_with?(Dir.tmpdir)
+ def validate_filepath
+ Gitlab::Utils.check_path_traversal!(filepath)
+ end
+
+ def validate_tmpdir
+ Gitlab::Utils.check_allowed_absolute_path!(tmpdir, [Dir.tmpdir])
end
def validate_decompressed_file_size
@@ -48,7 +57,7 @@ module BulkImports
end
def decompress_file
- gunzip(dir: dir, filename: filename)
+ gunzip(dir: tmpdir, filename: filename)
end
def size_validator
diff --git a/app/services/bulk_imports/file_download_service.rb b/app/services/bulk_imports/file_download_service.rb
index d08dc72e30b..8d6ba54cd50 100644
--- a/app/services/bulk_imports/file_download_service.rb
+++ b/app/services/bulk_imports/file_download_service.rb
@@ -1,6 +1,13 @@
# frozen_string_literal: true
-# Downloads a remote file. If no filename is given, it'll use the remote filename
+# File Download Service allows remote file download into tmp directory.
+#
+# @param configuration [BulkImports::Configuration] Config object containing url and access token
+# @param relative_url [String] Relative URL to download the file from
+# @param tmpdir [String] Temp directory to store downloaded file to. Must be located under `Dir.tmpdir`.
+# @param file_size_limit [Integer] Maximum allowed file size
+# @param allowed_content_types [Array<String>] Allowed file content types
+# @param filename [String] Name of the file to download, if known. Use remote filename if none given.
module BulkImports
class FileDownloadService
ServiceError = Class.new(StandardError)
@@ -13,20 +20,21 @@ module BulkImports
def initialize(
configuration:,
relative_url:,
- dir:,
+ tmpdir:,
file_size_limit: DEFAULT_FILE_SIZE_LIMIT,
allowed_content_types: DEFAULT_ALLOWED_CONTENT_TYPES,
filename: nil)
@configuration = configuration
@relative_url = relative_url
@filename = filename
- @dir = dir
+ @tmpdir = tmpdir
@file_size_limit = file_size_limit
@allowed_content_types = allowed_content_types
end
def execute
- validate_dir
+ validate_tmpdir
+ validate_filepath
validate_url
validate_content_type
validate_content_length
@@ -40,7 +48,7 @@ module BulkImports
private
- attr_reader :configuration, :relative_url, :dir, :file_size_limit, :allowed_content_types
+ attr_reader :configuration, :relative_url, :tmpdir, :file_size_limit, :allowed_content_types
def download_file
File.open(filepath, 'wb') do |file|
@@ -76,8 +84,12 @@ module BulkImports
@headers ||= http_client.head(relative_url).headers
end
- def validate_dir
- raise(ServiceError, 'Invalid target directory') unless dir.start_with?(Dir.tmpdir)
+ def validate_filepath
+ Gitlab::Utils.check_path_traversal!(filepath)
+ end
+
+ def validate_tmpdir
+ Gitlab::Utils.check_allowed_absolute_path!(tmpdir, [Dir.tmpdir])
end
def validate_symlink
@@ -119,7 +131,7 @@ module BulkImports
end
def filepath
- @filepath ||= File.join(@dir, filename)
+ @filepath ||= File.join(@tmpdir, filename)
end
def filename
diff --git a/app/services/bulk_imports/file_export_service.rb b/app/services/bulk_imports/file_export_service.rb
index a7e0f998666..a9d06d84277 100644
--- a/app/services/bulk_imports/file_export_service.rb
+++ b/app/services/bulk_imports/file_export_service.rb
@@ -26,8 +26,10 @@ module BulkImports
def export_service
case relation
- when FileTransfer::ProjectConfig::UPLOADS_RELATION
+ when FileTransfer::BaseConfig::UPLOADS_RELATION
UploadsExportService.new(portable, export_path)
+ when FileTransfer::ProjectConfig::LFS_OBJECTS_RELATION
+ LfsObjectsExportService.new(portable, export_path)
else
raise BulkImports::Error, 'Unsupported relation export type'
end
diff --git a/app/services/bulk_imports/lfs_objects_export_service.rb b/app/services/bulk_imports/lfs_objects_export_service.rb
new file mode 100644
index 00000000000..fa606e4e5a3
--- /dev/null
+++ b/app/services/bulk_imports/lfs_objects_export_service.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module BulkImports
+ class LfsObjectsExportService
+ include Gitlab::ImportExport::CommandLineUtil
+
+ BATCH_SIZE = 100
+
+ def initialize(portable, export_path)
+ @portable = portable
+ @export_path = export_path
+ @lfs_json = {}
+ end
+
+ def execute
+ portable.lfs_objects.find_in_batches(batch_size: BATCH_SIZE) do |batch| # rubocop: disable CodeReuse/ActiveRecord
+ batch.each do |lfs_object|
+ save_lfs_object(lfs_object)
+ end
+
+ append_lfs_json_for_batch(batch)
+ end
+
+ write_lfs_json
+ end
+
+ private
+
+ attr_reader :portable, :export_path, :lfs_json
+
+ def save_lfs_object(lfs_object)
+ destination_filepath = File.join(export_path, lfs_object.oid)
+
+ if lfs_object.local_store?
+ copy_files(lfs_object.file.path, destination_filepath)
+ else
+ download(lfs_object.file.url, destination_filepath)
+ end
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def append_lfs_json_for_batch(lfs_objects_batch)
+ lfs_objects_projects = LfsObjectsProject
+ .select('lfs_objects.oid, array_agg(distinct lfs_objects_projects.repository_type) as repository_types')
+ .joins(:lfs_object)
+ .where(project: portable, lfs_object: lfs_objects_batch)
+ .group('lfs_objects.oid')
+
+ lfs_objects_projects.each do |group|
+ oid = group.oid
+
+ lfs_json[oid] ||= []
+ lfs_json[oid] += group.repository_types
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def write_lfs_json
+ filepath = File.join(export_path, "#{BulkImports::FileTransfer::ProjectConfig::LFS_OBJECTS_RELATION}.json")
+
+ File.write(filepath, lfs_json.to_json)
+ end
+ end
+end
diff --git a/app/services/ci/archive_trace_service.rb b/app/services/ci/archive_trace_service.rb
index 17cac38ace2..7b1d2207460 100644
--- a/app/services/ci/archive_trace_service.rb
+++ b/app/services/ci/archive_trace_service.rb
@@ -27,6 +27,10 @@ module Ci
job.trace.archive!
job.remove_pending_state!
+ if Feature.enabled?(:datadog_integration_logs_collection, job.project) && job.job_artifacts_trace.present?
+ job.project.execute_integrations(Gitlab::DataBuilder::ArchiveTrace.build(job), :archive_trace_hooks)
+ end
+
# TODO: Remove this logging once we confirmed new live trace architecture is functional.
# See https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/4667.
unless job.has_archived_trace?
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index c1f35afba40..d53e136effb 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -95,7 +95,10 @@ module Ci
.build!
if pipeline.persisted?
- schedule_head_pipeline_update
+ Gitlab::EventStore.publish(
+ Ci::PipelineCreatedEvent.new(data: { pipeline_id: pipeline.id })
+ )
+
create_namespace_onboarding_action
else
# If pipeline is not persisted, try to recover IID
@@ -134,12 +137,6 @@ module Ci
commit.try(:id)
end
- def schedule_head_pipeline_update
- pipeline.all_merge_requests.opened.each do |merge_request|
- UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
- end
- end
-
def create_namespace_onboarding_action
Namespaces::OnboardingPipelineCreatedWorker.perform_async(project.namespace_id)
end
diff --git a/app/services/ci/destroy_pipeline_service.rb b/app/services/ci/destroy_pipeline_service.rb
index 6fbde5d291c..d85e52e1312 100644
--- a/app/services/ci/destroy_pipeline_service.rb
+++ b/app/services/ci/destroy_pipeline_service.rb
@@ -9,12 +9,12 @@ module Ci
pipeline.cancel_running if pipeline.cancelable?
- # Ci::Pipeline#destroy triggers `use_fast_destroy :job_artifacts` and
- # ci_builds has ON DELETE CASCADE to ci_pipelines. The pipeline, the builds,
- # job and pipeline artifacts all get destroyed here.
- ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/345664') do
- pipeline.reset.destroy!
- end
+ # The pipeline, the builds, job and pipeline artifacts all get destroyed here.
+ # Ci::Pipeline#destroy triggers fast destroy on job_artifacts and
+ # build_trace_chunks to remove the records and data stored in object storage.
+ # ci_builds records are deleted using ON DELETE CASCADE from ci_pipelines
+ #
+ pipeline.reset.destroy!
ServiceResponse.success(message: 'Pipeline not found')
rescue ActiveRecord::RecordNotFound
diff --git a/app/services/ci/job_artifacts/delete_project_artifacts_service.rb b/app/services/ci/job_artifacts/delete_project_artifacts_service.rb
new file mode 100644
index 00000000000..61394573748
--- /dev/null
+++ b/app/services/ci/job_artifacts/delete_project_artifacts_service.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Ci
+ module JobArtifacts
+ class DeleteProjectArtifactsService < BaseProjectService
+ def execute
+ ExpireProjectBuildArtifactsWorker.perform_async(project.id)
+ end
+ end
+ end
+end
diff --git a/app/services/ci/job_artifacts/destroy_all_expired_service.rb b/app/services/ci/job_artifacts/destroy_all_expired_service.rb
index 7fa56677a0c..c089567ec14 100644
--- a/app/services/ci/job_artifacts/destroy_all_expired_service.rb
+++ b/app/services/ci/job_artifacts/destroy_all_expired_service.rb
@@ -8,13 +8,15 @@ module Ci
BATCH_SIZE = 100
LOOP_TIMEOUT = 5.minutes
- LOOP_LIMIT = 1000
+ SMALL_LOOP_LIMIT = 100
+ LARGE_LOOP_LIMIT = 500
EXCLUSIVE_LOCK_KEY = 'expired_job_artifacts:destroy:lock'
LOCK_TIMEOUT = 6.minutes
def initialize
@removed_artifacts_count = 0
@start_at = Time.current
+ @loop_limit = ::Feature.enabled?(:ci_artifact_fast_removal_large_loop_limit, default_enabled: :yaml) ? LARGE_LOOP_LIMIT : SMALL_LOOP_LIMIT
end
##
@@ -24,6 +26,8 @@ module Ci
# preventing multiple `ExpireBuildArtifactsWorker` CRON jobs run concurrently,
# which is scheduled every 7 minutes.
def execute
+ return 0 unless ::Feature.enabled?(:ci_destroy_all_expired_service, default_enabled: :yaml)
+
in_lock(EXCLUSIVE_LOCK_KEY, ttl: LOCK_TIMEOUT, retries: 1) do
if ::Feature.enabled?(:ci_destroy_unlocked_job_artifacts)
destroy_unlocked_job_artifacts
@@ -38,34 +42,13 @@ module Ci
private
def destroy_unlocked_job_artifacts
- loop_until(timeout: LOOP_TIMEOUT, limit: LOOP_LIMIT) do
+ loop_until(timeout: LOOP_TIMEOUT, limit: @loop_limit) do
artifacts = Ci::JobArtifact.expired_before(@start_at).artifact_unlocked.limit(BATCH_SIZE)
service_response = destroy_batch(artifacts)
@removed_artifacts_count += service_response[:destroyed_artifacts_count]
-
- update_locked_status_on_unknown_artifacts if service_response[:destroyed_artifacts_count] == 0
-
- # Return a truthy value here to prevent exiting #loop_until
- @removed_artifacts_count
end
end
- def update_locked_status_on_unknown_artifacts
- build_ids = Ci::JobArtifact.expired_before(@start_at).artifact_unknown.limit(BATCH_SIZE).distinct_job_ids
-
- return unless build_ids.present?
-
- locked_pipeline_build_ids = ::Ci::Build.with_pipeline_locked_artifacts.id_in(build_ids).pluck_primary_key
- unlocked_pipeline_build_ids = build_ids - locked_pipeline_build_ids
-
- update_unknown_artifacts(locked_pipeline_build_ids, Ci::JobArtifact.lockeds[:artifacts_locked])
- update_unknown_artifacts(unlocked_pipeline_build_ids, Ci::JobArtifact.lockeds[:unlocked])
- end
-
- def update_unknown_artifacts(build_ids, locked_value)
- Ci::JobArtifact.for_job_ids(build_ids).update_all(locked: locked_value) if build_ids.any?
- end
-
def destroy_job_artifacts_with_slow_iteration
Ci::JobArtifact.expired_before(@start_at).each_batch(of: BATCH_SIZE, column: :expire_at, order: :desc) do |relation, index|
# For performance reasons, join with ci_pipelines after the batch is queried.
@@ -76,7 +59,7 @@ module Ci
@removed_artifacts_count += service_response[:destroyed_artifacts_count]
break if loop_timeout?
- break if index >= LOOP_LIMIT
+ break if index >= @loop_limit
end
end
diff --git a/app/services/ci/job_artifacts/expire_project_build_artifacts_service.rb b/app/services/ci/job_artifacts/expire_project_build_artifacts_service.rb
new file mode 100644
index 00000000000..836b1d39736
--- /dev/null
+++ b/app/services/ci/job_artifacts/expire_project_build_artifacts_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Ci
+ module JobArtifacts
+ class ExpireProjectBuildArtifactsService
+ BATCH_SIZE = 1000
+
+ def initialize(project_id, expiry_time)
+ @project_id = project_id
+ @expiry_time = expiry_time
+ end
+
+ # rubocop:disable CodeReuse/ActiveRecord
+ def execute
+ scope = Ci::JobArtifact.for_project(project_id).order(:id)
+ file_type_values = Ci::JobArtifact.erasable_file_types.map { |file_type| [Ci::JobArtifact.file_types[file_type]] }
+ from_sql = Arel::Nodes::Grouping.new(Arel::Nodes::ValuesList.new(file_type_values)).as('file_types (file_type)').to_sql
+ array_scope = Ci::JobArtifact.from(from_sql).select(:file_type)
+ array_mapping_scope = -> (file_type_expression) { Ci::JobArtifact.where(Ci::JobArtifact.arel_table[:file_type].eq(file_type_expression)) }
+
+ Gitlab::Pagination::Keyset::Iterator
+ .new(scope: scope, in_operator_optimization_options: { array_scope: array_scope, array_mapping_scope: array_mapping_scope })
+ .each_batch(of: BATCH_SIZE) do |batch|
+ ids = batch.reselect!(:id).to_a.map(&:id)
+ Ci::JobArtifact.unlocked.where(id: ids).update_all(locked: Ci::JobArtifact.lockeds[:unlocked], expire_at: expiry_time)
+ end
+ end
+ # rubocop:enable CodeReuse/ActiveRecord
+
+ private
+
+ attr_reader :project_id, :expiry_time
+ end
+ end
+end
diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb
index d8ce063ffb4..508d9c3f2e1 100644
--- a/app/services/ci/pipeline_processing/atomic_processing_service.rb
+++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb
@@ -36,9 +36,7 @@ module Ci
update_pipeline!
update_statuses_processed!
- if Feature.enabled?(:expire_job_and_pipeline_cache_synchronously, pipeline.project, default_enabled: :yaml)
- Ci::ExpirePipelineCacheService.new.execute(pipeline)
- end
+ Ci::ExpirePipelineCacheService.new.execute(pipeline)
true
end
diff --git a/app/services/ci/pipelines/add_job_service.rb b/app/services/ci/pipelines/add_job_service.rb
index 703bb22fb5d..fc852bc3edd 100644
--- a/app/services/ci/pipelines/add_job_service.rb
+++ b/app/services/ci/pipelines/add_job_service.rb
@@ -39,6 +39,12 @@ module Ci
job.pipeline = pipeline
job.project = pipeline.project
job.ref = pipeline.ref
+
+ # update metadata since it might have been lazily initialised before this call
+ # metadata is present on `Ci::Processable`
+ if job.respond_to?(:metadata) && job.metadata
+ job.metadata.project = pipeline.project
+ end
end
end
end
diff --git a/app/services/ci/play_build_service.rb b/app/services/ci/play_build_service.rb
index e2673c763f3..2d6b6aeee14 100644
--- a/app/services/ci/play_build_service.rb
+++ b/app/services/ci/play_build_service.rb
@@ -14,7 +14,10 @@ module Ci
AfterRequeueJobService.new(project, current_user).execute(build)
end
else
- Ci::Build.retry(build, current_user)
+ # Retrying in Ci::PlayBuildService is a legacy process that should be removed.
+ # Instead, callers should explicitly execute Ci::RetryBuildService.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/347493.
+ build.retryable? ? Ci::Build.retry(build, current_user) : build
end
end
diff --git a/app/services/ci/process_build_service.rb b/app/services/ci/process_build_service.rb
index 5271c0fe93d..e6ec65fcc91 100644
--- a/app/services/ci/process_build_service.rb
+++ b/app/services/ci/process_build_service.rb
@@ -4,14 +4,7 @@ module Ci
class ProcessBuildService < BaseService
def execute(build, current_status)
if valid_statuses_for_build(build).include?(current_status)
- if build.schedulable?
- build.schedule
- elsif build.action?
- build.actionize
- else
- enqueue(build)
- end
-
+ process(build)
true
else
build.skip
@@ -21,6 +14,16 @@ module Ci
private
+ def process(build)
+ if build.schedulable?
+ build.schedule
+ elsif build.action?
+ build.actionize
+ else
+ enqueue(build)
+ end
+ end
+
def enqueue(build)
build.enqueue
end
diff --git a/app/services/ci/process_sync_events_service.rb b/app/services/ci/process_sync_events_service.rb
index 6be8c41dc6a..11ce6e8eeaf 100644
--- a/app/services/ci/process_sync_events_service.rb
+++ b/app/services/ci/process_sync_events_service.rb
@@ -28,18 +28,16 @@ module Ci
return if events.empty?
- first = events.first
- last_processed = nil
+ processed_events = []
begin
events.each do |event|
@sync_class.sync!(event)
- last_processed = event
+ processed_events << event
end
ensure
- # remove events till the one that was last succesfully processed
- @sync_event_class.id_in(first.id..last_processed.id).delete_all if last_processed
+ @sync_event_class.id_in(processed_events).delete_all
end
end
diff --git a/app/services/ci/register_runner_service.rb b/app/services/ci/register_runner_service.rb
new file mode 100644
index 00000000000..0a2027e33ce
--- /dev/null
+++ b/app/services/ci/register_runner_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Ci
+ class RegisterRunnerService
+ def execute(registration_token, attributes)
+ runner_type_attrs = check_token_and_extract_attrs(registration_token)
+
+ return unless runner_type_attrs
+
+ ::Ci::Runner.create(attributes.merge(runner_type_attrs))
+ end
+
+ private
+
+ def check_token_and_extract_attrs(registration_token)
+ if runner_registration_token_valid?(registration_token)
+ # Create shared runner. Requires admin access
+ { runner_type: :instance_type }
+ elsif runner_registrar_valid?('project') && project = ::Project.find_by_runners_token(registration_token)
+ # Create a specific runner for the project
+ { runner_type: :project_type, projects: [project] }
+ elsif runner_registrar_valid?('group') && group = ::Group.find_by_runners_token(registration_token)
+ # Create a specific runner for the group
+ { runner_type: :group_type, groups: [group] }
+ end
+ end
+
+ def runner_registration_token_valid?(registration_token)
+ ActiveSupport::SecurityUtils.secure_compare(registration_token, Gitlab::CurrentSettings.runners_registration_token)
+ end
+
+ def runner_registrar_valid?(type)
+ Feature.disabled?(:runner_registration_control) || Gitlab::CurrentSettings.valid_runner_registrars.include?(type)
+ end
+ end
+end
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index 89fe4ff9f60..7e5d5373648 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -25,10 +25,6 @@ module Ci
Gitlab::OptimisticLocking.retry_lock(new_build, name: 'retry_build', &:enqueue)
AfterRequeueJobService.new(project, current_user).execute(build)
-
- ::MergeRequests::AddTodoWhenBuildFailsService
- .new(project: project, current_user: current_user)
- .close(new_build)
end
end
@@ -42,16 +38,25 @@ module Ci
check_access!(build)
new_build = clone_build(build)
+
+ new_build.run_after_commit do
+ ::MergeRequests::AddTodoWhenBuildFailsService
+ .new(project: project)
+ .close(new_build)
+ end
+
+ if create_deployment_in_separate_transaction?
+ new_build.run_after_commit do |new_build|
+ ::Deployments::CreateForBuildService.new.execute(new_build)
+ end
+ end
+
::Ci::Pipelines::AddJobService.new(build.pipeline).execute!(new_build) do |job|
BulkInsertableAssociations.with_bulk_insert do
job.save!
end
end
- if create_deployment_in_separate_transaction?
- clone_deployment!(new_build, build)
- end
-
build.reset # refresh the data to get new values of `retried` and `processed`.
new_build
@@ -95,20 +100,6 @@ module Ci
.deployment_attributes_for(new_build, old_build.persisted_environment)
end
- def clone_deployment!(new_build, old_build)
- return unless old_build.deployment.present?
-
- # We should clone the previous deployment attributes instead of initializing
- # new object with `Seed::Deployment`.
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/347206
- deployment = ::Gitlab::Ci::Pipeline::Seed::Deployment
- .new(new_build, old_build.persisted_environment).to_resource
-
- return unless deployment
-
- new_build.create_deployment!(deployment.attributes)
- end
-
def create_deployment_in_separate_transaction?
strong_memoize(:create_deployment_in_separate_transaction) do
::Feature.enabled?(:create_deployment_in_separate_transaction, project, default_enabled: :yaml)
diff --git a/app/services/ci/stuck_builds/drop_helpers.rb b/app/services/ci/stuck_builds/drop_helpers.rb
index f79b805c23d..048b52c6e13 100644
--- a/app/services/ci/stuck_builds/drop_helpers.rb
+++ b/app/services/ci/stuck_builds/drop_helpers.rb
@@ -34,7 +34,7 @@ module Ci
# rubocop: enable CodeReuse/ActiveRecord
def drop_build(type, build, reason)
- Gitlab::AppLogger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{build.status}, failure_reason: #{reason})"
+ log_dropping_message(type, build, reason)
Gitlab::OptimisticLocking.retry_lock(build, 3, name: 'stuck_ci_jobs_worker_drop_build') do |b|
b.drop(reason)
end
@@ -53,6 +53,16 @@ module Ci
project_id: build.project_id
)
end
+
+ def log_dropping_message(type, build, reason)
+ Gitlab::AppLogger.info(class: self.class.name,
+ message: "Dropping #{type} build",
+ build_stuck_type: type,
+ build_id: build.id,
+ runner_id: build.runner_id,
+ build_status: build.status,
+ build_failure_reason: reason)
+ end
end
end
end
diff --git a/app/services/ci/update_build_queue_service.rb b/app/services/ci/update_build_queue_service.rb
index 146239bb7e5..2e38969c7a9 100644
--- a/app/services/ci/update_build_queue_service.rb
+++ b/app/services/ci/update_build_queue_service.rb
@@ -99,17 +99,15 @@ module Ci
private
def tick_for(build, runners)
- ::Gitlab::Database.allow_cross_joins_across_databases(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/339937') do
- runners = runners.with_recent_runner_queue
- runners = runners.with_tags if Feature.enabled?(:ci_preload_runner_tags, default_enabled: :yaml)
+ runners = runners.with_recent_runner_queue
+ runners = runners.with_tags if Feature.enabled?(:ci_preload_runner_tags, default_enabled: :yaml)
- metrics.observe_active_runners(-> { runners.to_a.size })
+ metrics.observe_active_runners(-> { runners.to_a.size })
- runners.each do |runner|
- metrics.increment_runner_tick(runner)
+ runners.each do |runner|
+ metrics.increment_runner_tick(runner)
- runner.pick_build!(build)
- end
+ runner.pick_build!(build)
end
end
diff --git a/app/services/clusters/agent_tokens/create_service.rb b/app/services/clusters/agent_tokens/create_service.rb
index 5b8a0e46a6c..2539ffdc5ba 100644
--- a/app/services/clusters/agent_tokens/create_service.rb
+++ b/app/services/clusters/agent_tokens/create_service.rb
@@ -30,13 +30,14 @@ module Clusters
end
def log_activity_event!(token)
- token.agent.activity_events.create!(
+ Clusters::Agents::CreateActivityEventService.new(
+ token.agent,
kind: :token_created,
level: :info,
recorded_at: token.created_at,
user: current_user,
agent_token: token
- )
+ ).execute
end
end
end
diff --git a/app/services/clusters/agent_tokens/track_usage_service.rb b/app/services/clusters/agent_tokens/track_usage_service.rb
new file mode 100644
index 00000000000..fdc79ac0f8b
--- /dev/null
+++ b/app/services/clusters/agent_tokens/track_usage_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Clusters
+ module AgentTokens
+ class TrackUsageService
+ # The `UPDATE_USED_COLUMN_EVERY` defines how often the token DB entry can be updated
+ UPDATE_USED_COLUMN_EVERY = (40.minutes..55.minutes).freeze
+
+ delegate :agent, to: :token
+
+ def initialize(token)
+ @token = token
+ end
+
+ def execute
+ track_values = { last_used_at: Time.current.utc }
+
+ token.cache_attributes(track_values)
+
+ if can_update_track_values?
+ log_activity_event!(track_values[:last_used_at]) unless agent.connected?
+
+ # Use update_column so updated_at is skipped
+ token.update_columns(track_values)
+ end
+ end
+
+ private
+
+ attr_reader :token
+
+ def can_update_track_values?
+ # Use a random threshold to prevent beating DB updates.
+ last_used_at_max_age = Random.rand(UPDATE_USED_COLUMN_EVERY)
+
+ real_last_used_at = token.read_attribute(:last_used_at)
+
+ # Handle too many updates from high token traffic
+ real_last_used_at.nil? ||
+ (Time.current - real_last_used_at) >= last_used_at_max_age
+ end
+
+ def log_activity_event!(recorded_at)
+ Clusters::Agents::CreateActivityEventService.new(
+ agent,
+ kind: :agent_connected,
+ level: :info,
+ recorded_at: recorded_at,
+ agent_token: token
+ ).execute
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/agents/create_activity_event_service.rb b/app/services/clusters/agents/create_activity_event_service.rb
new file mode 100644
index 00000000000..886dddf1a52
--- /dev/null
+++ b/app/services/clusters/agents/create_activity_event_service.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Agents
+ class CreateActivityEventService
+ def initialize(agent, **params)
+ @agent = agent
+ @params = params
+ end
+
+ def execute
+ agent.activity_events.create!(params)
+
+ DeleteExpiredEventsWorker.perform_at(schedule_cleanup_at, agent.id)
+
+ ServiceResponse.success
+ end
+
+ private
+
+ attr_reader :agent, :params
+
+ def schedule_cleanup_at
+ 1.hour.from_now.change(min: agent.id % 60)
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/agents/delete_expired_events_service.rb b/app/services/clusters/agents/delete_expired_events_service.rb
new file mode 100644
index 00000000000..a0c0291c1fb
--- /dev/null
+++ b/app/services/clusters/agents/delete_expired_events_service.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Agents
+ class DeleteExpiredEventsService
+ def initialize(agent)
+ @agent = agent
+ end
+
+ def execute
+ agent.activity_events
+ .recorded_before(remove_events_before)
+ .each_batch { |batch| batch.delete_all }
+ end
+
+ private
+
+ attr_reader :agent
+
+ def remove_events_before
+ agent.activity_event_deletion_cutoff
+ end
+ end
+ end
+end
diff --git a/app/services/concerns/issues/issue_type_helpers.rb b/app/services/concerns/issues/issue_type_helpers.rb
index 44c20d20ff1..e6ac08a567d 100644
--- a/app/services/concerns/issues/issue_type_helpers.rb
+++ b/app/services/concerns/issues/issue_type_helpers.rb
@@ -5,7 +5,7 @@ module Issues
# @param object [Issue, Project]
# @param issue_type [String, Symbol]
def create_issue_type_allowed?(object, issue_type)
- WorkItem::Type.base_types.key?(issue_type.to_s) &&
+ WorkItems::Type.base_types.key?(issue_type.to_s) &&
can?(current_user, :"create_#{issue_type}", object)
end
end
diff --git a/app/services/dependency_proxy/download_blob_service.rb b/app/services/dependency_proxy/download_blob_service.rb
deleted file mode 100644
index b3548c8a126..00000000000
--- a/app/services/dependency_proxy/download_blob_service.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module DependencyProxy
- class DownloadBlobService < DependencyProxy::BaseService
- def initialize(image, blob_sha, token)
- @image = image
- @blob_sha = blob_sha
- @token = token
- @temp_file = Tempfile.new
- end
-
- def execute
- File.open(@temp_file.path, "wb") do |file|
- Gitlab::HTTP.get(blob_url, headers: auth_headers, stream_body: true) do |fragment|
- if [301, 302, 307].include?(fragment.code)
- # do nothing
- elsif fragment.code == 200
- file.write(fragment)
- else
- raise DownloadError.new('Non-success response code on downloading blob fragment', fragment.code)
- end
- end
- end
-
- success(file: @temp_file)
- rescue DownloadError => exception
- error(exception.message, exception.http_status)
- rescue Timeout::Error => exception
- error(exception.message, 599)
- end
-
- private
-
- def blob_url
- registry.blob_url(@image, @blob_sha)
- end
- end
-end
diff --git a/app/services/dependency_proxy/find_or_create_blob_service.rb b/app/services/dependency_proxy/find_or_create_blob_service.rb
deleted file mode 100644
index 1b43263a3ba..00000000000
--- a/app/services/dependency_proxy/find_or_create_blob_service.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-module DependencyProxy
- class FindOrCreateBlobService < DependencyProxy::BaseService
- def initialize(group, image, token, blob_sha)
- @group = group
- @image = image
- @token = token
- @blob_sha = blob_sha
- end
-
- def execute
- from_cache = true
- file_name = @blob_sha.sub('sha256:', '') + '.gz'
- blob = @group.dependency_proxy_blobs.active.find_or_build(file_name)
-
- unless blob.persisted?
- from_cache = false
- result = DependencyProxy::DownloadBlobService
- .new(@image, @blob_sha, @token).execute
-
- if result[:status] == :error
- log_failure(result)
-
- return error('Failed to download the blob', result[:http_status])
- end
-
- blob.file = result[:file]
- blob.size = result[:file].size
- blob.save!
- end
-
- blob.read! if from_cache
- success(blob: blob, from_cache: from_cache)
- end
-
- private
-
- def log_failure(result)
- log_error(
- "Dependency proxy: Failed to download the blob." \
- "Blob sha: #{@blob_sha}." \
- "Error message: #{result[:message][0, 100]}" \
- "HTTP status: #{result[:http_status]}"
- )
- end
- end
-end
diff --git a/app/services/deployments/archive_in_project_service.rb b/app/services/deployments/archive_in_project_service.rb
index a593721f390..d80ed637cd8 100644
--- a/app/services/deployments/archive_in_project_service.rb
+++ b/app/services/deployments/archive_in_project_service.rb
@@ -7,10 +7,6 @@ module Deployments
BATCH_SIZE = 100
def execute
- unless ::Feature.enabled?(:deployments_archive, project, default_enabled: :yaml)
- return error('Feature flag is not enabled')
- end
-
deployments = Deployment.archivables_in(project, limit: BATCH_SIZE)
return success(result: :empty) if deployments.empty?
diff --git a/app/services/deployments/create_for_build_service.rb b/app/services/deployments/create_for_build_service.rb
new file mode 100644
index 00000000000..b3e2d2edb59
--- /dev/null
+++ b/app/services/deployments/create_for_build_service.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Deployments
+ # This class creates a deployment record for a build (a pipeline job).
+ class CreateForBuildService
+ DeploymentCreationError = Class.new(StandardError)
+
+ def execute(build)
+ return unless build.instance_of?(::Ci::Build) && build.persisted_environment.present?
+
+ # TODO: Move all buisness logic in `Seed::Deployment` to this class after
+ # `create_deployment_in_separate_transaction` feature flag has been removed.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/348778
+ deployment = ::Gitlab::Ci::Pipeline::Seed::Deployment
+ .new(build, build.persisted_environment).to_resource
+
+ return unless deployment
+
+ build.create_deployment!(deployment.attributes)
+ rescue ActiveRecord::RecordInvalid => e
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(
+ DeploymentCreationError.new(e.message), build_id: build.id)
+ end
+ end
+end
diff --git a/app/services/deployments/update_environment_service.rb b/app/services/deployments/update_environment_service.rb
index 83c37257297..19b950044d0 100644
--- a/app/services/deployments/update_environment_service.rb
+++ b/app/services/deployments/update_environment_service.rb
@@ -26,7 +26,7 @@ module Deployments
end
def update_environment(deployment)
- ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
+ ApplicationRecord.transaction do
# Renew attributes at update
renew_external_url
renew_auto_stop_in
diff --git a/app/services/design_management/copy_design_collection/copy_service.rb b/app/services/design_management/copy_design_collection/copy_service.rb
index 5e557e9ea53..886077191ab 100644
--- a/app/services/design_management/copy_design_collection/copy_service.rb
+++ b/app/services/design_management/copy_design_collection/copy_service.rb
@@ -16,7 +16,7 @@ module DesignManagement
@temporary_branch = "CopyDesignCollectionService_#{SecureRandom.hex}"
# The user who triggered the copy may not have permissions to push
# to the design repository.
- @git_user = @target_project.default_owner
+ @git_user = @target_project.first_owner
@designs = DesignManagement::Design.unscoped.where(issue: issue).order(:id).load
@versions = DesignManagement::Version.unscoped.where(issue: issue).order(:id).includes(:designs).load
diff --git a/app/services/emails/confirm_service.rb b/app/services/emails/confirm_service.rb
index 38204e011dd..4ceff4e3cfa 100644
--- a/app/services/emails/confirm_service.rb
+++ b/app/services/emails/confirm_service.rb
@@ -7,3 +7,5 @@ module Emails
end
end
end
+
+Emails::ConfirmService.prepend_mod
diff --git a/app/services/error_tracking/collect_error_service.rb b/app/services/error_tracking/collect_error_service.rb
index 304e3898ee5..50508c9810a 100644
--- a/app/services/error_tracking/collect_error_service.rb
+++ b/app/services/error_tracking/collect_error_service.rb
@@ -2,6 +2,8 @@
module ErrorTracking
class CollectErrorService < ::BaseService
+ include Gitlab::Utils::StrongMemoize
+
def execute
# Error is a way to group events based on common data like name or cause
# of exception. We need to keep a sane balance here between taking too little
@@ -43,16 +45,29 @@ module ErrorTracking
end
def exception
- event['exception']['values'].first
+ strong_memoize(:exception) do
+ # Find the first exception that has a stacktrace since the first
+ # exception may not provide adequate context (e.g. in the Go SDK).
+ entries = event['exception']['values']
+ entries.find { |x| x.key?('stacktrace') } || entries.first
+ end
+ end
+
+ def stacktrace_frames
+ strong_memoize(:stacktrace_frames) do
+ exception.dig('stacktrace', 'frames')
+ end
end
def actor
return event['transaction'] if event['transaction']
- # Some SDK do not have transaction attribute.
+ # Some SDKs do not have a transaction attribute.
# So we build it by combining function name and module name from
# the last item in stacktrace.
- last_line = exception.dig('stacktrace', 'frames').last
+ return unless stacktrace_frames.present?
+
+ last_line = stacktrace_frames.last
"#{last_line['function']}(#{last_line['module']})"
end
diff --git a/app/services/events/destroy_service.rb b/app/services/events/destroy_service.rb
index fdb718f0fcb..3a0a7b339bf 100644
--- a/app/services/events/destroy_service.rb
+++ b/app/services/events/destroy_service.rb
@@ -2,20 +2,29 @@
module Events
class DestroyService
+ BATCH_SIZE = 50
+
def initialize(project)
@project = project
end
def execute
- project.events.all.delete_all
+ loop do
+ count = delete_events_in_batches
+ break if count < BATCH_SIZE
+ end
ServiceResponse.success(message: 'Events were deleted.')
- rescue StandardError
- ServiceResponse.error(message: 'Failed to remove events.')
+ rescue StandardError => e
+ ServiceResponse.error(message: e.message)
end
private
attr_reader :project
+
+ def delete_events_in_batches
+ project.events.limit(BATCH_SIZE).delete_all
+ end
end
end
diff --git a/app/services/google_cloud/create_service_accounts_service.rb b/app/services/google_cloud/create_service_accounts_service.rb
new file mode 100644
index 00000000000..fa025e8f672
--- /dev/null
+++ b/app/services/google_cloud/create_service_accounts_service.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+module GoogleCloud
+ class CreateServiceAccountsService < :: BaseService
+ def execute
+ service_account = google_api_client.create_service_account(gcp_project_id, service_account_name, service_account_desc)
+ service_account_key = google_api_client.create_service_account_key(gcp_project_id, service_account.unique_id)
+
+ service_accounts_service.add_for_project(
+ environment_name,
+ service_account.project_id,
+ service_account.to_json,
+ service_account_key.to_json,
+ environment_protected?
+ )
+
+ ServiceResponse.success(message: _('Service account generated successfully'), payload: {
+ service_account: service_account,
+ service_account_key: service_account_key
+ })
+ end
+
+ private
+
+ def google_oauth2_token
+ @params[:google_oauth2_token]
+ end
+
+ def gcp_project_id
+ @params[:gcp_project_id]
+ end
+
+ def environment_name
+ @params[:environment_name]
+ end
+
+ def google_api_client
+ GoogleApi::CloudPlatform::Client.new(google_oauth2_token, nil)
+ end
+
+ def service_accounts_service
+ GoogleCloud::ServiceAccountsService.new(project)
+ end
+
+ def service_account_name
+ "GitLab :: #{project.name} :: #{environment_name}"
+ end
+
+ def service_account_desc
+ "GitLab generated service account for project '#{project.name}' and environment '#{environment_name}'"
+ end
+
+ # Overriden in EE
+ def environment_protected?
+ false
+ end
+ end
+end
+
+GoogleCloud::CreateServiceAccountsService.prepend_mod
diff --git a/app/services/google_cloud/service_accounts_service.rb b/app/services/google_cloud/service_accounts_service.rb
index a512b27493d..3014daf08e2 100644
--- a/app/services/google_cloud/service_accounts_service.rb
+++ b/app/services/google_cloud/service_accounts_service.rb
@@ -27,39 +27,42 @@ module GoogleCloud
end
end
- def add_for_project(environment, gcp_project_id, service_account, service_account_key)
+ def add_for_project(environment, gcp_project_id, service_account, service_account_key, is_protected)
project_var_create_or_replace(
environment,
'GCP_PROJECT_ID',
- gcp_project_id
+ gcp_project_id,
+ is_protected
)
project_var_create_or_replace(
environment,
'GCP_SERVICE_ACCOUNT',
- service_account
+ service_account,
+ is_protected
)
project_var_create_or_replace(
environment,
'GCP_SERVICE_ACCOUNT_KEY',
- service_account_key
+ service_account_key,
+ is_protected
)
end
private
def group_vars_by_environment
- filtered_vars = @project.variables.filter { |variable| GCP_KEYS.include? variable.key }
+ filtered_vars = project.variables.filter { |variable| GCP_KEYS.include? variable.key }
filtered_vars.each_with_object({}) do |variable, grouped|
grouped[variable.environment_scope] ||= {}
grouped[variable.environment_scope][variable.key] = variable.value
end
end
- def project_var_create_or_replace(environment_scope, key, value)
+ def project_var_create_or_replace(environment_scope, key, value, is_protected)
params = { key: key, filter: { environment_scope: environment_scope } }
- existing_variable = ::Ci::VariablesFinder.new(@project, params).execute.first
+ existing_variable = ::Ci::VariablesFinder.new(project, params).execute.first
existing_variable.destroy if existing_variable
- @project.variables.create!(key: key, value: value, environment_scope: environment_scope, protected: true)
+ project.variables.create!(key: key, value: value, environment_scope: environment_scope, protected: is_protected)
end
end
end
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 2d6334251ad..b3b0397eac3 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -107,6 +107,7 @@ module Groups
def handle_changes
handle_settings_update
+ handle_crm_settings_update unless params[:crm_enabled].nil?
end
def handle_settings_update
@@ -116,6 +117,15 @@ module Groups
::NamespaceSettings::UpdateService.new(current_user, group, settings_params).execute
end
+ def handle_crm_settings_update
+ crm_enabled = params.delete(:crm_enabled)
+ return if group.crm_enabled? == crm_enabled
+
+ crm_settings = group.crm_settings || group.build_crm_settings
+ crm_settings.enabled = crm_enabled
+ crm_settings.save
+ end
+
def allowed_settings_params
SETTINGS_PARAMS
end
diff --git a/app/services/import/gitlab_projects/create_project_from_remote_file_service.rb b/app/services/import/gitlab_projects/create_project_from_remote_file_service.rb
index bbfdaf692f9..edb9dc8ad91 100644
--- a/app/services/import/gitlab_projects/create_project_from_remote_file_service.rb
+++ b/app/services/import/gitlab_projects/create_project_from_remote_file_service.rb
@@ -4,7 +4,10 @@ module Import
module GitlabProjects
class CreateProjectFromRemoteFileService < CreateProjectFromUploadedFileService
FILE_SIZE_LIMIT = 10.gigabytes
- ALLOWED_CONTENT_TYPES = ['application/gzip'].freeze
+ ALLOWED_CONTENT_TYPES = [
+ 'application/gzip', # most common content-type when fetching a tar.gz
+ 'application/x-tar' # aws-s3 uses x-tar for tar.gz files
+ ].freeze
validate :valid_remote_import_url?
validate :validate_file_size
@@ -44,17 +47,27 @@ module Import
end
def validate_content_type
+ # AWS-S3 presigned URLs don't respond to HTTP HEAD requests,
+ # so file type cannot be validated
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75170#note_748059103
+ return if amazon_s3?
+
if headers['content-type'].blank?
errors.add(:base, "Missing 'ContentType' header")
elsif !ALLOWED_CONTENT_TYPES.include?(headers['content-type'])
errors.add(:base, "Remote file content type '%{content_type}' not allowed. (Allowed content types: %{allowed})" % {
content_type: headers['content-type'],
- allowed: ALLOWED_CONTENT_TYPES.join(',')
+ allowed: ALLOWED_CONTENT_TYPES.join(', ')
})
end
end
def validate_file_size
+ # AWS-S3 presigned URLs don't respond to HTTP HEAD requests,
+ # so file size cannot be validated
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75170#note_748059103
+ return if amazon_s3?
+
if headers['content-length'].to_i == 0
errors.add(:base, "Missing 'ContentLength' header")
elsif headers['content-length'].to_i > FILE_SIZE_LIMIT
@@ -64,6 +77,10 @@ module Import
end
end
+ def amazon_s3?
+ headers['Server'] == 'AmazonS3' && headers['x-amz-request-id'].present?
+ end
+
def headers
return {} if params[:remote_import_url].blank? || !valid_remote_import_url?
diff --git a/app/services/import/validate_remote_git_endpoint_service.rb b/app/services/import/validate_remote_git_endpoint_service.rb
index afccb5373a9..1b8fa45e979 100644
--- a/app/services/import/validate_remote_git_endpoint_service.rb
+++ b/app/services/import/validate_remote_git_endpoint_service.rb
@@ -23,6 +23,8 @@ module Import
return ServiceResponse.error(message: "#{@params[:url]} is not a valid URL") unless uri
+ return ServiceResponse.success if uri.scheme == 'git'
+
uri.fragment = nil
url = Gitlab::Utils.append_path(uri.to_s, "/info/refs?service=#{GIT_SERVICE_NAME}")
diff --git a/app/services/incident_management/issuable_escalation_statuses/after_update_service.rb b/app/services/incident_management/issuable_escalation_statuses/after_update_service.rb
new file mode 100644
index 00000000000..a7a99f88b32
--- /dev/null
+++ b/app/services/incident_management/issuable_escalation_statuses/after_update_service.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+ module IssuableEscalationStatuses
+ class AfterUpdateService < ::BaseProjectService
+ def initialize(issuable, current_user)
+ @issuable = issuable
+ @escalation_status = issuable.escalation_status
+ @alert = issuable.alert_management_alert
+
+ super(project: issuable.project, current_user: current_user)
+ end
+
+ def execute
+ after_update
+
+ ServiceResponse.success(payload: { escalation_status: escalation_status })
+ end
+
+ private
+
+ attr_reader :issuable, :escalation_status, :alert
+
+ def after_update
+ sync_to_alert
+ end
+
+ def sync_to_alert
+ return unless alert
+ return if alert.status == escalation_status.status
+
+ ::AlertManagement::Alerts::UpdateService.new(
+ alert,
+ current_user,
+ status: escalation_status.status_name
+ ).execute
+ end
+ end
+ end
+end
+
+::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService.prepend_mod
diff --git a/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
new file mode 100644
index 00000000000..1a660e1a163
--- /dev/null
+++ b/app/services/incident_management/issuable_escalation_statuses/prepare_update_service.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+module IncidentManagement
+ module IssuableEscalationStatuses
+ class PrepareUpdateService
+ include Gitlab::Utils::StrongMemoize
+
+ SUPPORTED_PARAMS = %i[status].freeze
+
+ InvalidParamError = Class.new(StandardError)
+
+ def initialize(issuable, current_user, params)
+ @issuable = issuable
+ @current_user = current_user
+ @params = params.dup || {}
+ @project = issuable.project
+ end
+
+ def execute
+ return availability_error unless available?
+
+ filter_unsupported_params
+ filter_attributes
+ filter_redundant_params
+
+ ServiceResponse.success(payload: { escalation_status: params })
+ rescue InvalidParamError
+ invalid_param_error
+ end
+
+ private
+
+ attr_reader :issuable, :current_user, :params, :project
+
+ def available?
+ Feature.enabled?(:incident_escalations, project) &&
+ user_has_permissions? &&
+ issuable.supports_escalation? &&
+ escalation_status.present?
+ end
+
+ def user_has_permissions?
+ current_user&.can?(:update_escalation_status, issuable)
+ end
+
+ def escalation_status
+ strong_memoize(:escalation_status) do
+ issuable.escalation_status
+ end
+ end
+
+ def filter_unsupported_params
+ params.slice!(*supported_params)
+ end
+
+ def supported_params
+ SUPPORTED_PARAMS
+ end
+
+ def filter_attributes
+ filter_status
+ end
+
+ def filter_status
+ status = params.delete(:status)
+ return unless status
+
+ status_event = escalation_status.status_event_for(status)
+ raise InvalidParamError unless status_event
+
+ params[:status_event] = status_event
+ end
+
+ def filter_redundant_params
+ params.delete_if do |key, value|
+ current_params.key?(key) && current_params[key] == value
+ end
+ end
+
+ def current_params
+ strong_memoize(:current_params) do
+ {
+ status_event: escalation_status.status_event_for(escalation_status.status_name)
+ }
+ end
+ end
+
+ def availability_error
+ ServiceResponse.error(message: 'Escalation status updates are not available for this issue, user, or project.')
+ end
+
+ def invalid_param_error
+ ServiceResponse.error(message: 'Invalid value was provided for a parameter.')
+ end
+ end
+ end
+end
+
+::IncidentManagement::IssuableEscalationStatuses::PrepareUpdateService.prepend_mod
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 1d1d9b6bec7..ecf10cf97a8 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -63,6 +63,7 @@ class IssuableBaseService < ::BaseProjectService
filter_milestone
filter_labels
filter_severity(issuable)
+ filter_escalation_status(issuable)
end
def filter_assignees(issuable)
@@ -152,6 +153,18 @@ class IssuableBaseService < ::BaseProjectService
params[:issuable_severity_attributes] = { severity: severity }
end
+ def filter_escalation_status(issuable)
+ result = ::IncidentManagement::IssuableEscalationStatuses::PrepareUpdateService.new(
+ issuable,
+ current_user,
+ params.delete(:escalation_status)
+ ).execute
+
+ return unless result.success? && result.payload.present?
+
+ params[:incident_management_issuable_escalation_status_attributes] = result[:escalation_status]
+ end
+
def process_label_ids(attributes, existing_label_ids: nil, extra_label_ids: [])
label_ids = attributes.delete(:label_ids)
add_label_ids = attributes.delete(:add_label_ids)
@@ -471,6 +484,7 @@ class IssuableBaseService < ::BaseProjectService
associations[:description] = issuable.description
associations[:reviewers] = issuable.reviewers.to_a if issuable.allows_reviewers?
associations[:severity] = issuable.severity if issuable.supports_severity?
+ associations[:escalation_status] = issuable.escalation_status&.slice(:status, :policy_id) if issuable.supports_escalation?
associations
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 577f7dd1e3a..37d667d4be8 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -36,8 +36,8 @@ module Issues
private
def find_work_item_type_id(issue_type)
- work_item_type = WorkItem::Type.default_by_type(issue_type)
- work_item_type ||= WorkItem::Type.default_issue_type
+ work_item_type = WorkItems::Type.default_by_type(issue_type)
+ work_item_type ||= WorkItems::Type.default_issue_type
work_item_type.id
end
@@ -46,7 +46,6 @@ module Issues
super
params.delete(:issue_type) unless create_issue_type_allowed?(issue, params[:issue_type])
- filter_incident_label(issue) if params[:issue_type]
moved_issue = params.delete(:moved_issue)
@@ -89,37 +88,6 @@ module Issues
Milestones::IssuesCountService.new(milestone).delete_cache
end
-
- # @param issue [Issue]
- def filter_incident_label(issue)
- return unless add_incident_label?(issue) || remove_incident_label?(issue)
-
- label = ::IncidentManagement::CreateIncidentLabelService
- .new(project, current_user)
- .execute
- .payload[:label]
-
- # These(add_label_ids, remove_label_ids) are being added ahead of time
- # to be consumed by #process_label_ids, this allows system notes
- # to be applied correctly alongside the label updates.
- if add_incident_label?(issue)
- params[:add_label_ids] ||= []
- params[:add_label_ids] << label.id
- else
- params[:remove_label_ids] ||= []
- params[:remove_label_ids] << label.id
- end
- end
-
- # @param issue [Issue]
- def add_incident_label?(issue)
- issue.incident?
- end
-
- # @param _issue [Issue, nil]
- def remove_incident_label?(_issue)
- false
- end
end
end
diff --git a/app/services/issues/build_service.rb b/app/services/issues/build_service.rb
index 8fd844c4886..1ebf9bb6ba2 100644
--- a/app/services/issues/build_service.rb
+++ b/app/services/issues/build_service.rb
@@ -7,7 +7,7 @@ module Issues
def execute
filter_resolve_discussion_params
- @issue = project.issues.new(issue_params).tap do |issue|
+ @issue = model_klass.new(issue_params.merge(project: project)).tap do |issue|
ensure_milestone_available(issue)
end
end
@@ -62,16 +62,25 @@ module Issues
def issue_params
@issue_params ||= build_issue_params
- # If :issue_type is nil then params[:issue_type] was either nil
- # or not permitted. Either way, the :issue_type will default
- # to the column default of `issue`. And that means we need to
- # ensure the work_item_type_id is set
- @issue_params[:work_item_type_id] = get_work_item_type_id(@issue_params[:issue_type])
+ if @issue_params[:work_item_type].present?
+ @issue_params[:issue_type] = @issue_params[:work_item_type].base_type
+ else
+ # If :issue_type is nil then params[:issue_type] was either nil
+ # or not permitted. Either way, the :issue_type will default
+ # to the column default of `issue`. And that means we need to
+ # ensure the work_item_type_id is set
+ @issue_params[:work_item_type_id] = get_work_item_type_id(@issue_params[:issue_type])
+ end
+
@issue_params
end
private
+ def model_klass
+ ::Issue
+ end
+
def allowed_issue_params
allowed_params = [
:title,
@@ -79,8 +88,11 @@ module Issues
:confidential
]
+ params[:work_item_type] = WorkItems::Type.find_by(id: params[:work_item_type_id]) if params[:work_item_type_id].present? # rubocop: disable CodeReuse/ActiveRecord
+
allowed_params << :milestone_id if can?(current_user, :admin_issue, project)
allowed_params << :issue_type if create_issue_type_allowed?(project, params[:issue_type])
+ allowed_params << :work_item_type if create_issue_type_allowed?(project, params[:work_item_type]&.base_type)
params.slice(*allowed_params)
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index 79b59eee5e1..e29bcf4a453 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -12,13 +12,14 @@ module Issues
# spam_checking is likely to be necessary. However, if there is not a request available in scope
# in the caller (for example, an issue created via email) and the required arguments to the
# SpamParams constructor are not otherwise available, spam_params: must be explicitly passed as nil.
- def initialize(project:, current_user: nil, params: {}, spam_params:)
+ def initialize(project:, current_user: nil, params: {}, spam_params:, build_service: nil)
super(project: project, current_user: current_user, params: params)
@spam_params = spam_params
+ @build_service = build_service || BuildService.new(project: project, current_user: current_user, params: params)
end
def execute(skip_system_notes: false)
- @issue = BuildService.new(project: project, current_user: current_user, params: params).execute
+ @issue = @build_service.execute
filter_resolve_discussion_params
diff --git a/app/services/issues/set_crm_contacts_service.rb b/app/services/issues/set_crm_contacts_service.rb
index c435ab81b4d..947d46f0809 100644
--- a/app/services/issues/set_crm_contacts_service.rb
+++ b/app/services/issues/set_crm_contacts_service.rb
@@ -48,7 +48,7 @@ module Issues
end
def add_by_email
- contact_ids = ::CustomerRelations::Contact.find_ids_by_emails(project_group.id, params[:add_emails])
+ contact_ids = ::CustomerRelations::Contact.find_ids_by_emails(project_group, params[:add_emails])
add_by_id(contact_ids)
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 824a609dfb9..aecb22453b7 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -53,6 +53,7 @@ module Issues
old_mentioned_users = old_associations.fetch(:mentioned_users, [])
old_assignees = old_associations.fetch(:assignees, [])
old_severity = old_associations[:severity]
+ old_escalation_status = old_associations[:escalation_status]
if has_changes?(issue, old_labels: old_labels, old_assignees: old_assignees)
todo_service.resolve_todos_for_target(issue, current_user)
@@ -69,6 +70,7 @@ module Issues
handle_milestone_change(issue)
handle_added_mentions(issue, old_mentioned_users)
handle_severity_change(issue, old_severity)
+ handle_escalation_status_change(issue, old_escalation_status)
handle_issue_type_change(issue)
end
@@ -208,6 +210,13 @@ module Issues
::IncidentManagement::AddSeveritySystemNoteWorker.perform_async(issue.id, current_user.id)
end
+ def handle_escalation_status_change(issue, old_escalation_status)
+ return unless old_escalation_status.present?
+ return if issue.escalation_status&.slice(:status, :policy_id) == old_escalation_status
+
+ ::IncidentManagement::IssuableEscalationStatuses::AfterUpdateService.new(issue, current_user).execute
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def issuable_for_positioning(id, board_group_id = nil)
return unless id
@@ -227,16 +236,6 @@ module Issues
SystemNoteService.change_issue_confidentiality(issue, issue.project, current_user)
end
- override :add_incident_label?
- def add_incident_label?(issue)
- issue.issue_type != params[:issue_type] && !issue.incident?
- end
-
- override :remove_incident_label?
- def remove_incident_label?(issue)
- issue.issue_type != params[:issue_type] && issue.incident?
- end
-
def handle_issue_type_change(issue)
return unless issue.previous_changes.include?('issue_type')
@@ -245,6 +244,8 @@ module Issues
def do_handle_issue_type_change(issue)
SystemNoteService.change_issue_type(issue, current_user)
+
+ ::IncidentManagement::IssuableEscalationStatuses::CreateService.new(issue).execute if issue.supports_escalation?
end
end
end
diff --git a/app/services/labels/transfer_service.rb b/app/services/labels/transfer_service.rb
index 19d419609a5..67163cb8122 100644
--- a/app/services/labels/transfer_service.rb
+++ b/app/services/labels/transfer_service.rb
@@ -50,32 +50,17 @@ module Labels
# rubocop: disable CodeReuse/ActiveRecord
def group_labels_applied_to_issues
- @labels_applied_to_issues ||= if use_optimized_group_labels_query?
- Label.joins(:issues)
- .joins("INNER JOIN namespaces on namespaces.id = labels.group_id AND namespaces.type = 'Group'" )
- .where(issues: { project_id: project.id }).reorder(nil)
- else
- Label.joins(:issues).where(
- issues: { project_id: project.id },
- labels: { group_id: old_group.self_and_ancestors }
- )
- end
+ @labels_applied_to_issues ||= Label.joins(:issues)
+ .joins("INNER JOIN namespaces on namespaces.id = labels.group_id AND namespaces.type = 'Group'" )
+ .where(issues: { project_id: project.id }).reorder(nil)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def group_labels_applied_to_merge_requests
- @labels_applied_to_mrs ||= if use_optimized_group_labels_query?
- Label.joins(:merge_requests)
- .joins("INNER JOIN namespaces on namespaces.id = labels.group_id AND namespaces.type = 'Group'" )
- .where(merge_requests: { target_project_id: project.id }).reorder(nil)
- else
- Label.joins(:merge_requests)
- .where(
- merge_requests: { target_project_id: project.id },
- labels: { group_id: old_group.self_and_ancestors }
- )
- end
+ @labels_applied_to_mrs ||= Label.joins(:merge_requests)
+ .joins("INNER JOIN namespaces on namespaces.id = labels.group_id AND namespaces.type = 'Group'" )
+ .where(merge_requests: { target_project_id: project.id }).reorder(nil)
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -99,9 +84,5 @@ module Labels
.update_all(label_id: new_label_id)
end
# rubocop: enable CodeReuse/ActiveRecord
-
- def use_optimized_group_labels_query?
- Feature.enabled?(:use_optimized_group_labels_query, project.root_namespace, default_enabled: :yaml)
- end
end
end
diff --git a/app/services/loose_foreign_keys/process_deleted_records_service.rb b/app/services/loose_foreign_keys/process_deleted_records_service.rb
index 025829aa774..2826bdb4c3c 100644
--- a/app/services/loose_foreign_keys/process_deleted_records_service.rb
+++ b/app/services/loose_foreign_keys/process_deleted_records_service.rb
@@ -10,7 +10,6 @@ module LooseForeignKeys
def execute
modification_tracker = ModificationTracker.new
-
tracked_tables.cycle do |table|
records = load_batch_for_table(table)
diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb
index d3ef892875b..47cd19e9d8d 100644
--- a/app/services/merge_requests/add_todo_when_build_fails_service.rb
+++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb
@@ -16,9 +16,7 @@ module MergeRequests
# build is retried
#
def close(commit_status)
- pipeline_merge_requests(commit_status.pipeline) do |merge_request|
- todo_service.merge_request_build_retried(merge_request)
- end
+ close_all(commit_status.pipeline)
end
def close_all(pipeline)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index d744881549a..b0d0c32abd1 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -59,7 +59,9 @@ module MergeRequests
merge_request_activity_counter.track_users_review_requested(users: new_reviewers)
merge_request_activity_counter.track_reviewers_changed_action(user: current_user)
- remove_attention_requested(merge_request, current_user)
+ unless new_reviewers.include?(current_user)
+ remove_attention_requested(merge_request, current_user)
+ end
end
def cleanup_environments(merge_request)
diff --git a/app/services/merge_requests/handle_assignees_change_service.rb b/app/services/merge_requests/handle_assignees_change_service.rb
index 1d9f7ab59f4..97be9fe8d9f 100644
--- a/app/services/merge_requests/handle_assignees_change_service.rb
+++ b/app/services/merge_requests/handle_assignees_change_service.rb
@@ -23,7 +23,9 @@ module MergeRequests
execute_assignees_hooks(merge_request, old_assignees) if options[:execute_hooks]
- remove_attention_requested(merge_request, current_user)
+ unless new_assignees.include?(current_user)
+ remove_attention_requested(merge_request, current_user)
+ end
end
private
diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb
index 3b9d3bccacf..3e630d40b3d 100644
--- a/app/services/merge_requests/merge_base_service.rb
+++ b/app/services/merge_requests/merge_base_service.rb
@@ -56,7 +56,7 @@ module MergeRequests
def commit_message
params[:commit_message] ||
- merge_request.default_merge_commit_message
+ merge_request.default_merge_commit_message(user: current_user)
end
def squash_sha!
diff --git a/app/services/merge_requests/remove_approval_service.rb b/app/services/merge_requests/remove_approval_service.rb
index 872e7e0c89c..198a21884b8 100644
--- a/app/services/merge_requests/remove_approval_service.rb
+++ b/app/services/merge_requests/remove_approval_service.rb
@@ -17,6 +17,7 @@ module MergeRequests
reset_approvals_cache(merge_request)
create_note(merge_request)
merge_request_activity_counter.track_unapprove_mr_action(user: current_user)
+ remove_attention_requested(merge_request, current_user)
end
success
diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb
index 90cf4af7e41..69b9740c2a5 100644
--- a/app/services/merge_requests/squash_service.rb
+++ b/app/services/merge_requests/squash_service.rb
@@ -39,7 +39,7 @@ module MergeRequests
end
def message
- params[:squash_commit_message].presence || merge_request.default_squash_commit_message
+ params[:squash_commit_message].presence || merge_request.default_squash_commit_message(user: current_user)
end
end
end
diff --git a/app/services/packages/maven/metadata/sync_service.rb b/app/services/packages/maven/metadata/sync_service.rb
index 48e157d4930..4f35db36fb0 100644
--- a/app/services/packages/maven/metadata/sync_service.rb
+++ b/app/services/packages/maven/metadata/sync_service.rb
@@ -93,10 +93,15 @@ module Packages
def metadata_package_file_for(package)
return unless package
- package.package_files
- .with_file_name(Metadata.filename)
- .recent
- .first
+ package_files = if Feature.enabled?(:packages_installable_package_files, default_enabled: :yaml)
+ package.installable_package_files
+ else
+ package.package_files
+ end
+
+ package_files.with_file_name(Metadata.filename)
+ .recent
+ .first
end
def versionless_package_named(name)
diff --git a/app/services/packages/terraform_module/create_package_service.rb b/app/services/packages/terraform_module/create_package_service.rb
index 03f749edfa8..d1bc79089a3 100644
--- a/app/services/packages/terraform_module/create_package_service.rb
+++ b/app/services/packages/terraform_module/create_package_service.rb
@@ -7,7 +7,7 @@ module Packages
def execute
return error('Version is empty.', 400) if params[:module_version].blank?
- return error('Package already exists.', 403) if current_package_exists_elsewhere?
+ return error('Access Denied', 403) if current_package_exists_elsewhere?
return error('Package version already exists.', 403) if current_package_version_exists?
return error('File is too large.', 400) if file_size_exceeded?
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index aef92b8adee..5c4a0e947de 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -41,6 +41,8 @@ module Projects
true
rescue StandardError => error
+ context = Gitlab::ApplicationContext.current.merge(project_id: project.id)
+ Gitlab::ErrorTracking.track_exception(error, **context)
attempt_rollback(project, error.message)
false
rescue Exception => error # rubocop:disable Lint/RescueException
@@ -80,8 +82,14 @@ module Projects
end
def remove_events
+ log_info("Attempting to destroy events from #{project.full_path} (#{project.id})")
+
response = ::Events::DestroyService.new(project).execute
+ if response.error?
+ log_error("Event deletion failed on #{project.full_path} with the following message: #{response.message}")
+ end
+
response.success?
end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index 74b7d18f401..3e8d6563709 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -69,6 +69,8 @@ module Projects
new_params[:avatar] = @project.avatar
end
+ new_params[:mr_default_target_self] = target_mr_default_target_self unless target_mr_default_target_self.nil?
+
new_params.merge!(@project.object_pool_params)
new_params
@@ -127,5 +129,9 @@ module Projects
Gitlab::VisibilityLevel.closest_allowed_level(target_level)
end
+
+ def target_mr_default_target_self
+ @target_mr_default_target_self ||= params[:mr_default_target_self]
+ end
end
end
diff --git a/app/services/projects/overwrite_project_service.rb b/app/services/projects/overwrite_project_service.rb
index 2612001eb95..c58fba33b2a 100644
--- a/app/services/projects/overwrite_project_service.rb
+++ b/app/services/projects/overwrite_project_service.rb
@@ -11,7 +11,9 @@ module Projects
move_before_destroy_relationships(source_project)
# Reset is required in order to get the proper
# uncached fork network method calls value.
- destroy_old_project(source_project.reset)
+ ::Gitlab::Database::QueryAnalyzers::PreventCrossDatabaseModification.allow_cross_database_modification_within_transaction(url: 'https://gitlab.com/gitlab-org/gitlab/-/issues/340256') do
+ destroy_old_project(source_project.reset)
+ end
rename_project(source_project.name, source_project.path)
@project
diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb
index 56f65718d24..bc517ee3d6f 100644
--- a/app/services/projects/prometheus/alerts/notify_service.rb
+++ b/app/services/projects/prometheus/alerts/notify_service.rb
@@ -18,6 +18,14 @@ module Projects
SUPPORTED_VERSION = '4'
+ # If feature flag :prometheus_notify_max_alerts is enabled truncate
+ # alerts to 100 and process only them.
+ # If feature flag is disabled process any amount of alerts.
+ #
+ # This is to mitigate incident:
+ # https://gitlab.com/gitlab-com/gl-infra/production/-/issues/6086
+ PROCESS_MAX_ALERTS = 100
+
def initialize(project, payload)
@project = project
@payload = payload
@@ -28,6 +36,8 @@ module Projects
return unprocessable_entity unless self.class.processable?(payload)
return unauthorized unless valid_alert_manager_token?(token, integration)
+ truncate_alerts! if max_alerts_exceeded?
+
alert_responses = process_prometheus_alerts
alert_response(alert_responses)
@@ -49,12 +59,23 @@ module Projects
Gitlab::Utils::DeepSize.new(payload).valid?
end
- def firings
- @firings ||= alerts_by_status('firing')
+ def max_alerts_exceeded?
+ return false unless Feature.enabled?(:prometheus_notify_max_alerts, project, type: :ops)
+
+ alerts.size > PROCESS_MAX_ALERTS
end
- def alerts_by_status(status)
- alerts.select { |alert| alert['status'] == status }
+ def truncate_alerts!
+ Gitlab::AppLogger.warn(
+ message: 'Prometheus payload exceeded maximum amount of alerts. Truncating alerts.',
+ project_id: project.id,
+ alerts: {
+ total: alerts.size,
+ max: PROCESS_MAX_ALERTS
+ }
+ )
+
+ payload['alerts'] = alerts.first(PROCESS_MAX_ALERTS)
end
def alerts
@@ -137,7 +158,7 @@ module Projects
end
def alert_response(alert_responses)
- alerts = alert_responses.map { |resp| resp.payload[:alert] }.compact
+ alerts = alert_responses.flat_map { |resp| resp.payload[:alerts] }.compact
success(alerts)
end
diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb
deleted file mode 100644
index 4272e1dc8b6..00000000000
--- a/app/services/projects/update_pages_configuration_service.rb
+++ /dev/null
@@ -1,109 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- class UpdatePagesConfigurationService < BaseService
- include Gitlab::Utils::StrongMemoize
-
- attr_reader :project
-
- def initialize(project)
- @project = project
- end
-
- def execute
- return success unless ::Settings.pages.local_store.enabled
-
- # If the pages were never deployed, we can't write out the config, as the
- # directory would not exist.
- # https://gitlab.com/gitlab-org/gitlab/-/issues/235139
- return success unless project.pages_deployed?
-
- unless file_equals?(pages_config_file, pages_config_json)
- update_file(pages_config_file, pages_config_json)
- reload_daemon
- end
-
- success
- end
-
- private
-
- def pages_config_json
- strong_memoize(:pages_config_json) do
- pages_config.to_json
- end
- end
-
- def pages_config
- {
- domains: pages_domains_config,
- https_only: project.pages_https_only?,
- id: project.project_id,
- access_control: !project.public_pages?
- }
- end
-
- def pages_domains_config
- enabled_pages_domains.map do |domain|
- {
- domain: domain.domain,
- certificate: domain.certificate,
- key: domain.key,
- https_only: project.pages_https_only? && domain.https?,
- id: project.project_id,
- access_control: !project.public_pages?
- }
- end
- end
-
- def enabled_pages_domains
- if Gitlab::CurrentSettings.pages_domain_verification_enabled?
- project.pages_domains.enabled
- else
- project.pages_domains
- end
- end
-
- def reload_daemon
- # GitLab Pages daemon constantly watches for modification time of `pages.path`
- # It reloads configuration when `pages.path` is modified
- update_file(pages_update_file, SecureRandom.hex(64))
- end
-
- def pages_path
- @pages_path ||= project.pages_path
- end
-
- def pages_config_file
- File.join(pages_path, 'config.json')
- end
-
- def pages_update_file
- File.join(::Settings.pages.path, '.update')
- end
-
- def update_file(file, data)
- temp_file = "#{file}.#{SecureRandom.hex(16)}"
- File.open(temp_file, 'w') do |f|
- f.write(data)
- end
- FileUtils.move(temp_file, file, force: true)
- ensure
- # In case if the updating fails
- FileUtils.remove(temp_file, force: true)
- end
-
- def file_equals?(file, data)
- existing_data = read_file(file)
- data == existing_data.to_s
- end
-
- def read_file(file)
- File.open(file, 'r') do |f|
- f.read
- end
- rescue StandardError
- nil
- end
- end
-end
diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb
index 898125c181c..f3ea0967a99 100644
--- a/app/services/projects/update_remote_mirror_service.rb
+++ b/app/services/projects/update_remote_mirror_service.rb
@@ -41,18 +41,49 @@ module Projects
remote_mirror.update_start!
# LFS objects must be sent first, or the push has dangling pointers
- send_lfs_objects!(remote_mirror)
+ lfs_status = send_lfs_objects!(remote_mirror)
response = remote_mirror.update_repository
+ failed, failure_message = failure_status(lfs_status, response, remote_mirror)
+
+ # When the issue https://gitlab.com/gitlab-org/gitlab/-/issues/349262 is closed,
+ # we can move this block within failure_status.
+ if failed
+ remote_mirror.mark_as_failed!(failure_message)
+ else
+ remote_mirror.update_finish!
+ end
+ end
+
+ def failure_status(lfs_status, response, remote_mirror)
+ message = ''
+ failed = false
+ lfs_sync_failed = false
+
+ if lfs_status&.dig(:status) == :error
+ lfs_sync_failed = true
+ message += "Error synchronizing LFS files:"
+ message += "\n\n#{lfs_status[:message]}\n\n"
+
+ failed = Feature.enabled?(:remote_mirror_fail_on_lfs, project, default_enabled: :yaml)
+ end
if response.divergent_refs.any?
- message = "Some refs have diverged and have not been updated on the remote:"
+ message += "Some refs have diverged and have not been updated on the remote:"
message += "\n\n#{response.divergent_refs.join("\n")}"
+ failed = true
+ end
- remote_mirror.mark_as_failed!(message)
- else
- remote_mirror.update_finish!
+ if message.present?
+ Gitlab::AppJsonLogger.info(message: "Error synching remote mirror",
+ project_id: project.id,
+ project_path: project.full_path,
+ remote_mirror_id: remote_mirror.id,
+ lfs_sync_failed: lfs_sync_failed,
+ divergent_ref_list: response.divergent_refs)
end
+
+ [failed, message]
end
def send_lfs_objects!(remote_mirror)
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index b34ecf06e52..fb810af3e6b 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -104,7 +104,6 @@ module Projects
system_hook_service.execute_hooks_for(project, :update)
end
- update_pages_config if changing_pages_related_config?
update_pending_builds if runners_settings_toggled?
end
@@ -112,10 +111,6 @@ module Projects
AfterRenameService.new(project, path_before: project.path_before_last_save, full_path_before: project.full_path_before_last_save)
end
- def changing_pages_related_config?
- changing_pages_https_only? || changing_pages_access_level?
- end
-
def update_failed!
model_errors = project.errors.full_messages.to_sentence
error_message = model_errors.presence || s_('UpdateProject|Project could not be updated!')
@@ -143,10 +138,6 @@ module Projects
params.dig(:project_feature_attributes, :wiki_access_level).to_i > ProjectFeature::DISABLED
end
- def changing_pages_access_level?
- params.dig(:project_feature_attributes, :pages_access_level)
- end
-
def ensure_wiki_exists
return if project.create_wiki
@@ -154,16 +145,6 @@ module Projects
Gitlab::Metrics.counter(:wiki_can_not_be_created_total, 'Counts the times we failed to create a wiki').increment
end
- def update_pages_config
- return unless project.pages_deployed?
-
- PagesUpdateConfigurationWorker.perform_async(project.id)
- end
-
- def changing_pages_https_only?
- project.previous_changes.include?(:pages_https_only)
- end
-
def changing_repository_storage?
new_repository_storage = params[:repository_storage]
diff --git a/app/services/resource_access_tokens/create_service.rb b/app/services/resource_access_tokens/create_service.rb
index e0371e5d80f..28ea1ac8296 100644
--- a/app/services/resource_access_tokens/create_service.rb
+++ b/app/services/resource_access_tokens/create_service.rb
@@ -63,7 +63,7 @@ module ResourceAccessTokens
name: params[:name] || "#{resource.name.to_s.humanize} bot",
email: generate_email,
username: generate_username,
- user_type: "#{resource_type}_bot".to_sym,
+ user_type: :project_bot,
skip_confirmation: true # Bot users should always have their emails confirmed.
}
end
@@ -75,7 +75,8 @@ module ResourceAccessTokens
end
def generate_email
- email_pattern = "#{resource_type}#{resource.id}_bot%s@example.com"
+ # Default emaildomain need to be reworked. See gitlab-org/gitlab#260305
+ email_pattern = "#{resource_type}#{resource.id}_bot%s@noreply.#{Gitlab.config.gitlab.host}"
uniquify.string(-> (n) { Kernel.sprintf(email_pattern, n) }) do |s|
User.find_by_email(s)
diff --git a/app/services/resource_access_tokens/revoke_service.rb b/app/services/resource_access_tokens/revoke_service.rb
index 9543ea4b68d..2aaf4cc31d2 100644
--- a/app/services/resource_access_tokens/revoke_service.rb
+++ b/app/services/resource_access_tokens/revoke_service.rb
@@ -43,13 +43,9 @@ module ResourceAccessTokens
def find_member
strong_memoize(:member) do
- if resource.is_a?(Project)
- resource.project_member(bot_user)
- elsif resource.is_a?(Group)
- resource.group_member(bot_user)
- else
- false
- end
+ next false unless resource.is_a?(Project) || resource.is_a?(Group)
+
+ resource.member(bot_user)
end
end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 171d52c328d..28e487aa24d 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -7,6 +7,8 @@ class SearchService
DEFAULT_PER_PAGE = Gitlab::SearchResults::DEFAULT_PER_PAGE
MAX_PER_PAGE = 200
+ attr_reader :params
+
def initialize(current_user, params = {})
@current_user = current_user
@params = Gitlab::Search::Params.new(params, detect_abuse: prevent_abusive_searches?)
@@ -159,7 +161,7 @@ class SearchService
end
end
- attr_reader :current_user, :params
+ attr_reader :current_user
end
SearchService.prepend_mod_with('SearchService')
diff --git a/app/services/users/upsert_credit_card_validation_service.rb b/app/services/users/upsert_credit_card_validation_service.rb
index 61cf598f178..7190c82bea3 100644
--- a/app/services/users/upsert_credit_card_validation_service.rb
+++ b/app/services/users/upsert_credit_card_validation_service.rb
@@ -2,8 +2,9 @@
module Users
class UpsertCreditCardValidationService < BaseService
- def initialize(params)
+ def initialize(params, user)
@params = params.to_h.with_indifferent_access
+ @current_user = user
end
def execute
@@ -18,6 +19,8 @@ module Users
::Users::CreditCardValidation.upsert(@params)
+ ::Users::UpdateService.new(current_user, user: current_user, requires_credit_card_verification: false).execute!
+
ServiceResponse.success(message: 'CreditCardValidation was set')
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::NotNullViolation => e
ServiceResponse.error(message: "Could not set CreditCardValidation: #{e.message}")
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 79bdf34392f..33e34ec41e2 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -26,7 +26,6 @@ class WebHookService
end
REQUEST_BODY_SIZE_LIMIT = 25.megabytes
- GITLAB_EVENT_HEADER = 'X-Gitlab-Event'
attr_accessor :hook, :data, :hook_name, :request_options
attr_reader :uniqueness_token
@@ -50,6 +49,10 @@ class WebHookService
def execute
return { status: :error, message: 'Hook disabled' } unless hook.executable?
+ log_recursion_limit if recursion_blocked?
+
+ Gitlab::WebHooks::RecursionDetection.register!(hook)
+
start_time = Gitlab::Metrics::System.monotonic_time
response = if parsed_url.userinfo.blank?
@@ -95,6 +98,10 @@ class WebHookService
Gitlab::ApplicationContext.with_context(hook.application_context) do
break log_rate_limit if rate_limited?
+ log_recursion_limit if recursion_blocked?
+
+ data[:_gitlab_recursion_detection_request_uuid] = Gitlab::WebHooks::RecursionDetection::UUID.instance.request_uuid
+
WebHookWorker.perform_async(hook.id, data, hook_name)
end
end
@@ -108,7 +115,7 @@ class WebHookService
def make_request(url, basic_auth = false)
Gitlab::HTTP.post(url,
body: Gitlab::Json::LimitedEncoder.encode(data, limit: REQUEST_BODY_SIZE_LIMIT),
- headers: build_headers(hook_name),
+ headers: build_headers,
verify: hook.enable_ssl_verification,
basic_auth: basic_auth,
**request_options)
@@ -129,7 +136,7 @@ class WebHookService
trigger: trigger,
url: url,
execution_duration: execution_duration,
- request_headers: build_headers(hook_name),
+ request_headers: build_headers,
request_data: request_data,
response_headers: format_response_headers(response),
response_body: safe_response_body(response),
@@ -151,15 +158,16 @@ class WebHookService
end
end
- def build_headers(hook_name)
+ def build_headers
@headers ||= begin
- {
+ headers = {
'Content-Type' => 'application/json',
'User-Agent' => "GitLab/#{Gitlab::VERSION}",
- GITLAB_EVENT_HEADER => self.class.hook_to_event(hook_name)
- }.tap do |hash|
- hash['X-Gitlab-Token'] = Gitlab::Utils.remove_line_breaks(hook.token) if hook.token.present?
- end
+ Gitlab::WebHooks::GITLAB_EVENT_HEADER => self.class.hook_to_event(hook_name)
+ }
+
+ headers['X-Gitlab-Token'] = Gitlab::Utils.remove_line_breaks(hook.token) if hook.token.present?
+ headers.merge!(Gitlab::WebHooks::RecursionDetection.header(hook))
end
end
@@ -186,6 +194,10 @@ class WebHookService
)
end
+ def recursion_blocked?
+ Gitlab::WebHooks::RecursionDetection.block?(hook)
+ end
+
def rate_limit
@rate_limit ||= hook.rate_limit
end
@@ -199,4 +211,15 @@ class WebHookService
**Gitlab::ApplicationContext.current
)
end
+
+ def log_recursion_limit
+ Gitlab::AuthLogger.error(
+ message: 'Webhook recursion detected and will be blocked in future',
+ hook_id: hook.id,
+ hook_type: hook.type,
+ hook_name: hook_name,
+ recursion_detection: ::Gitlab::WebHooks::RecursionDetection.to_log(hook),
+ **Gitlab::ApplicationContext.current
+ )
+ end
end
diff --git a/app/services/work_items/build_service.rb b/app/services/work_items/build_service.rb
new file mode 100644
index 00000000000..15a26f81d7c
--- /dev/null
+++ b/app/services/work_items/build_service.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module WorkItems
+ class BuildService < ::Issues::BuildService
+ private
+
+ def model_klass
+ ::WorkItem
+ end
+ end
+end
diff --git a/app/services/work_items/create_service.rb b/app/services/work_items/create_service.rb
new file mode 100644
index 00000000000..49f7b89158b
--- /dev/null
+++ b/app/services/work_items/create_service.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module WorkItems
+ class CreateService
+ def initialize(project:, current_user: nil, params: {}, spam_params:)
+ @create_service = ::Issues::CreateService.new(
+ project: project,
+ current_user: current_user,
+ params: params,
+ spam_params: spam_params,
+ build_service: ::WorkItems::BuildService.new(project: project, current_user: current_user, params: params)
+ )
+ end
+
+ def execute
+ @create_service.execute
+ end
+ end
+end
diff --git a/app/uploaders/ci/secure_file_uploader.rb b/app/uploaders/ci/secure_file_uploader.rb
new file mode 100644
index 00000000000..514d88dd177
--- /dev/null
+++ b/app/uploaders/ci/secure_file_uploader.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Ci
+ class SecureFileUploader < GitlabUploader
+ include ObjectStorage::Concern
+
+ storage_options Gitlab.config.ci_secure_files
+
+ # Use Lockbox to encrypt/decrypt the stored file (registers CarrierWave callbacks)
+ encrypt(key: :key)
+
+ def key
+ OpenSSL::HMAC.digest('SHA256', Gitlab::Application.secrets.db_key_base, model.project_id.to_s)
+ end
+
+ def checksum
+ @checksum ||= Digest::SHA256.hexdigest(model.file.read)
+ end
+
+ def store_dir
+ dynamic_segment
+ end
+
+ private
+
+ def dynamic_segment
+ Gitlab::HashedPath.new('secure_files', model.id, root_hash: model.project_id)
+ end
+
+ class << self
+ # direct upload is disabled since the file
+ # must always be encrypted
+ def direct_upload_enabled?
+ false
+ end
+
+ def background_upload_enabled?
+ false
+ end
+
+ def default_store
+ object_store_enabled? ? ObjectStorage::Store::REMOTE : ObjectStorage::Store::LOCAL
+ end
+ end
+ end
+end
diff --git a/app/validators/json_schemas/error_tracking_event_payload.json b/app/validators/json_schemas/error_tracking_event_payload.json
index 1497a05a68f..d1728609315 100644
--- a/app/validators/json_schemas/error_tracking_event_payload.json
+++ b/app/validators/json_schemas/error_tracking_event_payload.json
@@ -13,57 +13,72 @@
"type": "object"
},
"exception": {
- "type": "object",
- "required": ["values"],
- "properties": {
- "values": {
+ "oneOf": [
+ {
"type": "array",
"items": {
- "type": "object",
- "required": [],
- "properties": {
- "type": {
- "type": "string"
- },
- "value": {
- "type": "string"
- },
- "stacktrace": {
+ "$ref": "#/definitions/exception"
+ }
+ },
+ {
+ "type": "object",
+ "required": ["values"],
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/definitions/exception"
+ }
+ }
+ }
+ }
+ ]
+ }
+ },
+ "definitions": {
+ "exception": {
+ "type": "object",
+ "required": [],
+ "properties": {
+ "type": {
+ "type": "string"
+ },
+ "value": {
+ "type": "string"
+ },
+ "stacktrace": {
+ "type": "object",
+ "required": [],
+ "properties": {
+ "frames": {
+ "type": "array",
+ "items": {
"type": "object",
"required": [],
"properties": {
- "frames": {
- "type": "array",
- "items": {
- "type": "object",
- "required": [],
- "properties": {
- "abs_path": {
- "type": "string"
- },
- "function": {
- "type": "string"
- },
- "lineno": {
- "type": "number"
- },
- "in_app": {
- "type": "boolean"
- },
- "filename": {
- "type": "string"
- },
- "pre_context": {
- "type": "array"
- },
- "context_line": {
- "type": ["string", "null"]
- },
- "post_context": {
- "type": "array"
- }
- }
- }
+ "abs_path": {
+ "type": "string"
+ },
+ "function": {
+ "type": "string"
+ },
+ "lineno": {
+ "type": "number"
+ },
+ "in_app": {
+ "type": "boolean"
+ },
+ "filename": {
+ "type": "string"
+ },
+ "pre_context": {
+ "type": "array"
+ },
+ "context_line": {
+ "type": ["string", "null"]
+ },
+ "post_context": {
+ "type": "array"
}
}
}
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index 65882491575..e46a88b2217 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -52,8 +52,7 @@
= f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control gl-form-input gl-mt-2'
.help-block
= _('Specify an email address regex pattern to identify default internal users.')
- = link_to _('Learn more'), help_page_path('user/permissions', anchor: 'setting-new-users-to-external'),
- target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/permissions', anchor: 'setting-new-users-to-external'), target: '_blank', rel: 'noopener noreferrer'
- unless Gitlab.com?
.form-group
= f.label :deactivate_dormant_users, _('Dormant users'), class: 'label-bold'
@@ -63,7 +62,7 @@
= _('Deactivate dormant users after 90 days of inactivity')
.help-block
= _('Users can reactivate their account by signing in.')
- = link_to _('Learn more'), help_page_path('user/admin_area/moderate_users', anchor: 'automatically-deactivate-dormant-users'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/admin_area/moderate_users', anchor: 'automatically-deactivate-dormant-users'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= f.label :personal_access_token_prefix, _('Personal Access Token prefix'), class: 'label-light'
= f.text_field :personal_access_token_prefix, placeholder: _('Maximum 20 characters'), class: 'form-control gl-form-input'
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index 8026ec4702b..41698f9720b 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -9,13 +9,13 @@
= s_('CICD|Default to Auto DevOps pipeline for all projects')
.form-text.text-muted
= s_('CICD|The Auto DevOps pipeline runs by default in all projects with no CI/CD configuration file.')
- = link_to _('What is Auto DevOps?'), help_page_path('topics/autodevops/index.md'), target: '_blank'
+ = link_to _('What is Auto DevOps?'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= f.label :auto_devops_domain, s_('AdminSettings|Auto DevOps domain'), class: 'label-bold'
= f.text_field :auto_devops_domain, class: 'form-control gl-form-input', placeholder: 'example.com'
.form-text.text-muted
= s_("AdminSettings|The default domain to use for Auto Review Apps and Auto Deploy stages in all projects.")
- = link_to _('Learn more.'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-review-apps'), target: '_blank'
+ = link_to _('Learn more.'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-review-apps'), target: '_blank', rel: 'noopener noreferrer'
.form-group
.form-check
@@ -68,7 +68,7 @@
= f.text_field :default_ci_config_path, class: 'form-control gl-form-input', placeholder: '.gitlab-ci.yml'
%p.form-text.text-muted
= _("The default CI/CD configuration file and path for new projects.").html_safe
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank', rel: 'noopener noreferrer'
.form-group
.form-check
= f.check_box :suggest_pipeline_enabled, class: 'form-check-input'
diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml
index 073c0bf619d..0ab462a3fa8 100644
--- a/app/views/admin/application_settings/_email.html.haml
+++ b/app/views/admin/application_settings/_email.html.haml
@@ -20,7 +20,7 @@
= f.label :commit_email_hostname, _('Custom hostname (for private commit emails)'), class: 'label-bold'
= f.text_field :commit_email_hostname, class: 'form-control gl-form-input'
.form-text.text-muted
- - commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('user/admin_area/settings/email.md', anchor: 'custom-hostname-for-private-commit-emails'), target: '_blank'
+ - commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('user/admin_area/settings/email.md', anchor: 'custom-hostname-for-private-commit-emails'), target: '_blank', rel: 'noopener noreferrer'
= _("Hostname used in private commit emails. %{learn_more}").html_safe % { learn_more: commit_email_hostname_docs_link }
= render_if_exists 'admin/application_settings/email_additional_text_setting', form: f
diff --git a/app/views/admin/application_settings/_external_authorization_service_form.html.haml b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
index 23484eaec32..4fb10d48540 100644
--- a/app/views/admin/application_settings/_external_authorization_service_form.html.haml
+++ b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
@@ -1,11 +1,12 @@
%section.settings.as-external-auth.no-animate#js-external-auth-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
- = _('External authentication')
+ = s_('ExternalAuthorization|External authorization')
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? 'Collapse' : 'Expand'
%p
- = _('External Classification Policy Authorization')
+ = s_('ExternalAuthorization|External classification policy authorization.')
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/external_authorization'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-external-auth-settings'), html: { class: 'fieldset-form', id: 'external-auth-settings' } do |f|
@@ -16,35 +17,37 @@
.form-check
= f.check_box :external_authorization_service_enabled, class: 'form-check-input'
= f.label :external_authorization_service_enabled, class: 'form-check-label' do
- = _('Enable classification control using an external service')
+ = s_('ExternalAuthorization|Enable classification control using an external service')
%span.form-text.text-muted
= external_authorization_description
- = link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/external_authorization')
.form-group
- = f.label :external_authorization_service_url, _('Service URL'), class: 'label-bold'
+ = f.label :external_authorization_service_url, s_('ExternalAuthorization|Service URL'), class: 'label-bold'
= f.text_field :external_authorization_service_url, class: 'form-control gl-form-input'
%span.form-text.text-muted
= external_authorization_url_help_text
.form-group
- = f.label :external_authorization_service_timeout, _('External authorization request timeout'), class: 'label-bold'
+ = f.label :external_authorization_service_timeout, s_('ExternalAuthorization|External authorization request timeout (seconds)'), class: 'label-bold'
= f.number_field :external_authorization_service_timeout, class: 'form-control gl-form-input', min: 0.001, max: 10, step: 0.001
%span.form-text.text-muted
= external_authorization_timeout_help_text
- = f.label :external_auth_client_cert, _('Client authentication certificate'), class: 'label-bold'
+ .form-group
+ = f.label :external_auth_client_cert, s_('ExternalAuthorization|Client authorization certificate'), class: 'label-bold'
= f.text_area :external_auth_client_cert, class: 'form-control gl-form-input'
%span.form-text.text-muted
= external_authorization_client_certificate_help_text
.form-group
- = f.label :external_auth_client_key, _('Client authentication key'), class: 'label-bold'
+ = f.label :external_auth_client_key, s_('ExternalAuthorization|Client authorization key'), class: 'label-bold'
= f.text_area :external_auth_client_key, class: 'form-control gl-form-input'
%span.form-text.text-muted
= external_authorization_client_key_help_text
.form-group
- = f.label :external_auth_client_key_pass, _('Client authentication key password'), class: 'label-bold'
+ = f.label :external_auth_client_key_pass, s_('ExternalAuthorization|Client authorization key password (optional)'), class: 'label-bold'
= f.password_field :external_auth_client_key_pass, class: 'form-control gl-form-input'
%span.form-text.text-muted
= external_authorization_client_pass_help_text
.form-group
- = f.label :external_authorization_service_default_label, _('Default classification label'), class: 'label-bold'
+ = f.label :external_authorization_service_default_label, s_('ExternalAuthorization|Default classification label'), class: 'label-bold'
= f.text_field :external_authorization_service_default_label, class: 'form-control gl-form-input'
+ %span.form-text.text-muted
+ = external_authorization_client_url_help_text
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
diff --git a/app/views/admin/application_settings/_floc.html.haml b/app/views/admin/application_settings/_floc.html.haml
index 52833b5cfc2..66259926064 100644
--- a/app/views/admin/application_settings/_floc.html.haml
+++ b/app/views/admin/application_settings/_floc.html.haml
@@ -8,7 +8,7 @@
= expanded ? _('Collapse') : _('Expand')
%p
= s_('FloC|Configure whether you want to participate in FloC.').html_safe
- = link_to sprite_icon('question-o'), 'https://github.com/WICG/floc', target: '_blank', class: 'has-tooltip', title: _('More information')
+ = link_to sprite_icon('question-o'), 'https://github.com/WICG/floc', target: '_blank', rel: 'noopener noreferrer', class: 'has-tooltip', title: _('More information')
.settings-content
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-floc-settings'), html: { class: 'fieldset-form', id: 'floc-settings' } do |f|
diff --git a/app/views/admin/application_settings/_localization.html.haml b/app/views/admin/application_settings/_localization.html.haml
index 5c8f3379fce..d0bb6a78ed6 100644
--- a/app/views/admin/application_settings/_localization.html.haml
+++ b/app/views/admin/application_settings/_localization.html.haml
@@ -7,7 +7,7 @@
= f.select :first_day_of_week, first_day_of_week_choices, {}, class: 'form-control'
.form-text.text-muted
= _('Default first day of the week in calendars and date pickers.')
- = link_to _('Learn more.'), help_page_path('user/admin_area/settings/index.md', anchor: 'default-first-day-of-the-week'), target: '_blank'
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/index.md', anchor: 'default-first-day-of-the-week'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= f.label :time_tracking, _('Time tracking'), class: 'label-bold'
@@ -17,7 +17,7 @@
= _('Limit display of time tracking units to hours.')
.form-text.text-muted
= _('Display time tracking in issues in total hours only.')
- = link_to _('What is time tracking?'), help_page_path('user/project/time_tracking.md'), target: '_blank'
+ = link_to _('What is time tracking?'), help_page_path('user/project/time_tracking.md'), target: '_blank', rel: 'noopener noreferrer'
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
diff --git a/app/views/admin/application_settings/_outbound.html.haml b/app/views/admin/application_settings/_outbound.html.haml
index 142a3fbfbd0..9a31fdd7fdf 100644
--- a/app/views/admin/application_settings/_outbound.html.haml
+++ b/app/views/admin/application_settings/_outbound.html.haml
@@ -6,25 +6,26 @@
.form-check
= f.check_box :allow_local_requests_from_web_hooks_and_services, class: 'form-check-input', data: { qa_selector: 'allow_requests_from_services_checkbox' }
= f.label :allow_local_requests_from_web_hooks_and_services, class: 'form-check-label' do
- = _('Allow requests to the local network from web hooks and services')
+ = s_('OutboundRequests|Allow requests to the local network from web hooks and services')
.form-check
= f.check_box :allow_local_requests_from_system_hooks, class: 'form-check-input'
= f.label :allow_local_requests_from_system_hooks, class: 'form-check-label' do
- = _('Allow requests to the local network from system hooks')
+ = s_('OutboundRequests|Allow requests to the local network from system hooks')
.form-group
= f.label :outbound_local_requests_allowlist_raw, class: 'label-bold' do
- = _('Local IP addresses and domain names that hooks and services may access.')
- = f.text_area :outbound_local_requests_allowlist_raw, placeholder: "example.com, 192.168.1.1", class: 'form-control gl-form-input', rows: 8
+ = s_('OutboundRequests|Local IP addresses and domain names that hooks and services may access')
+ = f.text_area :outbound_local_requests_allowlist_raw, placeholder: "example.com, 192.168.1.1, xn--itlab-j1a.com", class: 'form-control gl-form-input', rows: 8
%span.form-text.text-muted
- = _('Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The allowlist can hold a maximum of 1000 entries. Domains should use IDNA encoding. Ex: example.com, 192.168.1.1, 127.0.0.0/28, xn--itlab-j1a.com.')
+ = s_('OutboundRequests|Requests to these domains and IP addresses are accessible to both system hooks and web hooks even when local requests are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 and 127.0.0.0/28 are supported. Domain wildcards are not supported. To separate entries use commas, semicolons, or newlines. The allowlist can hold a maximum of 1000 entries. Domains must be IDNA encoded.')
+ = link_to _('Learn more.'), help_page_path('security/webhooks.md', anchor: 'allowlist-for-local-requests'), target: '_blank', rel: 'noopener noreferrer'
.form-group
.form-check
= f.check_box :dns_rebinding_protection_enabled, class: 'form-check-input'
= f.label :dns_rebinding_protection_enabled, class: 'form-check-label' do
- = _('Enforce DNS rebinding attack protection')
+ = s_('OutboundRequests|Enforce DNS rebinding attack protection')
%span.form-text.text-muted
- = _('Resolves IP addresses once and uses them to submit requests')
+ = s_('OutboundRequests|Resolve IP addresses once and uses them to submit requests.')
= f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/admin/application_settings/_prometheus.html.haml b/app/views/admin/application_settings/_prometheus.html.haml
index 59690fdee8b..08befa59952 100644
--- a/app/views/admin/application_settings/_prometheus.html.haml
+++ b/app/views/admin/application_settings/_prometheus.html.haml
@@ -9,7 +9,7 @@
= _("Enable health and performance metrics endpoint")
.form-text.text-muted
= _('Enable a Prometheus endpoint that exposes health and performance statistics. The Health Check menu item appears in the Monitoring section of the Admin Area. Restart required.')
- = link_to _('Learn More.'), help_page_path('administration/monitoring/prometheus/gitlab_metrics.md'), target: '_blank'
+ = link_to _('Learn More.'), help_page_path('administration/monitoring/prometheus/gitlab_metrics.md'), target: '_blank', rel: 'noopener noreferrer'
- unless Gitlab::Metrics.metrics_folder_present?
.form-text.text-muted
%strong.cred= _("WARNING:")
diff --git a/app/views/admin/application_settings/_registry.html.haml b/app/views/admin/application_settings/_registry.html.haml
index 78d4e8c8cc3..b55c2f05300 100644
--- a/app/views/admin/application_settings/_registry.html.haml
+++ b/app/views/admin/application_settings/_registry.html.haml
@@ -10,10 +10,10 @@
= f.check_box :container_expiration_policies_enable_historic_entries, class: 'form-check-input'
= f.label :container_expiration_policies_enable_historic_entries, class: 'form-check-label' do
= _("Enable container expiration and retention policies for projects created earlier than GitLab 12.7.")
- = link_to sprite_icon('question-o'), help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy')
+ = link_to sprite_icon('question-o'), help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy')
.form-text.text-muted
= _("Existing projects will be able to use expiration policies. Avoid enabling this if an external Container Registry is being used, as there is a performance risk if many images exist on one project.")
- = link_to sprite_icon('question-o'), help_page_path('user/packages/container_registry/index', anchor: 'use-with-external-container-registries')
+ = link_to sprite_icon('question-o'), help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'use-with-external-container-registries')
- if container_registry_expiration_policies_throttling?
.form-group
= f.label :container_registry_delete_tags_service_timeout, _('Cleanup policy maximum processing time (seconds)'), class: 'label-bold'
diff --git a/app/views/admin/application_settings/_runner_registrars_form.html.haml b/app/views/admin/application_settings/_runner_registrars_form.html.haml
index b7ab896533b..08b3d173d20 100644
--- a/app/views/admin/application_settings/_runner_registrars_form.html.haml
+++ b/app/views/admin/application_settings/_runner_registrars_form.html.haml
@@ -11,6 +11,6 @@
= s_("Runners|Members of the %{type} can register runners") % { type: type }
%span.form-text.gl-text-gray-600
= _('If no options are selected, only administrators can register runners.')
- = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'runner-registration'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'prevent-users-from-registering-runners'), target: '_blank', rel: 'noopener noreferrer'
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
diff --git a/app/views/admin/application_settings/_sourcegraph.html.haml b/app/views/admin/application_settings/_sourcegraph.html.haml
index d87ded09a2b..b92cf7b156a 100644
--- a/app/views/admin/application_settings/_sourcegraph.html.haml
+++ b/app/views/admin/application_settings/_sourcegraph.html.haml
@@ -12,7 +12,7 @@
- link_end = "#{sprite_icon('external-link', size: 12, css_class: 'ml-1 vertical-align-center')}</a>".html_safe
= s_('SourcegraphAdmin|Enable code intelligence powered by %{link_start}Sourcegraph%{link_end} on your GitLab instance\'s code views and merge requests.').html_safe % { link_start: link_start, link_end: link_end }
%span
- = link_to s_('SourcegraphAdmin|More information'), help_page_path('integration/sourcegraph.md'), target: '_blank'
+ = link_to s_('SourcegraphAdmin|More information'), help_page_path('integration/sourcegraph.md'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
diff --git a/app/views/admin/application_settings/_spam.html.haml b/app/views/admin/application_settings/_spam.html.haml
index 53ca4d4aa79..27113fddb27 100644
--- a/app/views/admin/application_settings/_spam.html.haml
+++ b/app/views/admin/application_settings/_spam.html.haml
@@ -27,7 +27,7 @@
= f.text_field :recaptcha_site_key, class: 'form-control gl-form-input'
.form-text.text-muted
= _("Generate site and private keys at")
- %a{ href: 'http://www.google.com/recaptcha', target: 'blank' } http://www.google.com/recaptcha
+ %a{ href: 'http://www.google.com/recaptcha', target: 'blank', rel: 'noopener noreferrer' } http://www.google.com/recaptcha
.form-group
= f.label :recaptcha_private_key, _('reCAPTCHA private key'), class: 'label-bold'
@@ -65,7 +65,7 @@
= f.text_field :akismet_api_key, class: 'form-control gl-form-input'
.form-text.text-muted
Generate API key at
- %a{ href: 'http://www.akismet.com', target: 'blank' } http://www.akismet.com
+ %a{ href: 'http://www.akismet.com', target: 'blank', rel: 'noopener noreferrer' } http://www.akismet.com
%h5
= _('IP address restrictions')
diff --git a/app/views/admin/application_settings/_third_party_offers.html.haml b/app/views/admin/application_settings/_third_party_offers.html.haml
index 9a34400092e..231c45ec46c 100644
--- a/app/views/admin/application_settings/_third_party_offers.html.haml
+++ b/app/views/admin/application_settings/_third_party_offers.html.haml
@@ -2,11 +2,11 @@
%section.settings.as-third-party-offers.no-animate#js-third-party-offers-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
- = _('Third-party offers')
+ = _('Customer experience improvement and third-party offers')
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
- = _('Control whether to display third-party offers in GitLab.')
+ = _('Control whether to display customer experience improvement content and third-party offers in GitLab.')
.settings-content
= form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-third-party-offers-settings'), html: { class: 'fieldset-form', id: 'third-party-offers-settings' } do |f|
= form_errors(@application_setting) if expanded
@@ -15,6 +15,6 @@
.form-group
.form-check
= f.check_box :hide_third_party_offers, class: 'form-check-input'
- = f.label :hide_third_party_offers, _('Do not display offers from third parties'), class: 'form-check-label'
+ = f.label :hide_third_party_offers, _('Do not display content for customer experience improvement and offers from third parties'), class: 'form-check-label'
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"
diff --git a/app/views/admin/application_settings/appearances/_form.html.haml b/app/views/admin/application_settings/appearances/_form.html.haml
index 3bd16e4c344..0f7f0109a54 100644
--- a/app/views/admin/application_settings/appearances/_form.html.haml
+++ b/app/views/admin/application_settings/appearances/_form.html.haml
@@ -20,7 +20,7 @@
%hr
= f.hidden_field :header_logo_cache
= f.file_field :header_logo, class: "", accept: 'image/*'
- .hint
+ .form-text.text-muted
= _('Maximum file size is 1MB. Pages are optimized for a 28px tall header logo')
%hr
.row
@@ -39,7 +39,7 @@
%hr
= f.hidden_field :favicon_cache
= f.file_field :favicon, class: '', accept: 'image/*'
- .hint
+ .form-text.text-muted
= _("Maximum file size is 1 MB. Image size must be 32 x 32 pixels. Allowed image formats are %{favicon_extension_whitelist}.") % { favicon_extension_whitelist: favicon_extension_whitelist }
%br
= _("Images with incorrect dimensions are not resized automatically, and may result in unexpected behavior.")
@@ -58,7 +58,7 @@
.form-group
= f.label :description, class: 'col-form-label label-bold'
= f.text_area :description, class: "form-control gl-form-input", rows: 10
- .hint
+ .form-text.text-muted
= parsed_with_gfm
.form-group
= f.label :logo, class: 'col-form-label label-bold pt-0'
@@ -71,7 +71,7 @@
%hr
= f.hidden_field :logo_cache
= f.file_field :logo, class: "", accept: 'image/*'
- .hint
+ .form-text.text-muted
= _('Maximum file size is 1MB. Pages are optimized for a 640x360 px logo.')
%hr
@@ -84,7 +84,7 @@
= f.label :new_project_guidelines, class: 'col-form-label label-bold'
%p
= f.text_area :new_project_guidelines, class: "form-control gl-form-input", rows: 10
- .hint
+ .form-text.text-muted
= parsed_with_gfm
%hr
@@ -97,7 +97,7 @@
= f.label :profile_image_guidelines, class: 'col-form-label label-bold'
%p
= f.text_area :profile_image_guidelines, class: "form-control gl-form-input", rows: 10
- .hint
+ .form-text.text-muted
= parsed_with_gfm
.gl-mt-3.gl-mb-3
diff --git a/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml b/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml
index 4571d34a497..1ce79e61ac6 100644
--- a/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml
+++ b/app/views/admin/application_settings/appearances/_system_header_footer_form.html.haml
@@ -19,7 +19,7 @@
= form.label :email_header_and_footer_enabled, class: 'label-bold' do
= _('Enable header and footer in emails')
- .hint
+ .form-text.text-muted
= _('Add header and footer to emails. Please note that color settings will only be applied within the application interface')
.form-group.js-toggle-colors-container
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index 53ba626760b..9eef4bc2a37 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -118,3 +118,4 @@
= render 'admin/application_settings/snowplow'
= render 'admin/application_settings/eks'
= render 'admin/application_settings/floc'
+= render_if_exists 'admin/application_settings/license_file'
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
index 61a2f97764f..242d0c364f4 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -75,11 +75,13 @@
%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'outbound_requests_content' } }
.settings-header
%h4
- = _('Outbound requests')
+ = s_('OutboundRequests|Outbound requests')
+
%button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
- = _('Allow requests to the local network from hooks and services.')
+ = s_('OutboundRequests|Allow requests to the local network from hooks and services.')
+ = link_to _('Learn more.'), help_page_path('security/webhooks.md'), target: '_blank', rel: 'noopener noreferrer'
.settings-content
= render 'outbound'
diff --git a/app/views/admin/background_migrations/_migration.html.haml b/app/views/admin/background_migrations/_migration.html.haml
index 4a7c0083bc7..b6077bc54d6 100644
--- a/app/views/admin/background_migrations/_migration.html.haml
+++ b/app/views/admin/background_migrations/_migration.html.haml
@@ -7,7 +7,7 @@
- else
= _('Unknown')
%td{ role: 'cell', data: { label: _('Status') } }
- %span.badge.badge-pill.gl-badge.sm{ class: batched_migration_status_badge_class_name(migration) }= migration.status.humanize
+ = gl_badge_tag migration.status.humanize, { size: :sm, variant: batched_migration_status_badge_variant(migration) }
%td{ role: 'cell', data: { label: _('Action') } }
- if migration.active?
= button_to pause_admin_background_migration_path(migration),
diff --git a/app/views/admin/background_migrations/index.html.haml b/app/views/admin/background_migrations/index.html.haml
index 9ccbdfb5f20..13ac511f1b4 100644
--- a/app/views/admin/background_migrations/index.html.haml
+++ b/app/views/admin/background_migrations/index.html.haml
@@ -8,18 +8,15 @@
%li.nav-item{ role: 'presentation' }
%a.nav-link.gl-tab-nav-item{ href: admin_background_migrations_path, class: (active_tab_classes if @current_tab == 'queued'), role: 'tab' }
= _('Queued')
- %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm
- = limited_counter_with_delimiter(@relations_by_tab['queued'])
+ = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['queued'])
%li.nav-item{ role: 'presentation' }
%a.nav-link.gl-tab-nav-item{ href: admin_background_migrations_path(tab: 'failed'), class: (active_tab_classes if @current_tab == 'failed'), role: 'tab' }
= _('Failed')
- %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm
- = limited_counter_with_delimiter(@relations_by_tab['failed'])
+ = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['failed'])
%li.nav-item{ role: 'presentation' }
%a.nav-link.gl-tab-nav-item{ href: admin_background_migrations_path(tab: 'finished'), class: (active_tab_classes if @current_tab == 'finished'), role: 'tab' }
= _('Finished')
- %span.badge.gl-tab-counter-badge.badge-muted.badge-pill.gl-badge.sm
- = limited_counter_with_delimiter(@relations_by_tab['finished'])
+ = gl_tab_counter_badge limited_counter_with_delimiter(@relations_by_tab['finished'])
.tab-content.gl-tab-content
.tab-pane.active{ role: 'tabpanel' }
diff --git a/app/views/admin/cohorts/_cohorts_table.html.haml b/app/views/admin/cohorts/_cohorts_table.html.haml
index a92cfb5851a..4e2292a9f67 100644
--- a/app/views/admin/cohorts/_cohorts_table.html.haml
+++ b/app/views/admin/cohorts/_cohorts_table.html.haml
@@ -2,7 +2,7 @@
.bs-callout.clearfix
%p
= s_("Cohorts|User cohorts are shown for the last %{months_included} months. Only users with activity are counted in the 'New users' column; inactive users are counted separately.") % { months_included: @cohorts[:months_included] }
- = link_to sprite_icon('question-o'), help_page_path('user/admin_area/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('user/admin_area/user_cohorts', anchor: 'cohorts'), title: 'About this feature', target: '_blank', rel: 'noopener noreferrer'
.table-holder.d-xl-table
%table.table
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 801b903395a..85b6ebfc63a 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -118,9 +118,9 @@
.gl-card-body
%h4
= s_('AdminArea|Components')
- - if Gitlab::CurrentSettings.version_check_enabled
+ - if show_version_check?
.float-right
- = version_status_badge
+ .js-gitlab-version-check{ data: { "size": "lg" } }
= link_to(sprite_icon('question'), "https://gitlab.com/gitlab-org/gitlab/-/blob/master/CHANGELOG.md", class: 'gl-ml-2', target: '_blank', rel: 'noopener noreferrer')
%p
= link_to _('GitLab'), general_admin_application_settings_path
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index ba4abdc02e4..de2a737faa1 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -1,44 +1,3 @@
- page_title _('Deploy Keys')
-- if Feature.enabled?(:admin_deploy_keys_vue, default_enabled: :yaml)
- #js-admin-deploy-keys-table{ data: admin_deploy_keys_data }
-- else
- - if @deploy_keys.any?
- %h3.page-title.deploy-keys-title
- = _('Public deploy keys (%{deploy_keys_count})') % { deploy_keys_count: @deploy_keys.load.size }
- = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'float-right btn gl-button btn-confirm btn-md gl-button'
- %table.table.b-table.gl-table.b-table-stacked-lg{ data: { testid: 'deploy-keys-list' } }
- %thead
- %tr
- %th= _('Title')
- %th= _('Fingerprint')
- %th= _('Projects with write access')
- %th= _('Created')
- %th.gl-lg-w-1px.gl-white-space-nowrap
- %span.gl-sr-only
- = _('Actions')
- %tbody
- - @deploy_keys.each do |deploy_key|
- %tr
- %td{ data: { label: _('Title') } }
- %div
- = deploy_key.title
- %td{ data: { label: _('Fingerprint') } }
- %div
- %code= deploy_key.fingerprint
- %td{ data: { label: _('Projects with write access') } }
- %div
- - deploy_key.projects_with_write_access.each do |project|
- = link_to project.full_name, admin_project_path(project), class: 'gl-display-block'
- %td{ data: { label: _('Created') } }
- %div
- = time_ago_with_tooltip(deploy_key.created_at)
- %td.gl-lg-w-1px.gl-white-space-nowrap{ data: { label: _('Actions') } }
- %div
- = link_to edit_admin_deploy_key_path(deploy_key), class: 'btn btn-default btn-md gl-button btn-icon gl-mr-3', aria: { label: _('Edit deploy key') } do
- = sprite_icon('pencil', css_class: 'gl-button-icon')
- = link_to admin_deploy_key_path(deploy_key), data: { confirm: _('Are you sure?') }, method: :delete, class: 'btn btn-danger btn-md gl-button btn-icon', aria: { label: _('Remove deploy key') } do
- = sprite_icon('remove', css_class: 'gl-button-icon')
-
- - else
- = render 'shared/empty_states/deploy_keys'
+#js-admin-deploy-keys-table{ data: admin_deploy_keys_data }
diff --git a/app/views/admin/groups/_group.html.haml b/app/views/admin/groups/_group.html.haml
index bbc65850794..0c2280a2f63 100644
--- a/app/views/admin/groups/_group.html.haml
+++ b/app/views/admin/groups/_group.html.haml
@@ -15,8 +15,7 @@
= markdown_field(group, :description)
.stats.gl-text-gray-500.gl-flex-shrink-0.gl-display-none.gl-sm-display-flex
- %span.badge.badge-muted.badge-pill.gl-badge.sm
- = storage_counter(group.storage_size)
+ = gl_badge_tag storage_counter(group.storage_size), size: :sm
= render_if_exists 'admin/namespace_plan_badge', namespace: group, css_class: 'gl-ml-5 gl-mr-0'
= render_if_exists 'admin/groups/marked_for_deletion_badge', group: group, css_class: 'gl-ml-5'
diff --git a/app/views/admin/labels/_form.html.haml b/app/views/admin/labels/_form.html.haml
deleted file mode 100644
index abf380474e4..00000000000
--- a/app/views/admin/labels/_form.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-= form_for [:admin, @label], html: { class: 'label-form js-requires-input' } do |f|
- = form_errors(@label)
-
- .form-group.row
- .col-sm-2.col-form-label
- = f.label :title
- .col-sm-10
- = f.text_field :title, class: "form-control gl-form-input", required: true
- .form-group.row
- .col-sm-2.col-form-label
- = f.label :description
- .col-sm-10
- = f.text_field :description, class: "form-control gl-form-input js-quick-submit"
- .form-group.row
- .col-sm-2.col-form-label
- = f.label :color, _("Background color")
- .col-sm-10
- .input-group
- .input-group-prepend
- .input-group-text.label-color-preview &nbsp;
- = f.text_field :color, class: "form-control gl-form-input"
- .form-text.text-muted
- = _('Choose any color.')
- %br
- = _("Or you can choose one of the suggested colors below")
-
- = render_suggested_colors
-
- .form-actions
- = f.submit _('Save'), class: 'btn gl-button btn-confirm js-save-button'
- = link_to _("Cancel"), admin_labels_path, class: 'btn gl-button btn-default btn-cancel'
diff --git a/app/views/admin/labels/edit.html.haml b/app/views/admin/labels/edit.html.haml
index 652ed095d00..44dd2b6a646 100644
--- a/app/views/admin/labels/edit.html.haml
+++ b/app/views/admin/labels/edit.html.haml
@@ -4,4 +4,4 @@
%h3.page-title
= _('Edit Label')
%hr
-= render 'form'
+= render 'shared/labels/form', url: admin_label_path(@label), back_path: admin_labels_path
diff --git a/app/views/admin/labels/new.html.haml b/app/views/admin/labels/new.html.haml
index 20103fb8a29..5166bdb4d20 100644
--- a/app/views/admin/labels/new.html.haml
+++ b/app/views/admin/labels/new.html.haml
@@ -2,4 +2,4 @@
%h3.page-title
= _('New Label')
%hr
-= render 'form'
+= render 'shared/labels/form', url: admin_labels_path, back_path: admin_labels_path
diff --git a/app/views/admin/runners/edit.html.haml b/app/views/admin/runners/edit.html.haml
new file mode 100644
index 00000000000..0257983016c
--- /dev/null
+++ b/app/views/admin/runners/edit.html.haml
@@ -0,0 +1,99 @@
+- add_page_specific_style 'page_bundles/ci_status'
+
+- runner_name = "##{@runner.id} (#{@runner.short_sha})"
+- if Feature.enabled?(:runner_read_only_admin_view)
+ - breadcrumb_title _('Edit')
+ - page_title _('Edit'), runner_name
+ - add_to_breadcrumbs _('Runners'), admin_runners_path
+ - add_to_breadcrumbs runner_name, admin_runner_path(@runner)
+- else
+ - breadcrumb_title runner_name
+ - page_title runner_name
+
+#js-admin-runner-edit{ data: {runner_id: @runner.id} }
+
+.row
+ .col-md-6
+ %h4= _('Restrict projects for this runner')
+ - if @runner.runner_projects.any?
+ %table.table{ data: { testid: 'assigned-projects' } }
+ %thead
+ %tr
+ %th= _('Assigned projects')
+ - @runner.runner_projects.each do |runner_project|
+ - project = runner_project.project
+ - if project
+ %tr
+ %td
+ .gl-alert.gl-alert-danger
+ .gl-alert-container
+ = sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-content
+ .gl-alert-body
+ %strong
+ = project.full_name
+ .gl-alert-actions
+ = link_to _('Disable'), admin_namespace_project_runner_project_path(project.namespace, project, runner_project), method: :delete, class: 'btn gl-alert-action btn-confirm btn-md gl-button'
+
+ %table.table{ data: { testid: 'unassigned-projects' } }
+ %thead
+ %tr
+ %th= _('Project')
+ %th
+
+ %tr
+ %td
+ = form_tag edit_admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
+ .input-group
+ = search_field_tag :search, params[:search], class: 'form-control gl-form-input', spellcheck: false
+ .input-group-append
+ = submit_tag _('Search'), class: 'gl-button btn btn-default'
+
+ %td
+ - @projects.each do |project|
+ %tr
+ %td
+ = project.full_name
+ %td
+ .float-right
+ = form_for project.runner_projects.new, url: admin_namespace_project_runner_projects_path(project.namespace, project), method: :post do |f|
+ = f.hidden_field :runner_id, value: @runner.id
+ = f.submit _('Enable'), aria: { label: s_('Runners|Change to project runner') }, class: 'gl-button btn btn-sm', data: { confirm: (s_('Runners|You are about to change this instance runner to a project runner. This operation is not reversible. Are you sure you want to continue?') if @runner.instance_type?), confirm_btn_variant: 'danger' }
+ = paginate_without_count @projects
+
+ .col-md-6
+ %h4= _('Recent jobs served by this runner')
+ %table.table.ci-table.runner-builds
+ %thead
+ %tr
+ %th= _('Job')
+ %th= _('Status')
+ %th= _('Project')
+ %th= _('Commit')
+ %th= _('Finished at')
+
+ - @builds.each do |build|
+ - project = build.project
+ %tr.build
+ %td.id
+ - if project
+ = link_to project_job_path(project, build) do
+ %strong ##{build.id}
+ - else
+ %strong ##{build.id}
+
+ %td.status
+ = render 'ci/status/badge', status: build.detailed_status(current_user)
+
+ %td.status
+ - if project
+ = project.full_name
+
+ %td.build-link
+ - if project
+ = link_to pipeline_path(build.pipeline) do
+ %strong= build.pipeline.short_sha
+
+ %td.timestamp
+ - if build.finished_at
+ %span= time_ago_with_tooltip build.finished_at
diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
index 808b2bb4f8e..7b4390ae463 100644
--- a/app/views/admin/runners/show.html.haml
+++ b/app/views/admin/runners/show.html.haml
@@ -1,93 +1,8 @@
- add_page_specific_style 'page_bundles/ci_status'
-- breadcrumb_title @runner.short_sha
-- page_title "##{@runner.id} (#{@runner.short_sha})"
+- title = "##{@runner.id} (#{@runner.short_sha})"
+- breadcrumb_title title
+- page_title title
- add_to_breadcrumbs _('Runners'), admin_runners_path
-#js-runner-details{ data: {runner_id: @runner.id} }
-
-.row
- .col-md-6
- %h4= _('Restrict projects for this runner')
- - if @runner.runner_projects.any?
- %table.table{ data: { testid: 'assigned-projects' } }
- %thead
- %tr
- %th= _('Assigned projects')
- - @runner.runner_projects.each do |runner_project|
- - project = runner_project.project
- - if project
- %tr
- %td
- .gl-alert.gl-alert-danger
- .gl-alert-container
- = sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
- .gl-alert-content
- .gl-alert-body
- %strong
- = project.full_name
- .gl-alert-actions
- = link_to _('Disable'), admin_namespace_project_runner_project_path(project.namespace, project, runner_project), method: :delete, class: 'btn gl-alert-action btn-confirm btn-md gl-button'
-
- %table.table{ data: { testid: 'unassigned-projects' } }
- %thead
- %tr
- %th= _('Project')
- %th
-
- %tr
- %td
- = form_tag admin_runner_path(@runner), id: 'runner-projects-search', class: 'form-inline', method: :get do
- .input-group
- = search_field_tag :search, params[:search], class: 'form-control gl-form-input', spellcheck: false
- .input-group-append
- = submit_tag _('Search'), class: 'gl-button btn btn-default'
-
- %td
- - @projects.each do |project|
- %tr
- %td
- = project.full_name
- %td
- .float-right
- = form_for project.runner_projects.new, url: admin_namespace_project_runner_projects_path(project.namespace, project), method: :post do |f|
- = f.hidden_field :runner_id, value: @runner.id
- = f.submit _('Enable'), class: 'gl-button btn btn-sm', data: { confirm: (s_('Runners|You are about to change this instance runner to a project runner. This operation is not reversible. Are you sure you want to continue?') if @runner.instance_type?) }
- = paginate_without_count @projects
-
- .col-md-6
- %h4= _('Recent jobs served by this runner')
- %table.table.ci-table.runner-builds
- %thead
- %tr
- %th= _('Job')
- %th= _('Status')
- %th= _('Project')
- %th= _('Commit')
- %th= _('Finished at')
-
- - @builds.each do |build|
- - project = build.project
- %tr.build
- %td.id
- - if project
- = link_to project_job_path(project, build) do
- %strong ##{build.id}
- - else
- %strong ##{build.id}
-
- %td.status
- = render 'ci/status/badge', status: build.detailed_status(current_user)
-
- %td.status
- - if project
- = project.full_name
-
- %td.build-link
- - if project
- = link_to pipeline_path(build.pipeline) do
- %strong= build.pipeline.short_sha
-
- %td.timestamp
- - if build.finished_at
- %span= time_ago_with_tooltip build.finished_at
+-# Empty view in development behind feature flag runner_read_only_admin_view
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index bafb2085589..ca14d898d79 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -3,23 +3,26 @@
%h3.page-title.gl-m-0
= @user.name
- if @user.blocked_pending_approval?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Pending approval)')
- elsif @user.banned?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Banned)')
- elsif @user.blocked?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Blocked)')
- if @user.internal?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Internal)')
- if @user.admin
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Admin)')
- if @user.deactivated?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Deactivated)')
+ - if @user.access_locked?
+ %span.gl-text-red-500
+ = s_('AdminUsers|(Locked)')
= render_if_exists 'admin/users/auditor_user_badge'
= render_if_exists 'admin/users/gma_user_badge'
diff --git a/app/views/admin/users/_users.html.haml b/app/views/admin/users/_users.html.haml
index e62e4cfa192..5edd5403d49 100644
--- a/app/views/admin/users/_users.html.haml
+++ b/app/views/admin/users/_users.html.haml
@@ -1,3 +1,11 @@
+- if registration_features_can_be_prompted?
+ = render 'shared/global_alert',
+ variant: :tip,
+ alert_class: 'gl-my-5',
+ dismissible: false do
+ .gl-alert-body
+ = render 'shared/registration_features_discovery_message', feature_title: s_('RegistrationFeatures|send emails to users')
+
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left
= sprite_icon('chevron-lg-left', size: 12)
@@ -51,7 +59,7 @@
= hidden_field_tag :sort, @sort
= sprite_icon('search', css_class: 'search-icon')
= button_tag s_('AdminUsers|Search users') if Rails.env.test?
- .dropdown.user-sort-dropdown
+ .dropdown.gl-ml-3
= label_tag 'Sort by', nil, class: 'label-bold'
- toggle_text = @sort.present? ? users_sort_options_hash[@sort] : sort_title_name
= dropdown_toggle(toggle_text, { toggle: 'dropdown' })
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
index b47ed38f65f..f51ac40df4f 100644
--- a/app/views/admin/users/projects.html.haml
+++ b/app/views/admin/users/projects.html.haml
@@ -5,7 +5,7 @@
- if @user.groups.any?
.card
- .card-header= _('Group projects')
+ .card-header= _('Groups')
%ul.hover-list
- @user.group_members.includes(:source).each do |group_member| # rubocop: disable CodeReuse/ActiveRecord
- group = group_member.group
diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml
index 24048a8b328..c872ee481ad 100644
--- a/app/views/ci/runner/_how_to_setup_runner.html.haml
+++ b/app/views/ci/runner/_how_to_setup_runner.html.haml
@@ -1,4 +1,4 @@
-- link = link_to _("Install GitLab Runner and ensure it's running."), 'https://docs.gitlab.com/runner/install/', target: '_blank'
+- link = link_to _("Install GitLab Runner and ensure it's running."), 'https://docs.gitlab.com/runner/install/', target: '_blank', rel: 'noopener noreferrer'
.gl-mb-3
%h5= _("Set up a %{type} Runner for a project") % { type: type }
%ol
diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml
index c84b3a923ca..59c8fe04b09 100644
--- a/app/views/clusters/clusters/_advanced_settings.html.haml
+++ b/app/views/clusters/clusters/_advanced_settings.html.haml
@@ -23,7 +23,7 @@
placeholder: _('Select project'), idAttribute: 'id', data: { order_by: 'last_activity_at', idattribute: 'id', simple_filter: true, allow_clear: true, include_groups: false, include_projects_in_subgroups: true, group_id: group_id, user_id: user_id }, value: @cluster.management_project_id)
.text-muted
= html_escape(s_('ClusterIntegration|A cluster management project can be used to run deployment jobs with Kubernetes %{code_open}cluster-admin%{code_close} privileges.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
- = link_to _('More information'), help_page_path('user/clusters/management_project.md'), target: '_blank'
+ = link_to _('More information'), help_page_path('user/clusters/management_project.md'), target: '_blank', rel: 'noopener noreferrer'
= field.submit _('Save changes'), class: 'btn gl-button btn-confirm'
.sub-section.form-group
diff --git a/app/views/clusters/clusters/_integrations.html.haml b/app/views/clusters/clusters/_integrations.html.haml
index f136091dad5..c670dafb947 100644
--- a/app/views/clusters/clusters/_integrations.html.haml
+++ b/app/views/clusters/clusters/_integrations.html.haml
@@ -1,7 +1,7 @@
.settings.expanded.border-0.m-0
%p
= s_('ClusterIntegration|Integrations allow you to use applications installed in your cluster as part of your GitLab workflow.')
- = link_to _('Learn more'), help_page_path('user/clusters/integrations.md'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/clusters/integrations.md'), target: '_blank', rel: 'noopener noreferrer'
.settings-content#integrations-settings-section
- if can?(current_user, :admin_cluster, @cluster)
.sub-section.form-group
diff --git a/app/views/clusters/clusters/_namespace.html.haml b/app/views/clusters/clusters/_namespace.html.haml
index 6412972e195..cedece5ad93 100644
--- a/app/views/clusters/clusters/_namespace.html.haml
+++ b/app/views/clusters/clusters/_namespace.html.haml
@@ -1,6 +1,6 @@
- managed_namespace_help_text = s_('ClusterIntegration|Set a prefix for your namespaces. If not set, defaults to your project path. If modified, existing environments will use their current namespaces until the cluster cache is cleared.')
- non_managed_namespace_help_text = s_('ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, logs, and Web terminals.')
-- managed_namespace_help_link = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank'
+- managed_namespace_help_link = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank', rel: 'noopener noreferrer'
.js-namespace-prefixed
= platform_field.text_field :namespace,
diff --git a/app/views/clusters/clusters/_provider_details_form.html.haml b/app/views/clusters/clusters/_provider_details_form.html.haml
index fe3d1998234..11277a83e3a 100644
--- a/app/views/clusters/clusters/_provider_details_form.html.haml
+++ b/app/views/clusters/clusters/_provider_details_form.html.haml
@@ -43,13 +43,13 @@
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespaces and service accounts for this cluster.')
- = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank'
+ = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= field.check_box :namespace_per_environment, { label: s_('ClusterIntegration|Namespace per environment'), label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared.')
- = link_to _('More information'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank'
+ = link_to _('More information'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank', rel: 'noopener noreferrer'
- if cluster.allow_user_defined_namespace?
= render('clusters/clusters/namespace', platform_field: platform_field, field: field)
diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index 173456926a5..c8105fd1152 100644
--- a/app/views/clusters/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -67,20 +67,20 @@
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Uses the Cloud Run, Istio, and HTTP Load Balancing addons for this cluster.')
- = link_to _('More information'), help_page_path('user/project/clusters/add_gke_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank'
+ = link_to _('More information'), help_page_path('user/project/clusters/add_gke_clusters.md', anchor: 'cloud-run-for-anthos'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespaces and service accounts for this cluster.')
- = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank'
+ = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= field.check_box :namespace_per_environment, { label: s_('ClusterIntegration|Namespace per environment'), label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared.')
- = link_to _('More information'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank'
+ = link_to _('More information'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank', rel: 'noopener noreferrer'
.form-group.js-gke-cluster-creation-submit-container
= field.submit s_('ClusterIntegration|Create Kubernetes cluster'),
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index a6efe597f0c..bf654999f2f 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -36,7 +36,7 @@
= sprite_icon('close', css_class: 'gl-icon')
.gl-alert-body
= s_('ClusterApplicationsRemoved|One-click application management was removed in GitLab 14.0. Your applications are still installed in your cluster, and integrations continue working.')
- = link_to _('More information.'), help_page_path("user/clusters/applications"), target: '_blank'
+ = link_to _('More information.'), help_page_path("user/clusters/applications"), target: '_blank', rel: 'noopener noreferrer'
- if cluster_created?(@cluster)
.js-toggle-container
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index e9b84952c15..29af79cee5f 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -1,7 +1,7 @@
- more_info_link = link_to _('More information'), help_page_path('user/project/clusters/add_remove_clusters.md',
- anchor: 'add-existing-cluster'), target: '_blank'
+ anchor: 'add-existing-cluster'), target: '_blank', rel: 'noopener noreferrer'
- rbac_help_link = link_to _('More information'), help_page_path('user/project/clusters/add_remove_clusters.md',
- anchor: 'access-controls'), target: '_blank'
+ anchor: 'access-controls'), target: '_blank', rel: 'noopener noreferrer'
- api_url_help_text = s_('ClusterIntegration|The URL used to access the Kubernetes API.')
- ca_cert_help_text = s_('ClusterIntegration|The Kubernetes certificate used to authenticate to the cluster.')
@@ -47,13 +47,13 @@
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespaces and service accounts for this cluster.')
- = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank'
+ = link_to _('More information'), help_page_path('user/project/clusters/gitlab_managed_clusters.md'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= field.check_box :namespace_per_environment, { label: s_('ClusterIntegration|Namespace per environment'), label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Deploy each environment to its own namespace. Otherwise, environments within a project share a project-wide namespace. Note that anyone who can trigger a deployment of a namespace can read its secrets. If modified, existing environments will use their current namespaces until the cluster cache is cleared.')
- = link_to _('More information'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank'
+ = link_to _('More information'), help_page_path('user/project/clusters/deploy_to_cluster.md', anchor: 'custom-namespace'), target: '_blank', rel: 'noopener noreferrer'
= field.fields_for :platform_kubernetes, @user_cluster.platform_kubernetes do |platform_kubernetes_field|
- if @user_cluster.allow_user_defined_namespace?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 5a7eb46771b..f0216253dad 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -11,7 +11,7 @@
- if current_user
.page-title-controls
- = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", with_feature_enabled: 'issues', type: :issues
+ = render 'shared/new_project_item_select', path: 'issues/new', label: _("issue"), with_feature_enabled: 'issues', type: :issues
.top-area
= render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index ae557b73620..90fb09ed909 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -9,7 +9,7 @@
- if current_user
.page-title-controls.ml-0.mb-3.ml-sm-auto.mb-sm-0
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", with_feature_enabled: 'merge_requests', type: :merge_requests
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: _("merge request"), with_feature_enabled: 'merge_requests', type: :merge_requests
.top-area
= render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index aa54a1e589e..f6dc62e1d44 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -6,26 +6,26 @@
- add_page_specific_style 'page_bundles/todos'
.page-title-holder.d-flex.align-items-center
- %h1.page-title= _('To-Do List')
+ %h1.page-title= _("To-Do List")
- if current_user.todos.any?
.top-area
= gl_tabs_nav({ class: 'gl-flex-grow-1 gl-border-0' }) do
= gl_tab_link_to todos_filter_path(state: 'pending'), item_active: params[:state].blank? || params[:state] == 'pending', class: "js-todos-pending" do
= _("To Do")
- = gl_tab_counter_badge number_with_delimiter(todos_pending_count)
+ = gl_tab_counter_badge(number_with_delimiter(todos_pending_count), { class: 'js-todos-badge' })
= gl_tab_link_to todos_filter_path(state: 'done'), item_active: params[:state] == 'done', class: "js-todos-done" do
= _("Done")
- = gl_tab_counter_badge number_with_delimiter(todos_done_count)
+ = gl_tab_counter_badge(number_with_delimiter(todos_done_count), { class: 'js-todos-badge' })
.nav-controls
- if @allowed_todos.any?(&:pending?)
.gl-mr-3
= link_to destroy_all_dashboard_todos_path(todos_filter_params), class: 'gl-button btn btn-default btn-loading align-items-center js-todos-mark-all', method: :delete, data: { href: destroy_all_dashboard_todos_path(todos_filter_params) } do
- Mark all as done
+ = s_("Todos|Mark all as done")
%span.gl-spinner.ml-1
= link_to bulk_restore_dashboard_todos_path, class: 'gl-button btn btn-default btn-loading align-items-center js-todos-undo-all hidden', method: :patch , data: { href: bulk_restore_dashboard_todos_path(todos_filter_params) } do
- Undo mark all as done
+ = s_("Todos|Undo mark all as done")
%span.gl-spinner.ml-1
.todos-filters
@@ -35,28 +35,23 @@
.filter-item.gl-m-2
- if params[:group_id].present?
= hidden_field_tag(:group_id, params[:group_id])
- = dropdown_tag(group_dropdown_label(params[:group_id], 'Group'), options: { toggle_class: 'js-group-search js-filter-submit gl-xs-w-full!', title: 'Filter by group', filter: true, filterInput: 'input#group-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-group js-filter-submit',
- placeholder: 'Search groups', data: { default_label: 'Group', display: 'static' } })
+ = dropdown_tag(group_dropdown_label(params[:group_id], _("Group")), options: { toggle_class: 'js-group-search js-filter-submit gl-xs-w-full!', title: s_("Todos|Filter by group"), filter: true, filterInput: 'input#group-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-group js-filter-submit', placeholder: _("Search groups"), data: { default_label: _("Group"), display: 'static' } })
.filter-item.gl-m-2
- if params[:project_id].present?
= hidden_field_tag(:project_id, params[:project_id])
- = dropdown_tag(project_dropdown_label(params[:project_id], 'Project'), options: { toggle_class: 'js-project-search js-filter-submit gl-xs-w-full!', title: 'Filter by project', filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit',
- placeholder: 'Search projects', data: { default_label: 'Project', display: 'static' } })
+ = dropdown_tag(project_dropdown_label(params[:project_id], _("Project")), options: { toggle_class: 'js-project-search js-filter-submit gl-xs-w-full!', title: s_("Todos|Filter by project"), filter: true, filterInput: 'input#project-search', dropdown_class: 'dropdown-menu-selectable dropdown-menu-project js-filter-submit', placeholder: _("Search projects"), data: { default_label: _("Project"), display: 'static' } })
.filter-item.gl-m-2
- if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
- = dropdown_tag(user_dropdown_label(params[:author_id], 'Author'), options: { toggle_class: 'js-user-search js-filter-submit js-author-search gl-xs-w-full!', title: 'Filter by author', filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit',
- placeholder: 'Search authors', data: { any_user: 'Any Author', first_user: (current_user.username if current_user), project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: 'Author', todo_filter: true, todo_state_filter: params[:state] || 'pending' } })
+ = dropdown_tag(user_dropdown_label(params[:author_id], _("Author")), options: { toggle_class: 'js-user-search js-filter-submit js-author-search gl-xs-w-full!', title: s_("Todos|Filter by author"), filter: true, filterInput: 'input#author-search', dropdown_class: 'dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit', placeholder: _("Search authors"), data: { any_user: _("Any Author"), first_user: (current_user.username if current_user), project_id: (@project.id if @project), selected: params[:author_id], field_name: 'author_id', default_label: _("Author"), todo_filter: true, todo_state_filter: params[:state] || 'pending' } })
.filter-item.gl-m-2
- if params[:type].present?
= hidden_field_tag(:type, params[:type])
- = dropdown_tag(todo_types_dropdown_label(params[:type], 'Type'), options: { toggle_class: 'js-type-search js-filter-submit gl-xs-w-full!', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit',
- data: { data: todo_types_options, default_label: 'Type' } })
+ = dropdown_tag(todo_types_dropdown_label(params[:type], _("Type")), options: { toggle_class: 'js-type-search js-filter-submit gl-xs-w-full!', dropdown_class: 'dropdown-menu-selectable dropdown-menu-type js-filter-submit', data: { data: todo_types_options, default_label: _("Type") } })
.filter-item.actions-filter.gl-m-2
- if params[:action_id].present?
= hidden_field_tag(:action_id, params[:action_id])
- = dropdown_tag(todo_actions_dropdown_label(params[:action_id], 'Action'), options: { toggle_class: 'js-action-search js-filter-submit gl-xs-w-full!', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit',
- data: { data: todo_actions_options, default_label: 'Action' } })
+ = dropdown_tag(todo_actions_dropdown_label(params[:action_id], _("Action")), options: { toggle_class: 'js-action-search js-filter-submit gl-xs-w-full!', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit', data: { data: todo_actions_options, default_label: _("Action") } })
.filter-item.sort-filter.gl-mt-3.gl-sm-mt-0.gl-mb-0.gl-sm-mb-0
.dropdown
%button.dropdown-menu-toggle.dropdown-menu-toggle-sort{ type: 'button', class: 'gl-xs-w-full!', 'data-toggle' => 'dropdown' }
@@ -85,40 +80,30 @@
.js-nothing-here-container.empty-state.hidden
.svg-content
= image_tag 'illustrations/todos_all_done.svg'
- .text-content
- %h4.text-center
- You're all done!
+ .text-content.gl-text-center
+ %h4
+ = s_("Todos|You're all done!")
- elsif current_user.todos.any?
.col.todos-all-done.empty-state
.svg-content.svg-250
= image_tag 'illustrations/todos_all_done.svg'
- .text-content
+ .text-content.gl-text-center
- if todos_filter_empty?
- %h4.text-center
+ %h4
= Gitlab.config.gitlab.no_todos_messages.sample
%p
- Are you looking for things to do? Take a look at
- = succeed "," do
- %strong
- = link_to "open issues", issues_dashboard_path
- contribute to
- %strong
- = link_to "a merge request\,", merge_requests_dashboard_path
- or mention someone in a comment to automatically assign them a new to-do item.
+ = (s_("Todos|Are you looking for things to do? Take a look at %{strongStart}%{openIssuesLinkStart}open issues%{openIssuesLinkEnd}%{strongEnd}, contribute to %{strongStart}%{mergeRequestLinkStart}a merge request%{mergeRequestLinkEnd}%{mergeRequestLinkEnd}%{strongEnd}, or mention someone in a comment to automatically assign them a new to-do item.") % { strongStart: '<strong>', strongEnd: '</strong>', openIssuesLinkStart: "<a href=\"#{issues_dashboard_path}\">", openIssuesLinkEnd: '</a>', mergeRequestLinkStart: "<a href=\"#{merge_requests_dashboard_path}\">", mergeRequestLinkEnd: '</a>' }).html_safe
- else
- %h4.text-center
- Nothing is on your to-do list. Nice work!
+ %h4
+ = s_("Todos|Nothing is on your to-do list. Nice work!")
- else
.col.empty-state
.svg-content
= image_tag 'illustrations/todos_empty.svg'
- .text-content
- %h4.text-center
- Your To-Do List shows what to work on next
+ .text-content.gl-text-center
+ %h4
+ = s_("Todos|Your To-Do List shows what to work on next")
%p
- When an issue or merge request is assigned to you, or when you receive a
- %strong
- @mention
- in a comment, this automatically triggers a new item in your To-Do List.
+ = (s_("Todos|When an issue or merge request is assigned to you, or when you receive a %{strongStart}@mention%{strongEnd} in a comment, this automatically triggers a new item in your To-Do List.") % { strongStart: '<strong>', strongEnd: '</strong>' }).html_safe
%p
- It's how you always know what to work on next.
+ = s_("Todos|It's how you always know what to work on next.")
diff --git a/app/views/devise/confirmations/almost_there.haml b/app/views/devise/confirmations/almost_there.haml
index 892ef730884..1d46a43e5bd 100644
--- a/app/views/devise/confirmations/almost_there.haml
+++ b/app/views/devise/confirmations/almost_there.haml
@@ -2,7 +2,9 @@
- request_link_start = '<a href="%{new_user_confirmation_path}">'.html_safe % { new_user_confirmation_path: new_user_confirmation_path }
- request_link_end = '</a>'.html_safe
- content_for :page_specific_javascripts do
+ = render "layouts/google_tag_manager_head"
= render "layouts/one_trust"
+= render "layouts/google_tag_manager_body"
.well-confirmation.gl-text-center.gl-mb-6
%h1.gl-mt-0
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 15143684b8b..982171b9e34 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -62,7 +62,7 @@
%div
- if show_recaptcha_sign_up?
= recaptcha_tags nonce: content_security_policy_nonce
- .submit-container
+ .submit-container.gl-mt-5
= f.submit button_text, class: 'btn gl-button btn-confirm gl-display-block gl-w-full', data: { qa_selector: 'new_user_register_button' }
= render 'devise/shared/terms_of_service_notice', button_text: button_text
- if show_omniauth_providers && omniauth_providers_placement == :bottom
diff --git a/app/views/devise/shared/_signup_omniauth_provider_list.haml b/app/views/devise/shared/_signup_omniauth_provider_list.haml
index c24e8770f05..6688308cd71 100644
--- a/app/views/devise/shared/_signup_omniauth_provider_list.haml
+++ b/app/views/devise/shared/_signup_omniauth_provider_list.haml
@@ -2,7 +2,7 @@
= _("Create an account using:")
.gl-display-flex.gl-justify-content-between.gl-flex-wrap
- providers.each do |provider|
- = link_to omniauth_authorize_path(:user, provider), method: :post, class: "btn gl-button btn-default gl-w-full gl-mb-3 js-oauth-login #{qa_class_for_provider(provider)}", id: "oauth-login-#{provider}" do
+ = link_to omniauth_authorize_path(:user, provider), method: :post, class: "btn gl-button btn-default gl-w-full gl-mb-3 js-oauth-login #{qa_class_for_provider(provider)}", data: { provider: provider }, id: "oauth-login-#{provider}" do
- if provider_has_icon?(provider)
= provider_image_tag(provider)
%span.gl-button-text
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index f0e7a96f69f..bc69da5775f 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -1,14 +1,14 @@
%main{ :role => "main" }
- .modal-dialog.modal-doorkeepr-auth
- .modal-content.gl-shadow-none
- .modal-header
- %h3.page-title
+ .doorkeeper-authorize.gl-mx-auto.gl-mt-6
+ .gl-border-gray-200.gl-border-1.gl-border-solid.gl-rounded-base
+ .gl-p-5.gl-border-b-gray-200.gl-border-b-1.gl-border-b-solid
+ %h4.gl-m-0
- link_to_client = link_to(@pre_auth.client.name, @pre_auth.redirect_uri, target: '_blank', rel: 'noopener noreferrer')
= _("Authorize %{link_to_client} to use your account?").html_safe % { link_to_client: link_to_client }
- .modal-body
+ .gl-p-5
- if current_user.admin?
- .text-warning
+ .gl-text-orange-500
%p
= sprite_icon('warning-solid')
= html_escape(_('You are an admin, which means granting access to %{client_name} will allow them to interact with GitLab as an admin as well. Proceed with caution.')) % { client_name: tag.strong(@pre_auth.client.name) }
@@ -27,25 +27,25 @@
- @pre_auth.scopes.each do |scope|
%li
%strong= t scope, scope: [:doorkeeper, :scopes]
- .text-secondary= t scope, scope: [:doorkeeper, :scope_desc]
- .form-actions.text-right
- = form_tag oauth_authorization_path, method: :delete, class: 'inline' do
- = hidden_field_tag :client_id, @pre_auth.client.uid
- = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
- = hidden_field_tag :state, @pre_auth.state
- = hidden_field_tag :response_type, @pre_auth.response_type
- = hidden_field_tag :scope, @pre_auth.scope
- = hidden_field_tag :nonce, @pre_auth.nonce
- = hidden_field_tag :code_challenge, @pre_auth.code_challenge
- = hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method
- = submit_tag _("Deny"), class: "gl-button btn btn-danger"
- = form_tag oauth_authorization_path, method: :post, class: 'inline' do
- = hidden_field_tag :client_id, @pre_auth.client.uid
- = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
- = hidden_field_tag :state, @pre_auth.state
- = hidden_field_tag :response_type, @pre_auth.response_type
- = hidden_field_tag :scope, @pre_auth.scope
- = hidden_field_tag :nonce, @pre_auth.nonce
- = hidden_field_tag :code_challenge, @pre_auth.code_challenge
- = hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method
- = submit_tag _("Authorize"), class: "gl-button btn btn-confirm gl-ml-3", data: { qa_selector: 'authorization_button' }
+ .gl-text-gray-500= t scope, scope: [:doorkeeper, :scope_desc]
+ .gl-p-5.gl-bg-gray-10.gl-border-t-gray-200.gl-border-t-1.gl-border-t-solid.gl-rounded-bottom-right-base.gl-rounded-bottom-left-base.gl-text-right
+ = form_tag oauth_authorization_path, method: :delete, class: 'inline' do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = hidden_field_tag :nonce, @pre_auth.nonce
+ = hidden_field_tag :code_challenge, @pre_auth.code_challenge
+ = hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method
+ = submit_tag _("Deny"), class: "btn btn-default gl-button"
+ = form_tag oauth_authorization_path, method: :post, class: 'inline' do
+ = hidden_field_tag :client_id, @pre_auth.client.uid
+ = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri
+ = hidden_field_tag :state, @pre_auth.state
+ = hidden_field_tag :response_type, @pre_auth.response_type
+ = hidden_field_tag :scope, @pre_auth.scope
+ = hidden_field_tag :nonce, @pre_auth.nonce
+ = hidden_field_tag :code_challenge, @pre_auth.code_challenge
+ = hidden_field_tag :code_challenge_method, @pre_auth.code_challenge_method
+ = submit_tag _("Authorize"), class: "btn btn-danger gl-button gl-ml-3", data: { qa_selector: 'authorization_button' }
diff --git a/app/views/graphiql/rails/editors/show.html.erb b/app/views/graphiql/rails/editors/show.html.erb
deleted file mode 100644
index b8f82ae8323..00000000000
--- a/app/views/graphiql/rails/editors/show.html.erb
+++ /dev/null
@@ -1,99 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <title>GraphiQL</title>
- <%= stylesheet_link_tag("graphiql/rails/application") %>
- <%# TODO: This file was included to fix a CSP failure. Please remove when https://github.com/rmosolgo/graphiql-rails/pull/71 will be released %>
- <%= javascript_include_tag("graphiql/rails/application", nonce: true) %>
- </head>
- <body>
- <div id="graphiql-container">
- Loading...
- </div>
- <%= javascript_tag nonce: true do -%>
- var parameters = {};
-
- <% if GraphiQL::Rails.config.query_params %>
- // Parse the search string to get url parameters.
- var search = window.location.search;
- search.substr(1).split('&').forEach(function (entry) {
- var eq = entry.indexOf('=');
- if (eq >= 0) {
- parameters[decodeURIComponent(entry.slice(0, eq))] =
- decodeURIComponent(entry.slice(eq + 1));
- }
- });
- // if variables was provided, try to format it.
- if (parameters.variables) {
- try {
- parameters.variables =
- JSON.stringify(JSON.parse(parameters.variables), null, 2);
- } catch (e) {
- // Do nothing, we want to display the invalid JSON as a string, rather
- // than present an error.
- }
- }
- // When the query and variables string is edited, update the URL bar so
- // that it can be easily shared
- function onEditQuery(newQuery) {
- parameters.query = newQuery;
- updateURL();
- }
- function onEditVariables(newVariables) {
- parameters.variables = newVariables;
- updateURL();
- }
- function updateURL() {
- var newSearch = '?' + Object.keys(parameters).map(function (key) {
- return encodeURIComponent(key) + '=' +
- encodeURIComponent(parameters[key]);
- }).join('&');
- history.replaceState(null, null, newSearch);
- }
- <% end %>
-
- // Defines a GraphQL fetcher using the fetch API.
- var graphQLEndpoint = "<%= graphql_endpoint_path %>";
- function graphQLFetcher(graphQLParams) {
- return fetch(graphQLEndpoint, {
- method: 'post',
- headers: <%= raw JSON.pretty_generate(GraphiQL::Rails.config.resolve_headers(self)) %>,
- body: JSON.stringify(graphQLParams),
- credentials: 'include',
- }).then(function(response) {
- return response.text();
- }).then(function(text) {
- try {
- return JSON.parse(text);
- } catch(error) {
- return {
- "message": "The server responded with invalid JSON, this is probably a server-side error",
- "response": text,
- };
- }
- })
- }
-
- <% if GraphiQL::Rails.config.initial_query %>
- var defaultQuery = "<%= GraphiQL::Rails.config.initial_query.gsub("\n", '\n').gsub('"', '\"').html_safe %>";
- <% else %>
- var defaultQuery = undefined
- <% end %>
-
- // Render <GraphiQL /> into the body.
- ReactDOM.render(
- React.createElement(GraphiQL, {
- fetcher: graphQLFetcher,
- defaultQuery: defaultQuery,
- <% if GraphiQL::Rails.config.query_params %>
- query: parameters.query,
- variables: parameters.variables,
- onEditQuery: onEditQuery,
- onEditVariables: onEditVariables
- <% end %>
- }),
- document.getElementById("graphiql-container")
- );
- <% end -%>
- </body>
-</html>
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index e530d9c60b6..e5d67831c71 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -9,17 +9,18 @@
= group_icon(@group, class: 'avatar avatar-tile s64', width: 64, height: 64, itemprop: 'logo')
.d-flex.flex-column.flex-wrap.align-items-baseline
.d-inline-flex.align-items-baseline
- %h1.home-panel-title.gl-mt-3.gl-mb-2.gl-ml-3{ itemprop: 'name' }
+ %h1.home-panel-title.gl-mt-3.gl-mb-2{ itemprop: 'name' }
= @group.name
%span.visibility-icon.text-secondary.gl-ml-2.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@group) }
= visibility_level_icon(@group.visibility_level, options: {class: 'icon'})
- .home-panel-metadata.text-secondary.gl-font-base.gl-font-weight-normal.gl-line-height-normal
+ .home-panel-metadata.text-secondary.gl-font-base.gl-font-weight-normal.gl-line-height-normal{ data: { qa_selector: 'group_id_content' }, itemprop: 'identifier' }
- if can?(current_user, :read_group, @group)
- - button_class = "btn gl-button btn-sm btn-tertiary btn-default-tertiary home-panel-metadata"
- - button_text = s_('GroupPage|Group ID: %{group_id}') % { group_id: @group.id }
- = clipboard_button(title: s_('GroupPage|Copy group ID'), text: @group.id, hide_button_icon: true, button_text: button_text, class: button_class, qa_selector: 'group_id_content', itemprop: 'identifier')
+ %span.gl-display-inline-block.gl-vertical-align-middle
+ = s_("GroupPage|Group ID: %{group_id}") % { group_id: @group.id }
+ - button_class = "btn gl-button btn-sm btn-tertiary btn-default-tertiary home-panel-metadata"
+ = clipboard_button(title: s_('GroupPage|Copy group ID'), text: @group.id, class: button_class)
- if current_user
- %span.gl-ml-3
+ %span.gl-ml-3.gl-mb-3
= render 'shared/members/access_request_links', source: @group
.home-panel-buttons.col-md-12.col-lg-6
diff --git a/app/views/groups/_new_group_fields.html.haml b/app/views/groups/_new_group_fields.html.haml
index 8ee7c91a938..fe2ee62d9be 100644
--- a/app/views/groups/_new_group_fields.html.haml
+++ b/app/views/groups/_new_group_fields.html.haml
@@ -7,14 +7,15 @@
= _('Visibility level')
%p
= _('Who will be able to see this group?')
- = link_to _('View the documentation'), help_page_path("public_access/public_access"), target: '_blank'
+ = link_to _('View the documentation'), help_page_path("public_access/public_access"), target: '_blank', rel: 'noopener noreferrer'
= render 'shared/visibility_level', f: f, visibility_level: default_group_visibility, can_change_visibility_level: true, form_model: @group, with_label: false
- if Gitlab.config.mattermost.enabled
.row
= render 'create_chat_team', f: f
-= render 'personalize', f: f
+- unless Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers?
+ = render 'personalize', f: f
.row.js-invite-members-section
.col-sm-4
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 420771257c9..f3494149087 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -20,11 +20,11 @@
%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded), data: { qa_selector: 'permission_lfs_2fa_content' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
- = _('Permissions, LFS, 2FA')
+ = _('Permissions and group features')
%button.btn.gl-button.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p
- = _('Configure advanced permissions, Large File Storage, and two-factor authentication settings.')
+ = _('Configure advanced permissions, Large File Storage, two-factor authentication, and customer relations settings.')
.settings-content
= render 'groups/settings/permissions'
@@ -60,4 +60,3 @@
= render_if_exists 'shared/groups/max_pages_size_setting'
-= render 'shared/confirm_modal', phrase: @group.path
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 0c6776a6038..a9258a4e0d0 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -18,21 +18,11 @@
- if @can_bulk_update
= render_if_exists 'shared/issuable/bulk_update_button', type: :issues
- = render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
+ = render 'shared/new_project_item_select', path: 'issues/new', label: _("issue"), type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
= render 'shared/issuable/search_bar', type: :issues
- if @can_bulk_update
= render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
- - if Feature.enabled?(:vue_issuables_list, @group) && @issues.to_a.any?
- - if use_startup_call?
- - add_page_startup_api_call(api_v4_groups_issues_path(id: @group.id, params: startup_call_params))
- .js-issuables-list{ data: { endpoint: expose_url(api_v4_groups_issues_path(id: @group.id)),
- 'can-bulk-edit': @can_bulk_update.to_json,
- 'empty-state-meta': { svg_path: image_path('illustrations/issues.svg') },
- 'sort-key': @sort,
- type: 'issues',
- 'scoped-labels-available': scoped_labels_available?(@group).to_json } }
- - else
- = render 'shared/issues', project_select_button: true
+ = render 'shared/issues', project_select_button: true
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index ad916e3aeec..011c76e5ae7 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -12,7 +12,7 @@
- if @can_bulk_update
= render_if_exists 'shared/issuable/bulk_update_button', type: :merge_requests
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests, with_feature_enabled: 'merge_requests', with_shared: false, include_projects_in_subgroups: true
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: _("merge request"), type: :merge_requests, with_feature_enabled: 'merge_requests', with_shared: false, include_projects_in_subgroups: true
= render 'shared/issuable/search_bar', type: :merge_requests
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index e55af71022e..750030601b7 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -6,7 +6,7 @@
.group-edit-container.gl-mt-5
- .js-new-group-creation{ data: { has_errors: @group.errors.any?.to_s } }
+ .js-new-group-creation{ data: { has_errors: @group.errors.any?.to_s }.merge(verification_for_group_creation_data) }
.row{ 'v-cloak': true }
#create-group-pane.tab-pane
diff --git a/app/views/groups/packages/index.html.haml b/app/views/groups/packages/index.html.haml
index d56a806f082..1c0627779ec 100644
--- a/app/views/groups/packages/index.html.haml
+++ b/app/views/groups/packages/index.html.haml
@@ -7,4 +7,7 @@
full_path: @group.full_path,
endpoint: group_packages_path(@group),
page_type: 'groups',
- empty_list_illustration: image_path('illustrations/no-packages.svg'), } }
+ empty_list_illustration: image_path('illustrations/no-packages.svg'),
+ npm_instance_url: package_registry_instance_url(:npm),
+ project_list_url: '',
+ group_list_url: group_packages_path(@group) } }
diff --git a/app/views/groups/runners/_group_runners.html.haml b/app/views/groups/runners/_group_runners.html.haml
index e7cfc87ac88..876642474cd 100644
--- a/app/views/groups/runners/_group_runners.html.haml
+++ b/app/views/groups/runners/_group_runners.html.haml
@@ -25,5 +25,5 @@
%br
- else
= _('Please contact an admin to register runners.')
- = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'runner-registration'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'prevent-users-from-registering-runners'), target: '_blank', rel: 'noopener noreferrer'
diff --git a/app/views/groups/runners/_runner.html.haml b/app/views/groups/runners/_runner.html.haml
index a76701ea5d2..78ce5b3e110 100644
--- a/app/views/groups/runners/_runner.html.haml
+++ b/app/views/groups/runners/_runner.html.haml
@@ -3,17 +3,13 @@
.table-mobile-header{ role: 'rowheader' }= _('Type')
.table-mobile-content
- if runner.group_type?
- %span.badge.badge-pill.gl-badge.sm.badge-success
- = s_('Runners|group')
+ = gl_badge_tag s_('Runners|group'), variant: :success, size: :sm
- else
- %span.badge.badge-pill.gl-badge.sm.badge-info
- = s_('Runners|specific')
+ = gl_badge_tag s_('Runners|specific'), variant: :info, size: :sm
- if runner.locked?
- %span.badge.badge-pill.gl-badge.sm.badge-warning
- = s_('Runners|locked')
+ = gl_badge_tag s_('Runners|locked'), variant: :warning, size: :sm
- unless runner.active?
- %span.badge.badge-pill.gl-badge.sm.badge-danger
- = s_('Runners|paused')
+ = gl_badge_tag s_('Runners|paused'), variant: :danger, size: :sm
.table-section.section-30
.table-mobile-header{ role: 'rowheader' }= s_('Runners|Runner')
@@ -50,8 +46,7 @@
.table-mobile-header{ role: 'rowheader' }= _('Tags')
.table-mobile-content
- runner.tags.map(&:name).sort.each do |tag|
- %span.badge.badge-primary.str-truncated.has-tooltip{ title: tag }
- = tag
+ = gl_badge_tag tag, { variant: :info }, { class: 'str-truncated has-tooltip', title: tag }
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }= _('Last contact')
@@ -81,5 +76,5 @@
= sprite_icon('close', css_class: 'gl-icon')
- else
.btn-group
- = link_to group_runner_path(@group, runner), method: :delete, class: 'gl-button btn btn-danger btn-icon has-tooltip', title: _('Remove'), ref: 'tooltip', aria: { label: _('Remove') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?') } do
+ = link_to group_runner_path(@group, runner), method: :delete, class: 'gl-button btn btn-danger btn-icon has-tooltip', title: _('Remove'), ref: 'tooltip', aria: { label: _('Remove') }, data: { placement: 'top', container: 'body', confirm: _('Are you sure?'), confirm_btn_variant: "danger" } do
= sprite_icon('close', css_class: 'gl-icon')
diff --git a/app/views/groups/settings/_ip_restriction_registration_features_cta.html.haml b/app/views/groups/settings/_ip_restriction_registration_features_cta.html.haml
new file mode 100644
index 00000000000..3067220ea8f
--- /dev/null
+++ b/app/views/groups/settings/_ip_restriction_registration_features_cta.html.haml
@@ -0,0 +1,8 @@
+- return unless registration_features_can_be_prompted?
+
+.form-group
+ = f.label :disabled_ip_restriction_ranges, class: 'label-bold' do
+ = _('Allow access to the following IP addresses')
+ = f.text_field :disabled_ip_restriction_ranges, value: '', class: 'form-control', disabled: true
+ %span.form-text.text-muted
+ = render 'shared/registration_features_discovery_message'
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index eb38aa43881..d4b74665398 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -29,8 +29,9 @@
checkbox_options: { checked: @group.mentions_disabled? },
help_text: s_('GroupSettings|Prevents group members from being notified if the group is mentioned.')
- = render 'groups/settings/project_access_token_creation', f: f, group: @group
+ = render 'groups/settings/resource_access_token_creation', f: f, group: @group
= render_if_exists 'groups/settings/delayed_project_removal', f: f, group: @group
+ = render 'groups/settings/ip_restriction_registration_features_cta', f: f
= render_if_exists 'groups/settings/ip_restriction', f: f, group: @group
= render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group
= render 'groups/settings/lfs', f: f
@@ -41,4 +42,13 @@
= render 'groups/settings/two_factor_auth', f: f, group: @group
= render_if_exists 'groups/personal_access_token_expiration_policy', f: f, group: @group
= render 'groups/settings/membership', f: f, group: @group
+
+ - if crm_feature_flag_enabled?(@group)
+ %h5= _('Customer relations')
+ .form-group.gl-mb-3
+ = f.gitlab_ui_checkbox_component :crm_enabled,
+ s_('GroupSettings|Enable customer relations'),
+ checkbox_options: { checked: @group.crm_enabled? },
+ help_text: s_('GroupSettings|Allows creating organizations and contacts and associating them with issues.')
+
= f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mt-3 js-dirty-submit', data: { qa_selector: 'save_permissions_changes_button' }
diff --git a/app/views/groups/settings/_project_access_token_creation.html.haml b/app/views/groups/settings/_project_access_token_creation.html.haml
deleted file mode 100644
index 948b25390ba..00000000000
--- a/app/views/groups/settings/_project_access_token_creation.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- return unless render_setting_to_allow_project_access_token_creation?(group)
-
-.form-group.gl-mb-3
- - project_access_tokens_link = help_page_path('user/project/settings/project_access_tokens')
- - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: project_access_tokens_link }
- = f.gitlab_ui_checkbox_component :resource_access_token_creation_allowed,
- s_('GroupSettings|Allow project access token creation'),
- checkbox_options: { checked: group.namespace_settings.resource_access_token_creation_allowed?, data: { qa_selector: 'resource_access_token_creation_allowed_checkbox' } },
- help_text: s_('GroupSettings|Users can create %{link_start}project access tokens%{link_end} for projects in this group.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
diff --git a/app/views/groups/settings/_resource_access_token_creation.html.haml b/app/views/groups/settings/_resource_access_token_creation.html.haml
new file mode 100644
index 00000000000..160f8ae1e07
--- /dev/null
+++ b/app/views/groups/settings/_resource_access_token_creation.html.haml
@@ -0,0 +1,11 @@
+- return unless render_setting_to_allow_project_access_token_creation?(group)
+
+.form-group.gl-mb-3
+ - project_access_tokens_link = help_page_path('user/project/settings/project_access_tokens')
+ - group_access_tokens_link = help_page_path('user/group/settings/group_access_tokens')
+ - link_start_project = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: project_access_tokens_link }
+ - link_start_group = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: group_access_tokens_link }
+ = f.gitlab_ui_checkbox_component :resource_access_token_creation_allowed,
+ s_('GroupSettings|Allow project and group access token creation'),
+ checkbox_options: { checked: group.namespace_settings.resource_access_token_creation_allowed?, data: { qa_selector: 'resource_access_token_creation_allowed_checkbox' } },
+ help_text: s_('GroupSettings|Users can create %{link_start_project}project access tokens%{link_end} and %{link_start_group}group access tokens%{link_end} in this group.').html_safe % { link_start_project: link_start_project, link_start_group: link_start_group, link_end: '</a>'.html_safe }
diff --git a/app/views/groups/settings/access_tokens/index.html.haml b/app/views/groups/settings/access_tokens/index.html.haml
new file mode 100644
index 00000000000..16ea96f0b08
--- /dev/null
+++ b/app/views/groups/settings/access_tokens/index.html.haml
@@ -0,0 +1,50 @@
+- breadcrumb_title s_('AccessTokens|Access Tokens')
+- page_title _('Group Access Tokens')
+- type = _('group access token')
+- type_plural = _('group access tokens')
+- @content_class = 'limit-container-width' unless fluid_layout
+
+.row.gl-mt-3
+ .col-lg-4
+ %h4.gl-mt-0
+ = page_title
+ %p
+ - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/group/settings/group_access_tokens') }
+ - if current_user.can?(:create_resource_access_tokens, @group)
+ = _('Generate group access tokens scoped to this group for your applications that need access to the GitLab API.')
+ %p
+ = _('You can also use group access tokens with Git to authenticate over HTTP(S). %{link_start}Learn more.%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
+ - else
+ = _('Group access token creation is disabled in this group. You can still use and manage existing tokens. %{link_start}Learn more.%{link_end}').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
+ %p
+ - root_group = @group.root_ancestor
+ - if current_user.can?(:admin_group, root_group)
+ - group_settings_link = edit_group_path(root_group)
+ - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: group_settings_link }
+ = _('You can enable group access token creation in %{link_start}group settings%{link_end}.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
+
+ .col-lg-8
+ - if @new_resource_access_token
+ = render 'shared/access_tokens/created_container',
+ type: type,
+ new_token_value: @new_resource_access_token
+
+ - if current_user.can?(:create_resource_access_tokens, @group)
+ = render 'shared/access_tokens/form',
+ type: type,
+ path: group_settings_access_tokens_path(@group),
+ resource: @group,
+ token: @resource_access_token,
+ scopes: @scopes,
+ access_levels: GroupMember.access_level_roles,
+ default_access_level: Gitlab::Access::MAINTAINER,
+ prefix: :resource_access_token,
+ help_path: help_page_path('user/group/settings/group_access_tokens', anchor: 'scopes-for-a-group-access-token')
+
+ = render 'shared/access_tokens/table',
+ active_tokens: @active_resource_access_tokens,
+ resource: @group,
+ type: type,
+ type_plural: type_plural,
+ revoke_route_helper: ->(token) { revoke_group_settings_access_token_path(id: token) },
+ no_active_tokens_message: _('This group has no active access tokens.')
diff --git a/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
index 32da444d058..12c0f15aff5 100644
--- a/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
+++ b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
@@ -11,5 +11,6 @@
= gl_badge_tag badge_for_auto_devops_scope(group), variant: :info
.form-text.text-muted
= s_('GroupSettings|The Auto DevOps pipeline runs if no alternative CI configuration file is found.')
- = link_to _('Learn more.'), help_page_path('topics/autodevops/index.md'), target: '_blank'
+ = link_to _('Learn more.'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer'
+
= f.submit _('Save changes'), class: 'btn gl-button btn-confirm gl-mt-5'
diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml
index f81afd0a82e..3992cb527ed 100644
--- a/app/views/help/index.html.haml
+++ b/app/views/help/index.html.haml
@@ -4,12 +4,15 @@
= markdown_field(Gitlab::CurrentSettings.current_application_settings, :help_page_text)
%hr
-%h1
- = default_brand_title
- - if user_signed_in?
- %span= link_to_version
- = version_status_badge
- %hr
+.gl-display-flex.gl-align-items-flex-end
+ %h1.gl-mt-5.gl-mb-3
+ = default_brand_title
+ - if user_signed_in?
+ %span= link_to_version
+ - if show_version_check?
+ %span.gl-mt-5.gl-mb-3.gl-ml-3
+ .js-gitlab-version-check{ data: { "size": "lg" } }
+%hr
- unless Gitlab::CurrentSettings.help_page_hide_commercial_content?
%p.slead
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 509f5be8097..46d84d9f883 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -1,5 +1,5 @@
- object = @target_project || @project || @group
-- noteable_type = @noteable.class if @noteable.present?
+- noteable_type = @noteable_type || @noteable&.class
- datasources = autocomplete_data_sources(object, noteable_type)
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 69b8518ef33..d0a06c7d5bf 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -19,8 +19,18 @@
%span.gl-badge.gl-bg-green-500.gl-text-white.gl-rounded-pill.gl-font-weight-bold.gl-py-1
= _('Next')
- .gl-display-none.gl-sm-display-block
- = render "layouts/nav/top_nav"
+ - if current_user
+ .gl-display-none.gl-sm-display-block
+ = render "layouts/nav/top_nav"
+ - else
+ - experiment(:logged_out_marketing_header, actor: nil) do |e|
+ - e.candidate do
+ = render 'layouts/header/marketing_links'
+ - e.try(:trial_focused) do
+ = render 'layouts/header/marketing_links'
+ - e.control do
+ .gl-display-none.gl-sm-display-block
+ = render "layouts/nav/top_nav"
.navbar-collapse.collapse
%ul.nav.navbar-nav
@@ -38,7 +48,7 @@
'autocomplete-path' => search_autocomplete_path } }
.gl-search-box-by-type
= sprite_icon('search', css_class: 'gl-search-box-by-type-search-icon gl-icon')
- %input{ type: "text", placeholder: _('Search or jump to...'), class: 'form-control gl-form-input gl-search-box-by-type-input', id: 'search', autocomplete: 'off' }
+ %input{ type: "text", placeholder: s_('GlobalSearch|Search GitLab'), class: 'form-control gl-form-input gl-search-box-by-type-input', id: 'search', autocomplete: 'off' }
- else
= render 'layouts/search'
%li.nav-item{ class: 'd-none d-sm-inline-block d-lg-none' }
@@ -104,6 +114,15 @@
= sprite_icon('chevron-down', css_class: 'caret-down')
.dropdown-menu.dropdown-menu-right
= render 'layouts/header/help_dropdown'
+ - unless current_user
+ - experiment(:logged_out_marketing_header, actor: nil) do |e|
+ - e.candidate do
+ %li.nav-item.gl-display-none.gl-sm-display-block
+ = render "layouts/nav/top_nav"
+ - e.try(:trial_focused) do
+ %li.nav-item.gl-display-none.gl-sm-display-block
+ = render "layouts/nav/top_nav"
+ - e.control {}
- if header_link?(:user_dropdown)
%li.nav-item.header-user.js-nav-user-dropdown.dropdown{ data: { track_label: "profile_dropdown", track_action: "click_dropdown", track_value: "", qa_selector: 'user_menu' }, class: ('mr-0' if has_impersonation_link) }
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
@@ -117,10 +136,23 @@
= link_to admin_impersonation_path, class: 'nav-link impersonation-btn', method: :delete, title: _('Stop impersonation'), aria: { label: _('Stop impersonation') }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body', qa_selector: 'stop_impersonation_link' } do
= sprite_icon('incognito', size: 18)
- if header_link?(:sign_in)
- %li.nav-item
- %div
- - sign_in_text = allow_signup? ? _('Sign in / Register') : _('Sign in')
- = link_to sign_in_text, new_session_path(:user, redirect_to_referer: 'yes'), class: 'gl-button btn btn-default btn-sign-in'
+ - experiment(:logged_out_marketing_header, actor: nil) do |e|
+ - e.candidate do
+ %li.nav-item.gl-display-none.gl-sm-display-block
+ = link_to _('Sign up now'), new_user_registration_path, class: 'gl-button btn btn-default btn-sign-in'
+ %li.nav-item.gl-display-none.gl-sm-display-block
+ = link_to _('Login'), new_session_path(:user, redirect_to_referer: 'yes')
+ = render 'layouts/header/sign_in_register_button', class: 'gl-sm-display-none'
+ - e.try(:trial_focused) do
+ %li.nav-item.gl-display-none.gl-sm-display-block
+ = link_to _('Get a free trial'), 'https://about.gitlab.com/free-trial/', class: 'gl-button btn btn-default btn-sign-in'
+ %li.nav-item.gl-display-none.gl-sm-display-block
+ = link_to _('Sign up'), new_user_registration_path
+ %li.nav-item.gl-display-none.gl-sm-display-block
+ = link_to _('Login'), new_session_path(:user, redirect_to_referer: 'yes')
+ = render 'layouts/header/sign_in_register_button', class: 'gl-sm-display-none'
+ - e.control do
+ = render 'layouts/header/sign_in_register_button'
%button.navbar-toggler.d-block.d-sm-none{ type: 'button', class: 'gl-border-none!', data: { testid: 'top-nav-responsive-toggle', qa_selector: 'mobile_navbar_button' } }
%span.sr-only= _('Toggle navigation')
diff --git a/app/views/layouts/header/_gitlab_version.html.haml b/app/views/layouts/header/_gitlab_version.html.haml
new file mode 100644
index 00000000000..125fbaa084c
--- /dev/null
+++ b/app/views/layouts/header/_gitlab_version.html.haml
@@ -0,0 +1,11 @@
+- return unless show_version_check?
+
+.gl-display-flex.gl-flex-direction-column.gl-px-4.gl-py-3
+ %span
+ = s_("VersionCheck|Your GitLab Version")
+ = emoji_icon('rocket')
+ %span
+ %span.gl-font-sm.gl-text-gray-500
+ #{Gitlab.version_info.major}.#{Gitlab.version_info.minor}
+ %span.gl-ml-2
+ .js-gitlab-version-check{ data: { "size": "sm" } }
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index e2c7781da54..738bca2f2cc 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -1,5 +1,6 @@
%ul
- if current_user_menu?(:help)
+ = render 'layouts/header/gitlab_version'
= render 'layouts/header/whats_new_dropdown_item'
%li
= link_to _("Help"), help_path
diff --git a/app/views/layouts/header/_marketing_links.html.haml b/app/views/layouts/header/_marketing_links.html.haml
new file mode 100644
index 00000000000..24069de394d
--- /dev/null
+++ b/app/views/layouts/header/_marketing_links.html.haml
@@ -0,0 +1,34 @@
+%ul.nav.navbar-sub-nav.gl-display-none.gl-lg-display-flex.gl-align-items-center
+ %li.dropdown.gl-mr-3
+ %button{ type: "button", data: { toggle: "dropdown" } }
+ = s_('LoggedOutMarketingHeader|About GitLab')
+ = sprite_icon('chevron-down', css_class: 'caret-down')
+ .dropdown-menu
+ %ul
+ %li
+ = link_to 'https://about.gitlab.com/stages-devops-lifecycle/' do
+ = s_('LoggedOutMarketingHeader|GitLab: the DevOps platform')
+ %li
+ = link_to explore_root_path do
+ = s_('LoggedOutMarketingHeader|Explore GitLab')
+ %li
+ = link_to 'https://about.gitlab.com/install/' do
+ = s_('LoggedOutMarketingHeader|Install GitLab')
+ %li
+ = link_to 'https://about.gitlab.com/is-it-any-good/' do
+ = s_('LoggedOutMarketingHeader|How GitLab compares')
+ %li
+ = link_to 'https://about.gitlab.com/get-started/' do
+ = s_('LoggedOutMarketingHeader|Get started')
+ %li
+ = link_to 'https://docs.gitlab.com/' do
+ = s_('LoggedOutMarketingHeader|GitLab docs')
+ %li
+ = link_to 'https://about.gitlab.com/learn/' do
+ = s_('LoggedOutMarketingHeader|GitLab Learn')
+ %li.gl-mr-3
+ = link_to 'https://about.gitlab.com/pricing/' do
+ = s_('LoggedOutMarketingHeader|Pricing')
+ %li.gl-mr-3
+ = link_to 'https://about.gitlab.com/sales/' do
+ = s_('LoggedOutMarketingHeader|Talk to an expert')
diff --git a/app/views/layouts/header/_sign_in_register_button.html.haml b/app/views/layouts/header/_sign_in_register_button.html.haml
new file mode 100644
index 00000000000..992e8785251
--- /dev/null
+++ b/app/views/layouts/header/_sign_in_register_button.html.haml
@@ -0,0 +1,6 @@
+- top_class = local_assigns.fetch(:class, nil)
+
+%li.nav-item{ class: top_class }
+ %div
+ - sign_in_text = allow_signup? ? _('Sign in / Register') : _('Sign in')
+ = link_to sign_in_text, new_session_path(:user, redirect_to_referer: 'yes'), class: 'gl-button btn btn-default btn-sign-in'
diff --git a/app/views/notify/_note_email.html.haml b/app/views/notify/_note_email.html.haml
index ae9c8554e73..ad0c873bf56 100644
--- a/app/views/notify/_note_email.html.haml
+++ b/app/views/notify/_note_email.html.haml
@@ -26,12 +26,10 @@
= stylesheet_link_tag 'mailers/highlighted_diff_email'
%table
- = render partial: "projects/diffs/line",
+ = render partial: "projects/diffs/email_line",
collection: discussion.truncated_diff_lines(diff_limit: diff_limit),
as: :line,
- locals: { diff_file: discussion.diff_file,
- plain: true,
- email: true }
+ locals: { diff_file: discussion.diff_file }
%div{ style: note_style }
= markdown(note.note, pipeline: :email, author: note.author, current_user: @recipient, issuable_reference_expansion_enabled: true)
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 5c5520f4cb8..93806e6de8e 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -74,7 +74,7 @@
- blob = diff_file.blob
- if blob && blob.readable_text?
%table.code.white
- = render partial: "projects/diffs/line", collection: diff_file.highlighted_diff_lines, as: :line, locals: { diff_file: diff_file, plain: true, email: true }
+ = render partial: "projects/diffs/email_line", collection: diff_file.highlighted_diff_lines, as: :line, locals: { diff_file: diff_file }
- else
No preview for this file type
%br
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 809dc3320ff..97056db6b74 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -55,7 +55,7 @@
%p
= s_('Profiles|Changing your username can have unintended side effects.')
= succeed '.' do
- = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'change-your-username'), target: '_blank'
+ = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'change-your-username'), target: '_blank', rel: 'noopener noreferrer'
.col-lg-8
- data = { initial_username: current_user.username, root_url: root_url, action_url: update_username_profile_path(format: :json) }
#update-username{ data: data }
diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml
index 24c25bc1ab2..3206fca6bcd 100644
--- a/app/views/profiles/chat_names/_chat_name.html.haml
+++ b/app/views/profiles/chat_names/_chat_name.html.haml
@@ -10,7 +10,7 @@
%td
%strong
- if can?(current_user, :admin_project, project)
- = link_to integration.title, edit_project_service_path(project, integration)
+ = link_to integration.title, edit_project_integration_path(project, integration)
- else
= integration.title
%td
diff --git a/app/views/profiles/chat_names/index.html.haml b/app/views/profiles/chat_names/index.html.haml
index 782850afcda..54c34228800 100644
--- a/app/views/profiles/chat_names/index.html.haml
+++ b/app/views/profiles/chat_names/index.html.haml
@@ -9,7 +9,8 @@
= _('You can see your chat accounts.')
.col-lg-8
- %h5.gl-mt-0 Active chat names (#{@chat_names.size})
+ %h5.gl-mt-0
+ = sprintf(_('Active chat names (%{count})'), { count: @chat_names.size })
- if @chat_names.present?
.table-responsive
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 0cfe65994da..fef55f5d384 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -37,23 +37,23 @@
%li
= render partial: 'shared/email_with_badge', locals: { email: @primary_email, verified: current_user.confirmed? }
%span.float-right
- %span.badge.badge-muted.badge-pill.gl-badge.badge-success= s_('Profiles|Primary email')
+ = gl_badge_tag s_('Profiles|Primary email'), variant: :success
- if @primary_email === current_user.commit_email_or_default
- %span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Commit email')
+ = gl_badge_tag s_('Profiles|Commit email'), variant: :info
- if @primary_email === current_user.public_email
- %span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Public email')
+ = gl_badge_tag s_('Profiles|Public email'), variant: :info
- if @primary_email === current_user.notification_email_or_default
- %span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Default notification email')
+ = gl_badge_tag s_('Profiles|Default notification email'), variant: :info
- @emails.reject(&:user_primary_email?).each do |email|
%li{ data: { qa_selector: 'email_row_content' } }
= render partial: 'shared/email_with_badge', locals: { email: email.email, verified: email.confirmed? }
%span.float-right
- if email.email === current_user.commit_email_or_default
- %span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Commit email')
+ = gl_badge_tag s_('Profiles|Commit email'), variant: :info
- if email.email === current_user.public_email
- %span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Public email')
+ = gl_badge_tag s_('Profiles|Public email'), variant: :info
- if email.email === current_user.notification_email_or_default
- %span.badge.badge-muted.badge-pill.gl-badge.badge-info= s_('Profiles|Notification email')
+ = gl_badge_tag s_('Profiles|Notification email'), variant: :info
- unless email.confirmed?
- confirm_title = "#{email.confirmation_sent_at ? _('Resend confirmation email') : _('Send confirmation email')}"
= link_to confirm_title, resend_confirmation_instructions_profile_email_path(email), method: :put, class: 'gl-button btn btn-sm btn-warning gl-ml-3'
diff --git a/app/views/profiles/gpg_keys/index.html.haml b/app/views/profiles/gpg_keys/index.html.haml
index bf9c77cb3ec..91af6953ee1 100644
--- a/app/views/profiles/gpg_keys/index.html.haml
+++ b/app/views/profiles/gpg_keys/index.html.haml
@@ -12,7 +12,7 @@
= _('Add a GPG key')
%p.profile-settings-content
- help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('user/project/repository/gpg_signed_commits/index.md') }
- = _('Before you can add a GPG key you need to %{help_link_start}Generate it.%{help_link_end}'.html_safe) % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
+ = _('Add a GPG key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}').html_safe % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
= render 'form'
%hr
%h5
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index 2b3109225a8..bebbe49a485 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -5,8 +5,8 @@
.form-group
= f.label :key, s_('Profiles|Key'), class: 'label-bold'
- %p= _("Paste your public SSH key, which is usually contained in the file '~/.ssh/id_ed25519.pub' or '~/.ssh/id_rsa.pub' and begins with 'ssh-ed25519' or 'ssh-rsa'. Do not paste your private SSH key, as that can compromise your identity.")
- = f.text_area :key, class: "form-control gl-form-input js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true, placeholder: s_('Profiles|Typically starts with "ssh-ed25519 …" or "ssh-rsa …"')
+ = f.text_area :key, class: "form-control gl-form-input js-add-ssh-key-validation-input qa-key-public-key-field", rows: 8, required: true, data: { supported_algorithms: Gitlab::SSHPublicKey.supported_algorithms }
+ %p.form-text.text-muted= s_('Profiles|Begins with %{ssh_key_algorithms}.') % { ssh_key_algorithms: ssh_key_allowed_algorithms }
.form-row
.col.form-group
= f.label :title, _('Title'), class: 'label-bold'
diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index 584bd44e386..7d4c3b6115f 100644
--- a/app/views/profiles/keys/index.html.haml
+++ b/app/views/profiles/keys/index.html.haml
@@ -11,11 +11,8 @@
%h5.gl-mt-0
= _('Add an SSH key')
%p.profile-settings-content
- - generate_link_url = help_page_path("ssh/index", anchor: 'generate-an-ssh-key-pair')
- - existing_link_url = help_page_path("ssh/index", anchor: 'see-if-you-have-an-existing-ssh-key-pair')
- - generate_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: generate_link_url }
- - existing_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: existing_link_url }
- = _('To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}.').html_safe % { generate_link_start: generate_link_start, existing_link_start: existing_link_start, link_end: '</a>'.html_safe }
+ - help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: help_page_path('ssh/index.md') }
+ = _('Add an SSH key for secure access to GitLab. %{help_link_start}Learn more.%{help_link_end}').html_safe % {help_link_start: help_link_start, help_link_end: '</a>'.html_safe }
= render 'form'
%hr
%h5
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index a8275576327..887d07f7a20 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -32,64 +32,5 @@
type_plural: type_plural,
active_tokens: @active_personal_access_tokens,
revoke_route_helper: ->(token) { revoke_profile_personal_access_token_path(token) }
-- if Feature.enabled?(:hide_access_tokens, default_enabled: :yaml)
- #js-tokens-app{ data: { tokens_data: tokens_app_data } }
-- else
- - unless Gitlab::CurrentSettings.disable_feed_token
- .col-lg-12
- %hr
- .row.gl-mt-3.js-search-settings-section
- .col-lg-4.profile-settings-sidebar
- %h4.gl-mt-0
- = s_('AccessTokens|Feed token')
- %p
- = s_('AccessTokens|Your feed token authenticates you when your RSS reader loads a personalized RSS feed or when your calendar application loads a personalized calendar. It is visible in those feed URLs.')
- %p
- = s_('AccessTokens|It cannot be used to access any other data.')
- .col-lg-8.feed-token-reset
- = label_tag :feed_token, s_('AccessTokens|Feed token'), class: 'label-bold'
- = text_field_tag :feed_token, current_user.feed_token, class: 'form-control gl-form-input js-select-on-focus', readonly: true
- %p.form-text.text-muted
- - reset_link = link_to s_('AccessTokens|reset this token'), [:reset, :feed_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.'), testid: :reset_feed_token_link }
- - reset_message = s_('AccessTokens|Keep this token secret. Anyone who has it can read activity and issue RSS feeds or your calendar feed as if they were you. If that happens, %{link_reset_it}.') % { link_reset_it: reset_link }
- = reset_message.html_safe
- - if incoming_email_token_enabled?
- .col-lg-12
- %hr
- .row.gl-mt-3.js-search-settings-section
- .col-lg-4.profile-settings-sidebar
- %h4.gl-mt-0
- = s_('AccessTokens|Incoming email token')
- %p
- = s_('AccessTokens|Your incoming email token authenticates you when you create a new issue by email, and is included in your personal project-specific email addresses.')
- %p
- = s_('AccessTokens|It cannot be used to access any other data.')
- .col-lg-8.incoming-email-token-reset
- = label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: 'label-bold'
- = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control gl-form-input js-select-on-focus', readonly: true
- %p.form-text.text-muted
- - reset_link = link_to s_('AccessTokens|reset this token'), [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.'), testid: :reset_email_token_link }
- - reset_message = s_('AccessTokens|Keep this token secret. Anyone who has it can create issues as if they were you. If that happens, %{link_reset_it}.') % { link_reset_it: reset_link }
- = reset_message.html_safe
-
- - if static_objects_external_storage_enabled?
- .col-lg-12
- %hr
- .row.gl-mt-3.js-search-settings-section
- .col-lg-4
- %h4.gl-mt-0
- = s_('AccessTokens|Static object token')
- %p
- = s_('AccessTokens|Your static object token authenticates you when repository static objects (such as archives or blobs) are served from an external storage.')
- %p
- = s_('AccessTokens|It cannot be used to access any other data.')
- .col-lg-8
- = label_tag :static_object_token, s_('AccessTokens|Static object token'), class: "label-bold"
- = text_field_tag :static_object_token, current_user.static_object_token, class: 'form-control gl-form-input', readonly: true, onclick: 'this.select()'
- %p.form-text.text-muted
- - reset_link = url_for [:reset, :static_object_token, :profile]
- - reset_link_start = '<a data-confirm="%{confirm}" rel="nofollow" data-method="put" href="%{url}">'.html_safe % { confirm: s_('AccessTokens|Are you sure?'), url: reset_link }
- - reset_link_end = '</a>'.html_safe
- - reset_message = s_('AccessTokens|Keep this token secret. Anyone who has it can access repository static objects as if they were you. If that ever happens, %{reset_link_start}reset this token%{reset_link_end}.') % { reset_link_start: reset_link_start, reset_link_end: reset_link_end }
- = reset_message.html_safe
+#js-tokens-app{ data: { tokens_data: tokens_app_data } }
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index e52a345bd86..48be2001c9c 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -2,7 +2,7 @@
- @content_class = "limit-container-width" unless fluid_layout
- user_theme_id = Gitlab::Themes.for_user(@user).id
- user_fields = { theme: user_theme_id, gitpod_enabled: @user.gitpod_enabled, sourcegraph_enabled: @user.sourcegraph_enabled }.to_json
-- @themes = Gitlab::Themes::THEMES.to_json
+- @themes = Gitlab::Themes::available_themes.to_json
- data_attributes = { themes: @themes, integration_views: integration_views.to_json, user_fields: user_fields, body_classes: Gitlab::Themes.body_classes, profile_preferences_path: profile_preferences_path }
- Gitlab::Themes.each do |theme|
@@ -33,7 +33,7 @@
%p
= s_('Preferences|This setting allows you to customize the appearance of the syntax.')
= succeed '.' do
- = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'syntax-highlighting-theme'), target: '_blank', rel: 'noopener noreferrer'
.col-lg-8.syntax-theme
- Gitlab::ColorSchemes.each do |scheme|
= label_tag do
@@ -51,7 +51,7 @@
%p
= s_('Preferences|This setting allows you to customize the behavior of the system layout and default views.')
= succeed '.' do
- = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'behavior'), target: '_blank', rel: 'noopener noreferrer'
.col-lg-8
.form-group
= f.label :layout, class: 'label-bold' do
@@ -94,8 +94,7 @@
= s_('Preferences|Surround text selection when typing quotes or brackets')
.form-text.text-muted
- supported_characters = %w(" ' ` \( [ { < * _).map {|char| "<code>#{char}</code>" }.join(', ')
- - msg = "Preferences|When you type in a description or comment box, selected text is surrounded by the corresponding character after typing one of the following characters: #{supported_characters}."
- = s_(msg).html_safe
+ = sprintf(s_( "Preferences|When you type in a description or comment box, selected text is surrounded by the corresponding character after typing one of the following characters: %{supported_characters}."), { supported_characters: supported_characters }).html_safe
.form-group
= f.label :tab_width, s_('Preferences|Tab width'), class: 'label-bold'
@@ -116,7 +115,7 @@
%p
= _('Customize language and region related settings.')
= succeed '.' do
- = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'localization'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'localization'), target: '_blank', rel: 'noopener noreferrer'
.col-lg-8
.form-group
= f.label :preferred_language, class: 'label-bold' do
@@ -137,7 +136,7 @@
%p
= s_('Preferences|Configure how dates and times display for you.')
= succeed '.' do
- = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'time-preferences'), target: '_blank'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'time-preferences'), target: '_blank', rel: 'noopener noreferrer'
.col-lg-8
.form-group.form-check
= f.check_box :time_display_relative, class: 'form-check-input'
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index f3993ad8c33..531e72b7cc2 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -97,19 +97,19 @@
= render 'profiles/name', form: f, user: @user
= f.text_field :id, class: 'gl-form-input', readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' }
- = f.text_field :pronouns, class: 'input-md gl-form-input', help: s_("Profiles|Enter your pronouns to let people know how to refer to you")
- = f.text_field :pronunciation, class: 'input-md gl-form-input', help: s_("Profiles|Enter how your name is pronounced to help people address you correctly")
+ = f.text_field :pronouns, label: s_('Profiles|Pronouns'), class: 'input-md gl-form-input', help: s_("Profiles|Enter your pronouns to let people know how to refer to you")
+ = f.text_field :pronunciation, label: s_('Profiles|Pronunciation'), class: 'input-md gl-form-input', help: s_("Profiles|Enter how your name is pronounced to help people address you correctly")
= render_if_exists 'profiles/extra_settings', form: f
= render_if_exists 'profiles/email_settings', form: f
= f.text_field :skype, class: 'input-md gl-form-input', placeholder: s_("Profiles|username")
= f.text_field :linkedin, class: 'input-md gl-form-input', help: s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename")
= f.text_field :twitter, class: 'input-md gl-form-input', placeholder: s_("Profiles|@username")
- = f.text_field :website_url, class: 'input-lg gl-form-input', placeholder: s_("Profiles|https://website.com")
+ = f.text_field :website_url, label: s_('Profiles|Website url'), class: 'input-lg gl-form-input', placeholder: s_("Profiles|https://website.com")
- if @user.read_only_attribute?(:location)
= f.text_field :location, class: 'gl-form-input', readonly: true, help: s_("Profiles|Your location was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:location) }
- else
= f.text_field :location, label: s_('Profiles|Location'), class: 'input-lg gl-form-input', placeholder: s_("Profiles|City, country")
- = f.text_field :job_title, class: 'input-md gl-form-input'
+ = f.text_field :job_title, label: s_('Profiles|Job title'), class: 'input-md gl-form-input'
= f.text_field :organization, label: s_('Profiles|Organization'), class: 'input-md gl-form-input', help: s_("Profiles|Who you represent or work for")
= f.text_area :bio, class: 'gl-form-input', label: s_('Profiles|Bio'), rows: 4, maxlength: 250, help: s_("Profiles|Tell us about yourself in fewer than 250 characters")
%hr
@@ -142,10 +142,10 @@
%img.modal-profile-crop-image{ alt: s_("Profiles|Avatar cropper") }
.crop-controls
.btn-group
- %button.btn.gl-button.btn-confirm{ data: { method: 'zoom', option: '-0.1' } }
+ %button.btn.gl-button.btn-default{ data: { method: 'zoom', option: '-0.1' } }
%span
= sprite_icon('search-minus')
- %button.btn.gl-button.btn-confirm{ data: { method: 'zoom', option: '0.1' } }
+ %button.btn.gl-button.btn-default{ data: { method: 'zoom', option: '0.1' } }
%span
= sprite_icon('search-plus')
.modal-footer
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 1f2c16324fb..7e8daea5651 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -10,18 +10,19 @@
= project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64, itemprop: 'image')
.d-flex.flex-column.flex-wrap.align-items-baseline
.d-inline-flex.align-items-baseline
- %h1.home-panel-title.gl-mt-3.gl-mb-2.gl-font-size-h1.gl-line-height-24.gl-font-weight-bold.gl-ml-3{ data: { qa_selector: 'project_name_content' }, itemprop: 'name' }
+ %h1.home-panel-title.gl-mt-3.gl-mb-2.gl-font-size-h1.gl-line-height-24.gl-font-weight-bold{ data: { qa_selector: 'project_name_content' }, itemprop: 'name' }
= @project.name
%span.visibility-icon.text-secondary.gl-ml-2.has-tooltip{ data: { container: 'body' }, title: visibility_icon_description(@project) }
= visibility_level_icon(@project.visibility_level, options: { class: 'icon' })
= render_if_exists 'compliance_management/compliance_framework/compliance_framework_badge', project: @project
- .home-panel-metadata.text-secondary.gl-font-base.gl-font-weight-normal.gl-line-height-normal
+ .home-panel-metadata.text-secondary.gl-font-base.gl-font-weight-normal.gl-line-height-normal{ data: { qa_selector: 'project_id_content' }, itemprop: 'identifier' }
- if can?(current_user, :read_project, @project)
- - button_class = "btn gl-button btn-sm btn-tertiary btn-default-tertiary home-panel-metadata"
- - button_text = s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id }
- = clipboard_button(title: s_('ProjectPage|Copy project ID'), text: @project.id, hide_button_icon: true, button_text: button_text, class: button_class, qa_selector: 'project_id_content', itemprop: 'identifier')
+ %span.gl-display-inline-block.gl-vertical-align-middle
+ = s_('ProjectPage|Project ID: %{project_id}') % { project_id: @project.id }
+ - button_class = "btn gl-button btn-sm btn-tertiary btn-default-tertiary home-panel-metadata"
+ = clipboard_button(title: s_('ProjectPage|Copy project ID'), text: @project.id, class: button_class)
- if current_user
- %span.gl-display-inline-block.gl-vertical-align-middle.gl-ml-3
+ %span.gl-ml-3.gl-mb-3
= render 'shared/members/access_request_links', source: @project
.gl-mt-3.gl-pl-3.gl-w-full
diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml
index 81d9726fcdc..63f09a065df 100644
--- a/app/views/projects/_import_project_pane.html.haml
+++ b/app/views/projects/_import_project_pane.html.haml
@@ -8,22 +8,22 @@
.import-buttons
- if gitlab_project_import_enabled?
.import_gitlab_project.has-tooltip{ data: { container: 'body', qa_selector: 'gitlab_import_button' } }
- = link_to new_import_gitlab_project_path, class: 'gl-button btn-default btn btn_import_gitlab_project', **tracking_attrs(track_label, 'click_button', 'gitlab_export') do
+ = link_to new_import_gitlab_project_path, class: 'gl-button btn-default btn btn_import_gitlab_project js-import-project-btn', data: { platform: 'gitlab_export', **tracking_attrs_data(track_label, 'click_button', 'gitlab_export') } do
.gl-button-icon
= sprite_icon('tanuki')
= _("GitLab export")
- if github_import_enabled?
%div
- = link_to new_import_github_path, class: 'gl-button btn-default btn js-import-github', **tracking_attrs(track_label, 'click_button', 'github') do
+ = link_to new_import_github_path, class: 'gl-button btn-default btn js-import-github js-import-project-btn', data: { platform: 'github', **tracking_attrs_data(track_label, 'click_button', 'github') } do
.gl-button-icon
= sprite_icon('github')
GitHub
- if bitbucket_import_enabled?
%div
- = link_to status_import_bitbucket_path, class: "gl-button btn-default btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}",
- **tracking_attrs(track_label, 'click_button', 'bitbucket_cloud') do
+ = link_to status_import_bitbucket_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn #{'how_to_import_link' unless bitbucket_import_configured?}",
+ data: { platform: 'bitbucket_cloud', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_cloud') } do
.gl-button-icon
= sprite_icon('bitbucket')
Bitbucket Cloud
@@ -31,15 +31,14 @@
= render 'projects/bitbucket_import_modal'
- if bitbucket_server_import_enabled?
%div
- = link_to status_import_bitbucket_server_path, class: "gl-button btn-default btn import_bitbucket", **tracking_attrs(track_label, 'click_button', 'bitbucket_server') do
+ = link_to status_import_bitbucket_server_path, class: "gl-button btn-default btn import_bitbucket js-import-project-btn", data: { platform: 'bitbucket_server', **tracking_attrs_data(track_label, 'click_button', 'bitbucket_server') } do
.gl-button-icon
= sprite_icon('bitbucket')
Bitbucket Server
%div
- if gitlab_import_enabled?
%div
- = link_to status_import_gitlab_path, class: "gl-button btn-default btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}",
- **tracking_attrs(track_label, 'click_button', 'gitlab_com') do
+ = link_to status_import_gitlab_path, class: "gl-button btn-default btn import_gitlab js-import-project-btn #{'how_to_import_link' unless gitlab_import_configured?}", data: { platform: 'gitlab_com', **tracking_attrs_data(track_label, 'click_button', 'gitlab_com') } do
.gl-button-icon
= sprite_icon('tanuki')
= _("GitLab.com")
@@ -48,35 +47,35 @@
- if fogbugz_import_enabled?
%div
- = link_to new_import_fogbugz_path, class: 'gl-button btn-default btn import_fogbugz', **tracking_attrs(track_label, 'click_button', 'fogbugz') do
+ = link_to new_import_fogbugz_path, class: 'gl-button btn-default btn import_fogbugz js-import-project-btn', data: { platform: 'fogbugz', **tracking_attrs_data(track_label, 'click_button', 'fogbugz') } do
.gl-button-icon
= sprite_icon('bug')
FogBugz
- if gitea_import_enabled?
%div
- = link_to new_import_gitea_path, class: 'gl-button btn-default btn import_gitea', **tracking_attrs(track_label, 'click_button', 'gitea') do
+ = link_to new_import_gitea_path, class: 'gl-button btn-default btn import_gitea js-import-project-btn', data: { platform: 'gitea', **tracking_attrs_data(track_label, 'click_button', 'gitea') } do
.gl-button-icon
= custom_icon('gitea_logo')
Gitea
- if git_import_enabled?
%div
- %button.gl-button.btn-default.btn.btn-svg.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' }, **tracking_attrs(track_label, 'click_button', 'repo_url') }
+ %button.gl-button.btn-default.btn.btn-svg.js-toggle-button.js-import-git-toggle-button.js-import-project-btn{ type: "button", data: { platform: 'repo_url', toggle_open_class: 'active', **tracking_attrs_data(track_label, 'click_button', 'repo_url') } }
.gl-button-icon
= sprite_icon('link', css_class: 'gl-icon')
= _('Repo by URL')
- if manifest_import_enabled?
%div
- = link_to new_import_manifest_path, class: 'gl-button btn-default btn import_manifest', **tracking_attrs(track_label, 'click_button', 'manifest_file') do
+ = link_to new_import_manifest_path, class: 'gl-button btn-default btn import_manifest js-import-project-btn', data: { platform: 'manifest_file', **tracking_attrs_data(track_label, 'click_button', 'manifest_file') } do
.gl-button-icon
= sprite_icon('doc-text')
Manifest file
- if phabricator_import_enabled?
%div
- = link_to new_import_phabricator_path, class: 'gl-button btn-default btn import_phabricator', data: { track_label: "#{track_label}", track_action: "click_button", track_property: "phabricator" } do
+ = link_to new_import_phabricator_path, class: 'gl-button btn-default btn import_phabricator js-import-project-btn', data: { platform: 'phabricator', track_label: "#{track_label}", track_action: "click_button", track_property: "phabricator" } do
.gl-button-icon
= custom_icon('issues')
= _("Phabricator Tasks")
diff --git a/app/views/projects/_merge_request_merge_checks_settings.html.haml b/app/views/projects/_merge_request_merge_checks_settings.html.haml
index fbc58283cbf..6b25c5ddaef 100644
--- a/app/views/projects/_merge_request_merge_checks_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_checks_settings.html.haml
@@ -8,7 +8,7 @@
= form.label :only_allow_merge_if_pipeline_succeeds, class: 'form-check-label' do
= s_('ProjectSettings|Pipelines must succeed')
.text-secondary
- - configuring_pipelines_for_merge_requests_help_link_url = help_page_path('ci/pipelines/merge_request_pipelines.md', anchor: 'configure-pipelines-for-merge-requests')
+ - configuring_pipelines_for_merge_requests_help_link_url = help_page_path('ci/pipelines/merge_request_pipelines.md', anchor: 'prerequisites')
- configuring_pipelines_for_merge_requests_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: configuring_pipelines_for_merge_requests_help_link_url }
= s_('ProjectSettings|To enable this feature, configure pipelines. %{link_start}How to configure pipelines for merge requests?%{link_end}').html_safe % { link_start: configuring_pipelines_for_merge_requests_help_link_start, link_end: '</a>'.html_safe }
.form-check.mb-2
diff --git a/app/views/projects/_merge_request_merge_commit_template.html.haml b/app/views/projects/_merge_request_merge_commit_template.html.haml
index 869d2d5d9ec..1c023ae6ceb 100644
--- a/app/views/projects/_merge_request_merge_commit_template.html.haml
+++ b/app/views/projects/_merge_request_merge_commit_template.html.haml
@@ -3,15 +3,12 @@
.form-group
%b= s_('ProjectSettings|Merge commit message template')
%p.text-secondary
- - configure_the_merge_commit_message_help_link_url = help_page_path('user/project/merge_requests/commit_templates.md')
- - configure_the_merge_commit_message_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: configure_the_merge_commit_message_help_link_url }
- = s_('ProjectSettings|The commit message used when merging, if the merge method creates a merge commit. %{link_start}Learn more about syntax and variables.%{link_end}').html_safe % { link_start: configure_the_merge_commit_message_help_link_start, link_end: '</a>'.html_safe }
+ = s_('ProjectSettings|The commit message used when merging, if the merge method creates a merge commit.')
.mb-2
- default_merge_commit_template = "Merge branch '%{source_branch}' into '%{target_branch}'\n\n%{title}\n\n%{issues}\n\nSee merge request %{reference}"
= form.text_area :merge_commit_template, class: 'form-control gl-form-input', rows: 8, maxlength: 500, placeholder: default_merge_commit_template
%p.form-text.text-muted
= s_('ProjectSettings|Maximum 500 characters.')
- = s_('ProjectSettings|Supported variables:')
- - Gitlab::MergeRequests::CommitMessageGenerator::PLACEHOLDERS.keys.each do |placeholder|
- %code
- = "%{#{placeholder}}".html_safe
+ - configure_the_merge_commit_message_help_link_url = help_page_path('user/project/merge_requests/commit_templates.md')
+ - configure_the_merge_commit_message_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: configure_the_merge_commit_message_help_link_url }
+ = s_('ProjectSettings|%{link_start}What variables can I use?%{link_end}').html_safe % { link_start: configure_the_merge_commit_message_help_link_start, link_end: '</a>'.html_safe }
diff --git a/app/views/projects/_merge_request_merge_method_settings.html.haml b/app/views/projects/_merge_request_merge_method_settings.html.haml
index 2d18285ba80..b0e3bda2b4f 100644
--- a/app/views/projects/_merge_request_merge_method_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_method_settings.html.haml
@@ -31,3 +31,6 @@
= s_('ProjectSettings|Fast-forward merges only.')
%br
= s_('ProjectSettings|When there is a merge conflict, the user is given the option to rebase.')
+ %div
+ = s_('ProjectSettings|If merge trains are enabled, merging is only possible if the branch can be rebased without conflicts.')
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/merge_trains.md', anchor: 'enable-merge-trains'), target: '_blank', rel: 'noopener noreferrer'
diff --git a/app/views/projects/_merge_request_merge_suggestions_settings.html.haml b/app/views/projects/_merge_request_merge_suggestions_settings.html.haml
index 6e3c366da82..9ed21593203 100644
--- a/app/views/projects/_merge_request_merge_suggestions_settings.html.haml
+++ b/app/views/projects/_merge_request_merge_suggestions_settings.html.haml
@@ -3,13 +3,10 @@
.form-group
%b= s_('ProjectSettings|Merge suggestions')
%p.text-secondary
- - configure_the_commit_message_for_applied_suggestions_help_link_url = help_page_path('user/project/merge_requests/reviews/suggestions.md', anchor: 'configure-the-commit-message-for-applied-suggestions')
- - configure_the_commit_message_for_applied_suggestions_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: configure_the_commit_message_for_applied_suggestions_help_link_url }
- = s_('ProjectSettings|The commit message used when applying merge request suggestions. %{link_start}Learn more about suggestions.%{link_end}').html_safe % { link_start: configure_the_commit_message_for_applied_suggestions_help_link_start, link_end: '</a>'.html_safe }
+ = s_('ProjectSettings|The commit message used when applying merge request suggestions.')
.mb-2
= form.text_field :suggestion_commit_message, class: 'form-control mb-2', placeholder: Gitlab::Suggestions::CommitMessage::DEFAULT_SUGGESTION_COMMIT_MESSAGE
%p.form-text.text-muted
- = s_('ProjectSettings|Supported variables:')
- - Gitlab::Suggestions::CommitMessage::PLACEHOLDERS.keys.each do |placeholder|
- %code
- = "%{#{placeholder}}".html_safe
+ - configure_the_commit_message_for_applied_suggestions_help_link_url = help_page_path('user/project/merge_requests/reviews/suggestions.md', anchor: 'configure-the-commit-message-for-applied-suggestions')
+ - configure_the_commit_message_for_applied_suggestions_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: configure_the_commit_message_for_applied_suggestions_help_link_url }
+ = s_('ProjectSettings|%{link_start}What variables can I use?%{link_end}').html_safe % { link_start: configure_the_commit_message_for_applied_suggestions_help_link_start, link_end: '</a>'.html_safe }
diff --git a/app/views/projects/_merge_request_squash_commit_template.html.haml b/app/views/projects/_merge_request_squash_commit_template.html.haml
index 81e4bbed166..be1d78154c6 100644
--- a/app/views/projects/_merge_request_squash_commit_template.html.haml
+++ b/app/views/projects/_merge_request_squash_commit_template.html.haml
@@ -3,14 +3,11 @@
.form-group
%b= s_('ProjectSettings|Squash commit message template')
%p.text-secondary
- - configure_the_squash_commit_message_help_link_url = help_page_path('user/project/merge_requests/commit_templates.md')
- - configure_the_squash_commit_message_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: configure_the_squash_commit_message_help_link_url }
- = s_('ProjectSettings|The commit message used when squashing commits. %{link_start}Learn more about syntax and variables.%{link_end}').html_safe % { link_start: configure_the_squash_commit_message_help_link_start, link_end: '</a>'.html_safe }
+ = s_('ProjectSettings|The commit message used when squashing commits.')
.mb-2
= form.text_area :squash_commit_template, class: 'form-control gl-form-input', rows: 8, maxlength: 500, placeholder: '%{title}'
%p.form-text.text-muted
= s_('ProjectSettings|Maximum 500 characters.')
- = s_('ProjectSettings|Supported variables:')
- - Gitlab::MergeRequests::CommitMessageGenerator::PLACEHOLDERS.keys.each do |placeholder|
- %code
- = "%{#{placeholder}}".html_safe
+ - configure_the_squash_commit_message_help_link_url = help_page_path('user/project/merge_requests/commit_templates.md')
+ - configure_the_squash_commit_message_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: configure_the_squash_commit_message_help_link_url }
+ = s_('ProjectSettings|%{link_start}What variables can I use?%{link_end}').html_safe % { link_start: configure_the_squash_commit_message_help_link_start, link_end: '</a>'.html_safe }
diff --git a/app/views/projects/_merge_request_squash_options_settings.html.haml b/app/views/projects/_merge_request_squash_options_settings.html.haml
index 61ffd4814f1..bf3000f2b5e 100644
--- a/app/views/projects/_merge_request_squash_options_settings.html.haml
+++ b/app/views/projects/_merge_request_squash_options_settings.html.haml
@@ -5,9 +5,7 @@
%b= s_('ProjectSettings|Squash commits when merging')
%p.text-secondary
= s_('ProjectSettings|Set the default behavior of this option in merge requests. Changes to this are also applied to existing merge requests.')
- = link_to "What is squashing?",
- help_page_path('user/project/merge_requests/squash_and_merge.md'),
- target: '_blank'
+ = link_to "What is squashing?", help_page_path('user/project/merge_requests/squash_and_merge.md'), target: '_blank', rel: 'noopener noreferrer'
.form-check.gl-mb-2
= settings.radio_button :squash_option, :never, class: "form-check-input"
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index 6fc78003df4..0b5da84e4e3 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -15,23 +15,12 @@
.input-group.gl-flex-nowrap
- if current_user.can_select_namespace?
- namespace_id = namespace_id_from(params)
- - if Feature.enabled?(:paginatable_namespace_drop_down_for_project_creation, current_user, default_enabled: :yaml)
- .js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path,
- namespace_id: namespace_id,
- root_url: root_url,
- track_label: track_label,
- user_namespace_full_path: current_user.namespace.full_path,
- user_namespace_id: current_user.namespace.id } }
- - else
- .input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
- .input-group-text
- = root_url
- = f.select(:namespace_id,
- namespaces_options_with_developer_maintainer_access(selected: namespace_id,
- display_path: true,
- extra_group: namespace_id),
- {},
- { class: 'select2 js-select-namespace qa-project-namespace-select block-truncated', data: { track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_path", track_value: "", qa_selector: "select_namespace_dropdown" }})
+ .js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path,
+ namespace_id: namespace_id,
+ root_url: root_url,
+ track_label: track_label,
+ user_namespace_full_path: current_user.namespace.full_path,
+ user_namespace_id: current_user.namespace.id } }
- else
.input-group-prepend.static-namespace.flex-shrink-0.has-tooltip{ title: user_url(current_user.username) + '/' }
.input-group-text.border-0
@@ -98,7 +87,7 @@
= check_box_tag 'project[initialize_with_sast]', '1', true, class: 'form-check-input', data: { qa_selector: 'initialize_with_sast_checkbox', track_experiment: e.name, track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_sast' }
= label_tag 'project[initialize_with_sast]', class: 'form-check-label' do
= s_('ProjectsNew|Enable Static Application Security Testing (SAST)')
- %span.badge.badge-info.badge-pill.gl-badge.sm= _('Free')
+ = gl_badge_tag _('Free'), variant: :info, size: :sm
.form-text.text-muted
= s_('ProjectsNew|Analyze your source code for known security vulnerabilities.')
= link_to _('Learn more.'), help_page_path('user/application_security/sast/index'), target: '_blank', rel: 'noopener noreferrer', data: { track_action: 'followed', track_experiment: e.name }
@@ -108,7 +97,7 @@
= check_box_tag 'project[initialize_with_sast]', '1', false, class: 'form-check-input', data: { qa_selector: 'initialize_with_sast_checkbox', track_experiment: e.name, track_label: track_label, track_action: 'activate_form_input', track_property: 'init_with_sast' }
= label_tag 'project[initialize_with_sast]', class: 'form-check-label' do
= s_('ProjectsNew|Enable Static Application Security Testing (SAST)')
- %span.badge.badge-info.badge-pill.gl-badge.sm= _('Free')
+ = gl_badge_tag _('Free'), variant: :info, size: :sm
.form-text.text-muted
= s_('ProjectsNew|Analyze your source code for known security vulnerabilities.')
= link_to _('Learn more.'), help_page_path('user/application_security/sast/index'), target: '_blank', rel: 'noopener noreferrer', data: { track_action: 'followed', track_experiment: e.name }
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index be6efa310b9..f5e61c010cc 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -8,14 +8,11 @@
= link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name gl-ml-3 qa-branch-name' do
= branch.name
- if branch.name == @repository.root_ref
- %span.badge.gl-badge.sm.badge-pill.badge-primary.gl-ml-2 default
+ = gl_badge_tag s_('DefaultBranchLabel|default'), { variant: :info, size: :sm }, { class: 'gl-ml-2' }
- elsif merged
- %span.badge.gl-badge.sm.badge-pill.badge-info.has-tooltip.gl-ml-2{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } }
- = s_('Branches|merged')
-
+ = gl_badge_tag s_('Branches|merged'), { variant: :info, size: :sm }, { class: 'gl-ml-2', title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref }, data: { toggle: 'tooltip', container: 'body' } }
- if protected_branch?(@project, branch)
- %span.badge.gl-badge.sm.badge-pill.badge-success.gl-ml-2
- = s_('Branches|protected')
+ = gl_badge_tag s_('Branches|protected'), { variant: :success, size: :sm }, { class: 'gl-ml-2' }
= render_if_exists 'projects/branches/diverged_from_upstream', branch: branch
@@ -48,38 +45,4 @@
= render 'projects/buttons/download', project: @project, ref: branch.name, pipeline: @refs_pipelines[branch.name], class: 'gl-vertical-align-top'
- - if Feature.enabled?(:delete_branch_confirmation_modals, @project, default_enabled: :yaml)
- = render 'projects/branches/delete_branch_modal_button', project: @project, branch: branch, merged: merged
-
- - elsif can?(current_user, :push_code, @project)
- - if branch.name == @project.repository.root_ref
- - delete_default_branch_tooltip = s_('Branches|The default branch cannot be deleted')
- %span.gl-display-inline-block.has-tooltip{ title: delete_default_branch_tooltip }
- %button{ class: 'gl-button btn btn-default btn-icon disabled', disabled: true, 'aria-label' => delete_default_branch_tooltip }
- = sprite_icon('remove', css_class: 'gl-button-icon gl-icon')
- - elsif protected_branch?(@project, branch)
- - if can?(current_user, :push_to_delete_protected_branch, @project)
- - delete_protected_branch_tooltip = s_('Branches|Delete protected branch')
- %button{ class: 'gl-button btn btn-default btn-icon has-tooltip',
- title: delete_protected_branch_tooltip,
- 'aria-label' => delete_protected_branch_tooltip,
- data: { toggle: 'modal',
- target: '#modal-delete-branch',
- delete_path: project_branch_path(@project, branch.name),
- branch_name: branch.name,
- is_merged: ('true' if merged) } }
- = sprite_icon('remove', css_class: 'gl-button-icon gl-icon')
- - else
- - delete_protected_branch_disabled_tooltip = s_('Branches|Only a project maintainer or owner can delete a protected branch')
- %span.has-tooltip{ title: delete_protected_branch_disabled_tooltip }
- %button{ class: 'gl-button btn btn-default btn-icon disabled', disabled: true, 'aria-label' => delete_protected_branch_disabled_tooltip, data: { testid: 'remove-protected-branch' } }
- = sprite_icon('remove', css_class: 'gl-button-icon gl-icon')
- - else
- = link_to project_branch_path(@project, branch.name),
- class: 'gl-button btn btn-default btn-icon js-remove-row qa-remove-btn js-ajax-loading-spinner has-tooltip',
- title: s_('Branches|Delete branch'),
- method: :delete,
- data: { confirm: s_("Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?") % { branch_name: branch.name } },
- remote: true,
- 'aria-label' => s_('Branches|Delete branch') do
- = sprite_icon('remove', css_class: 'gl-button-icon gl-icon')
+ = render 'projects/branches/delete_branch_modal_button', project: @project, branch: branch, merged: merged
diff --git a/app/views/projects/branches/_delete_protected_modal.html.haml b/app/views/projects/branches/_delete_protected_modal.html.haml
deleted file mode 100644
index 2b45b4eddcc..00000000000
--- a/app/views/projects/branches/_delete_protected_modal.html.haml
+++ /dev/null
@@ -1,42 +0,0 @@
-#modal-delete-branch.modal{ tabindex: -1 }
- .modal-dialog
- .modal-content
- .modal-header
- %h3.page-title
- - title_branch_name = capture do
- %span.js-branch-name.ref-name>[branch name]
- = s_("Branches|Delete protected branch '%{branch_name}'?").html_safe % { branch_name: title_branch_name }
- %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
- %span{ "aria-hidden": "true" } &times;
-
- .modal-body
- %p
- - branch_name = capture do
- %strong.js-branch-name.ref-name>[branch name]
- = s_('Branches|You’re about to permanently delete the protected branch %{branch_name}.').html_safe % { branch_name: branch_name }
- %p.js-not-merged
- - default_branch = capture do
- %span.ref-name= @repository.root_ref
- = s_('Branches|This branch hasn’t been merged into %{default_branch}.').html_safe % { default_branch: default_branch }
- = s_('Branches|To avoid data loss, consider merging this branch before deleting it.')
- %p
- - delete_protected_branch = capture do
- %strong
- = s_('Branches|Delete protected branch')
- = s_('Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered.').html_safe % { delete_protected_branch: delete_protected_branch }
- %p
- - branch_name_confirmation = capture do
- %kbd.js-branch-name [branch name]
- %strong
- = s_('Branches|To confirm, type %{branch_name_confirmation}:').html_safe % { branch_name_confirmation: branch_name_confirmation }
-
- .form-group
- = text_field_tag 'delete_branch_input', '', class: 'form-control js-delete-branch-input'
-
- .modal-footer
- %button.gl-button.btn.btn-default{ data: { dismiss: 'modal' } } Cancel
- = link_to s_('Branches|Delete protected branch'), '',
- class: "gl-button btn btn-danger js-delete-branch",
- title: s_('Branches|Delete branch'),
- method: :delete,
- 'aria-label' => s_('Branches|Delete branch')
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 1c543d47ecf..2121d15643c 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -50,7 +50,5 @@
.nothing-here-block
= s_('Branches|No branches to show')
-- if Feature.enabled?(:delete_branch_confirmation_modals, @project, default_enabled: :yaml) && can?(current_user, :push_code, @project)
+- if can?(current_user, :push_code, @project)
.js-delete-branch-modal
-- elsif can?(current_user, :push_code, @project)
- = render 'projects/branches/delete_protected_modal'
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
index 3cec7fd9eb8..c57b6dbe28c 100644
--- a/app/views/projects/buttons/_fork.html.haml
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -1,7 +1,7 @@
- unless @project.empty_repo?
- if current_user
.count-badge.btn-group
- - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+ - if current_user.already_forked?(@project) && current_user.forkable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: s_('ProjectOverview|Go to your fork'), class: 'gl-button btn btn-default btn-sm has-tooltip fork-btn' do
= sprite_icon('fork', css_class: 'icon')
%span= s_('ProjectOverview|Fork')
diff --git a/app/views/projects/ci/pipeline_editor/show.html.haml b/app/views/projects/ci/pipeline_editor/show.html.haml
index ce6f7553ab4..c4757ea9c26 100644
--- a/app/views/projects/ci/pipeline_editor/show.html.haml
+++ b/app/views/projects/ci/pipeline_editor/show.html.haml
@@ -1,3 +1,4 @@
+- @force_fluid_layout = true
- add_page_specific_style 'page_bundles/pipelines'
- page_title s_('Pipelines|Pipeline Editor')
diff --git a/app/views/projects/commit/_limit_exceeded_message.html.haml b/app/views/projects/commit/_limit_exceeded_message.html.haml
index 236418ecd0e..444e7d209f1 100644
--- a/app/views/projects/commit/_limit_exceeded_message.html.haml
+++ b/app/views/projects/commit/_limit_exceeded_message.html.haml
@@ -1,8 +1,5 @@
-.has-tooltip{ class: "limit-box limit-box-#{objects} gl-ml-2", data: { title: _('Project has too many %{label_for_message} to search') % { label_for_message: label_for_message } } }
- .limit-icon
- - if objects == :branch
- = sprite_icon('fork', size: 12)
- - else
- = sprite_icon('tag')
- .limit-message
- %span= _('%{label_for_message} unavailable') % { label_for_message: label_for_message.capitalize }
+- icon = objects == :branch ? 'fork' : 'tag'
+- text = _('%{label_for_message} unavailable') % { label_for_message: label_for_message.capitalize }
+- tooltip_title = _('Project has too many %{label_for_message} to search') % { label_for_message: label_for_message }
+
+= gl_badge_tag(text, { variant: :danger, icon: icon }, { class: 'has-tooltip gl-ml-2', data: { title: tooltip_title } })
diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml
index 0b8e5105bc0..d08ace98408 100644
--- a/app/views/projects/commit/branches.html.haml
+++ b/app/views/projects/commit/branches.html.haml
@@ -6,8 +6,9 @@
- if @branches.any? || @tags.any? || @tags_limit_exceeded
%span
- = link_to "#", class: "js-details-expand badge badge-gray ref-name" do
- = sprite_icon('ellipsis_h', size: 12, css_class: 'vertical-align-middle')
+ = gl_badge_tag(_("Expand"),
+ { variant: :info, icon: 'ellipsis_h', icon_only: true },
+ { href: '#', class: 'js-details-expand gl-font-monospace' })
%span.js-details-content.hide
= commit_branches_links(@project, @branches)
- if @tags_limit_exceeded
diff --git a/app/views/projects/default_branch/_show.html.haml b/app/views/projects/default_branch/_show.html.haml
index e5f911d6f8b..f9d3af7aa36 100644
--- a/app/views/projects/default_branch/_show.html.haml
+++ b/app/views/projects/default_branch/_show.html.haml
@@ -26,6 +26,6 @@
%strong= _("Auto-close referenced issues on default branch")
.form-text.text-muted
= _("When merge requests and commits in the default branch close, any issues they reference also close.")
- = link_to sprite_icon('question-o'), help_page_path('user/project/issues/managing_issues.md', anchor: 'closing-issues-automatically'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('user/project/issues/managing_issues.md', anchor: 'closing-issues-automatically'), target: '_blank', rel: 'noopener noreferrer'
= f.submit _('Save changes'), class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/projects/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index f18574c3ad5..504bbf3a304 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -27,8 +27,7 @@
= link_to deployment_path(deployment), class: 'build-link' do
#{deployment.deployable.name} (##{deployment.deployable.id})
- else
- .badge.badge-info.gl-cursor-help{ title: s_('Deployment|This deployment was created using the API') }
- = s_('Deployment|API')
+ = gl_badge_tag s_('Deployment|API'), { variant: :info }, { class: 'gl-cursor-help', data: { toggle: 'tooltip' }, title: s_('Deployment|This deployment was created using the API') }
.table-section.section-10{ role: 'gridcell' }
.table-mobile-header{ role: 'rowheader' }= _("Created")
diff --git a/app/views/projects/diffs/_email_line.html.haml b/app/views/projects/diffs/_email_line.html.haml
new file mode 100644
index 00000000000..dfff143196c
--- /dev/null
+++ b/app/views/projects/diffs/_email_line.html.haml
@@ -0,0 +1,21 @@
+-# This template is used when rendering diffs in email notifications
+-# Called inside: app/views/notify/repository_push_email.html.haml
+-# app/views/notify/_note_email.html.haml
+
+%tr.line_holder{ class: line.type }
+ - case line.type
+ - when 'match'
+ = diff_match_line line.old_pos, line.new_pos, text: line.text
+ - when 'old-nonewline', 'new-nonewline'
+ %td.old_line.diff-line-num
+ %td.new_line.diff-line-num
+ %td.line_content.match= line.text
+ - else
+ %td.old_line.diff-line-num{ class: line.type, data: { linenumber: line.old_pos } }
+ = diff_link_number(line.type, "new", line.old_pos)
+
+ %td.new_line.diff-line-num{ class: line.type, data: { linenumber: line.new_pos } }
+ = diff_link_number(line.type, "old", line.new_pos)
+
+ %td.line_content{ class: line.type }<
+ %pre= line.rich_text
diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml
index d1792826522..afca27c5430 100644
--- a/app/views/projects/diffs/_file_header.html.haml
+++ b/app/views/projects/diffs/_file_header.html.haml
@@ -36,5 +36,5 @@
#{diff_file.a_mode} → #{diff_file.b_mode}
- if diff_file.stored_externally? && diff_file.external_storage == :lfs
- %span.badge.label-lfs.gl-mr-2 LFS
+ = gl_badge_tag(_('LFS'), variant: :neutral)
diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml
index de7f9eba158..330e2f564c9 100644
--- a/app/views/projects/diffs/_line.html.haml
+++ b/app/views/projects/diffs/_line.html.haml
@@ -1,3 +1,5 @@
+-# This file is deprecated in favour of inline rendering:
+-# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57237
- plain = local_assigns.fetch(:plain, false)
- discussions = local_assigns.fetch(:discussions, nil)
- line_code = diff_file.line_code(line)
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index bf946b0ce73..6e7e0244721 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -4,10 +4,38 @@
%a.show-suppressed-diff.cursor-pointer.js-show-suppressed-diff= _("Changes suppressed. Click to show.")
%table.text-file.diff-wrap-lines.code.code-commit.js-syntax-highlight.commit-diff{ data: diff_view_data, class: too_big ? 'hide' : '' }
- = render partial: "projects/diffs/line",
- collection: diff_file.highlighted_diff_lines,
- as: :line,
- locals: { diff_file: diff_file, discussions: @grouped_diff_discussions }
+ - if Feature.enabled?(:inline_haml_diff_line_rendering, @project, default_enabled: :yaml)
+ - diff_file.highlighted_diff_lines.each do |line|
+ - line_code = diff_file.line_code(line)
+
+ %tr.line_holder{ class: line.type, id: line_code }
+ - case line.type
+ - when 'match'
+ = diff_match_line line.old_pos, line.new_pos, text: line.text
+ - when 'old-nonewline', 'new-nonewline'
+ %td.old_line.diff-line-num
+ %td.new_line.diff-line-num
+ %td.line_content.match= line.text
+ - else
+ %td.old_line.diff-line-num{ class: "#{line.type} js-avatar-container", data: { linenumber: line.old_pos } }
+ = add_diff_note_button(line_code, diff_file.position(line), line.type)
+ %a{ href: "##{line_code}", data: { linenumber: diff_link_number(line.type, "new", line.old_pos) } }
+
+ %td.new_line.diff-line-num{ class: line.type, data: { linenumber: line.new_pos } }
+ %a{ href: "##{line_code}", data: { linenumber: diff_link_number(line.type, "old", line.new_pos) } }
+
+ %td.line_content{ class: line.type }<
+ = diff_line_content(line.rich_text)
+
+ - if line.discussable? && @grouped_diff_discussions.present? && @grouped_diff_discussions[line_code]
+ - line_discussions = @grouped_diff_discussions[line_code]
+ = render "discussions/diff_discussion", discussions: line_discussions, expanded: line_discussions.any?(&:expanded?)
+
+ - else
+ = render partial: "projects/diffs/line",
+ collection: diff_file.highlighted_diff_lines,
+ as: :line,
+ locals: { diff_file: diff_file, discussions: @grouped_diff_discussions }
- if !diff_file.new_file? && !diff_file.deleted_file? && diff_file.highlighted_diff_lines.any?
- last_line = diff_file.highlighted_diff_lines.last
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 6421aef14cf..aa9a3ea61f7 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -2,6 +2,7 @@
- page_title _("General")
- @content_class = "limit-container-width" unless fluid_layout
- expanded = expanded_by_default?
+- reduce_visibility_form_id = 'reduce-visibility-form'
%section.settings.general-settings.no-animate.expanded#js-general-settings
.settings-header
@@ -17,13 +18,10 @@
%p= _('Choose visibility level, enable/disable project features and their permissions, disable email notifications, and show default award emoji.')
.settings-content
- = form_for @project, html: { multipart: true, class: "sharing-permissions-form" }, authenticity_token: true do |f|
+ = form_for @project, html: { multipart: true, class: "sharing-permissions-form", id: reduce_visibility_form_id }, authenticity_token: true do |f|
%input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' }
%template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project).to_json.html_safe
- .js-project-permissions-form
- - if show_visibility_confirm_modal?(@project)
- = render "visibility_modal"
- = f.submit _('Save changes'), class: "btn gl-button btn-confirm #{('js-legacy-confirm-danger' if show_visibility_confirm_modal?(@project))}", data: { qa_selector: 'visibility_features_permissions_save_button', check_field_name: ("project[visibility_level]" if show_visibility_confirm_modal?(@project)), check_compare_value: @project.visibility_level }
+ .js-project-permissions-form{ data: visibility_confirm_modal_data(@project, reduce_visibility_form_id) }
%section.rspec-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)], data: { qa_selector: 'merge_request_settings_content' } }
.settings-header
@@ -111,5 +109,3 @@
.gl-spinner.gl-spinner-md.align-text-bottom
= _('Saving project.')
%p= _('Please wait a moment, this page will automatically refresh when ready.')
-
-= render 'shared/confirm_modal', phrase: @project.path
diff --git a/app/views/projects/forks/index.html.haml b/app/views/projects/forks/index.html.haml
index d6f421e8ad6..5330c3aa6d6 100644
--- a/app/views/projects/forks/index.html.haml
+++ b/app/views/projects/forks/index.html.haml
@@ -25,7 +25,7 @@
= forks_sort_direction_button(sort_value)
- if current_user && can?(current_user, :fork_project, @project)
- - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+ - if current_user.already_forked?(@project) && current_user.forkable_namespaces.size < 2
= link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: _('Go to your fork'), class: 'btn gl-button btn-confirm gl-md-ml-3' do
= sprite_icon('fork', size: 12)
%span= _('Fork')
diff --git a/app/views/projects/import/jira/show.html.haml b/app/views/projects/import/jira/show.html.haml
index 29296ce23c9..1feae7baa02 100644
--- a/app/views/projects/import/jira/show.html.haml
+++ b/app/views/projects/import/jira/show.html.haml
@@ -1,6 +1,6 @@
.js-jira-import-root{ data: { project_path: @project.full_path,
issues_path: project_issues_path(@project),
- jira_integration_path: edit_project_service_path(@project, :jira),
+ jira_integration_path: edit_project_integration_path(@project, :jira),
is_jira_configured: @project.jira_integration&.configured?.to_s,
in_progress_illustration: image_path('illustrations/export-import.svg'),
project_id: @project.id,
diff --git a/app/views/projects/issues/_by_email_description.html.haml b/app/views/projects/issues/_by_email_description.html.haml
index 0ff852352e1..aeed5fb69c9 100644
--- a/app/views/projects/issues/_by_email_description.html.haml
+++ b/app/views/projects/issues/_by_email_description.html.haml
@@ -1,6 +1,6 @@
The subject will be used as the title of the new issue, and the message will be the description.
-= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank'
+= link_to 'Quick actions', help_page_path('user/project/quick_actions'), target: '_blank', rel: 'noopener noreferrer'
and styling with
-= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank'
+= link_to 'Markdown', help_page_path('user/markdown'), target: '_blank', rel: 'noopener noreferrer'
are supported.
diff --git a/app/views/projects/issues/_design_management.html.haml b/app/views/projects/issues/_design_management.html.haml
index a2ff9620c0c..c5ce0549816 100644
--- a/app/views/projects/issues/_design_management.html.haml
+++ b/app/views/projects/issues/_design_management.html.haml
@@ -8,7 +8,11 @@
- if @project.design_management_enabled?
- add_page_startup_graphql_call('design_management/get_design_list', { fullPath: @project.full_path, iid: @issue.iid.to_s, atVersion: nil })
- add_page_startup_graphql_call('design_management/design_permissions', { fullPath: @project.full_path, iid: @issue.iid.to_s })
- .js-design-management{ data: { project_path: @project.full_path, issue_iid: @issue.iid, issue_path: project_issue_path(@project, @issue) } }
+ .js-design-management{ data: { project_path: @project.full_path,
+ issue_iid: @issue.iid,
+ issue_path: project_issue_path(@project, @issue),
+ register_path: new_user_registration_path(redirect_to_referer: 'yes'),
+ sign_in_path: new_session_path(:user, redirect_to_referer: 'yes') } }
- else
.gl-border-solid.gl-border-1.gl-border-gray-100.gl-rounded-base.gl-mt-5.gl-p-3.gl-text-center
= enable_lfs_message
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 2dc21685057..4c96875ce42 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,4 +1,3 @@
--# DANGER: Any changes to this file need to be reflected in issuables_list/components/issuable.vue!
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue), data: { labels: issue.label_ids, id: issue.id, qa_selector: 'issue_container', qa_issue_title: issue.title } }
.issuable-info-container
- if @can_bulk_update
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 1ab51df6d24..09b0b7a4d9b 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,25 +1,10 @@
-- is_project_overview = local_assigns.fetch(:is_project_overview, false)
= render 'shared/alerts/positioning_disabled' if @sort == 'relative_position'
-- if Feature.enabled?(:vue_issuables_list, @project) && !is_project_overview
- - data_endpoint = local_assigns.fetch(:data_endpoint, expose_path(api_v4_projects_issues_path(id: @project.id)))
- - default_empty_state_meta = { create_issue_path: new_project_issue_path(@project), svg_path: image_path('illustrations/issues.svg') }
- - data_empty_state_meta = local_assigns.fetch(:data_empty_state_meta, default_empty_state_meta)
- - type = local_assigns.fetch(:type, 'issues')
- - if type == 'issues' && use_startup_call?
- - add_page_startup_api_call(api_v4_projects_issues_path(id: @project.id, params: startup_call_params))
- .js-issuables-list{ data: { endpoint: data_endpoint,
- 'empty-state-meta': data_empty_state_meta.to_json,
- 'can-bulk-edit': @can_bulk_update.to_json,
- 'sort-key': @sort,
- type: type,
- 'scoped-labels-available': scoped_labels_available?(@project).to_json } }
-- else
- - empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues')
- %ul.content-list.issues-list.issuable-list{ class: issue_manual_ordering_class }
- = render partial: "projects/issues/issue", collection: @issues
- - if @issues.blank?
- = render empty_state_path
+%ul.content-list.issues-list.issuable-list{ class: issue_manual_ordering_class }
+ = render partial: "projects/issues/issue", collection: @issues
+ - if @issues.blank?
+ - empty_state_path = local_assigns.fetch(:empty_state_path, 'shared/empty_states/issues')
+ = render empty_state_path
- - if @issues.present?
- = paginate_collection @issues, total_pages: @total_pages
+- if @issues.present?
+ = paginate_collection @issues, total_pages: @total_pages
diff --git a/app/views/projects/issues/_service_desk_empty_state.html.haml b/app/views/projects/issues/_service_desk_empty_state.html.haml
index afeb2a23ea2..3e0b80700fe 100644
--- a/app/views/projects/issues/_service_desk_empty_state.html.haml
+++ b/app/views/projects/issues/_service_desk_empty_state.html.haml
@@ -27,7 +27,11 @@
.svg-content
= render 'shared/empty_states/icons/service_desk_setup.svg'
.text-content
- %h4= s_('ServiceDesk|Service Desk is enabled but not yet active')
- %p
- = s_("ServiceDesk|To activate Service Desk on this instance, an instance administrator must first set up incoming email.")
- = link_to _('Learn more.'), help_page_path('administration/incoming_email', anchor: 'set-it-up')
+ - if can_edit_project_settings
+ %h4= s_('ServiceDesk|Service Desk is not supported')
+ %p
+ = s_("ServiceDesk|To enable Service Desk on this instance, an instance administrator must first set up incoming email.")
+ = link_to _('Learn more.'), help_page_path('administration/incoming_email', anchor: 'set-it-up')
+ - else
+ %h4= s_('ServiceDesk|Service Desk is not enabled')
+ %p= s_("ServiceDesk|For help setting up the Service Desk for your instance, please contact an administrator.")
diff --git a/app/views/projects/issues/service_desk.html.haml b/app/views/projects/issues/service_desk.html.haml
index b0d8791c566..fb5880f633a 100644
--- a/app/views/projects/issues/service_desk.html.haml
+++ b/app/views/projects/issues/service_desk.html.haml
@@ -7,9 +7,7 @@
- support_bot_attrs = { service_desk_enabled: @project.service_desk_enabled?, **UserSerializer.new.represent(User.support_bot) }.to_json
-- data_endpoint = "#{expose_path(api_v4_projects_issues_path(id: @project.id))}?author_username=#{User.support_bot.username}"
-
-%div{ class: "js-service-desk-issues service-desk-issues", data: { support_bot: support_bot_attrs, service_desk_meta: service_desk_meta(@project) } }
+.js-service-desk-issues.service-desk-issues{ data: { support_bot: support_bot_attrs } }
.top-area
= render 'shared/issuable/nav', type: :issues
.nav-controls.d-block.d-sm-none
@@ -20,12 +18,5 @@
- if Gitlab::ServiceDesk.supported?
= render 'service_desk_info_content'
- -# TODO Remove empty_state_path once vue_issuables_list FF is removed.
- -# https://gitlab.com/gitlab-org/gitlab/-/issues/235652
- -# `empty_state_path` is used to render the empty state in the HAML version of issuables list.
.issues-holder
- = render 'projects/issues/issues',
- empty_state_path: 'service_desk_empty_state',
- data_endpoint: data_endpoint,
- data_empty_state_meta: service_desk_meta(@project),
- type: 'service_desk'
+ = render 'projects/issues/issues', empty_state_path: 'service_desk_empty_state'
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 7af825b2819..fedc1291a92 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -10,4 +10,4 @@
- if @build.is_a? ::Ci::Build
#js-job-page{ data: jobs_data }
- else
- #js-bridge-page{ data: bridge_data(@build) }
+ #js-bridge-page{ data: bridge_data(@build, @project) }
diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml
index adef11f315a..1efec0c017c 100644
--- a/app/views/projects/mattermosts/_no_teams.html.haml
+++ b/app/views/projects/mattermosts/_no_teams.html.haml
@@ -15,4 +15,4 @@
and try again.
%hr
.clearfix
- = link_to 'Go back', edit_project_service_path(@project, @integration), class: 'gl-button btn btn-lg float-right'
+ = link_to 'Go back', edit_project_integration_path(@project, @integration), class: 'gl-button btn btn-lg float-right'
diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml
index 4832880eefc..d52d980c364 100644
--- a/app/views/projects/mattermosts/_team_selection.html.haml
+++ b/app/views/projects/mattermosts/_team_selection.html.haml
@@ -42,5 +42,5 @@
%hr
.clearfix
.float-right
- = link_to _('Cancel'), edit_project_service_path(@project, @integration), class: 'gl-button btn btn-lg'
+ = link_to _('Cancel'), edit_project_integration_path(@project, @integration), class: 'gl-button btn btn-lg'
= f.submit 'Install', class: 'gl-button btn btn-success btn-lg'
diff --git a/app/views/projects/merge_requests/_widget.html.haml b/app/views/projects/merge_requests/_widget.html.haml
index 0c8af873095..4e69dad2e12 100644
--- a/app/views/projects/merge_requests/_widget.html.haml
+++ b/app/views/projects/merge_requests/_widget.html.haml
@@ -1,24 +1,25 @@
-= cache_if(Feature.enabled?(:cached_mr_widget, @merge_request.project), [@merge_request.project, @merge_request, current_user], expires_in: 10.minutes) do
- - artifacts_endpoint_placeholder = ':pipeline_artifacts_id'
+- artifacts_endpoint_placeholder = ':pipeline_artifacts_id'
- = javascript_tag do
- :plain
- window.gl = window.gl || {};
- window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget', issues_links: true)}
+= javascript_tag do
+ :plain
+ window.gl = window.gl || {};
+ window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget', issues_links: true)}
- window.gl.mrWidgetData.artifacts_endpoint = '#{downloadable_artifacts_project_pipeline_path(@project, artifacts_endpoint_placeholder, format: :json)}';
- window.gl.mrWidgetData.artifacts_endpoint_placeholder = '#{artifacts_endpoint_placeholder}';
- window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}';
- window.gl.mrWidgetData.ci_troubleshooting_docs_path = '#{help_page_path('ci/troubleshooting.md')}';
- window.gl.mrWidgetData.mr_troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests/reviews/index.md', anchor: 'troubleshooting')}';
- window.gl.mrWidgetData.pipeline_must_succeed_docs_path = '#{help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds.md', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds')}';
- window.gl.mrWidgetData.security_approvals_help_page_path = '#{help_page_path('user/application_security/index.md', anchor: 'security-approvals-in-merge-requests')}';
- window.gl.mrWidgetData.license_compliance_docs_path = '#{help_page_path('user/compliance/license_compliance/index.md', anchor: 'policies')}';
- window.gl.mrWidgetData.eligible_approvers_docs_path = '#{help_page_path('user/project/merge_requests/approvals/rules.md', anchor: 'eligible-approvers')}';
- window.gl.mrWidgetData.approvals_help_path = '#{help_page_path("user/project/merge_requests/approvals/index.md")}';
- window.gl.mrWidgetData.pipelines_empty_svg_path = '#{image_path('illustrations/pipelines_empty.svg')}';
- window.gl.mrWidgetData.codequality_help_path = '#{help_page_path("user/project/merge_requests/code_quality", anchor: "code-quality-reports")}';
- window.gl.mrWidgetData.false_positive_doc_url = '#{help_page_path('user/application_security/vulnerabilities/index')}';
- window.gl.mrWidgetData.can_view_false_positive = '#{@merge_request.project.licensed_feature_available?(:sast_fp_reduction).to_s}';
+ window.gl.mrWidgetData.artifacts_endpoint = '#{downloadable_artifacts_project_pipeline_path(@project, artifacts_endpoint_placeholder, format: :json)}';
+ window.gl.mrWidgetData.artifacts_endpoint_placeholder = '#{artifacts_endpoint_placeholder}';
+ window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}';
+ window.gl.mrWidgetData.ci_troubleshooting_docs_path = '#{help_page_path('ci/troubleshooting.md')}';
+ window.gl.mrWidgetData.mr_troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests/reviews/index.md', anchor: 'troubleshooting')}';
+ window.gl.mrWidgetData.pipeline_must_succeed_docs_path = '#{help_page_path('user/project/merge_requests/merge_when_pipeline_succeeds.md', anchor: 'only-allow-merge-requests-to-be-merged-if-the-pipeline-succeeds')}';
+ window.gl.mrWidgetData.security_approvals_help_page_path = '#{help_page_path('user/application_security/index.md', anchor: 'security-approvals-in-merge-requests')}';
+ window.gl.mrWidgetData.license_compliance_docs_path = '#{help_page_path('user/compliance/license_compliance/index.md', anchor: 'policies')}';
+ window.gl.mrWidgetData.eligible_approvers_docs_path = '#{help_page_path('user/project/merge_requests/approvals/rules.md', anchor: 'eligible-approvers')}';
+ window.gl.mrWidgetData.approvals_help_path = '#{help_page_path("user/project/merge_requests/approvals/index.md")}';
+ window.gl.mrWidgetData.pipelines_empty_svg_path = '#{image_path('illustrations/pipelines_empty.svg')}';
+ window.gl.mrWidgetData.codequality_help_path = '#{help_page_path("user/project/merge_requests/code_quality", anchor: "code-quality-reports")}';
+ window.gl.mrWidgetData.false_positive_doc_url = '#{help_page_path('user/application_security/vulnerabilities/index')}';
+ window.gl.mrWidgetData.can_view_false_positive = '#{@merge_request.project.licensed_feature_available?(:sast_fp_reduction).to_s}';
+ window.gl.mrWidgetData.user_preferences_gitpod_path = '#{profile_preferences_path(anchor: 'user_gitpod_enabled')}';
+ window.gl.mrWidgetData.user_profile_enable_gitpod_path = '#{profile_path(user: { gitpod_enabled: true })}';
- #js-vue-mr-widget.mr-widget
+#js-vue-mr-widget.mr-widget
diff --git a/app/views/projects/merge_requests/invalid.html.haml b/app/views/projects/merge_requests/invalid.html.haml
index eb8de425f61..28fd0b83824 100644
--- a/app/views/projects/merge_requests/invalid.html.haml
+++ b/app/views/projects/merge_requests/invalid.html.haml
@@ -1,11 +1,8 @@
- page_title "#{@merge_request.title} (#{@merge_request.to_reference}", _("Merge requests")
-- badge_start = '<span class="badge badge-pill gl-badge sm badge-info">'.html_safe
-- badge_end = '</span>'.html_safe
-
- err_fork_project_removed = s_("MergeRequest|Can't show this merge request because the fork project was deleted.")
-- err_source_branch = s_("MergeRequest|Can't show this merge request because the source branch %{badge_start}%{source_branch}%{badge_end} is missing from project %{badge_start}%{project_path}%{badge_end}. Close this merge request or update the source branch.")
-- err_target_branch = s_("MergeRequest|Can't show this merge request because the target branch %{badge_start}%{target_branch}%{badge_end} is missing from project %{badge_start}%{project_path}%{badge_end}. Close this merge request or update the target branch.")
+- err_source_branch = s_("MergeRequest|Can't show this merge request because the source branch %{branch_badge} is missing from project %{path_badge}. Close this merge request or update the source branch.")
+- err_target_branch = s_("MergeRequest|Can't show this merge request because the target branch %{branch_badge} is missing from project %{path_badge}. Close this merge request or update the target branch.")
- err_internal = s_("MergeRequest|Can't show this merge request because of an internal error. Contact your administrator.")
.merge-request
@@ -20,8 +17,8 @@
- if @merge_request.for_fork? && !@merge_request.source_project
= err_fork_project_removed
- elsif !@merge_request.source_branch_exists?
- = err_source_branch.html_safe % { badge_start: badge_start, badge_end: badge_end, source_branch: @merge_request.source_branch, project_path: @merge_request.source_project_path }
+ = err_source_branch.html_safe % { branch_badge: gl_badge_tag(@merge_request.source_branch, variant: :info, size: :sm), path_badge: gl_badge_tag(@merge_request.source_project_path, variant: :info, size: :sm) }
- elsif !@merge_request.target_branch_exists?
- = err_target_branch.html_safe % { badge_start: badge_start, badge_end: badge_end, target_branch: @merge_request.target_branch, project_path: @merge_request.source_project_path }
+ = err_target_branch.html_safe % { branch_badge: gl_badge_tag(@merge_request.target_branch, variant: :info, size: :sm), path_badge: gl_badge_tag(@merge_request.source_project_path, variant: :info, size: :sm) }
- else
= err_internal
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index eb1c9712c52..a0e78b7570a 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -97,6 +97,4 @@
#js-review-bar
= render 'projects/invite_members_modal', project: @project
-- if Gitlab::CurrentSettings.gitpod_enabled && !current_user&.gitpod_enabled
- = render 'shared/gitpod/enable_gitpod_modal'
= render 'shared/web_ide_path'
diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml
index 5f31ec4087e..e9e3645d7f2 100644
--- a/app/views/projects/mirrors/_authentication_method.html.haml
+++ b/app/views/projects/mirrors/_authentication_method.html.haml
@@ -6,7 +6,7 @@
.select-wrapper
= f.select :auth_method,
options_for_select(auth_options, mirror.auth_method),
- {}, { class: "form-control gl-form-input select-control js-mirror-auth-type qa-authentication-method" }
+ {}, { class: "form-control gl-form-select select-control js-mirror-auth-type qa-authentication-method" }
= sprite_icon('chevron-down', css_class: "gl-icon gl-absolute gl-top-3 gl-right-3 gl-text-gray-200")
= f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index d253ab8e32b..b2fa735f76f 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -69,7 +69,7 @@
- if mirror.disabled?
= render 'projects/mirrors/disabled_mirror_badge'
- if mirror.last_error.present?
- .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
+ = gl_badge_tag _('Error'), { variant: :danger }, { data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge' }, title: html_escape(mirror.last_error.try(:strip)) }
%td.gl-display-flex
- if mirror_settings_enabled
%button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-icon.gl-button.btn-danger.gl-mr-3{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= sprite_icon('remove')
diff --git a/app/views/projects/mirrors/_mirror_repos_form.html.haml b/app/views/projects/mirrors/_mirror_repos_form.html.haml
index dca01ebbe90..34b7c75debf 100644
--- a/app/views/projects/mirrors/_mirror_repos_form.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos_form.html.haml
@@ -1,7 +1,7 @@
.form-group
= label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
.select-wrapper
- = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control gl-form-input select-control js-mirror-direction qa-mirror-direction', disabled: true
+ = select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control gl-form-select select-control js-mirror-direction qa-mirror-direction', disabled: true
= sprite_icon('chevron-down', css_class: "gl-icon gl-absolute gl-top-3 gl-right-3 gl-text-gray-200")
= render partial: "projects/mirrors/mirror_repos_push", locals: { f: f }
diff --git a/app/views/projects/packages/packages/index.html.haml b/app/views/projects/packages/packages/index.html.haml
index c67b06218e2..4ab16f25dd2 100644
--- a/app/views/projects/packages/packages/index.html.haml
+++ b/app/views/projects/packages/packages/index.html.haml
@@ -7,4 +7,7 @@
full_path: @project.full_path,
endpoint: project_packages_path(@project),
page_type: 'projects',
- empty_list_illustration: image_path('illustrations/no-packages.svg'), } }
+ empty_list_illustration: image_path('illustrations/no-packages.svg'),
+ npm_instance_url: package_registry_instance_url(:npm),
+ project_list_url: project_packages_path(@project),
+ group_list_url: '' } }
diff --git a/app/views/projects/packages/packages/show.html.haml b/app/views/projects/packages/packages/show.html.haml
deleted file mode 100644
index ebdc9e654f6..00000000000
--- a/app/views/projects/packages/packages/show.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- add_to_breadcrumbs _("Package Registry"), project_packages_path(@project)
-- add_to_breadcrumbs @package.name, project_packages_path(@project)
-- breadcrumb_title @package.version
-- page_title _("Package Registry")
-- @content_class = "limit-container-width" unless fluid_layout
-
-.row
- .col-12
- #js-vue-packages-detail-new{ data: package_details_data(@project, @package) }
diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
index 40352e79175..4e9c77564da 100644
--- a/app/views/projects/pages/_list.html.haml
+++ b/app/views/projects/pages/_list.html.haml
@@ -15,11 +15,9 @@
= external_link(domain.url, domain.url)
- if domain.certificate
%div
- %span.badge.badge-gray
- = s_('GitLabPages|Certificate: %{subject}') % { subject: domain.pages_domain.subject }
+ = gl_badge_tag(s_('GitLabPages|Certificate: %{subject}') % { subject: domain.pages_domain.subject })
- if domain.expired?
- %span.badge.badge-danger
- = s_('GitLabPages|Expired')
+ = gl_badge_tag s_('GitLabPages|Expired'), variant: :danger
%div
= link_to s_('GitLabPages|Edit'), project_pages_domain_path(@project, domain), class: "btn gl-button btn-sm btn-grouped btn-confirm btn-inverted"
= link_to s_('GitLabPages|Remove'), project_pages_domain_path(@project, domain), data: { confirm: s_('GitLabPages|Are you sure?')}, method: :delete, class: "btn gl-button btn-danger btn-sm btn-grouped"
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 0bfdee088b4..13a77dbf2fd 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -22,41 +22,25 @@
.icon-container
= sprite_icon('flag')
- if @pipeline.child?
- %span.js-pipeline-child.badge.badge-pill.gl-badge.sm.badge-primary.has-tooltip{ title: s_("Pipelines|This is a child pipeline within the parent pipeline") }
- = s_('Pipelines|Child pipeline')
- = surround '(', ')' do
- = link_to s_('Pipelines|parent'), pipeline_path(@pipeline.triggered_by_pipeline), class: 'text-white text-underline'
+ - text = sprintf(s_('Pipelines|Child pipeline (%{link_start}parent%{link_end})'), { link_start: "<a href='#{pipeline_path(@pipeline.triggered_by_pipeline)}' class='text-underline'>", link_end: "</a>"}).html_safe
+ = gl_badge_tag text, { variant: :info, size: :sm }, { class: 'js-pipeline-child has-tooltip', title: s_("Pipelines|This is a child pipeline within the parent pipeline") }
- if @pipeline.latest?
- %span.js-pipeline-url-latest.badge.badge-pill.gl-badge.sm.badge-success.has-tooltip{ title: _("Latest pipeline for the most recent commit on this branch") }
- latest
+ = gl_badge_tag s_('Pipelines|latest'), { variant: :success, size: :sm }, { class: 'js-pipeline-url-latest has-tooltip', title: _("Latest pipeline for the most recent commit on this branch") }
- if @pipeline.merge_train_pipeline?
- %span.js-pipeline-url-train.badge.badge-pill.gl-badge.sm.badge-info.has-tooltip{ title: _("This is a merge train pipeline") }
- train
+ = gl_badge_tag s_('Pipelines|train'), { variant: :info, size: :sm }, { class: 'js-pipeline-url-train has-tooltip', title: _("This is a merge train pipeline") }
- if @pipeline.has_yaml_errors?
- %span.js-pipeline-url-yaml.badge.badge-pill.gl-badge.sm.badge-danger.has-tooltip{ title: @pipeline.yaml_errors }
- yaml invalid
+ = gl_badge_tag s_('Pipelines|yaml invalid'), { variant: :danger, size: :sm }, { class: 'js-pipeline-url-yaml has-tooltip', title: @pipeline.yaml_errors }
- if @pipeline.failure_reason?
- %span.js-pipeline-url-failure.badge.badge-pill.gl-badge.sm.badge-danger.has-tooltip{ title: @pipeline.failure_reason }
- error
+ = gl_badge_tag s_('Pipelines|error'), { variant: :danger, size: :sm }, { class: 'js-pipeline-url-failure has-tooltip', title: @pipeline.failure_reason }
- if @pipeline.auto_devops_source?
- popover_title_text = html_escape(_('This pipeline makes use of a predefined CI/CD configuration enabled by %{b_open}Auto DevOps.%{b_close}')) % { b_open: '<b>'.html_safe, b_close: '</b>'.html_safe }
- popover_content_url = help_page_path('topics/autodevops/index.md')
- popover_content_text = _('Learn more about Auto DevOps')
- %a.js-pipeline-url-autodevops.badge.badge-pill.gl-badge.sm.badge-info.autodevops-badge{ href: "#", tabindex: "0", role: "button", data: { container: "body",
- toggle: "popover",
- placement: "top",
- html: "true",
- triggers: "focus",
- title: "<div class='gl-font-weight-normal gl-line-height-normal'>#{popover_title_text}</div>",
- content: "<a href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>",
- } }
- Auto DevOps
+ = gl_badge_tag s_('Pipelines|Auto DevOps'), { variant: :info, size: :sm }, { class: 'js-pipeline-url-autodevops', href: "#", tabindex: "0", role: "button", data: { container: 'body', toggle: 'popover', placement: 'top', html: 'true', triggers: 'focus', title: "<div class='gl-font-weight-normal gl-line-height-normal'>#{popover_title_text}</div>", content: "<a href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>" } }
- if @pipeline.detached_merge_request_pipeline?
- %span.js-pipeline-url-mergerequest.badge.badge-pill.gl-badge.sm.badge-info.has-tooltip{ title: _('Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.') }
- detached
+ = gl_badge_tag s_('Pipelines|detached'), { variant: :info, size: :sm }, { class: 'js-pipeline-url-mergerequest has-tooltip', title: _('Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.') }
- if @pipeline.stuck?
- %span.js-pipeline-url-stuck.badge.badge-pill.gl-badge.sm.badge-warning
- stuck
+ = gl_badge_tag s_('Pipelines|stuck'), { variant: :warning, size: :sm }, { class: 'js-pipeline-url-stuck has-tooltip' }
.well-segment.branch-info
.icon-container.commit-icon
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index ac5d34bfd44..d654d0e04d7 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -1,3 +1,4 @@
+- @force_fluid_layout = true
- add_to_breadcrumbs _('Pipelines'), project_pipelines_path(@project)
- breadcrumb_title "##{@pipeline.id}"
- page_title _('Pipeline')
diff --git a/app/views/projects/prometheus/metrics/edit.html.haml b/app/views/projects/prometheus/metrics/edit.html.haml
index d308824571e..146bf6b6853 100644
--- a/app/views/projects/prometheus/metrics/edit.html.haml
+++ b/app/views/projects/prometheus/metrics/edit.html.haml
@@ -1,6 +1,6 @@
- add_to_breadcrumbs _("Settings"), edit_project_path(@project)
- add_to_breadcrumbs _("Integrations"), project_settings_integrations_path(@project)
-- add_to_breadcrumbs "Prometheus", edit_project_service_path(@project, ::Integrations::Prometheus)
+- add_to_breadcrumbs "Prometheus", edit_project_integration_path(@project, ::Integrations::Prometheus)
- breadcrumb_title s_('Metrics|Edit metric')
- page_title @metric.title, s_('Metrics|Edit metric')
= render 'form', project: @project, metric: @metric
diff --git a/app/views/projects/prometheus/metrics/new.html.haml b/app/views/projects/prometheus/metrics/new.html.haml
index 8415ec9ee41..ad8463d1804 100644
--- a/app/views/projects/prometheus/metrics/new.html.haml
+++ b/app/views/projects/prometheus/metrics/new.html.haml
@@ -1,6 +1,6 @@
- add_to_breadcrumbs _("Settings"), edit_project_path(@project)
- add_to_breadcrumbs _("Integrations"), project_settings_integrations_path(@project)
-- add_to_breadcrumbs "Prometheus", edit_project_service_path(@project, ::Integrations::Prometheus)
+- add_to_breadcrumbs "Prometheus", edit_project_integration_path(@project, ::Integrations::Prometheus)
- breadcrumb_title s_('Metrics|New metric')
- page_title s_('Metrics|New metric')
= render 'form', project: @project, metric: @metric
diff --git a/app/views/projects/protected_branches/shared/_branches_list.html.haml b/app/views/projects/protected_branches/shared/_branches_list.html.haml
index b13117960dd..5964f2bfeda 100644
--- a/app/views/projects/protected_branches/shared/_branches_list.html.haml
+++ b/app/views/projects/protected_branches/shared/_branches_list.html.haml
@@ -1,7 +1,7 @@
.protected-branches-list.js-protected-branches-list.qa-protected-branches-list
- if @protected_branches.empty?
.card-header.bg-white
- = s_("ProtectedBranch|Protected branch (%{protected_branches_count})") % { protected_branches_count: @protected_branches_count }
+ = s_("ProtectedBranch|Protected branch (%{protected_branches_count})") % { protected_branches_count: 0 }
%p.settings-message.text-center
= s_("ProtectedBranch|There are currently no protected branches, protect a branch with the form above.")
- else
diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
index 02ec778b97c..f3bb2a66a4c 100644
--- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml
@@ -5,11 +5,11 @@
%span.ref-name= protected_branch.name
- if @project.root_ref?(protected_branch.name)
- %span.badge.gl-badge.badge-pill.badge-info.d-inline default
+ = gl_badge_tag s_('ProtectedBranch|default'), variant: :info
%div
- if protected_branch.wildcard?
- - matching_branches = protected_branch.matching(repository.branches)
+ - matching_branches = protected_branch.matching(repository.branch_names)
= link_to pluralize(matching_branches.count, "matching branch"), namespace_project_protected_branch_path(@project.namespace, @project, protected_branch)
- elsif !protected_branch.commit
%span.text-muted Branch was deleted.
@@ -20,4 +20,4 @@
- if can_admin_project
%td
- = link_to 'Unprotect', [@project, protected_branch, { update_section: 'js-protected-branches-settings' }], disabled: local_assigns[:disabled], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn gl-button btn-warning"
+ = link_to s_('ProtectedBranch|Unprotect'), [@project, protected_branch, { update_section: 'js-protected-branches-settings' }], disabled: local_assigns[:disabled], data: { confirm: s_('ProtectedBranch|Branch will be writable for developers. Are you sure?') }, method: :delete, class: "btn gl-button btn-warning"
diff --git a/app/views/projects/protected_tags/shared/_protected_tag.html.haml b/app/views/projects/protected_tags/shared/_protected_tag.html.haml
index 70357f39e44..b312a09aadd 100644
--- a/app/views/projects/protected_tags/shared/_protected_tag.html.haml
+++ b/app/views/projects/protected_tags/shared/_protected_tag.html.haml
@@ -6,7 +6,7 @@
= gl_badge_tag s_('ProtectedTags|default'), variant: :info, class: 'gl-ml-2'
%td
- if protected_tag.wildcard?
- - matching_tags = protected_tag.matching(repository.tags)
+ - matching_tags = protected_tag.matching(repository.tag_names)
= link_to pluralize(matching_tags.count, "matching tag"), project_protected_tag_path(@project, protected_tag)
- else
- if commit = protected_tag.commit
diff --git a/app/views/projects/protected_tags/shared/_tags_list.html.haml b/app/views/projects/protected_tags/shared/_tags_list.html.haml
index f3cf4013898..5f3ea281278 100644
--- a/app/views/projects/protected_tags/shared/_tags_list.html.haml
+++ b/app/views/projects/protected_tags/shared/_tags_list.html.haml
@@ -1,7 +1,7 @@
.protected-tags-list.js-protected-tags-list
- if @protected_tags.empty?
.card-header
- Protected tags (#{@protected_tags_count})
+ Protected tags (0)
%p.settings-message.text-center
No tags are protected.
- else
diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml
index 03927cd3bfa..3efe1fd2e82 100644
--- a/app/views/projects/registry/repositories/index.html.haml
+++ b/app/views/projects/registry/repositories/index.html.haml
@@ -12,7 +12,7 @@
"containers_error_image" => image_path('illustrations/docker-error-state.svg'),
"repository_url" => escape_once(@project.container_registry_url),
"registry_host_url_with_port" => escape_once(registry_config.host_port),
- "expiration_policy_help_page_path" => help_page_path('user/packages/container_registry/index.md', anchor: 'cleanup-policy'),
+ "expiration_policy_help_page_path" => help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy'),
"garbage_collection_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'container-registry-garbage-collection'),
"run_cleanup_policies_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'run-the-cleanup-policy-now'),
"project_path": @project.full_path,
diff --git a/app/views/projects/runners/_group_runners.html.haml b/app/views/projects/runners/_group_runners.html.haml
index 183e747afdd..c25fd7a7587 100644
--- a/app/views/projects/runners/_group_runners.html.haml
+++ b/app/views/projects/runners/_group_runners.html.haml
@@ -29,9 +29,9 @@
- if can?(current_user, :admin_group_runners, @project.group)
- group_link = link_to _("group's CI/CD settings."), group_settings_ci_cd_path(@project.group)
- = _('Group maintainers can register group runners in the %{link}').html_safe % { link: group_link }
+ = _('Group owners can register group runners in the %{link}').html_safe % { link: group_link }
- else
- = _('Ask your group maintainer to set up a group runner.')
+ = _('Ask your group owner to set up a group runner.')
- else
%h4.underlined-title
diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml
index 5e999b7afb3..1357846876e 100644
--- a/app/views/projects/runners/_specific_runners.html.haml
+++ b/app/views/projects/runners/_specific_runners.html.haml
@@ -17,7 +17,7 @@
group_path: '' }
- else
= _('Please contact an admin to register runners.')
- = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'runner-registration'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'prevent-users-from-registering-runners'), target: '_blank', rel: 'noopener noreferrer'
%hr
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index dbc204ff9bf..419dd827e49 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -6,9 +6,12 @@
- if integration.operating?
= sprite_icon('check', css_class: 'gl-text-green-500')
-= form_for(integration, as: :service, url: scoped_integration_path(integration, project: @project, group: @group), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => test_project_service_path(@project, integration) } }) do |form|
- = render 'shared/service_settings', form: form, integration: integration
- %input{ id: 'services_redirect_to', type: 'hidden', name: 'redirect_to', value: request.referer }
+- if vue_integration_form_enabled?
+ = render 'shared/integration_settings', integration: integration
+- else
+ = form_for(integration, as: :service, url: scoped_integration_path(integration, project: @project, group: @group), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => test_project_integration_path(@project, integration), testid: 'integration-form' } }) do |form|
+ = render 'shared/integration_settings', form: form, integration: integration
+ %input{ id: 'services_redirect_to', type: 'hidden', name: 'redirect_to', value: request.referer }
- if lookup_context.template_exists?('show', "projects/services/#{integration.to_param}", true)
%hr
diff --git a/app/views/projects/settings/access_tokens/index.html.haml b/app/views/projects/settings/access_tokens/index.html.haml
index 4e946050881..e4b027fcc44 100644
--- a/app/views/projects/settings/access_tokens/index.html.haml
+++ b/app/views/projects/settings/access_tokens/index.html.haml
@@ -5,7 +5,7 @@
- @content_class = 'limit-container-width' unless fluid_layout
.row.gl-mt-3
- .col-lg-4.profile-settings-sidebar
+ .col-lg-4
%h4.gl-mt-0
= page_title
%p
@@ -24,26 +24,26 @@
= _('You can enable project access token creation in %{link_start}group settings%{link_end}.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.col-lg-8
- - if @new_project_access_token
+ - if @new_resource_access_token
= render 'shared/access_tokens/created_container',
type: type,
- new_token_value: @new_project_access_token
+ new_token_value: @new_resource_access_token
- if current_user.can?(:create_resource_access_tokens, @project)
= render 'shared/access_tokens/form',
type: type,
path: project_settings_access_tokens_path(@project),
- project: @project,
- token: @project_access_token,
+ resource: @project,
+ token: @resource_access_token,
scopes: @scopes,
access_levels: ProjectMember.access_level_roles,
default_access_level: Gitlab::Access::MAINTAINER,
- prefix: :project_access_token,
+ prefix: :resource_access_token,
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')
= render 'shared/access_tokens/table',
- active_tokens: @active_project_access_tokens,
- project: @project,
+ active_tokens: @active_resource_access_tokens,
+ resource: @project,
type: type,
type_plural: type_plural,
revoke_route_helper: ->(token) { revoke_namespace_project_settings_access_token_path(id: token) },
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index e200635ba82..43d34173af6 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -26,7 +26,7 @@
= gl_badge_tag badge_for_auto_devops_scope(@project), { variant: :info }, { class: 'js-instance-default-badge'}
.form-text.text-muted
= s_('CICD|The Auto DevOps pipeline runs if no alternative CI configuration file is found.')
- = link_to _('Learn more.'), help_page_path('topics/autodevops/index.md'), target: '_blank'
+ = link_to _('Learn more.'), help_page_path('topics/autodevops/index.md'), target: '_blank', rel: 'noopener noreferrer'
.card-footer.js-extra-settings{ class: auto_devops_enabled || 'hidden' }
- if @project.all_clusters.empty?
%p.settings-message.text-center
@@ -40,18 +40,18 @@
= form.radio_button :deploy_strategy, 'continuous', class: 'form-check-input'
= form.label :deploy_strategy_continuous, class: 'form-check-label' do
= s_('CICD|Continuous deployment to production')
- = link_to sprite_icon('question-o'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-deploy'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('topics/autodevops/stages.md', anchor: 'auto-deploy'), target: '_blank', rel: 'noopener noreferrer'
.form-check
= form.radio_button :deploy_strategy, 'timed_incremental', class: 'form-check-input'
= form.label :deploy_strategy_timed_incremental, class: 'form-check-label' do
= s_('CICD|Continuous deployment to production using timed incremental rollout')
- = link_to sprite_icon('question-o'), help_page_path('topics/autodevops/customize.md', anchor: 'timed-incremental-rollout-to-production'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('topics/autodevops/customize.md', anchor: 'timed-incremental-rollout-to-production'), target: '_blank', rel: 'noopener noreferrer'
.form-check
= form.radio_button :deploy_strategy, 'manual', class: 'form-check-input'
= form.label :deploy_strategy_manual, class: 'form-check-label' do
= s_('CICD|Automatic deployment to staging, manual deployment to production')
- = link_to sprite_icon('question-o'), help_page_path('topics/autodevops/customize.md', anchor: 'incremental-rollout-to-production'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('topics/autodevops/customize.md', anchor: 'incremental-rollout-to-production'), target: '_blank', rel: 'noopener noreferrer'
= f.submit _('Save changes'), class: "btn gl-button btn-confirm gl-mt-5", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index a91c12d01ad..d3cdfc4f7c9 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -10,7 +10,7 @@
%strong= _("Public pipelines")
.form-text.text-muted
= _("Allow public access to pipelines and job details, including output logs and artifacts.")
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'change-which-users-can-view-your-pipelines'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'change-which-users-can-view-your-pipelines'), target: '_blank', rel: 'noopener noreferrer'
.form-group
.form-check
@@ -19,7 +19,7 @@
%strong= _("Auto-cancel redundant pipelines")
.form-text.text-muted
= _("New pipelines cause older pending or running pipelines on the same branch to be cancelled.")
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'auto-cancel-redundant-pipelines'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'auto-cancel-redundant-pipelines'), target: '_blank', rel: 'noopener noreferrer'
.form-group
.form-check
@@ -29,14 +29,14 @@
%strong= _("Skip outdated deployment jobs")
.form-text.text-muted
= _("When a deployment job is successful, skip older deployment jobs that are still pending.")
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'skip-outdated-deployment-jobs'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'skip-outdated-deployment-jobs'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= f.label :ci_config_path, _('CI/CD configuration file'), class: 'label-bold'
= f.text_field :ci_config_path, class: 'form-control', placeholder: '.gitlab-ci.yml'
%p.form-text.text-muted
= html_escape(_("The name of the CI/CD configuration file. A path relative to the root directory is optional (for example %{code_open}my/path/.myfile.yml%{code_close}).")) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank', rel: 'noopener noreferrer'
%hr
.form-group
@@ -44,7 +44,7 @@
= _("Git strategy")
%p
= _("Choose which Git strategy to use when fetching the project.")
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'choose-the-default-git-strategy'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'choose-the-default-git-strategy'), target: '_blank', rel: 'noopener noreferrer'
.form-check
= f.radio_button :build_allow_git_fetch, 'false', { class: 'form-check-input' }
= f.label :build_allow_git_fetch_false, class: 'form-check-label' do
@@ -66,7 +66,7 @@
= form.number_field :default_git_depth, { class: 'form-control gl-form-input', min: 0, max: 1000 }
%p.form-text.text-muted
= html_escape(_('The number of changes to fetch from GitLab when cloning a repository. Lower values can speed up pipeline execution. Set to %{code_open}0%{code_close} or blank to fetch all branches and tags for each job')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'limit-the-number-of-changes-fetched-during-clone'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'limit-the-number-of-changes-fetched-during-clone'), target: '_blank', rel: 'noopener noreferrer'
%hr
.form-group
@@ -74,7 +74,7 @@
= f.text_field :build_timeout_human_readable, class: 'form-control gl-form-input'
%p.form-text.text-muted
= html_escape(_('Jobs fail if they run longer than the timeout time. Input value is in seconds by default. Human readable input is also accepted, for example %{code_open}1 hour%{code_close}.')) % { code_open: '<code>'.html_safe, code_close: '</code>'.html_safe }
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'set-a-limit-for-how-long-jobs-can-run'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'set-a-limit-for-how-long-jobs-can-run'), target: '_blank', rel: 'noopener noreferrer'
- if can?(current_user, :update_max_artifacts_size, @project)
.form-group
@@ -82,7 +82,7 @@
= f.number_field :max_artifacts_size, class: 'form-control gl-form-input'
%p.form-text.text-muted
= _("The maximum file size in megabytes for individual job artifacts.")
- = link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= f.label :build_coverage_regex, _("Test coverage parsing"), class: 'label-bold'
@@ -94,7 +94,7 @@
.input-group-text /
%p.form-text.text-muted
= html_escape(_('The regular expression used to find test coverage output in the job log. For example, use %{regex} for Simplecov (Ruby). Leave blank to disable.')) % { regex: '<code>\(\d+.\d+%\)</code>'.html_safe }
- = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'add-test-coverage-results-to-a-merge-request'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'add-test-coverage-results-to-a-merge-request'), target: '_blank', rel: 'noopener noreferrer'
= f.submit _('Save changes'), class: "btn gl-button btn-confirm", data: { qa_selector: 'save_general_pipelines_changes_button' }
diff --git a/app/views/projects/settings/packages_and_registries/show.html.haml b/app/views/projects/settings/packages_and_registries/show.html.haml
index 626ddc20431..07910899aa0 100644
--- a/app/views/projects/settings/packages_and_registries/show.html.haml
+++ b/app/views/projects/settings/packages_and_registries/show.html.haml
@@ -10,6 +10,6 @@
is_admin: current_user&.admin.to_s,
admin_settings_path: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'),
enable_historic_entries: container_expiration_policies_historic_entry_enabled?(@project).to_s,
- help_page_path: help_page_path('user/packages/container_registry/index', anchor: 'cleanup-policy'),
+ help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'cleanup-policy'),
show_cleanup_policy_on_alert: show_cleanup_policy_on_alert(@project).to_s,
- tags_regex_help_page_path: help_page_path('user/packages/container_registry/index', anchor: 'regex-pattern-examples') } }
+ tags_regex_help_page_path: help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'regex-pattern-examples') } }
diff --git a/app/views/projects/starrers/index.html.haml b/app/views/projects/starrers/index.html.haml
index 7c8314c157d..618c4c249a1 100644
--- a/app/views/projects/starrers/index.html.haml
+++ b/app/views/projects/starrers/index.html.haml
@@ -12,7 +12,7 @@
= search_field_tag :search, params[:search], { placeholder: _('Search'), class: 'form-control', spellcheck: false }
%button.user-search-btn{ type: "submit", "aria-label" => _("Submit search") }
= sprite_icon('search')
- .dropdown.inline.user-sort-dropdown
+ .dropdown.inline.gl-ml-3
= dropdown_toggle(starrers_sort_options_hash[@sort], { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%li.dropdown-header
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 60cc043f813..202c0f22420 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -4,8 +4,9 @@
#js-repo-breadcrumb{ data: breadcrumb_data_attributes }
+#js-blob-controls
.tree-controls
- .d-block.d-sm-flex.flex-wrap.align-items-start.gl-children-ml-sm-3<
+ .d-block.d-sm-flex.flex-wrap.align-items-start.gl-children-ml-sm-3.gl-first-child-ml-sm-0<
= render_if_exists 'projects/tree/lock_link'
#js-tree-history-link{ data: { history_link: project_commits_path(@project, @ref) } }
diff --git a/app/views/projects/triggers/_index.html.haml b/app/views/projects/triggers/_index.html.haml
index 85ecfe7a982..8b3d0ef17a4 100644
--- a/app/views/projects/triggers/_index.html.haml
+++ b/app/views/projects/triggers/_index.html.haml
@@ -45,6 +45,7 @@
%pre
:plain
curl -X POST \
+ --fail \
-F token=TOKEN \
-F ref=REF_NAME \
#{builds_trigger_url(@project.id)}
@@ -54,7 +55,7 @@
%pre
:plain
script:
- - "curl -X POST -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
+ - "curl -X POST --fail -F token=TOKEN -F ref=REF_NAME #{builds_trigger_url(@project.id)}"
%h5.gl-mt-3
= _('Use webhook')
@@ -73,6 +74,7 @@
%pre
:plain
curl -X POST \
+ --fail \
-F token=TOKEN \
-F "ref=REF_NAME" \
-F "variables[RUN_NIGHTLY_BUILD]=true" \
diff --git a/app/views/registrations/welcome/show.html.haml b/app/views/registrations/welcome/show.html.haml
index 65a1ffa3e46..ca2f225a2d8 100644
--- a/app/views/registrations/welcome/show.html.haml
+++ b/app/views/registrations/welcome/show.html.haml
@@ -2,6 +2,10 @@
- page_title _('Your profile')
- add_page_specific_style 'page_bundles/signup'
- gitlab_experience_text = _('To personalize your GitLab experience, we\'d like to know a bit more about you')
+- content_for :page_specific_javascripts do
+ = render "layouts/google_tag_manager_head"
+ = render "layouts/one_trust"
+= render "layouts/google_tag_manager_body"
.row.gl-flex-grow-1
.d-flex.gl-flex-direction-column.gl-align-items-center.gl-w-full.gl-px-5.gl-pb-5
@@ -18,7 +22,7 @@
.row
.form-group.col-sm-12
= f.label :role, _('Role'), class: 'label-bold'
- = f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, {}, class: 'form-control js-user-role-dropdown', autofocus: true
+ = f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { include_blank: _('Select a role') }, class: 'form-control js-user-role-dropdown', autofocus: true, required: true, data: { qa_selector: 'role_dropdown' }
- if Feature.enabled?(:user_other_role_details)
.row
.form-group.col-sm-12.js-other-role-group.hidden
diff --git a/app/views/sandbox/mermaid.html.erb b/app/views/sandbox/mermaid.html.erb
new file mode 100644
index 00000000000..2d2391c8866
--- /dev/null
+++ b/app/views/sandbox/mermaid.html.erb
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <%= webpack_bundle_tag("sandboxed_mermaid") %>
+ </head>
+ <body>
+ <div id="app"></div>
+ </body>
+</html>
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
deleted file mode 100644
index 4cb3f6d1739..00000000000
--- a/app/views/shared/_confirm_modal.html.haml
+++ /dev/null
@@ -1,21 +0,0 @@
-#modal-confirm-danger.modal.qa-confirm-modal{ tabindex: -1 }
- .modal-dialog
- .modal-content
- .modal-header
- %h3.page-title= _('Confirmation required')
- %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
- %span{ "aria-hidden": "true" } &times;
-
- .modal-body
- %p.text-danger.js-confirm-text
-
- %p
- %span.js-warning-text= _('This action can lead to data loss. To prevent accidental actions we ask you to confirm your intention.')
- %br
- - phrase_code = '<code class="js-legacy-confirm-danger-match">%{phrase_name}</code>'.html_safe % { phrase_name: phrase }
- = _('Please type %{phrase_code} to proceed or close this modal to cancel.').html_safe % { phrase_code: phrase_code }
-
- .form-group
- = text_field_tag 'confirm_name_input', '', class: 'form-control js-legacy-confirm-danger-input qa-confirm-input'
- .form-actions
- = submit_tag _('Confirm'), class: "gl-button btn btn-danger js-legacy-confirm-danger-submit qa-confirm-button"
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_integration_settings.html.haml
index adacaeadfab..93606ca0aba 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_integration_settings.html.haml
@@ -1,6 +1,6 @@
= form_errors(integration)
-.service-settings
+%div{ data: { testid: "integration-settings-form" } }
- if @default_integration
.js-vue-default-integration-settings{ data: integration_form_data(@default_integration, group: @group, project: @project) }
.js-vue-integration-settings{ data: integration_form_data(integration, group: @group, project: @project) }
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index e4ef0a52eba..9428813f6b0 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -10,18 +10,18 @@
= render "shared/label_row", label: label, force_priority: force_priority
%ul.label-actions-list
- if can?(current_user, :admin_label, @project)
- %li.inline.js-toggle-priority{ data: { url: remove_priority_project_label_path(@project, label),
+ %li.gl-display-inline-block.js-toggle-priority.gl-ml-3{ data: { url: remove_priority_project_label_path(@project, label),
dom_id: dom_id(label), type: label.type } }
- %button.add-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip.gl-ml-3{ title: _('Prioritize'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Prioritize label') }
+ %button.add-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip{ title: _('Prioritize'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Prioritize label') }
= sprite_icon('star-o')
- %button.remove-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip.gl-ml-3{ title: _('Remove priority'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Deprioritize label') }
+ %button.remove-priority.btn.gl-button.btn-default-tertiary.btn-sm.has-tooltip{ title: _('Remove priority'), type: 'button', data: { placement: 'bottom' }, aria_label: _('Deprioritize label') }
= sprite_icon('star')
- if can?(current_user, :admin_label, label)
- %li.inline
+ %li.gl-display-inline-block
= link_to label.edit_path, class: 'btn gl-button btn-default-tertiary btn-sm edit has-tooltip', title: _('Edit'), data: { placement: 'bottom' }, aria_label: _('Edit') do
= sprite_icon('pencil')
- if can?(current_user, :admin_label, label)
- %li.inline
+ %li.gl-display-inline-block
.dropdown
%button{ type: 'button', class: 'btn gl-button btn-default-tertiary btn-sm js-label-options-dropdown', data: { toggle: 'dropdown' }, aria_label: _('Label actions dropdown') }
= sprite_icon('ellipsis_v')
@@ -41,23 +41,23 @@
%button.text-danger.js-delete-label-modal-button{ type: 'button', data: { label_name: label.name, subject_name: label.subject_name, destroy_path: label.destroy_path } }
= _('Delete')
- if current_user
- %li.inline.label-subscription
+ %li.gl-display-inline-block.label-subscription.js-label-subscription.gl-ml-3
- if label.can_subscribe_to_label_in_different_levels?
- %button.js-unsubscribe-button.gl-button.label-subscribe-button.btn.btn-default.gl-ml-3{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
- %span= _('Unsubscribe')
+ %button.js-unsubscribe-button.gl-button.btn.btn-default.gl-w-full{ class: ('hidden' if status.unsubscribed?), data: { url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
+ %span.gl-button-text= _('Unsubscribe')
.dropdown.dropdown-group-label{ class: ('hidden' unless status.unsubscribed?) }
- %button.gl-button.label-subscribe-button.btn.btn-default.gl-ml-3{ data: { toggle: 'dropdown' } }
- %span
+ %button.gl-button.btn.btn-default.gl-w-full{ data: { toggle: 'dropdown' } }
+ %span.gl-button-text
= _('Subscribe')
= sprite_icon('chevron-down')
.dropdown-menu.dropdown-open-left
%ul
%li
- %button.js-subscribe-button.label-subscribe-button.gl-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_project_label_path(@project, label) } }
- %span= _('Subscribe at project level')
+ %button.js-subscribe-button{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_project_label_path(@project, label) } }
+ %span.gl-button-text= _('Subscribe at project level')
%li
- %button.js-subscribe-button.js-group-level.label-subscribe-button.gl-button.btn.btn-default{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } }
- %span= _('Subscribe at group level')
+ %button.js-subscribe-button.js-group-level{ class: ('hidden' unless status.unsubscribed?), data: { status: status, url: toggle_subscription_group_label_path(label.group, label) } }
+ %span.gl-button-text= _('Subscribe at group level')
- else
- %button.gl-button.js-subscribe-button.label-subscribe-button.btn.btn-default.gl-ml-3{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
- %span= label_subscription_toggle_button_text(label, @project)
+ %button.gl-button.js-subscribe-button.btn.btn-default.gl-w-full{ data: { status: status, url: toggle_subscription_path, toggle: 'tooltip' }, title: tooltip_title }
+ %span.gl-button-text= label_subscription_toggle_button_text(label, @project)
diff --git a/app/views/shared/_new_project_item_select.html.haml b/app/views/shared/_new_project_item_select.html.haml
index d5f4add2796..08003346d09 100644
--- a/app/views/shared/_new_project_item_select.html.haml
+++ b/app/views/shared/_new_project_item_select.html.haml
@@ -1,6 +1,6 @@
- if any_projects?(@projects)
.project-item-select-holder.btn-group.gl-ml-auto.gl-mr-auto.gl-relative.gl-overflow-hidden{ class: 'gl-display-flex!' }
- %a.btn.gl-button.btn-confirm.new-project-item-link.block-truncated.qa-new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] }, class: "gl-m-0!" }
+ %a.btn.gl-button.btn-confirm.js-new-project-item-link.block-truncated.qa-new-project-item-link{ href: '', data: { label: local_assigns[:label], type: local_assigns[:type] }, class: "gl-m-0!" }
= loading_icon(color: 'light')
= project_select_tag :project_path, class: "project-item-select gl-absolute! gl-visibility-hidden", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at', relative_path: local_assigns[:path], with_shared: local_assigns[:with_shared], include_projects_in_subgroups: local_assigns[:include_projects_in_subgroups] }, with_feature_enabled: local_assigns[:with_feature_enabled]
%button.btn.dropdown-toggle.btn-confirm.btn-md.gl-button.gl-dropdown-toggle.dropdown-toggle-split.new-project-item-select-button.qa-new-project-item-select-button.gl-p-0.gl-w-100{ class: "gl-m-0!", 'aria-label': _('Toggle project select') }
diff --git a/app/views/shared/_old_visibility_level.html.haml b/app/views/shared/_old_visibility_level.html.haml
index 8a9cc7ab8a2..104c722ee65 100644
--- a/app/views/shared/_old_visibility_level.html.haml
+++ b/app/views/shared/_old_visibility_level.html.haml
@@ -1,6 +1,6 @@
.form-group.row
.col-sm-2.col-form-label
= _('Visibility level')
- = link_to sprite_icon('question-o'), help_page_path('public_access/public_access'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('public_access/public_access'), target: '_blank', rel: 'noopener noreferrer'
.col-sm-10
= render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: can_change_visibility_level, form_model: form_model, with_label: with_label
diff --git a/app/views/shared/_registration_features_discovery_message.html.haml b/app/views/shared/_registration_features_discovery_message.html.haml
index 8bcd826d8c0..8a16d36b836 100644
--- a/app/views/shared/_registration_features_discovery_message.html.haml
+++ b/app/views/shared/_registration_features_discovery_message.html.haml
@@ -1,9 +1,9 @@
-- license = local_assigns.fetch(:license)
+- feature_title = local_assigns.fetch(:feature_title, s_('RegistrationFeatures|use this feature'))
- registration_features_docs_path = help_page_path('development/service_ping/index.md', anchor: 'registration-features-program')
- service_ping_settings_path = metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings')
%div
- %span= s_('RegistrationFeatures|Want to use this feature for free?')
- - if license.present?
+ %span= sprintf(s_('RegistrationFeatures|Want to %{feature_title} for free?'), { feature_title: feature_title })
+ - if Gitlab.ee?
= link_to s_('RegistrationFeatures|Enable Service Ping and register for this feature.'), service_ping_settings_path
- = sprintf(s_('RegistrationFeatures|Read more about the %{linkStart}Registration Features Program%{linkEnd}.') , { linkStart: "<a href=\"#{registration_features_docs_path}\" target=\"_blank\">", linkEnd: "</a>", }).html_safe
+ = sprintf(s_('RegistrationFeatures|Read more about the %{linkStart}%{label}%{linkEnd}.') , { linkStart: "<a href=\"#{registration_features_docs_path}\" target=\"_blank\">", label: s_('RegistrationFeatures|Registration Features Program'), linkEnd: "</a>" }).html_safe
diff --git a/app/views/shared/_web_ide_button.html.haml b/app/views/shared/_web_ide_button.html.haml
index f9c6afcbc32..82af52cdd59 100644
--- a/app/views/shared/_web_ide_button.html.haml
+++ b/app/views/shared/_web_ide_button.html.haml
@@ -6,5 +6,3 @@
= render 'shared/confirm_fork_modal', fork_path: fork_and_edit_path(@project, @ref, @path), type: 'edit'
- if show_web_ide_button?
= render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path), type: 'webide'
-- if show_gitpod_button?
- = render 'shared/gitpod/enable_gitpod_modal'
diff --git a/app/views/shared/access_tokens/_form.html.haml b/app/views/shared/access_tokens/_form.html.haml
index 6435475a9a3..a52b7236137 100644
--- a/app/views/shared/access_tokens/_form.html.haml
+++ b/app/views/shared/access_tokens/_form.html.haml
@@ -1,7 +1,7 @@
- title = local_assigns.fetch(:title, _('Add a %{type}') % { type: type })
- prefix = local_assigns.fetch(:prefix, :personal_access_token)
- help_path = local_assigns.fetch(:help_path)
-- project = local_assigns.fetch(:project, false)
+- resource = local_assigns.fetch(:resource, false)
- access_levels = local_assigns.fetch(:access_levels, false)
- default_access_level = local_assigns.fetch(:default_access_level, false)
@@ -32,12 +32,12 @@
.js-access-tokens-expires-at
= f.text_field :expires_at, class: 'datepicker gl-datepicker-input form-control gl-form-input', placeholder: 'YYYY-MM-DD', autocomplete: 'off', data: { js_name: 'expiresAt' }
- - if project
+ - if resource
.row
.form-group.col-md-6
= label_tag :access_level, _("Select a role"), class: "label-bold"
.select-wrapper
- = select_tag :"#{prefix}[access_level]", options_for_select(access_levels, default_access_level), class: "form-control project-access-select select-control", data: { qa_selector: 'access_token_access_level' }
+ = select_tag :"#{prefix}[access_level]", options_for_select(access_levels, default_access_level), class: "form-control select-control", data: { qa_selector: 'access_token_access_level' }
= sprite_icon('chevron-down', css_class: "gl-icon gl-absolute gl-top-3 gl-right-3 gl-text-gray-200")
.form-group
@@ -45,7 +45,7 @@
= s_('Tokens|Select scopes')
%p.text-secondary#select_scope_help_text
= s_('Tokens|Scopes set the permission levels granted to the token.')
- = link_to "Learn more.", help_path, target: '_blank'
+ = link_to _("Learn more."), help_path, target: '_blank', rel: 'noopener noreferrer'
= render 'shared/tokens/scopes_form', prefix: prefix, token: token, scopes: scopes
- if prefix == :personal_access_token && Feature.enabled?(:personal_access_tokens_scoped_to_projects, current_user)
diff --git a/app/views/shared/access_tokens/_table.html.haml b/app/views/shared/access_tokens/_table.html.haml
index c096044e439..aa579b4a672 100644
--- a/app/views/shared/access_tokens/_table.html.haml
+++ b/app/views/shared/access_tokens/_table.html.haml
@@ -1,7 +1,7 @@
- no_active_tokens_message = local_assigns.fetch(:no_active_tokens_message, _('This user has no active %{type}.') % { type: type_plural })
- impersonation = local_assigns.fetch(:impersonation, false)
-- project = local_assigns.fetch(:project, false)
-- personal = !impersonation && !project
+- resource = local_assigns.fetch(:resource, false)
+- personal = !impersonation && !resource
%hr
@@ -28,9 +28,9 @@
%th= s_('AccessTokens|Created')
%th
= _('Last Used')
- = link_to sprite_icon('question-o'), help_page_path('user/profile/personal_access_tokens.md', anchor: 'view-the-last-time-a-token-was-used'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('user/profile/personal_access_tokens.md', anchor: 'view-the-last-time-a-token-was-used'), target: '_blank', rel: 'noopener noreferrer'
%th= _('Expires')
- - if project
+ - if resource
%th= _('Role')
%th
%tbody
@@ -54,8 +54,8 @@
= time_ago_with_tooltip(token.expires_at)
- else
%span.token-never-expires-label= _('Never')
- - if project
- %td= project.project_member(token.user).human_access
+ - if resource
+ %td= resource.member(token.user).human_access
%td= link_to _('Revoke'), revoke_route_helper.call(token), method: :put, class: "gl-button btn btn-danger btn-sm float-right qa-revoke-button #{'btn-danger-secondary' unless token.expires?}", data: { confirm: _('Are you sure you want to revoke this %{type}? This action cannot be undone.') % { type: type } }
- else
.settings-message.text-center
diff --git a/app/views/shared/empty_states/_deploy_keys.html.haml b/app/views/shared/empty_states/_deploy_keys.html.haml
deleted file mode 100644
index 6c615de9c56..00000000000
--- a/app/views/shared/empty_states/_deploy_keys.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-.empty-state.gl-display-flex.gl-flex-direction-column.gl-flex-wrap.gl-text-center
- .gl-flex-grow-0.gl-flex-shrink-0
- .svg-250.svg-content
- = image_tag 'illustrations/empty-state/empty-deploy-keys-lg.svg'
- .gl-flex-grow-0.gl-flex-shrink-0
- .text-content.gl-mx-auto.gl-my-0.gl-p-5
- %h4.h4= _('Deploy Keys')
- %p= _('Deploy keys grant read/write access to all repositories in your instance')
- = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'gl-button btn btn-confirm btn-md'
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 9842457a2eb..6b571794625 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -42,7 +42,7 @@
- if has_button
.text-center
- if project_select_button
- = render 'shared/new_project_item_select', path: 'issues/new', label: _('New issue'), type: :issues, with_feature_enabled: 'issues'
+ = render 'shared/new_project_item_select', path: 'issues/new', label: _('issue'), type: :issues, with_feature_enabled: 'issues'
- elsif show_new_issue_link?(@project)
= link_to _('New issue'), button_path, class: 'gl-button btn btn-confirm', id: 'new_issue_link'
diff --git a/app/views/shared/empty_states/_merge_requests.html.haml b/app/views/shared/empty_states/_merge_requests.html.haml
index 72db4d4c846..d0c4fb2432c 100644
--- a/app/views/shared/empty_states/_merge_requests.html.haml
+++ b/app/views/shared/empty_states/_merge_requests.html.haml
@@ -40,6 +40,6 @@
- if has_button
.text-center
- if project_select_button
- = render 'shared/new_project_item_select', path: 'merge_requests/new', label: _('New merge request'), type: :merge_requests, with_feature_enabled: 'merge_requests'
+ = render 'shared/new_project_item_select', path: 'merge_requests/new', label: _('merge request'), type: :merge_requests, with_feature_enabled: 'merge_requests'
- else
= link_to _('New merge request'), button_path, class: 'gl-button btn btn-confirm', title: _('New merge request'), id: 'new_merge_request_link'
diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml
index 917ef666e85..c3120774826 100644
--- a/app/views/shared/empty_states/_wikis.html.haml
+++ b/app/views/shared/empty_states/_wikis.html.haml
@@ -13,7 +13,7 @@
= create_link
- if show_enable_confluence_integration?(@wiki.container)
= link_to s_('WikiEmpty|Enable the Confluence Wiki integration'),
- edit_project_service_path(@project, :confluence),
+ edit_project_integration_path(@project, :confluence),
class: 'btn gl-button', title: s_('WikiEmpty|Enable the Confluence Wiki integration')
- elsif @project && can?(current_user, :read_issue, @project)
diff --git a/app/views/shared/gitpod/_enable_gitpod_modal.html.haml b/app/views/shared/gitpod/_enable_gitpod_modal.html.haml
deleted file mode 100644
index 6a8ff98a09e..00000000000
--- a/app/views/shared/gitpod/_enable_gitpod_modal.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-#modal-enable-gitpod.modal.qa-enable-gitpod-modal
- .modal-dialog
- .modal-content
- .modal-header
- %h3.page-title= _('Enable Gitpod?')
- %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
- %span{ "aria-hidden": "true" } &times;
- .modal-body.p-3
- %p= (_("To use Gitpod you must first enable the feature in the integrations section of your %{user_prefs}.") % { user_prefs: link_to(_('user preferences'), profile_preferences_path(anchor: 'gitpod')) }).html_safe
- .modal-footer
- = link_to _('Cancel'), '#', class: "gl-button btn btn-default btn-cancel", "data-dismiss" => "modal"
- = link_to _('Enable Gitpod'), profile_path(user: { gitpod_enabled: true}), class: 'gl-button btn btn-confirm', method: :put
diff --git a/app/views/shared/hook_logs/_content.html.haml b/app/views/shared/hook_logs/_content.html.haml
index ab6875a09f7..d358340814c 100644
--- a/app/views/shared/hook_logs/_content.html.haml
+++ b/app/views/shared/hook_logs/_content.html.haml
@@ -2,8 +2,7 @@
%h4
POST
= hook_log.url
- %span.badge.gl-badge.badge-pill.badge-muted.sm.gl-ml-3
- = hook_log.trigger.singularize.titleize
+ = gl_badge_tag hook_log.trigger.singularize.titleize, { size: :sm }, { class: 'gl-ml-3' }
%p
= _('Completed in %{duration_seconds} seconds (%{relative_time})').html_safe % { duration_seconds: number_with_precision(hook_log.execution_duration, precision: 2), relative_time: time_ago_with_tooltip(hook_log.created_at) }
diff --git a/app/views/shared/hook_logs/_recent_deliveries_table.html.haml b/app/views/shared/hook_logs/_recent_deliveries_table.html.haml
index 31ef8560781..3c91c2f6ab4 100644
--- a/app/views/shared/hook_logs/_recent_deliveries_table.html.haml
+++ b/app/views/shared/hook_logs/_recent_deliveries_table.html.haml
@@ -13,8 +13,7 @@
%td
= render partial: 'shared/hook_logs/status_label', locals: { hook_log: hook_log }
%td.d-none.d-sm-table-cell
- %span.badge.badge-pill.gl-badge.badge-muted.sm
- = hook_log.trigger.singularize.titleize
+ = gl_badge_tag hook_log.trigger.singularize.titleize, { size: :sm }
%td
#{number_with_precision(hook_log.execution_duration, precision: 2)} sec
%td
diff --git a/app/views/shared/hook_logs/_status_label.html.haml b/app/views/shared/hook_logs/_status_label.html.haml
index b930074303c..6f82dc280f3 100644
--- a/app/views/shared/hook_logs/_status_label.html.haml
+++ b/app/views/shared/hook_logs/_status_label.html.haml
@@ -1,3 +1,4 @@
-- label_status = hook_log.success? ? 'badge-success' : 'badge-danger'
-%span{ class: "badge badge-pill gl-badge sm #{label_status}" }
- = hook_log.internal_error? ? _('Error') : hook_log.response_status
+- badge_variant = hook_log.success? ? :success : :danger
+- badge_text = hook_log.internal_error? ? _('Error') : hook_log.response_status
+
+= gl_badge_tag badge_text, { variant: badge_variant, size: :sm }
diff --git a/app/views/shared/integrations/_form.html.haml b/app/views/shared/integrations/_form.html.haml
index 89c127408e1..e2457bc0632 100644
--- a/app/views/shared/integrations/_form.html.haml
+++ b/app/views/shared/integrations/_form.html.haml
@@ -1,4 +1,4 @@
- integration = local_assigns.fetch(:integration)
-= form_for integration, as: :service, url: scoped_integration_path(integration, group: @group), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => scoped_test_integration_path(integration, group: @group) } } do |form|
- = render 'shared/service_settings', form: form, integration: integration
+= form_for integration, as: :service, url: scoped_integration_path(integration, group: @group), method: :put, html: { class: 'gl-show-field-errors integration-settings-form js-integration-settings-form', data: { 'test-url' => scoped_test_integration_path(integration, group: @group), testid: 'integration-form' } } do |form|
+ = render 'shared/integration_settings', form: form, integration: integration
diff --git a/app/views/shared/integrations/edit.html.haml b/app/views/shared/integrations/edit.html.haml
index acb0c7ee52e..4ceaedc2a69 100644
--- a/app/views/shared/integrations/edit.html.haml
+++ b/app/views/shared/integrations/edit.html.haml
@@ -7,4 +7,7 @@
= @integration.title
= render 'shared/integrations/tabs', integration: @integration, active_tab: 'edit' do
- = render 'shared/integrations/form', integration: @integration
+ - if vue_integration_form_enabled?
+ = render 'shared/integration_settings', integration: @integration
+ - else
+ = render 'shared/integrations/form', integration: @integration
diff --git a/app/views/shared/integrations/overrides.html.haml b/app/views/shared/integrations/overrides.html.haml
index b8585fdef1f..4619675cfef 100644
--- a/app/views/shared/integrations/overrides.html.haml
+++ b/app/views/shared/integrations/overrides.html.haml
@@ -6,5 +6,4 @@
%h3.page-title
= @integration.title
-= render 'shared/integrations/tabs', integration: @integration, active_tab: 'overrides' do
- .js-vue-integration-overrides{ data: integration_overrides_data(@integration) }
+.js-vue-integration-overrides{ data: integration_overrides_data(@integration, project: @project, group: @group) }
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 4024c5b77f6..16301789b65 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -64,9 +64,9 @@
for this project.
- if issuable.new_record?
- = form.submit "#{_('Create')} #{issuable.class.model_name.human.downcase}", class: 'gl-button btn btn-confirm gl-mr-2', data: { qa_selector: 'issuable_create_button' }
+ = form.submit "#{_('Create')} #{issuable.class.model_name.human.downcase}", class: 'gl-button btn btn-confirm gl-mr-2', data: { qa_selector: 'issuable_create_button', track_experiment: 'promote_mr_approvals_in_free', track_action: 'click_button', track_label: 'submit_mr', track_value: 0 }
- else
- = form.submit _('Save changes'), class: 'gl-button btn btn-confirm gl-mr-2'
+ = form.submit _('Save changes'), class: 'gl-button btn btn-confirm gl-mr-2', data: { track_experiment: 'promote_mr_approvals_in_free', track_action: 'click_button', track_label: 'submit_mr', track_value: 0 }
- if issuable.new_record?
= link_to _('Cancel'), polymorphic_path([@project, issuable.class]), class: 'btn gl-button btn-default'
diff --git a/app/views/shared/issuable/_label_page_create.html.haml b/app/views/shared/issuable/_label_page_create.html.haml
index cf7e6cf8365..84cdf129cb2 100644
--- a/app/views/shared/issuable/_label_page_create.html.haml
+++ b/app/views/shared/issuable/_label_page_create.html.haml
@@ -6,7 +6,10 @@
.dropdown-page-two.dropdown-new-label
= dropdown_title(create_label_title(subject), options: { back: true, close: show_close })
= dropdown_content do
- .dropdown-labels-error.js-label-error
+ .js-label-error.gl-alert.gl-alert-danger.gl-mb-3
+ .gl-alert-container
+ = sprite_icon('error', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
+ .gl-alert-content
%input#new_label_name.default-dropdown-input{ type: "text", placeholder: _('Name new label') }
.suggest-colors.suggest-colors-dropdown
= render_suggested_colors
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index e097852216c..3975748ba57 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -5,6 +5,11 @@
- placeholder = local_assigns[:placeholder] || _('Search or filter results...')
- block_css_class = type != :productivity_analytics ? 'row-content-block second-block' : ''
- is_epic_board = board&.to_type == "EpicBoard"
+- if @group.present?
+ - ff_resource = @group
+- else
+ - ff_resource = board&.resource_parent&.group
+
- if is_epic_board
- user_can_admin_list = can?(current_user, :admin_epic_board_list, board.resource_parent)
- elsif board
@@ -26,7 +31,7 @@
= check_box_tag checkbox_id, nil, false, class: "check-all-issues left"
- if is_epic_board
#js-board-filtered-search{ data: { full_path: @group&.full_path } }
- - elsif Feature.enabled?(:issue_boards_filtered_search, board&.resource_parent) && board
+ - elsif Feature.enabled?(:issue_boards_filtered_search, ff_resource) && board
#js-issue-board-filtered-search
- else
.issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index 7233e671caa..76feb4d1613 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -25,7 +25,7 @@
= check_box_tag 'merge_request[squash]', '1', issuable_squash_option?(issuable, project), class: 'form-check-input'
= label_tag 'merge_request[squash]', class: 'form-check-label' do
Squash commits when merge request is accepted.
- = link_to sprite_icon('question-o'), help_page_path('user/project/merge_requests/squash_and_merge'), target: '_blank'
+ = link_to sprite_icon('question-o'), help_page_path('user/project/merge_requests/squash_and_merge'), target: '_blank', rel: 'noopener noreferrer'
- if project.squash_always?
.gl-text-gray-400
= _('Required in this project.')
diff --git a/app/views/shared/labels/_form.html.haml b/app/views/shared/labels/_form.html.haml
index 6f65dbe4811..29f6dc02749 100644
--- a/app/views/shared/labels/_form.html.haml
+++ b/app/views/shared/labels/_form.html.haml
@@ -5,30 +5,35 @@
.col-sm-2.col-form-label
= f.label :title
.col-sm-10
- = f.text_field :title, class: "form-control js-label-title qa-label-title", required: true, autofocus: true
+ = f.text_field :title, class: "gl-form-input form-control js-label-title qa-label-title", required: true, autofocus: true
= render_if_exists 'shared/labels/create_label_help_text'
.form-group.row
.col-sm-2.col-form-label
= f.label :description
.col-sm-10
- = f.text_field :description, class: "form-control js-quick-submit qa-label-description"
+ = f.text_field :description, class: "gl-form-input form-control js-quick-submit qa-label-description"
.form-group.row
.col-sm-2.col-form-label
- = f.label :color, "Background color"
+ = f.label :color, _("Background color")
.col-sm-10
.input-group
.input-group-prepend
.input-group-text.label-color-preview &nbsp;
- = f.text_field :color, class: "form-control qa-label-color"
+ = f.text_field :color, class: "gl-form-input form-control qa-label-color"
.form-text.text-muted
- Choose any color.
+ = _('Choose any color.')
%br
- Or you can choose one of the suggested colors below
+ = _("Or you can choose one of the suggested colors below")
= render_suggested_colors
- .form-actions
+ .gl-display-flex.gl-justify-content-space-between.gl-p-5.gl-bg-gray-10.gl-border-t-solid.gl-border-t-gray-100.gl-border-t-1
+ %div
+ - if @label.persisted?
+ = f.submit _('Save changes'), class: 'btn gl-button btn-confirm js-save-button'
+ - else
+ = f.submit _('Create label'), class: 'btn gl-button btn-confirm js-save-button qa-label-create-button'
+ = link_to _('Cancel'), back_path, class: 'btn gl-button btn-default btn-cancel'
- if @label.persisted?
- = f.submit _('Save changes'), class: 'btn gl-button btn-confirm js-save-button'
- - else
- = f.submit 'Create label', class: 'btn gl-button btn-confirm js-save-button qa-label-create-button'
- = link_to _('Cancel'), back_path, class: 'btn gl-button btn-default btn-cancel'
+ - presented_label = @label.present
+ %button.btn.btn-danger.gl-button.btn-danger-secondary.js-delete-label-modal-button{ type: 'button', data: { label_name: presented_label.name, subject_name: presented_label.subject_name, destroy_path: presented_label.destroy_path } }
+ %span.gl-button-text= _('Delete')
diff --git a/app/views/shared/members/_badge.html.haml b/app/views/shared/members/_badge.html.haml
index 5f925ff0cad..e7bd055fe5d 100644
--- a/app/views/shared/members/_badge.html.haml
+++ b/app/views/shared/members/_badge.html.haml
@@ -1,4 +1,4 @@
-- type ||= 'info'
+- variant ||= :info
%span.px-1.py-1
- %span{ class: "badge badge-pill gl-badge sm badge-#{type}" }= yield
+ = gl_badge_tag yield, variant: variant, size: :sm
diff --git a/app/views/shared/members/_blocked_badge.html.haml b/app/views/shared/members/_blocked_badge.html.haml
index 95335ebe74d..6795128279f 100644
--- a/app/views/shared/members/_blocked_badge.html.haml
+++ b/app/views/shared/members/_blocked_badge.html.haml
@@ -1,3 +1,3 @@
- if user.blocked?
- = render 'shared/members/badge', type: 'danger' do
+ = render 'shared/members/badge', variant: :danger do
= _("Blocked")
diff --git a/app/views/shared/members/_its_you_badge.html.haml b/app/views/shared/members/_its_you_badge.html.haml
index b53ffd8032d..4442e57f86e 100644
--- a/app/views/shared/members/_its_you_badge.html.haml
+++ b/app/views/shared/members/_its_you_badge.html.haml
@@ -1,3 +1,3 @@
- if user == current_user
- = render 'shared/members/badge', type: 'success' do
+ = render 'shared/members/badge', variant: :success do
= _("It's you")
diff --git a/app/views/shared/members/_two_factor_auth_badge.html.haml b/app/views/shared/members/_two_factor_auth_badge.html.haml
index 34850c135d6..f1cb7a5bd85 100644
--- a/app/views/shared/members/_two_factor_auth_badge.html.haml
+++ b/app/views/shared/members/_two_factor_auth_badge.html.haml
@@ -1,3 +1,3 @@
- if user.two_factor_enabled?
- = render 'shared/members/badge', type: 'info' do
+ = render 'shared/members/badge', variant: :info do
= _("2FA")
diff --git a/app/views/shared/nav/_sidebar_menu.html.haml b/app/views/shared/nav/_sidebar_menu.html.haml
index 4c4ceb9ea70..bc0648c14e0 100644
--- a/app/views/shared/nav/_sidebar_menu.html.haml
+++ b/app/views/shared/nav/_sidebar_menu.html.haml
@@ -13,7 +13,7 @@
%span.nav-item-name{ **sidebar_menu.title_html_options }
= sidebar_menu.title
- if sidebar_menu.has_pill?
- %span.badge.badge-pill.count{ **sidebar_menu.pill_html_options }
+ = gl_badge_tag({ variant: :info, size: :sm }, { class: "count #{sidebar_menu.pill_html_options[:class]}" }) do
= number_with_delimiter(sidebar_menu.pill_count)
= render partial: 'shared/nav/sidebar_submenu', locals: { sidebar_menu: sidebar_menu }
diff --git a/app/views/shared/notes/_hints.html.haml b/app/views/shared/notes/_hints.html.haml
index 6231f817704..6c8b2a9e5bb 100644
--- a/app/views/shared/notes/_hints.html.haml
+++ b/app/views/shared/notes/_hints.html.haml
@@ -2,10 +2,10 @@
- supports_file_upload = local_assigns.fetch(:supports_file_upload, true)
.comment-toolbar.clearfix
.toolbar-text
- = link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank'
+ = link_to _('Markdown'), help_page_path('user/markdown'), target: '_blank', rel: 'noopener noreferrer'
- if supports_quick_actions
and
- = link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank'
+ = link_to _('quick actions'), help_page_path('user/project/quick_actions'), target: '_blank', rel: 'noopener noreferrer'
are
- else
is
diff --git a/app/views/shared/runners/_shared_runners_description.html.haml b/app/views/shared/runners/_shared_runners_description.html.haml
index e2b57a7fd73..2779901ca0c 100644
--- a/app/views/shared/runners/_shared_runners_description.html.haml
+++ b/app/views/shared/runners/_shared_runners_description.html.haml
@@ -1,5 +1,5 @@
-# "MaxBuilds" is a runner configuration keyword so it must not be translated.
-- link = link_to 'MaxBuilds', 'https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersmachine-section', target: '_blank'
+- link = link_to 'MaxBuilds', 'https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runnersmachine-section', target: '_blank', rel: 'noopener noreferrer'
%h4
= _('Shared runners')
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index 18912bf149f..5650f08b2a9 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -4,12 +4,14 @@
= form.label :url, s_('Webhooks|URL'), class: 'label-bold'
= form.text_field :url, class: 'form-control gl-form-input', placeholder: 'http://example.com/trigger-ci.json'
%p.form-text.text-muted
- = s_('Webhooks|URL must be percent-encoded if neccessary.')
+ = s_('Webhooks|URL must be percent-encoded if it contains one or more special characters.')
.form-group
= form.label :token, s_('Webhooks|Secret token'), class: 'label-bold'
= form.text_field :token, class: 'form-control gl-form-input', placeholder: ''
%p.form-text.text-muted
- = s_('Webhooks|Use this token to validate received payloads. It is sent with the request in the X-Gitlab-Token HTTP header.')
+ - code_start = '<code>'.html_safe
+ - code_end = '</code>'.html_safe
+ = s_('Webhooks|Used to validate received payloads. Sent with the request in the %{code_start}X-Gitlab-Token HTTP%{code_end} header.').html_safe % { code_start: code_start, code_end: code_end }
.form-group
= form.label :url, s_('Webhooks|Trigger'), class: 'label-bold'
%ul.list-unstyled.gl-ml-6
@@ -19,37 +21,37 @@
%strong= s_('Webhooks|Push events')
= form.text_field :push_events_branch_filter, class: 'form-control gl-form-input', placeholder: 'Branch name or wildcard pattern to trigger on (leave blank for all)'
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered by a push to the repository')
+ = s_('Webhooks|Push to the repository.')
%li
= form.check_box :tag_push_events, class: 'form-check-input'
= form.label :tag_push_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Tag push events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when a new tag is pushed to the repository')
+ = s_('Webhooks|A new tag is pushed to the repository.')
%li
= form.check_box :note_events, class: 'form-check-input'
= form.label :note_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Comments')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when someone adds a comment')
+ = s_('Webhooks|A comment is added to an issue.')
%li
= form.check_box :confidential_note_events, class: 'form-check-input'
= form.label :confidential_note_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Confidential comments')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when someone adds a comment on a confidential issue')
+ = s_('Webhooks|A comment is added to a confidential issue.')
%li
= form.check_box :issues_events, class: 'form-check-input'
= form.label :issues_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Issues events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when an issue is created, updated, closed, or reopened')
+ = s_('Webhooks|An issue is created, updated, closed, or reopened.')
%li
= form.check_box :confidential_issues_events, class: 'form-check-input'
= form.label :confidential_issues_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Confidential issues events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when a confidential issue is created, updated, closed, or reopened')
+ = s_('Webhooks|A confidential issue is created, updated, closed, or reopened.')
- if @group
= render_if_exists 'groups/hooks/member_events', form: form
= render_if_exists 'groups/hooks/subgroup_events', form: form
@@ -58,43 +60,43 @@
= form.label :merge_requests_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Merge request events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when a merge request is created, updated, or merged')
+ = s_('Webhooks|A merge request is created, updated, or merged.')
%li
= form.check_box :job_events, class: 'form-check-input'
= form.label :job_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Job events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when the job status changes')
+ = s_("Webhooks|A job's status changes.")
%li
= form.check_box :pipeline_events, class: 'form-check-input'
= form.label :pipeline_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Pipeline events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when the pipeline status changes')
+ = s_("Webhooks|A pipeline's status changes.")
%li
= form.check_box :wiki_page_events, class: 'form-check-input'
= form.label :wiki_page_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Wiki page events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when a wiki page is created or updated')
+ = s_('Webhooks|A wiki page is created or updated.')
%li
= form.check_box :deployment_events, class: 'form-check-input'
= form.label :deployment_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Deployment events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when a deployment starts, finishes, fails, or is canceled')
+ = s_('Webhooks|A deployment starts, finishes, fails, or is canceled.')
%li
= form.check_box :feature_flag_events, class: 'form-check-input'
= form.label :feature_flag_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Feature flag events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when a feature flag is turned on or off')
+ = s_('Webhooks|A feature flag is turned on or off.')
%li
= form.check_box :releases_events, class: 'form-check-input'
= form.label :releases_events, class: 'list-label form-check-label gl-ml-1' do
%strong= s_('Webhooks|Releases events')
%p.text-muted.gl-ml-1
- = s_('Webhooks|URL is triggered when a release is created or updated')
+ = s_('Webhooks|A release is created or updated.')
.form-group
= form.label :enable_ssl_verification, s_('Webhooks|SSL verification'), class: 'label-bold checkbox'
.form-check
diff --git a/app/views/shared/web_hooks/_hook.html.haml b/app/views/shared/web_hooks/_hook.html.haml
index 45baa7e2184..c5a03ef4dc1 100644
--- a/app/views/shared/web_hooks/_hook.html.haml
+++ b/app/views/shared/web_hooks/_hook.html.haml
@@ -1,22 +1,23 @@
+- sslStatus = hook.enable_ssl_verification ? _('enabled') : _('disabled')
+- sslBadgeText = _('SSL Verification:') + ' ' + sslStatus
+
%li
.row
.col-md-8.col-lg-7
%strong.light-header
= hook.url
- if hook.rate_limited?
- %span.gl-badge.badge-danger.badge-pill.sm= _('Disabled')
+ = gl_badge_tag(_('Disabled'), variant: :danger, size: :sm)
- elsif hook.permanently_disabled?
- %span.gl-badge.badge-danger.badge-pill.sm= s_('Webhooks|Failed to connect')
+ = gl_badge_tag(s_('Webhooks|Failed to connect'), variant: :danger, size: :sm)
- elsif hook.temporarily_disabled?
- %span.gl-badge.badge-warning.badge-pill.sm= s_('Webhooks|Fails to connect')
+ = gl_badge_tag(s_('Webhooks|Fails to connect'), variant: :warning, size: :sm)
%div
- hook.class.triggers.each_value do |trigger|
- if hook.public_send(trigger)
- %span.gl-badge.badge-muted.badge-pill.sm.gl-mt-2.deploy-project-label= trigger.to_s.titleize
- %span.gl-badge.badge-muted.badge-pill.sm.gl-mt-2
- = _('SSL Verification:')
- = hook.enable_ssl_verification ? _('enabled') : _('disabled')
+ = gl_badge_tag(trigger.to_s.titleize, size: :sm)
+ = gl_badge_tag(sslBadgeText, size: :sm)
.col-md-4.col-lg-5.text-right-md.gl-mt-2
%span>= render 'shared/web_hooks/test_button', hook: hook, button_class: 'btn-sm btn-default gl-mr-3'
diff --git a/app/views/shared/web_hooks/_index.html.haml b/app/views/shared/web_hooks/_index.html.haml
index f1eef5d7f0f..5d07b0f95ab 100644
--- a/app/views/shared/web_hooks/_index.html.haml
+++ b/app/views/shared/web_hooks/_index.html.haml
@@ -11,4 +11,4 @@
= render 'shared/web_hooks/hook', hook: hook
- else
%p.text-center.gl-mt-3.gl-mb-3
- = _('No webhooks found, add one in the form above.')
+ = _('No webhooks enabled. Select trigger events above.')
diff --git a/app/views/sherlock/file_samples/show.html.haml b/app/views/sherlock/file_samples/show.html.haml
deleted file mode 100644
index b7e6f883667..00000000000
--- a/app/views/sherlock/file_samples/show.html.haml
+++ /dev/null
@@ -1,55 +0,0 @@
-- page_title t('sherlock.title'), t('sherlock.transaction'),
- t('sherlock.file_sample')
-
-- header_title t('sherlock.title'), sherlock_transactions_path
-
-.row-content-block
- .float-right
- = link_to(sherlock_transaction_path(@transaction), class: 'btn gl-button') do
- = sprite_icon('arrow-left')
- = t('sherlock.transaction')
- .oneline
- = t('sherlock.file_sample')
- = @file_sample.id
-
-.gl-mt-3
- %p
- %span.light
- #{t('sherlock.time')}:
- %strong
- = @file_sample.duration.round(2)
- = t('sherlock.milliseconds')
- %p
- %span.light
- #{t('sherlock.events')}:
- %strong
- = @file_sample.events
-
-%article.file-holder
- .js-file-title.file-title
- = sprite_icon("doc-text")
- %strong
- = @file_sample.file
- .code.file-content.js-syntax-highlight
- .line-numbers
- %table.sherlock-line-samples-table.gl-mb-0
- %thead
- %tr
- %th= t('sherlock.line_capitalized')
- %th= t('sherlock.events')
- %th= t('sherlock.time')
- %th= t('sherlock.percent')
- %tbody
- - @file_sample.line_samples.each_with_index do |sample, index|
- %tr{ class: sample.majority_of?(@file_sample.duration) ? 'slow' : '' }
- %td= index + 1
- %td= sample.events
- %td
- = sample.duration.round(2)
- = t('sherlock.milliseconds')
- %td
- = sample.percentage_of(@file_sample.duration).round
- = t('sherlock.percent')
-
- .sherlock-file-sample
- = highlight(@file_sample.file, @file_sample.source)
diff --git a/app/views/sherlock/queries/_backtrace.html.haml b/app/views/sherlock/queries/_backtrace.html.haml
deleted file mode 100644
index 425113ba325..00000000000
--- a/app/views/sherlock/queries/_backtrace.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-.gl-mt-3
- .card
- .card-header
- %strong
- = t('sherlock.application_backtrace')
- %ul.content-list
- - @query.application_backtrace.each do |location|
- %li
- %strong
- - if defined?(BetterErrors)
- = link_to(location.path, BetterErrors.editor.url(location.path, location.line))
- - else
- = location.path
- %small.light
- = t('sherlock.line')
- = location.line
-
- .card
- .card-header
- %strong
- = t('sherlock.full_backtrace')
- %ul.content-list
- - @query.backtrace.each do |location|
- %li
- - if location.application?
- %strong= location.path
- - else
- = location.path
- %small.light
- = t('sherlock.line')
- = location.line
diff --git a/app/views/sherlock/queries/_general.html.haml b/app/views/sherlock/queries/_general.html.haml
deleted file mode 100644
index a16314213c4..00000000000
--- a/app/views/sherlock/queries/_general.html.haml
+++ /dev/null
@@ -1,54 +0,0 @@
-.gl-mt-3
- .card
- .card-header
- %strong
- = t('sherlock.general')
- %ul.content-list
- %li
- %span.light
- #{t('sherlock.time')}:
- %strong
- = @query.duration.round(4)
- = t('sherlock.milliseconds')
- %li
- - frame = @query.last_application_frame
- %span.light
- #{t('sherlock.origin')}:
- %strong
- - if defined?(BetterErrors)
- = link_to(frame.path, BetterErrors.editor.url(frame.path, frame.line))
- - else
- = frame.path
- %small.light
- = t('sherlock.line')
- = frame.line
-
- .card
- .card-header
- .float-right
- %button.js-clipboard-trigger.gl-button.btn.btn-default.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button }
- = sprite_icon('copy-to-clipboard')
- %pre.hidden
- = @query.formatted_query
- %strong
- = t('sherlock.query')
- %ul.content-list
- %li
- .code.js-syntax-highlight.sherlock-code
- :preserve
- #{highlight("#{@query.id}.sql", @query.formatted_query, language: 'sql')}
-
- .card
- .card-header
- .float-right
- %button.js-clipboard-trigger.gl-button.btn.btn-default.btn-sm{ title: t('sherlock.copy_to_clipboard'), type: :button }
- = sprite_icon('copy-to-clipboard')
- %pre.hidden
- = @query.explain
- %strong
- = t('sherlock.query_plan')
- %ul.content-list
- %li
- .code.js-syntax-highlight.sherlock-code
- %pre
- %code= @query.explain
diff --git a/app/views/sherlock/queries/show.html.haml b/app/views/sherlock/queries/show.html.haml
deleted file mode 100644
index eea13d105d8..00000000000
--- a/app/views/sherlock/queries/show.html.haml
+++ /dev/null
@@ -1,26 +0,0 @@
-- page_title t('sherlock.title'), t('sherlock.transaction'), t('sherlock.query')
-- header_title t('sherlock.title'), sherlock_transactions_path
-
-%ul.nav-links.nav.nav-tabs
- %li.active
- %a{ href: "#tab-general", data: { toggle: "tab" } }
- = t('sherlock.general')
- %li
- %a{ href: "#tab-backtrace", data: { toggle: "tab" } }
- = t('sherlock.backtrace')
-
-.row-content-block
- .float-right
- = link_to(sherlock_transaction_path(@transaction), class: 'btn gl-button btn-default') do
- = sprite_icon('arrow-left')
- = t('sherlock.transaction')
- .oneline
- = t('sherlock.query')
- = @query.id
-
-.tab-content
- .tab-pane.active#tab-general
- = render(partial: 'general')
-
- .tab-pane#tab-backtrace
- = render(partial: 'backtrace')
diff --git a/app/views/sherlock/transactions/_file_samples.html.haml b/app/views/sherlock/transactions/_file_samples.html.haml
deleted file mode 100644
index 110eb42f9ea..00000000000
--- a/app/views/sherlock/transactions/_file_samples.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-- if @transaction.file_samples.empty?
- .nothing-here-block
- = t('sherlock.no_file_samples')
-- else
- .table-holder
- %table.table
- %thead
- %tr
- %th= t('sherlock.time_inclusive')
- %th= t('sherlock.count')
- %th= t('sherlock.path')
- %th
- %tbody
- - @transaction.sorted_file_samples.each do |sample|
- %tr
- %td
- = sample.duration.round(2)
- = t('sherlock.milliseconds')
- %td= @transaction.view_counts.fetch(sample.file, 1)
- %td= sample.relative_path
- %td
- = link_to(t('sherlock.view'),
- sherlock_transaction_file_sample_path(@transaction, sample),
- class: 'gl-button btn btn-sm')
diff --git a/app/views/sherlock/transactions/_general.html.haml b/app/views/sherlock/transactions/_general.html.haml
deleted file mode 100644
index 7cf6f27e1af..00000000000
--- a/app/views/sherlock/transactions/_general.html.haml
+++ /dev/null
@@ -1,38 +0,0 @@
-.gl-mt-3
- .card
- .card-header
- %strong
- = t('sherlock.general')
- %ul.content-list
- %li
- %span.light
- #{t('sherlock.id')}:
- %strong
- = @transaction.id
- %li
- %span.light
- #{t('sherlock.type')}:
- %strong
- = @transaction.type
- %li
- %span.light
- #{t('sherlock.path')}:
- %strong
- = @transaction.path
- %li
- %span.light
- #{t('sherlock.time')}:
- %strong
- = @transaction.duration.round(2)
- = t('sherlock.seconds')
- %li
- %span.light
- #{t('sherlock.query_time')}
- %strong
- = @transaction.query_duration.round(2)
- = t('sherlock.seconds')
- %li
- %span.light
- #{t('sherlock.finished_at')}:
- %strong
- = time_ago_with_tooltip @transaction.finished_at
diff --git a/app/views/sherlock/transactions/_queries.html.haml b/app/views/sherlock/transactions/_queries.html.haml
deleted file mode 100644
index afe23d61bcd..00000000000
--- a/app/views/sherlock/transactions/_queries.html.haml
+++ /dev/null
@@ -1,24 +0,0 @@
-- if @transaction.queries.empty?
- .nothing-here-block
- = t('sherlock.no_queries')
-- else
- .table-holder
- %table.table#sherlock-queries
- %thead
- %tr
- %th= t('sherlock.time')
- %th= t('sherlock.query')
- %th
- %tbody
- - @transaction.sorted_queries.each do |query|
- %tr
- %td
- = query.duration.round(2)
- = t('sherlock.milliseconds')
- %td
- .code.js-syntax-highlight.sherlock-code
- = highlight("#{query.id}.sql", query.formatted_query, language: 'sql')
- %td
- = link_to(t('sherlock.view'),
- sherlock_transaction_query_path(@transaction, query),
- class: 'gl-button btn btn-sm')
diff --git a/app/views/sherlock/transactions/index.html.haml b/app/views/sherlock/transactions/index.html.haml
deleted file mode 100644
index a2be6cae1e0..00000000000
--- a/app/views/sherlock/transactions/index.html.haml
+++ /dev/null
@@ -1,41 +0,0 @@
-- page_title t('sherlock.title')
-- header_title t('sherlock.title'), sherlock_transactions_path
-
-.row-content-block
- .float-right
- = link_to(destroy_all_sherlock_transactions_path,
- class: 'gl-button btn btn-danger',
- method: :delete) do
- = sprite_icon('remove')
- = t('sherlock.delete_all_transactions')
- .oneline= t('sherlock.introduction')
-
-- if @transactions.empty?
- .nothing-here-block= t('sherlock.no_transactions')
-- else
- .table-holder
- %table.table
- %thead
- %tr
- %th= t('sherlock.type')
- %th= t('sherlock.path')
- %th= t('sherlock.time')
- %th= t('sherlock.queries')
- %th= t('sherlock.finished_at')
- %th
- %tbody
- - @transactions.each do |trans|
- %tr
- %td= trans.type
- %td
- %span{ title: trans.path }
- = truncate(trans.path, length: 70)
- %td
- = trans.duration.round(2)
- = t('sherlock.seconds')
- %td= trans.queries.length
- %td
- = time_ago_with_tooltip trans.finished_at
- %td
- = link_to(sherlock_transaction_path(trans), class: 'gl-button btn btn-sm') do
- = t('sherlock.view')
diff --git a/app/views/sherlock/transactions/show.html.haml b/app/views/sherlock/transactions/show.html.haml
deleted file mode 100644
index 162b14f01e1..00000000000
--- a/app/views/sherlock/transactions/show.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-- page_title t('sherlock.title'), t('sherlock.transaction')
-- header_title t('sherlock.title'), sherlock_transactions_path
-
-%ul.nav-links.nav.nav-tabs
- %li.active
- %a{ href: "#tab-general", data: { toggle: "tab" } }
- = t('sherlock.general')
- %li
- %a{ href: "#tab-queries", data: { toggle: "tab" } }
- = t('sherlock.queries')
- %span.badge.badge-pill
- #{@transaction.queries.length}
- %li
- %a{ href: "#tab-file-samples", data: { toggle: "tab" } }
- = t('sherlock.file_samples')
- %span.badge.badge-pill
- #{@transaction.file_samples.length}
-
-.row-content-block
- .float-right
- = link_to(sherlock_transactions_path, class: 'gl-button btn') do
- = sprite_icon('arrow-left', css_class: 'gl-mr-3')
- = t('sherlock.all_transactions')
- .oneline
- = t('sherlock.transaction')
- = @transaction.id
-
-.tab-content
- .tab-pane.active#tab-general
- = render(partial: 'general')
-
- .tab-pane#tab-queries
- = render(partial: 'queries')
-
- .tab-pane#tab-file-samples
- = render(partial: 'file_samples')
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index ca276519758..d5a1f3884c9 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -98,15 +98,15 @@
- unless @user.skype.blank?
= render 'middle_dot_divider' do
= link_to "skype:#{@user.skype}", class: 'gl-hover-text-decoration-none', title: "Skype" do
- = sprite_icon('skype')
+ = sprite_icon('skype', css_class: 'skype-icon')
- unless @user.linkedin.blank?
= render 'middle_dot_divider' do
= link_to linkedin_url(@user), class: 'gl-hover-text-decoration-none', title: "LinkedIn", target: '_blank', rel: 'noopener noreferrer nofollow' do
- = sprite_icon('linkedin')
+ = sprite_icon('linkedin', css_class: 'linkedin-icon')
- unless @user.twitter.blank?
= render 'middle_dot_divider', breakpoint: 'sm' do
= link_to twitter_url(@user), class: 'gl-hover-text-decoration-none', title: "Twitter", target: '_blank', rel: 'noopener noreferrer nofollow' do
- = sprite_icon('twitter')
+ = sprite_icon('twitter', css_class: 'twitter-icon')
- unless @user.website_url.blank?
= render 'middle_dot_divider', stacking: true do
- if Feature.enabled?(:security_auto_fix) && @user.bot?
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index f2961d825a0..14fa6599073 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -129,6 +129,15 @@
:weight: 2
:idempotent:
:tags: []
+- :name: cluster_agent:clusters_agents_delete_expired_events
+ :worker_name: Clusters::Agents::DeleteExpiredEventsWorker
+ :feature_category: :kubernetes_management
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: container_repository:cleanup_container_repository
:worker_name: CleanupContainerRepositoryWorker
:feature_category: :container_registry
@@ -435,6 +444,15 @@
:weight: 1
:idempotent:
:tags: []
+- :name: cronjob:packages_cleanup_package_registry
+ :worker_name: Packages::CleanupPackageRegistryWorker
+ :feature_category: :package_registry
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: cronjob:packages_composer_cache_cleanup
:worker_name: Packages::Composer::CacheCleanupWorker
:feature_category: :package_registry
@@ -1357,6 +1375,15 @@
:weight: 1
:idempotent:
:tags: []
+- :name: package_cleanup:packages_cleanup_package_file
+ :worker_name: Packages::CleanupPackageFileWorker
+ :feature_category: :package_registry
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: package_repositories:packages_debian_generate_distribution
:worker_name: Packages::Debian::GenerateDistributionWorker
:feature_category: :package_registry
@@ -1539,7 +1566,7 @@
:tags: []
- :name: pipeline_creation:ci_external_pull_requests_create_pipeline
:worker_name: Ci::ExternalPullRequests::CreatePipelineWorker
- :feature_category: :pipeline_authoring
+ :feature_category: :continuous_integration
:has_external_dependencies:
:urgency: :high
:resource_boundary: :cpu
@@ -1987,6 +2014,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: ci_job_artifacts_expire_project_build_artifacts
+ :worker_name: Ci::JobArtifacts::ExpireProjectBuildArtifactsWorker
+ :feature_category: :build_artifacts
+ :has_external_dependencies:
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: create_commit_signature
:worker_name: CreateCommitSignatureWorker
:feature_category: :source_code_management
@@ -2420,6 +2456,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: merge_requests_update_head_pipeline
+ :worker_name: MergeRequests::UpdateHeadPipelineWorker
+ :feature_category: :code_review
+ :has_external_dependencies:
+ :urgency: :high
+ :resource_boundary: :cpu
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: metrics_dashboard_prune_old_annotations
:worker_name: Metrics::Dashboard::PruneOldAnnotationsWorker
:feature_category: :metrics
@@ -2909,7 +2954,7 @@
:tags: []
- :name: update_external_pull_requests
:worker_name: UpdateExternalPullRequestsWorker
- :feature_category: :source_code_management
+ :feature_category: :continuous_integration
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
diff --git a/app/workers/ci/build_finished_worker.rb b/app/workers/ci/build_finished_worker.rb
index aa12bdb009e..56cfaa7e674 100644
--- a/app/workers/ci/build_finished_worker.rb
+++ b/app/workers/ci/build_finished_worker.rb
@@ -40,7 +40,7 @@ module Ci
BuildHooksWorker.perform_async(build.id)
ChatNotificationWorker.perform_async(build.id) if build.pipeline.chat?
- if build.failed?
+ if build.failed? && !build.auto_retry_expected?
::Ci::MergeRequests::AddTodoWhenBuildFailsWorker.perform_async(build.id)
end
diff --git a/app/workers/ci/external_pull_requests/create_pipeline_worker.rb b/app/workers/ci/external_pull_requests/create_pipeline_worker.rb
index 211ea1f2990..334ff099ea2 100644
--- a/app/workers/ci/external_pull_requests/create_pipeline_worker.rb
+++ b/app/workers/ci/external_pull_requests/create_pipeline_worker.rb
@@ -7,7 +7,7 @@ module Ci
data_consistency :always
queue_namespace :pipeline_creation
- feature_category :pipeline_authoring
+ feature_category :continuous_integration
urgency :high
worker_resource_boundary :cpu
diff --git a/app/workers/ci/job_artifacts/expire_project_build_artifacts_worker.rb b/app/workers/ci/job_artifacts/expire_project_build_artifacts_worker.rb
new file mode 100644
index 00000000000..299b9bbe3d3
--- /dev/null
+++ b/app/workers/ci/job_artifacts/expire_project_build_artifacts_worker.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Ci
+ module JobArtifacts
+ class ExpireProjectBuildArtifactsWorker
+ include ApplicationWorker
+
+ data_consistency :always
+
+ feature_category :build_artifacts
+ idempotent!
+
+ def perform(project_id)
+ return unless Project.id_in(project_id).exists?
+
+ ExpireProjectBuildArtifactsService.new(project_id, Time.current).execute
+ end
+ end
+ end
+end
diff --git a/app/workers/clusters/agents/delete_expired_events_worker.rb b/app/workers/clusters/agents/delete_expired_events_worker.rb
new file mode 100644
index 00000000000..3414365a243
--- /dev/null
+++ b/app/workers/clusters/agents/delete_expired_events_worker.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Agents
+ class DeleteExpiredEventsWorker
+ include ApplicationWorker
+ include ClusterAgentQueue
+
+ deduplicate :until_executed, including_scheduled: true
+ idempotent!
+
+ data_consistency :always
+
+ def perform(agent_id)
+ if agent = Clusters::Agent.find_by_id(agent_id)
+ Clusters::Agents::DeleteExpiredEventsService.new(agent).execute
+ end
+ end
+ end
+ end
+end
diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb
index d0b09c15289..83261d9e42e 100644
--- a/app/workers/concerns/application_worker.rb
+++ b/app/workers/concerns/application_worker.rb
@@ -189,12 +189,8 @@ module ApplicationWorker
schedule_at = bulk_schedule_at
end
- if Feature.enabled?(:sidekiq_push_bulk_in_batches)
- in_safe_limit_batches(args_list, schedule_at) do |args_batch, schedule_at_for_batch|
- Sidekiq::Client.push_bulk('class' => self, 'args' => args_batch, 'at' => schedule_at_for_batch)
- end
- else
- Sidekiq::Client.push_bulk('class' => self, 'args' => args_list, 'at' => schedule_at)
+ in_safe_limit_batches(args_list, schedule_at) do |args_batch, schedule_at_for_batch|
+ Sidekiq::Client.push_bulk('class' => self, 'args' => args_batch, 'at' => schedule_at_for_batch)
end
end
@@ -207,12 +203,8 @@ module ApplicationWorker
private
def do_push_bulk(args_list)
- if Feature.enabled?(:sidekiq_push_bulk_in_batches)
- in_safe_limit_batches(args_list) do |args_batch, _|
- Sidekiq::Client.push_bulk('class' => self, 'args' => args_batch)
- end
- else
- Sidekiq::Client.push_bulk('class' => self, 'args' => args_list)
+ in_safe_limit_batches(args_list) do |args_batch, _|
+ Sidekiq::Client.push_bulk('class' => self, 'args' => args_batch)
end
end
diff --git a/app/workers/concerns/cluster_agent_queue.rb b/app/workers/concerns/cluster_agent_queue.rb
new file mode 100644
index 00000000000..68de7cca135
--- /dev/null
+++ b/app/workers/concerns/cluster_agent_queue.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module ClusterAgentQueue
+ extend ActiveSupport::Concern
+
+ included do
+ queue_namespace :cluster_agent
+ feature_category :kubernetes_management
+ end
+end
diff --git a/app/workers/concerns/dependency_proxy/expireable.rb b/app/workers/concerns/dependency_proxy/expireable.rb
index 9650ac85c6c..7e37db36bef 100644
--- a/app/workers/concerns/dependency_proxy/expireable.rb
+++ b/app/workers/concerns/dependency_proxy/expireable.rb
@@ -10,7 +10,7 @@ module DependencyProxy
def expire_artifacts(collection)
collection.each_batch(of: UPDATE_BATCH_SIZE) do |batch|
- batch.update_all(status: :expired)
+ batch.update_all(status: :pending_destruction)
end
end
end
diff --git a/app/workers/concerns/dependency_proxy/cleanup_worker.rb b/app/workers/concerns/packages/cleanup_artifact_worker.rb
index b668634f233..db6c7330ea3 100644
--- a/app/workers/concerns/dependency_proxy/cleanup_worker.rb
+++ b/app/workers/concerns/packages/cleanup_artifact_worker.rb
@@ -1,8 +1,9 @@
# frozen_string_literal: true
-module DependencyProxy
- module CleanupWorker
+module Packages
+ module CleanupArtifactWorker
extend ActiveSupport::Concern
+ include LimitedCapacity::Worker
include Gitlab::Utils::StrongMemoize
def perform_work
@@ -15,12 +16,8 @@ module DependencyProxy
artifact&.error!
end
- def max_running_jobs
- ::Gitlab::CurrentSettings.dependency_proxy_ttl_group_policy_worker_capacity
- end
-
def remaining_work_count
- expired_artifacts.limit(max_running_jobs + 1).count
+ artifacts_pending_destruction.limit(max_running_jobs + 1).count
end
private
@@ -52,12 +49,12 @@ module DependencyProxy
end
end
- def expired_artifacts
- model.expired
+ def artifacts_pending_destruction
+ model.pending_destruction
end
def next_item
- expired_artifacts.lock_next_by(:updated_at).first
+ model.next_pending_destruction(order_by: :updated_at)
end
end
end
diff --git a/app/workers/dependency_proxy/cleanup_blob_worker.rb b/app/workers/dependency_proxy/cleanup_blob_worker.rb
index 054bc5854a3..eab536cd191 100644
--- a/app/workers/dependency_proxy/cleanup_blob_worker.rb
+++ b/app/workers/dependency_proxy/cleanup_blob_worker.rb
@@ -3,9 +3,8 @@
module DependencyProxy
class CleanupBlobWorker
include ApplicationWorker
- include LimitedCapacity::Worker
+ include ::Packages::CleanupArtifactWorker
include Gitlab::Utils::StrongMemoize
- include DependencyProxy::CleanupWorker
data_consistency :always
@@ -17,6 +16,10 @@ module DependencyProxy
worker_resource_boundary :unknown
idempotent!
+ def max_running_jobs
+ ::Gitlab::CurrentSettings.dependency_proxy_ttl_group_policy_worker_capacity
+ end
+
private
def model
diff --git a/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb b/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb
index d77c782267a..6a958d6e9d7 100644
--- a/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb
+++ b/app/workers/dependency_proxy/cleanup_dependency_proxy_worker.rb
@@ -11,8 +11,8 @@ module DependencyProxy
feature_category :dependency_proxy
def perform
- enqueue_blob_cleanup_job if DependencyProxy::Blob.expired.any?
- enqueue_manifest_cleanup_job if DependencyProxy::Manifest.expired.any?
+ enqueue_blob_cleanup_job if DependencyProxy::Blob.pending_destruction.any?
+ enqueue_manifest_cleanup_job if DependencyProxy::Manifest.pending_destruction.any?
end
private
diff --git a/app/workers/dependency_proxy/cleanup_manifest_worker.rb b/app/workers/dependency_proxy/cleanup_manifest_worker.rb
index 1186efa2034..4445ee32e6d 100644
--- a/app/workers/dependency_proxy/cleanup_manifest_worker.rb
+++ b/app/workers/dependency_proxy/cleanup_manifest_worker.rb
@@ -3,9 +3,8 @@
module DependencyProxy
class CleanupManifestWorker
include ApplicationWorker
- include LimitedCapacity::Worker
+ include ::Packages::CleanupArtifactWorker
include Gitlab::Utils::StrongMemoize
- include DependencyProxy::CleanupWorker
data_consistency :always
@@ -17,6 +16,10 @@ module DependencyProxy
worker_resource_boundary :unknown
idempotent!
+ def max_running_jobs
+ ::Gitlab::CurrentSettings.dependency_proxy_ttl_group_policy_worker_capacity
+ end
+
private
def model
diff --git a/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb b/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb
index 3de2364fc71..89099ab6398 100644
--- a/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb
+++ b/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb
@@ -26,8 +26,8 @@ module DependencyProxy
def log_counts
use_replica_if_available do
- expired_blob_count = DependencyProxy::Blob.expired.count
- expired_manifest_count = DependencyProxy::Manifest.expired.count
+ expired_blob_count = DependencyProxy::Blob.pending_destruction.count
+ expired_manifest_count = DependencyProxy::Manifest.pending_destruction.count
processing_blob_count = DependencyProxy::Blob.processing.count
processing_manifest_count = DependencyProxy::Manifest.processing.count
error_blob_count = DependencyProxy::Blob.error.count
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index 321d492f0f3..54689df4d7b 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -90,37 +90,6 @@ class EmailReceiverWorker # rubocop:disable Scalability/IdempotentWorker
def handle_failure(error)
return unless raw.present?
- can_retry = false
- reason =
- case error
- when Gitlab::Email::UnknownIncomingEmail
- s_("EmailError|We couldn't figure out what the email is for. Please create your issue or comment through the web interface.")
- when Gitlab::Email::SentNotificationNotFoundError
- s_("EmailError|We couldn't figure out what the email is in reply to. Please create your comment through the web interface.")
- when Gitlab::Email::ProjectNotFound
- s_("EmailError|We couldn't find the project. Please check if there's any typo.")
- when Gitlab::Email::EmptyEmailError
- can_retry = true
- s_("EmailError|It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies.")
- when Gitlab::Email::UserNotFoundError
- s_("EmailError|We couldn't figure out what user corresponds to the email. Please create your comment through the web interface.")
- when Gitlab::Email::UserBlockedError
- s_("EmailError|Your account has been blocked. If you believe this is in error, contact a staff member.")
- when Gitlab::Email::UserNotAuthorizedError
- s_("EmailError|You are not allowed to perform this action. If you believe this is in error, contact a staff member.")
- when Gitlab::Email::NoteableNotFoundError
- s_("EmailError|The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member.")
- when Gitlab::Email::InvalidAttachment
- error.message
- when Gitlab::Email::InvalidRecordError
- can_retry = true
- error.message
- end
-
- if reason
- receiver.mail.body = nil
-
- EmailRejectionMailer.rejection(reason, receiver.mail.encoded, can_retry).deliver_later
- end
+ Gitlab::Email::FailureHandler.handle(receiver, error)
end
end
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index 295703cc1c3..9edff55b838 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -13,8 +13,8 @@ class ExpireBuildArtifactsWorker # rubocop:disable Scalability/IdempotentWorker
feature_category :build_artifacts
def perform
- service = Ci::JobArtifacts::DestroyAllExpiredService.new
- artifacts_count = service.execute
+ artifacts_count = Ci::JobArtifacts::DestroyAllExpiredService.new.execute
+
log_extra_metadata_on_done(:destroyed_job_artifacts_count, artifacts_count)
end
end
diff --git a/app/workers/loose_foreign_keys/cleanup_worker.rb b/app/workers/loose_foreign_keys/cleanup_worker.rb
index b4565dbf624..c3492fed77b 100644
--- a/app/workers/loose_foreign_keys/cleanup_worker.rb
+++ b/app/workers/loose_foreign_keys/cleanup_worker.rb
@@ -6,6 +6,7 @@ module LooseForeignKeys
include Gitlab::ExclusiveLeaseHelpers
include CronjobQueue # rubocop: disable Scalability/CronWorkerContext
+ sidekiq_options retry: false
feature_category :sharding
data_consistency :always
idempotent!
@@ -13,13 +14,30 @@ module LooseForeignKeys
def perform
return if Feature.disabled?(:loose_foreign_key_cleanup, default_enabled: :yaml)
- ttl = ModificationTracker::MAX_RUNTIME + 1.minute
- in_lock(self.class.name.underscore, ttl: ttl, retries: 0) do
- # TODO: Iterate over the connections
- # https://gitlab.com/gitlab-org/gitlab/-/issues/341513
- stats = ProcessDeletedRecordsService.new(connection: ApplicationRecord.connection).execute
+ in_lock(self.class.name.underscore, ttl: ModificationTracker::MAX_RUNTIME, retries: 0) do
+ stats = {}
+
+ connection_name, base_model = current_connection_name_and_base_model
+
+ Gitlab::Database::SharedModel.using_connection(base_model.connection) do
+ stats = ProcessDeletedRecordsService.new(connection: base_model.connection).execute
+ stats[:connection] = connection_name
+ end
+
log_extra_metadata_on_done(:stats, stats)
end
end
+
+ private
+
+ # Rotate the databases every minute
+ #
+ # If one DB is configured: every minute use the configured DB
+ # If two DBs are configured (Main, CI): minute 1 -> Main, minute 2 -> CI
+ def current_connection_name_and_base_model
+ minutes_since_epoch = Time.current.to_i / 60
+ connections_with_name = Gitlab::Database.database_base_models.to_a # this will never be empty
+ connections_with_name[minutes_since_epoch % connections_with_name.count]
+ end
end
end
diff --git a/app/workers/merge_requests/update_head_pipeline_worker.rb b/app/workers/merge_requests/update_head_pipeline_worker.rb
new file mode 100644
index 00000000000..c8dc9d1f7c8
--- /dev/null
+++ b/app/workers/merge_requests/update_head_pipeline_worker.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module MergeRequests
+ class UpdateHeadPipelineWorker
+ include ApplicationWorker
+ include Gitlab::EventStore::Subscriber
+
+ feature_category :code_review
+ urgency :high
+ worker_resource_boundary :cpu
+ data_consistency :always
+
+ idempotent!
+
+ def handle_event(event)
+ Ci::Pipeline.find_by_id(event.data[:pipeline_id]).try do |pipeline|
+ pipeline.all_merge_requests.opened.each do |merge_request|
+ UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id)
+ end
+ end
+ end
+ end
+end
diff --git a/app/workers/metrics/dashboard/sync_dashboards_worker.rb b/app/workers/metrics/dashboard/sync_dashboards_worker.rb
index fe8694582c4..63ca27d9c44 100644
--- a/app/workers/metrics/dashboard/sync_dashboards_worker.rb
+++ b/app/workers/metrics/dashboard/sync_dashboards_worker.rb
@@ -18,7 +18,7 @@ module Metrics
dashboard_paths = ::Gitlab::Metrics::Dashboard::RepoDashboardFinder.list_dashboards(project)
dashboard_paths.each do |dashboard_path|
- ::Gitlab::Metrics::Dashboard::Importer.new(dashboard_path, project).execute!
+ ::Gitlab::Metrics::Dashboard::Importer.new(dashboard_path, project).execute
end
end
end
diff --git a/app/workers/packages/cleanup_package_file_worker.rb b/app/workers/packages/cleanup_package_file_worker.rb
new file mode 100644
index 00000000000..cb2b2a12c5e
--- /dev/null
+++ b/app/workers/packages/cleanup_package_file_worker.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Packages
+ class CleanupPackageFileWorker
+ include ApplicationWorker
+ include ::Packages::CleanupArtifactWorker
+ include Gitlab::Utils::StrongMemoize
+
+ data_consistency :always
+ queue_namespace :package_cleanup
+ feature_category :package_registry
+ urgency :low
+ worker_resource_boundary :unknown
+ idempotent!
+
+ def max_running_jobs
+ ::Gitlab::CurrentSettings.packages_cleanup_package_file_worker_capacity
+ end
+
+ private
+
+ def model
+ Packages::PackageFile
+ end
+
+ def next_item
+ model.next_pending_destruction
+ end
+
+ def log_metadata(package_file)
+ log_extra_metadata_on_done(:package_file_id, package_file.id)
+ log_extra_metadata_on_done(:package_id, package_file.package_id)
+ end
+
+ def log_cleanup_item(package_file)
+ logger.info(
+ structured_payload(
+ package_id: package_file.package_id,
+ package_file_id: package_file.id
+ )
+ )
+ end
+ end
+end
diff --git a/app/workers/packages/cleanup_package_registry_worker.rb b/app/workers/packages/cleanup_package_registry_worker.rb
new file mode 100644
index 00000000000..a849e055b64
--- /dev/null
+++ b/app/workers/packages/cleanup_package_registry_worker.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+module Packages
+ class CleanupPackageRegistryWorker
+ include ApplicationWorker
+ include CronjobQueue # rubocop:disable Scalability/CronWorkerContext
+
+ data_consistency :always
+ idempotent!
+
+ feature_category :package_registry
+
+ def perform
+ enqueue_package_file_cleanup_job if Packages::PackageFile.pending_destruction.exists?
+
+ log_counts
+ end
+
+ private
+
+ def enqueue_package_file_cleanup_job
+ Packages::CleanupPackageFileWorker.perform_with_capacity
+ end
+
+ def log_counts
+ use_replica_if_available do
+ pending_destruction_package_files_count = Packages::PackageFile.pending_destruction.count
+ processing_package_files_count = Packages::PackageFile.processing.count
+ error_package_files_count = Packages::PackageFile.error.count
+
+ log_extra_metadata_on_done(:pending_destruction_package_files_count, pending_destruction_package_files_count)
+ log_extra_metadata_on_done(:processing_package_files_count, processing_package_files_count)
+ log_extra_metadata_on_done(:error_package_files_count, error_package_files_count)
+ end
+ end
+
+ def use_replica_if_available(&block)
+ ::Gitlab::Database::LoadBalancing::Session.current.use_replicas_for_read_queries(&block)
+ end
+ end
+end
diff --git a/app/workers/pages_update_configuration_worker.rb b/app/workers/pages_update_configuration_worker.rb
index 3dfd82ed517..9c58b40e098 100644
--- a/app/workers/pages_update_configuration_worker.rb
+++ b/app/workers/pages_update_configuration_worker.rb
@@ -1,25 +1,17 @@
# frozen_string_literal: true
+# TODO: remove this in 14.7 https://gitlab.com/gitlab-org/gitlab/-/issues/348582
class PagesUpdateConfigurationWorker
include ApplicationWorker
data_consistency :always
- sidekiq_options retry: 3
+ sidekiq_options retry: 1
idempotent!
feature_category :pages
- def self.perform_async(*args)
- return unless ::Settings.pages.local_store.enabled
-
- super(*args)
- end
-
- def perform(project_id)
- project = Project.find_by_id(project_id)
- return unless project
-
- Projects::UpdatePagesConfigurationService.new(project).execute
+ def perform(_project_id)
+ # Do nothing
end
end
diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb
index cceafbc7d2d..3aff4b42629 100644
--- a/app/workers/pages_worker.rb
+++ b/app/workers/pages_worker.rb
@@ -16,14 +16,7 @@ class PagesWorker # rubocop:disable Scalability/IdempotentWorker
def deploy(build_id)
build = Ci::Build.find_by_id(build_id)
- update_contents = Projects::UpdatePagesService.new(build.project, build).execute
- if update_contents[:status] == :success
- Projects::UpdatePagesConfigurationService.new(build.project).execute
- end
- end
- def remove(namespace_path, project_path)
- full_path = File.join(Settings.pages.path, namespace_path, project_path)
- FileUtils.rm_r(full_path, force: true)
+ Projects::UpdatePagesService.new(build.project, build).execute
end
end
diff --git a/app/workers/update_external_pull_requests_worker.rb b/app/workers/update_external_pull_requests_worker.rb
index 2a319a7f6f9..4b3cda6b8f3 100644
--- a/app/workers/update_external_pull_requests_worker.rb
+++ b/app/workers/update_external_pull_requests_worker.rb
@@ -7,7 +7,7 @@ class UpdateExternalPullRequestsWorker # rubocop:disable Scalability/IdempotentW
sidekiq_options retry: 3
- feature_category :source_code_management
+ feature_category :continuous_integration
weight 3
loggable_arguments 2
diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb
index 61fe278e016..3a2447b2108 100644
--- a/app/workers/update_head_pipeline_for_merge_request_worker.rb
+++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb
@@ -8,8 +8,10 @@ class UpdateHeadPipelineForMergeRequestWorker
sidekiq_options retry: 3
include PipelineQueue
+ # NOTE: this worker belongs to :code_review since there is no CI logic.
queue_namespace :pipeline_processing
feature_category :continuous_integration
+
urgency :high
worker_resource_boundary :cpu
diff --git a/app/workers/web_hook_worker.rb b/app/workers/web_hook_worker.rb
index 5b4567dde29..952ac94d5e6 100644
--- a/app/workers/web_hook_worker.rb
+++ b/app/workers/web_hook_worker.rb
@@ -13,11 +13,21 @@ class WebHookWorker
worker_has_external_dependencies!
- def perform(hook_id, data, hook_name)
+ # Webhook recursion detection properties are passed through the `data` arg.
+ # This will be migrated to the `params` arg over the next few releases.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/347389.
+ def perform(hook_id, data, hook_name, params = {})
hook = WebHook.find_by_id(hook_id)
return unless hook
data = data.with_indifferent_access
+
+ # Before executing the hook, reapply any recursion detection UUID that was
+ # initially present in the request header so the hook can pass this same header
+ # value in its request.
+ recursion_detection_uuid = data.delete(:_gitlab_recursion_detection_request_uuid)
+ Gitlab::WebHooks::RecursionDetection.set_request_uuid(recursion_detection_uuid)
+
WebHookService.new(hook, data, hook_name, jid).execute
end
end