summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc2
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--GITALY_SERVER_VERSION1
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--app/assets/javascripts/abuse_reports.js (renamed from app/assets/javascripts/abuse_reports.js.es6)0
-rw-r--r--app/assets/javascripts/activities.js (renamed from app/assets/javascripts/activities.js.es6)0
-rw-r--r--app/assets/javascripts/blob/blob_ci_yaml.js (renamed from app/assets/javascripts/blob/blob_ci_yaml.js.es6)0
-rw-r--r--app/assets/javascripts/blob/blob_dockerfile_selector.js (renamed from app/assets/javascripts/blob/blob_dockerfile_selector.js.es6)0
-rw-r--r--app/assets/javascripts/blob/blob_dockerfile_selectors.js (renamed from app/assets/javascripts/blob/blob_dockerfile_selectors.js.es6)0
-rw-r--r--app/assets/javascripts/blob/blob_license_selectors.js (renamed from app/assets/javascripts/blob/blob_license_selectors.js.es6)0
-rw-r--r--app/assets/javascripts/blob/template_selector.js (renamed from app/assets/javascripts/blob/template_selector.js.es6)0
-rw-r--r--app/assets/javascripts/boards/boards_bundle.js (renamed from app/assets/javascripts/boards/boards_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/board.js (renamed from app/assets/javascripts/boards/components/board.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.js (renamed from app/assets/javascripts/boards/components/board_blank_state.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/board_delete.js (renamed from app/assets/javascripts/boards/components/board_delete.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/board_list.js (renamed from app/assets/javascripts/boards/components/board_list.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js (renamed from app/assets/javascripts/boards/components/board_sidebar.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.js (renamed from app/assets/javascripts/boards/components/issue_card_inner.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.js (renamed from app/assets/javascripts/boards/components/modal/empty_state.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/filters.js (renamed from app/assets/javascripts/boards/components/modal/filters.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/filters/label.js (renamed from app/assets/javascripts/boards/components/modal/filters/label.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/filters/milestone.js (renamed from app/assets/javascripts/boards/components/modal/filters/milestone.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/filters/user.js (renamed from app/assets/javascripts/boards/components/modal/filters/user.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.js (renamed from app/assets/javascripts/boards/components/modal/footer.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/header.js (renamed from app/assets/javascripts/boards/components/modal/header.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/index.js (renamed from app/assets/javascripts/boards/components/modal/index.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/list.js (renamed from app/assets/javascripts/boards/components/modal/list.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.js (renamed from app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.js (renamed from app/assets/javascripts/boards/components/modal/tabs.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/new_list_dropdown.js (renamed from app/assets/javascripts/boards/components/new_list_dropdown.js.es6)0
-rw-r--r--app/assets/javascripts/boards/components/sidebar/remove_issue.js (renamed from app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6)0
-rw-r--r--app/assets/javascripts/boards/filters/due_date_filters.js (renamed from app/assets/javascripts/boards/filters/due_date_filters.js.es6)0
-rw-r--r--app/assets/javascripts/boards/mixins/modal_mixins.js (renamed from app/assets/javascripts/boards/mixins/modal_mixins.js.es6)0
-rw-r--r--app/assets/javascripts/boards/mixins/sortable_default_options.js (renamed from app/assets/javascripts/boards/mixins/sortable_default_options.js.es6)0
-rw-r--r--app/assets/javascripts/boards/models/issue.js (renamed from app/assets/javascripts/boards/models/issue.js.es6)0
-rw-r--r--app/assets/javascripts/boards/models/label.js (renamed from app/assets/javascripts/boards/models/label.js.es6)0
-rw-r--r--app/assets/javascripts/boards/models/list.js (renamed from app/assets/javascripts/boards/models/list.js.es6)0
-rw-r--r--app/assets/javascripts/boards/models/milestone.js (renamed from app/assets/javascripts/boards/models/milestone.js.es6)0
-rw-r--r--app/assets/javascripts/boards/models/user.js (renamed from app/assets/javascripts/boards/models/user.js.es6)0
-rw-r--r--app/assets/javascripts/boards/services/board_service.js (renamed from app/assets/javascripts/boards/services/board_service.js.es6)0
-rw-r--r--app/assets/javascripts/boards/stores/boards_store.js (renamed from app/assets/javascripts/boards/stores/boards_store.js.es6)0
-rw-r--r--app/assets/javascripts/boards/stores/modal_store.js (renamed from app/assets/javascripts/boards/stores/modal_store.js.es6)0
-rw-r--r--app/assets/javascripts/build_variables.js (renamed from app/assets/javascripts/build_variables.js.es6)0
-rw-r--r--app/assets/javascripts/ci_lint_editor.js (renamed from app/assets/javascripts/ci_lint_editor.js.es6)0
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_bundle.js (renamed from app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_service.js (renamed from app/assets/javascripts/commit/pipelines/pipelines_service.js.es6)0
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_store.js (renamed from app/assets/javascripts/commit/pipelines/pipelines_store.js.es6)0
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.js (renamed from app/assets/javascripts/commit/pipelines/pipelines_table.js.es6)0
-rw-r--r--app/assets/javascripts/commons/bootstrap.js10
-rw-r--r--app/assets/javascripts/commons/index.js2
-rw-r--r--app/assets/javascripts/commons/jquery.js11
-rw-r--r--app/assets/javascripts/compare_autocomplete.js (renamed from app/assets/javascripts/compare_autocomplete.js.es6)0
-rw-r--r--app/assets/javascripts/copy_as_gfm.js (renamed from app/assets/javascripts/copy_as_gfm.js.es6)0
-rw-r--r--app/assets/javascripts/create_label.js (renamed from app/assets/javascripts/create_label.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_code_component.js (renamed from app/assets/javascripts/cycle_analytics/components/stage_code_component.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_issue_component.js (renamed from app/assets/javascripts/cycle_analytics/components/stage_issue_component.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_plan_component.js (renamed from app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_production_component.js (renamed from app/assets/javascripts/cycle_analytics/components/stage_production_component.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_review_component.js (renamed from app/assets/javascripts/cycle_analytics/components/stage_review_component.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_staging_component.js (renamed from app/assets/javascripts/cycle_analytics/components/stage_staging_component.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_test_component.js (renamed from app/assets/javascripts/cycle_analytics/components/stage_test_component.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/components/total_time_component.js (renamed from app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js (renamed from app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_service.js (renamed from app/assets/javascripts/cycle_analytics/cycle_analytics_service.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_store.js (renamed from app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6)0
-rw-r--r--app/assets/javascripts/cycle_analytics/default_event_objects.js (renamed from app/assets/javascripts/cycle_analytics/default_event_objects.js.es6)0
-rw-r--r--app/assets/javascripts/diff.js (renamed from app/assets/javascripts/diff.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/components/comment_resolve_btn.js (renamed from app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/components/jump_to_discussion.js (renamed from app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_btn.js (renamed from app/assets/javascripts/diff_notes/components/resolve_btn.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_count.js (renamed from app/assets/javascripts/diff_notes/components/resolve_count.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js (renamed from app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/diff_notes_bundle.js (renamed from app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/mixins/discussion.js (renamed from app/assets/javascripts/diff_notes/mixins/discussion.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/models/discussion.js (renamed from app/assets/javascripts/diff_notes/models/discussion.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/models/note.js (renamed from app/assets/javascripts/diff_notes/models/note.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/services/resolve.js (renamed from app/assets/javascripts/diff_notes/services/resolve.js.es6)0
-rw-r--r--app/assets/javascripts/diff_notes/stores/comments.js (renamed from app/assets/javascripts/diff_notes/stores/comments.js.es6)0
-rw-r--r--app/assets/javascripts/dispatcher.js (renamed from app/assets/javascripts/dispatcher.js.es6)0
-rw-r--r--app/assets/javascripts/due_date_select.js (renamed from app/assets/javascripts/due_date_select.js.es6)0
-rw-r--r--app/assets/javascripts/environments/components/environment.js (renamed from app/assets/javascripts/environments/components/environment.js.es6)0
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.js (renamed from app/assets/javascripts/environments/components/environment_actions.js.es6)0
-rw-r--r--app/assets/javascripts/environments/components/environment_external_url.js (renamed from app/assets/javascripts/environments/components/environment_external_url.js.es6)0
-rw-r--r--app/assets/javascripts/environments/components/environment_item.js (renamed from app/assets/javascripts/environments/components/environment_item.js.es6)0
-rw-r--r--app/assets/javascripts/environments/components/environment_rollback.js (renamed from app/assets/javascripts/environments/components/environment_rollback.js.es6)0
-rw-r--r--app/assets/javascripts/environments/components/environment_stop.js (renamed from app/assets/javascripts/environments/components/environment_stop.js.es6)0
-rw-r--r--app/assets/javascripts/environments/components/environment_terminal_button.js (renamed from app/assets/javascripts/environments/components/environment_terminal_button.js.es6)0
-rw-r--r--app/assets/javascripts/environments/components/environments_table.js (renamed from app/assets/javascripts/environments/components/environments_table.js.es6)0
-rw-r--r--app/assets/javascripts/environments/environments_bundle.js (renamed from app/assets/javascripts/environments/environments_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js (renamed from app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.js (renamed from app/assets/javascripts/environments/folder/environments_folder_view.js.es6)0
-rw-r--r--app/assets/javascripts/environments/services/environments_service.js (renamed from app/assets/javascripts/environments/services/environments_service.js.es6)0
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js (renamed from app/assets/javascripts/environments/stores/environments_store.js.es6)0
-rw-r--r--app/assets/javascripts/extensions/array.js (renamed from app/assets/javascripts/extensions/array.js.es6)0
-rw-r--r--app/assets/javascripts/extensions/custom_event.js (renamed from app/assets/javascripts/extensions/custom_event.js.es6)0
-rw-r--r--app/assets/javascripts/extensions/element.js (renamed from app/assets/javascripts/extensions/element.js.es6)0
-rw-r--r--app/assets/javascripts/extensions/object.js (renamed from app/assets/javascripts/extensions/object.js.es6)0
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_hint.js (renamed from app/assets/javascripts/filtered_search/dropdown_hint.js.es6)0
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_non_user.js (renamed from app/assets/javascripts/filtered_search/dropdown_non_user.js.es6)0
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_user.js (renamed from app/assets/javascripts/filtered_search/dropdown_user.js.es6)0
-rw-r--r--app/assets/javascripts/filtered_search/dropdown_utils.js (renamed from app/assets/javascripts/filtered_search/dropdown_utils.js.es6)0
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown.js (renamed from app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6)0
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js (renamed from app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6)0
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js (renamed from app/assets/javascripts/filtered_search/filtered_search_manager.js.es6)0
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_token_keys.js (renamed from app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6)0
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_tokenizer.js (renamed from app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6)0
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js (renamed from app/assets/javascripts/gfm_auto_complete.js.es6)0
-rw-r--r--app/assets/javascripts/gl_field_error.js (renamed from app/assets/javascripts/gl_field_error.js.es6)0
-rw-r--r--app/assets/javascripts/gl_field_errors.js (renamed from app/assets/javascripts/gl_field_errors.js.es6)0
-rw-r--r--app/assets/javascripts/gl_form.js (renamed from app/assets/javascripts/gl_form.js.es6)0
-rw-r--r--app/assets/javascripts/graphs/graphs_bundle.js2
-rw-r--r--app/assets/javascripts/group_label_subscription.js (renamed from app/assets/javascripts/group_label_subscription.js.es6)0
-rw-r--r--app/assets/javascripts/issuable.js (renamed from app/assets/javascripts/issuable.js.es6)0
-rw-r--r--app/assets/javascripts/issuable/issuable_bundle.js (renamed from app/assets/javascripts/issuable/issuable_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js (renamed from app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6)0
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js (renamed from app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es6)0
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js (renamed from app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es6)0
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/help_state.js (renamed from app/assets/javascripts/issuable/time_tracking/components/help_state.js.es6)0
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js (renamed from app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es6)0
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js (renamed from app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es6)0
-rw-r--r--app/assets/javascripts/issuable/time_tracking/components/time_tracker.js (renamed from app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6)0
-rw-r--r--app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js (renamed from app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6)2
-rw-r--r--app/assets/javascripts/issues_bulk_assignment.js (renamed from app/assets/javascripts/issues_bulk_assignment.js.es6)0
-rw-r--r--app/assets/javascripts/label_manager.js (renamed from app/assets/javascripts/label_manager.js.es6)0
-rw-r--r--app/assets/javascripts/lib/chart.js3
-rw-r--r--app/assets/javascripts/lib/cropper.js7
-rw-r--r--app/assets/javascripts/lib/d3.js3
-rw-r--r--app/assets/javascripts/lib/raphael.js9
-rw-r--r--app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js (renamed from app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6)0
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js (renamed from app/assets/javascripts/lib/utils/common_utils.js.es6)0
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js (renamed from app/assets/javascripts/lib/utils/datetime_utility.js.es6)0
-rw-r--r--app/assets/javascripts/lib/utils/pretty_time.js (renamed from app/assets/javascripts/lib/utils/pretty_time.js.es6)0
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js (renamed from app/assets/javascripts/lib/utils/url_utility.js.es6)0
-rw-r--r--app/assets/javascripts/lib/vue_resource.js.es62
-rw-r--r--app/assets/javascripts/main.js (renamed from app/assets/javascripts/application.js)48
-rw-r--r--app/assets/javascripts/member_expiration_date.js (renamed from app/assets/javascripts/member_expiration_date.js.es6)0
-rw-r--r--app/assets/javascripts/members.js (renamed from app/assets/javascripts/members.js.es6)0
-rw-r--r--app/assets/javascripts/merge_conflicts/components/diff_file_editor.js (renamed from app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6)0
-rw-r--r--app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js (renamed from app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6)0
-rw-r--r--app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js (renamed from app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6)0
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_service.js (renamed from app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6)0
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_store.js (renamed from app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6)0
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js (renamed from app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js (renamed from app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6)0
-rw-r--r--app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js (renamed from app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6)0
-rw-r--r--app/assets/javascripts/merge_request_tabs.js (renamed from app/assets/javascripts/merge_request_tabs.js.es6)0
-rw-r--r--app/assets/javascripts/merge_request_widget.js (renamed from app/assets/javascripts/merge_request_widget.js.es6)0
-rw-r--r--app/assets/javascripts/merge_request_widget/ci_bundle.js (renamed from app/assets/javascripts/merge_request_widget/ci_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/mini_pipeline_graph_dropdown.js (renamed from app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6)0
-rw-r--r--app/assets/javascripts/network/branch_graph.js703
-rw-r--r--app/assets/javascripts/network/network.js33
-rw-r--r--app/assets/javascripts/network/network_bundle.js26
-rw-r--r--app/assets/javascripts/network/raphael.js74
-rw-r--r--app/assets/javascripts/notes.js2
-rw-r--r--app/assets/javascripts/pager.js (renamed from app/assets/javascripts/pager.js.es6)0
-rw-r--r--app/assets/javascripts/pipelines.js (renamed from app/assets/javascripts/pipelines.js.es6)0
-rw-r--r--app/assets/javascripts/profile/gl_crop.js (renamed from app/assets/javascripts/profile/gl_crop.js.es6)2
-rw-r--r--app/assets/javascripts/profile/profile.js (renamed from app/assets/javascripts/profile/profile.js.es6)0
-rw-r--r--app/assets/javascripts/project_label_subscription.js (renamed from app/assets/javascripts/project_label_subscription.js.es6)0
-rw-r--r--app/assets/javascripts/project_variables.js (renamed from app/assets/javascripts/project_variables.js.es6)0
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js (renamed from app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6)0
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_create.js (renamed from app/assets/javascripts/protected_branches/protected_branch_create.js.es6)0
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_dropdown.js (renamed from app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6)0
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit.js (renamed from app/assets/javascripts/protected_branches/protected_branch_edit.js.es6)0
-rw-r--r--app/assets/javascripts/protected_branches/protected_branch_edit_list.js (renamed from app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6)0
-rw-r--r--app/assets/javascripts/search_autocomplete.js (renamed from app/assets/javascripts/search_autocomplete.js.es6)0
-rw-r--r--app/assets/javascripts/shortcuts_blob.js (renamed from app/assets/javascripts/shortcuts_blob.js.es6)0
-rw-r--r--app/assets/javascripts/signin_tabs_memoizer.js (renamed from app/assets/javascripts/signin_tabs_memoizer.js.es6)0
-rw-r--r--app/assets/javascripts/smart_interval.js (renamed from app/assets/javascripts/smart_interval.js.es6)0
-rw-r--r--app/assets/javascripts/snippets_list.js (renamed from app/assets/javascripts/snippets_list.js.es6)0
-rw-r--r--app/assets/javascripts/subbable_resource.js (renamed from app/assets/javascripts/subbable_resource.js.es6)0
-rw-r--r--app/assets/javascripts/subscription.js (renamed from app/assets/javascripts/subscription.js.es6)0
-rw-r--r--app/assets/javascripts/templates/issuable_template_selector.js (renamed from app/assets/javascripts/templates/issuable_template_selector.js.es6)0
-rw-r--r--app/assets/javascripts/templates/issuable_template_selectors.js (renamed from app/assets/javascripts/templates/issuable_template_selectors.js.es6)0
-rw-r--r--app/assets/javascripts/terminal/terminal.js (renamed from app/assets/javascripts/terminal/terminal.js.es6)0
-rw-r--r--app/assets/javascripts/terminal/terminal_bundle.js (renamed from app/assets/javascripts/terminal/terminal_bundle.js.es6)0
-rw-r--r--app/assets/javascripts/todos.js (renamed from app/assets/javascripts/todos.js.es6)0
-rw-r--r--app/assets/javascripts/u2f/authenticate.js (renamed from app/assets/javascripts/u2f/authenticate.js.es6)0
-rw-r--r--app/assets/javascripts/user.js (renamed from app/assets/javascripts/user.js.es6)0
-rw-r--r--app/assets/javascripts/user_tabs.js (renamed from app/assets/javascripts/user_tabs.js.es6)0
-rw-r--r--app/assets/javascripts/username_validator.js (renamed from app/assets/javascripts/username_validator.js.es6)0
-rw-r--r--app/assets/javascripts/users/calendar.js3
-rw-r--r--app/assets/javascripts/version_check_image.js (renamed from app/assets/javascripts/version_check_image.js.es6)0
-rw-r--r--app/assets/javascripts/visibility_select.js (renamed from app/assets/javascripts/visibility_select.js.es6)0
-rw-r--r--app/assets/javascripts/vue_pipelines_index/index.js (renamed from app/assets/javascripts/vue_pipelines_index/index.js.es6)0
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipeline_actions.js (renamed from app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6)0
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipeline_url.js (renamed from app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6)0
-rw-r--r--app/assets/javascripts/vue_pipelines_index/pipelines.js (renamed from app/assets/javascripts/vue_pipelines_index/pipelines.js.es6)0
-rw-r--r--app/assets/javascripts/vue_pipelines_index/stage.js (renamed from app/assets/javascripts/vue_pipelines_index/stage.js.es6)0
-rw-r--r--app/assets/javascripts/vue_pipelines_index/status.js (renamed from app/assets/javascripts/vue_pipelines_index/status.js.es6)0
-rw-r--r--app/assets/javascripts/vue_pipelines_index/store.js (renamed from app/assets/javascripts/vue_pipelines_index/store.js.es6)0
-rw-r--r--app/assets/javascripts/vue_pipelines_index/time_ago.js (renamed from app/assets/javascripts/vue_pipelines_index/time_ago.js.es6)0
-rw-r--r--app/assets/javascripts/vue_realtime_listener/index.js (renamed from app/assets/javascripts/vue_realtime_listener/index.js.es6)0
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.js (renamed from app/assets/javascripts/vue_shared/components/commit.js.es6)0
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table.js (renamed from app/assets/javascripts/vue_shared/components/pipelines_table.js.es6)0
-rw-r--r--app/assets/javascripts/vue_shared/components/pipelines_table_row.js (renamed from app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6)0
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.js (renamed from app/assets/javascripts/vue_shared/components/table_pagination.js.es6)0
-rw-r--r--app/assets/javascripts/vue_shared/vue_resource_interceptor.js (renamed from app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6)0
-rw-r--r--app/assets/javascripts/wikis.js (renamed from app/assets/javascripts/wikis.js.es6)0
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss2
-rw-r--r--app/assets/stylesheets/framework/filters.scss15
-rw-r--r--app/assets/stylesheets/framework/header.scss28
-rw-r--r--app/assets/stylesheets/framework/layout.scss13
-rw-r--r--app/assets/stylesheets/framework/zen.scss3
-rw-r--r--app/assets/stylesheets/pages/todos.scss10
-rw-r--r--app/controllers/admin/application_settings_controller.rb3
-rw-r--r--app/controllers/application_controller.rb4
-rw-r--r--app/controllers/profiles/keys_controller.rb5
-rw-r--r--app/controllers/profiles_controller.rb11
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/notes_controller.rb7
-rw-r--r--app/finders/issuable_finder.rb35
-rw-r--r--app/finders/issues_finder.rb4
-rw-r--r--app/finders/merge_requests_finder.rb6
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/mattermost_helper.rb6
-rw-r--r--app/helpers/triggers_helper.rb4
-rw-r--r--app/models/application_setting.rb13
-rw-r--r--app/models/ci/trigger.rb11
-rw-r--r--app/models/external_issue.rb5
-rw-r--r--app/models/note.rb13
-rw-r--r--app/models/user.rb1
-rw-r--r--app/services/ci/create_trigger_request_service.rb2
-rw-r--r--app/services/projects/update_pages_service.rb2
-rw-r--r--app/views/admin/application_settings/_form.html.haml23
-rw-r--r--app/views/dashboard/todos/index.html.haml8
-rw-r--r--app/views/layouts/_head.html.haml4
-rw-r--r--app/views/layouts/application.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/profiles/_head.html.haml1
-rw-r--r--app/views/profiles/accounts/show.html.haml2
-rw-r--r--app/views/profiles/update_username.js.haml7
-rw-r--r--app/views/projects/boards/_show.html.haml1
-rw-r--r--app/views/projects/commit/_pipelines_list.haml1
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml1
-rw-r--r--app/views/projects/environments/folder.html.haml1
-rw-r--r--app/views/projects/environments/index.html.haml1
-rw-r--r--app/views/projects/graphs/charts.html.haml2
-rw-r--r--app/views/projects/graphs/show.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml2
-rw-r--r--app/views/projects/mattermosts/_team_selection.html.haml13
-rw-r--r--app/views/projects/mattermosts/new.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml1
-rw-r--r--app/views/projects/merge_requests/conflicts.html.haml2
-rw-r--r--app/views/projects/network/show.html.haml1
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml2
-rw-r--r--app/views/projects/pipelines/charts.html.haml2
-rw-r--r--app/views/projects/pipelines/index.html.haml1
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml1
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--app/workers/stuck_ci_builds_worker.rb19
-rw-r--r--app/workers/stuck_ci_jobs_worker.rb59
-rw-r--r--changelogs/unreleased/1648-remove-remnants-of-git-annex-from-ce.yml4
-rw-r--r--changelogs/unreleased/22562-todos-filters.yml4
-rw-r--r--changelogs/unreleased/25503_issues_finder_performance.yml4
-rw-r--r--changelogs/unreleased/27520-option-to-prevent-signing-in-from-multiple-ips.yml4
-rw-r--r--changelogs/unreleased/27523-make-stuck-build-detection-more-performant.yml4
-rw-r--r--changelogs/unreleased/28655-current-path-text-is-not-updated-after-setting-the-new-username.yml4
-rw-r--r--changelogs/unreleased/28704-fullscreen-zen-mode-is-broken.yml4
-rw-r--r--changelogs/unreleased/28898-fix-search-branches-in-cherry-picking.yml4
-rw-r--r--changelogs/unreleased/api-drop-subscribed.yml5
-rw-r--r--changelogs/unreleased/backup_storage_class.yml4
-rw-r--r--changelogs/unreleased/commons-chunk-plugin.yml5
-rw-r--r--changelogs/unreleased/delete-artifacts-for-pages.yml4
-rw-r--r--changelogs/unreleased/etag-notes-polling.yml4
-rw-r--r--changelogs/unreleased/fix-mentioned-issues-for-external-trackers.yml4
-rw-r--r--changelogs/unreleased/format-timeago-date.yml4
-rw-r--r--changelogs/unreleased/introduce-pipeline-triggers.yml4
-rw-r--r--changelogs/unreleased/remove-es6-extension.yml4
-rw-r--r--changelogs/unreleased/workhorse-1-4-0.yml4
-rw-r--r--changelogs/unreleased/zj-builds-to-jobs-api.yml4
-rw-r--r--config/application.rb3
-rw-r--r--config/gitlab.yml.example8
-rw-r--r--config/initializers/1_settings.rb7
-rw-r--r--config/initializers/8_metrics.rb (renamed from config/initializers/metrics.rb)0
-rw-r--r--config/initializers/etag_caching.rb4
-rw-r--r--config/initializers/request_context.rb3
-rw-r--r--config/initializers/warden.rb5
-rw-r--r--config/karma.config.js13
-rw-r--r--config/routes/profile.rb2
-rw-r--r--config/routes/project.rb4
-rw-r--r--config/webpack.config.js54
-rw-r--r--db/migrate/20170210131347_add_unique_ips_limit_to_application_settings.rb17
-rw-r--r--db/migrate/20170217151948_add_owner_id_to_triggers.rb9
-rw-r--r--db/migrate/20170217151949_add_description_to_triggers.rb9
-rw-r--r--db/migrate/20170305203726_add_owner_id_foreign_key.rb11
-rw-r--r--db/schema.rb12
-rw-r--r--doc/api/README.md2
-rw-r--r--doc/api/build_triggers.md109
-rw-r--r--doc/api/groups.md20
-rw-r--r--doc/api/issues.md3
-rw-r--r--doc/api/jobs.md (renamed from doc/api/builds.md)202
-rw-r--r--doc/api/merge_requests.md1
-rw-r--r--doc/api/pipeline_triggers.md170
-rw-r--r--doc/api/pipelines.md4
-rw-r--r--doc/api/projects.md52
-rw-r--r--doc/api/services.md14
-rw-r--r--doc/api/v3_to_v4.md9
-rw-r--r--doc/ci/triggers/README.md16
-rw-r--r--doc/ci/variables/README.md4
-rw-r--r--doc/development/instrumentation.md2
-rw-r--r--doc/downgrade_ee_to_ce/README.md7
-rw-r--r--doc/raketasks/backup_restore.md2
-rw-r--r--doc/university/README.md2
-rw-r--r--doc/university/support/README.md1
-rw-r--r--doc/user/project/pages/getting_started_part_four.md3
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md7
-rw-r--r--lib/api/api.rb7
-rw-r--r--lib/api/entities.rb53
-rw-r--r--lib/api/helpers.rb23
-rw-r--r--lib/api/issues.rb12
-rw-r--r--lib/api/jobs.rb (renamed from lib/api/builds.rb)130
-rw-r--r--lib/api/merge_requests.rb12
-rw-r--r--lib/api/milestones.rb11
-rw-r--r--lib/api/services.rb10
-rw-r--r--lib/api/triggers.rb76
-rw-r--r--lib/api/v3/builds.rb263
-rw-r--r--lib/api/v3/deployments.rb41
-rw-r--r--lib/api/v3/entities.rb64
-rw-r--r--lib/api/v3/merge_request_diffs.rb43
-rw-r--r--lib/api/v3/merge_requests.rb8
-rw-r--r--lib/api/v3/milestones.rb21
-rw-r--r--lib/api/v3/project_hooks.rb106
-rw-r--r--lib/api/v3/services.rb68
-rw-r--r--lib/api/v3/triggers.rb77
-rw-r--r--lib/backup/manager.rb3
-rw-r--r--lib/backup/repository.rb5
-rw-r--r--lib/gitlab/auth.rb21
-rw-r--r--lib/gitlab/auth/too_many_ips.rb17
-rw-r--r--lib/gitlab/auth/unique_ips_limiter.rb43
-rw-r--r--lib/gitlab/database.rb6
-rw-r--r--lib/gitlab/etag_caching/middleware.rb66
-rw-r--r--lib/gitlab/etag_caching/store.rb32
-rw-r--r--lib/gitlab/request_context.rb21
-rw-r--r--lib/mattermost/team.rb2
-rw-r--r--package.json1
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb10
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb23
-rw-r--r--spec/controllers/projects/graphs_controller_spec.rb10
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb27
-rw-r--r--spec/controllers/sessions_controller_spec.rb12
-rw-r--r--spec/factories/projects.rb23
-rw-r--r--spec/factories/services.rb9
-rw-r--r--spec/features/commits_spec.rb2
-rw-r--r--spec/features/profile_spec.rb14
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb42
-rw-r--r--spec/fixtures/api/schemas/public_api/v3/issues.json77
-rw-r--r--spec/fixtures/api/schemas/public_api/v3/merge_requests.json89
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/issues.json76
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/merge_requests.json88
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/user/login.json (renamed from spec/fixtures/api/schemas/user/login.json)0
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/user/public.json (renamed from spec/fixtures/api/schemas/user/public.json)0
-rw-r--r--spec/helpers/application_helper_spec.rb6
-rw-r--r--spec/initializers/8_metrics_spec.rb (renamed from spec/initializers/metrics_spec.rb)2
-rw-r--r--spec/javascripts/abuse_reports_spec.js (renamed from spec/javascripts/abuse_reports_spec.js.es6)0
-rw-r--r--spec/javascripts/activities_spec.js (renamed from spec/javascripts/activities_spec.js.es6)0
-rw-r--r--spec/javascripts/boards/boards_store_spec.js (renamed from spec/javascripts/boards/boards_store_spec.js.es6)0
-rw-r--r--spec/javascripts/boards/issue_card_spec.js (renamed from spec/javascripts/boards/issue_card_spec.js.es6)0
-rw-r--r--spec/javascripts/boards/issue_spec.js (renamed from spec/javascripts/boards/issue_spec.js.es6)0
-rw-r--r--spec/javascripts/boards/list_spec.js (renamed from spec/javascripts/boards/list_spec.js.es6)0
-rw-r--r--spec/javascripts/boards/mock_data.js (renamed from spec/javascripts/boards/mock_data.js.es6)0
-rw-r--r--spec/javascripts/boards/modal_store_spec.js (renamed from spec/javascripts/boards/modal_store_spec.js.es6)0
-rw-r--r--spec/javascripts/bootstrap_linked_tabs_spec.js (renamed from spec/javascripts/bootstrap_linked_tabs_spec.js.es6)0
-rw-r--r--spec/javascripts/build_spec.js (renamed from spec/javascripts/build_spec.js.es6)0
-rw-r--r--spec/javascripts/commit/pipelines/mock_data.js (renamed from spec/javascripts/commit/pipelines/mock_data.js.es6)0
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js (renamed from spec/javascripts/commit/pipelines/pipelines_spec.js.es6)0
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_store_spec.js (renamed from spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6)0
-rw-r--r--spec/javascripts/commits_spec.js (renamed from spec/javascripts/commits_spec.js.es6)0
-rw-r--r--spec/javascripts/datetime_utility_spec.js (renamed from spec/javascripts/datetime_utility_spec.js.es6)0
-rw-r--r--spec/javascripts/diff_comments_store_spec.js (renamed from spec/javascripts/diff_comments_store_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/environment_actions_spec.js (renamed from spec/javascripts/environments/environment_actions_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/environment_external_url_spec.js (renamed from spec/javascripts/environments/environment_external_url_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/environment_item_spec.js (renamed from spec/javascripts/environments/environment_item_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/environment_rollback_spec.js (renamed from spec/javascripts/environments/environment_rollback_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/environment_spec.js (renamed from spec/javascripts/environments/environment_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/environment_stop_spec.js (renamed from spec/javascripts/environments/environment_stop_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/environment_table_spec.js (renamed from spec/javascripts/environments/environment_table_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/environments_store_spec.js (renamed from spec/javascripts/environments/environments_store_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js (renamed from spec/javascripts/environments/folder/environments_folder_view_spec.js.es6)0
-rw-r--r--spec/javascripts/environments/mock_data.js (renamed from spec/javascripts/environments/mock_data.js.es6)0
-rw-r--r--spec/javascripts/extensions/array_spec.js (renamed from spec/javascripts/extensions/array_spec.js.es6)0
-rw-r--r--spec/javascripts/extensions/element_spec.js (renamed from spec/javascripts/extensions/element_spec.js.es6)0
-rw-r--r--spec/javascripts/extensions/object_spec.js (renamed from spec/javascripts/extensions/object_spec.js.es6)0
-rw-r--r--spec/javascripts/filtered_search/dropdown_user_spec.js (renamed from spec/javascripts/filtered_search/dropdown_user_spec.js.es6)0
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js (renamed from spec/javascripts/filtered_search/dropdown_utils_spec.js.es6)0
-rw-r--r--spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js (renamed from spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6)0
-rw-r--r--spec/javascripts/filtered_search/filtered_search_manager_spec.js (renamed from spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6)0
-rw-r--r--spec/javascripts/filtered_search/filtered_search_token_keys_spec.js (renamed from spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6)0
-rw-r--r--spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js (renamed from spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6)0
-rw-r--r--spec/javascripts/gfm_auto_complete_spec.js (renamed from spec/javascripts/gfm_auto_complete_spec.js.es6)0
-rw-r--r--spec/javascripts/gl_dropdown_spec.js (renamed from spec/javascripts/gl_dropdown_spec.js.es6)0
-rw-r--r--spec/javascripts/gl_field_errors_spec.js (renamed from spec/javascripts/gl_field_errors_spec.js.es6)0
-rw-r--r--spec/javascripts/gl_form_spec.js (renamed from spec/javascripts/gl_form_spec.js.es6)0
-rw-r--r--spec/javascripts/helpers/class_spec_helper.js (renamed from spec/javascripts/helpers/class_spec_helper.js.es6)0
-rw-r--r--spec/javascripts/helpers/class_spec_helper_spec.js (renamed from spec/javascripts/helpers/class_spec_helper_spec.js.es6)0
-rw-r--r--spec/javascripts/issuable_spec.js (renamed from spec/javascripts/issuable_spec.js.es6)0
-rw-r--r--spec/javascripts/issuable_time_tracker_spec.js (renamed from spec/javascripts/issuable_time_tracker_spec.js.es6)0
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js (renamed from spec/javascripts/labels_issue_sidebar_spec.js.es6)0
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js (renamed from spec/javascripts/lib/utils/common_utils_spec.js.es6)0
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js (renamed from spec/javascripts/lib/utils/text_utility_spec.js.es6)0
-rw-r--r--spec/javascripts/mini_pipeline_graph_dropdown_spec.js (renamed from spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6)0
-rw-r--r--spec/javascripts/pipelines_spec.js (renamed from spec/javascripts/pipelines_spec.js.es6)0
-rw-r--r--spec/javascripts/pretty_time_spec.js (renamed from spec/javascripts/pretty_time_spec.js.es6)0
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js (renamed from spec/javascripts/signin_tabs_memoizer_spec.js.es6)0
-rw-r--r--spec/javascripts/smart_interval_spec.js (renamed from spec/javascripts/smart_interval_spec.js.es6)0
-rw-r--r--spec/javascripts/subbable_resource_spec.js (renamed from spec/javascripts/subbable_resource_spec.js.es6)0
-rw-r--r--spec/javascripts/test_bundle.js13
-rw-r--r--spec/javascripts/user_callout_spec.js (renamed from spec/javascripts/user_callout_spec.js.es6)0
-rw-r--r--spec/javascripts/version_check_image_spec.js (renamed from spec/javascripts/version_check_image_spec.js.es6)0
-rw-r--r--spec/javascripts/visibility_select_spec.js (renamed from spec/javascripts/visibility_select_spec.js.es6)0
-rw-r--r--spec/javascripts/vue_shared/components/commit_spec.js (renamed from spec/javascripts/vue_shared/components/commit_spec.js.es6)0
-rw-r--r--spec/javascripts/vue_shared/components/pipelines_table_row_spec.js (renamed from spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6)0
-rw-r--r--spec/javascripts/vue_shared/components/pipelines_table_spec.js (renamed from spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6)0
-rw-r--r--spec/javascripts/vue_shared/components/table_pagination_spec.js (renamed from spec/javascripts/vue_shared/components/table_pagination_spec.js.es6)0
-rw-r--r--spec/lib/gitlab/auth/unique_ips_limiter_spec.rb57
-rw-r--r--spec/lib/gitlab/auth_spec.rb14
-rw-r--r--spec/lib/gitlab/etag_caching/middleware_spec.rb163
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml1
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml2
-rw-r--r--spec/lib/gitlab/request_context_spec.rb30
-rw-r--r--spec/lib/mattermost/team_spec.rb6
-rw-r--r--spec/models/ci/trigger_spec.rb14
-rw-r--r--spec/models/external_issue_spec.rb8
-rw-r--r--spec/models/merge_request_spec.rb17
-rw-r--r--spec/models/note_spec.rb12
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb2
-rw-r--r--spec/models/user_spec.rb1
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb16
-rw-r--r--spec/requests/api/groups_spec.rb4
-rw-r--r--spec/requests/api/issues_spec.rb7
-rw-r--r--spec/requests/api/jobs_spec.rb408
-rw-r--r--spec/requests/api/merge_requests_spec.rb7
-rw-r--r--spec/requests/api/milestones_spec.rb7
-rw-r--r--spec/requests/api/project_hooks_spec.rb8
-rw-r--r--spec/requests/api/projects_spec.rb4
-rw-r--r--spec/requests/api/triggers_spec.rb147
-rw-r--r--spec/requests/api/users_spec.rb8
-rw-r--r--spec/requests/api/v3/builds_spec.rb (renamed from spec/requests/api/builds_spec.rb)30
-rw-r--r--spec/requests/api/v3/deployments_spec.rb71
-rw-r--r--spec/requests/api/v3/issues_spec.rb7
-rw-r--r--spec/requests/api/v3/merge_request_diffs_spec.rb49
-rw-r--r--spec/requests/api/v3/merge_requests_spec.rb7
-rw-r--r--spec/requests/api/v3/milestones_spec.rb7
-rw-r--r--spec/requests/api/v3/project_hooks_spec.rb216
-rw-r--r--spec/requests/api/v3/triggers_spec.rb171
-rw-r--r--spec/routing/project_routing_spec.rb18
-rw-r--r--spec/services/ci/create_trigger_request_service_spec.rb18
-rw-r--r--spec/services/projects/update_pages_service_spec.rb22
-rw-r--r--spec/support/unique_ip_check_shared_examples.rb62
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb21
-rw-r--r--spec/workers/stuck_ci_builds_worker_spec.rb57
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb129
-rw-r--r--vendor/assets/javascripts/g.bar.js674
-rw-r--r--vendor/assets/javascripts/g.raphael.js861
-rw-r--r--vendor/assets/javascripts/raphael.js8239
-rw-r--r--yarn.lock10
456 files changed, 4472 insertions, 11095 deletions
diff --git a/.eslintrc b/.eslintrc
index 0fcd866778f..b0ae2a31919 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -23,7 +23,7 @@
}
},
"rules": {
- "filenames/match-regex": [2, "^[a-z0-9_]+(.js)?$"],
+ "filenames/match-regex": [2, "^[a-z0-9_]+$"],
"no-multiple-empty-lines": ["error", { "max": 1 }]
}
}
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 9006fef6a27..deeb01f9a3c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -421,6 +421,7 @@ pages:
- public
only:
- master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
# Insurance in case a gem needed by one of our releases gets yanked from
# rubygems.org in the future.
@@ -437,3 +438,4 @@ cache gems:
- vendor/cache
only:
- master@gitlab-org/gitlab-ce
+ - master@gitlab-org/gitlab-ee
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
new file mode 100644
index 00000000000..0d91a54c7d4
--- /dev/null
+++ b/GITALY_SERVER_VERSION
@@ -0,0 +1 @@
+0.3.0
diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index f0bb29e7638..88c5fb891dc 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-1.3.0
+1.4.0
diff --git a/app/assets/javascripts/abuse_reports.js.es6 b/app/assets/javascripts/abuse_reports.js
index 8a260aae1b1..8a260aae1b1 100644
--- a/app/assets/javascripts/abuse_reports.js.es6
+++ b/app/assets/javascripts/abuse_reports.js
diff --git a/app/assets/javascripts/activities.js.es6 b/app/assets/javascripts/activities.js
index 648cb4d5d85..648cb4d5d85 100644
--- a/app/assets/javascripts/activities.js.es6
+++ b/app/assets/javascripts/activities.js
diff --git a/app/assets/javascripts/blob/blob_ci_yaml.js.es6 b/app/assets/javascripts/blob/blob_ci_yaml.js
index ec1c018424d..ec1c018424d 100644
--- a/app/assets/javascripts/blob/blob_ci_yaml.js.es6
+++ b/app/assets/javascripts/blob/blob_ci_yaml.js
diff --git a/app/assets/javascripts/blob/blob_dockerfile_selector.js.es6 b/app/assets/javascripts/blob/blob_dockerfile_selector.js
index d4f60cc6ecd..d4f60cc6ecd 100644
--- a/app/assets/javascripts/blob/blob_dockerfile_selector.js.es6
+++ b/app/assets/javascripts/blob/blob_dockerfile_selector.js
diff --git a/app/assets/javascripts/blob/blob_dockerfile_selectors.js.es6 b/app/assets/javascripts/blob/blob_dockerfile_selectors.js
index 9cee79fa5d5..9cee79fa5d5 100644
--- a/app/assets/javascripts/blob/blob_dockerfile_selectors.js.es6
+++ b/app/assets/javascripts/blob/blob_dockerfile_selectors.js
diff --git a/app/assets/javascripts/blob/blob_license_selectors.js.es6 b/app/assets/javascripts/blob/blob_license_selectors.js
index c5067b0feae..c5067b0feae 100644
--- a/app/assets/javascripts/blob/blob_license_selectors.js.es6
+++ b/app/assets/javascripts/blob/blob_license_selectors.js
diff --git a/app/assets/javascripts/blob/template_selector.js.es6 b/app/assets/javascripts/blob/template_selector.js
index 7e03ec3b391..7e03ec3b391 100644
--- a/app/assets/javascripts/blob/template_selector.js.es6
+++ b/app/assets/javascripts/blob/template_selector.js
diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js
index 55d13be6e5f..55d13be6e5f 100644
--- a/app/assets/javascripts/boards/boards_bundle.js.es6
+++ b/app/assets/javascripts/boards/boards_bundle.js
diff --git a/app/assets/javascripts/boards/components/board.js.es6 b/app/assets/javascripts/boards/components/board.js
index 18324de18b3..18324de18b3 100644
--- a/app/assets/javascripts/boards/components/board.js.es6
+++ b/app/assets/javascripts/boards/components/board.js
diff --git a/app/assets/javascripts/boards/components/board_blank_state.js.es6 b/app/assets/javascripts/boards/components/board_blank_state.js
index d76314c1892..d76314c1892 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.js.es6
+++ b/app/assets/javascripts/boards/components/board_blank_state.js
diff --git a/app/assets/javascripts/boards/components/board_delete.js.es6 b/app/assets/javascripts/boards/components/board_delete.js
index 861600424a5..861600424a5 100644
--- a/app/assets/javascripts/boards/components/board_delete.js.es6
+++ b/app/assets/javascripts/boards/components/board_delete.js
diff --git a/app/assets/javascripts/boards/components/board_list.js.es6 b/app/assets/javascripts/boards/components/board_list.js
index 2d52e96e7fb..2d52e96e7fb 100644
--- a/app/assets/javascripts/boards/components/board_list.js.es6
+++ b/app/assets/javascripts/boards/components/board_list.js
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js.es6 b/app/assets/javascripts/boards/components/board_sidebar.js
index dfc6eed785c..dfc6eed785c 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js.es6
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.js.es6 b/app/assets/javascripts/boards/components/issue_card_inner.js
index 22a8b971ff8..22a8b971ff8 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.js.es6
+++ b/app/assets/javascripts/boards/components/issue_card_inner.js
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.js.es6 b/app/assets/javascripts/boards/components/modal/empty_state.js
index 9538f5b69e9..9538f5b69e9 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.js.es6
+++ b/app/assets/javascripts/boards/components/modal/empty_state.js
diff --git a/app/assets/javascripts/boards/components/modal/filters.js.es6 b/app/assets/javascripts/boards/components/modal/filters.js
index 6de06811d94..6de06811d94 100644
--- a/app/assets/javascripts/boards/components/modal/filters.js.es6
+++ b/app/assets/javascripts/boards/components/modal/filters.js
diff --git a/app/assets/javascripts/boards/components/modal/filters/label.js.es6 b/app/assets/javascripts/boards/components/modal/filters/label.js
index 4fc8f72a145..4fc8f72a145 100644
--- a/app/assets/javascripts/boards/components/modal/filters/label.js.es6
+++ b/app/assets/javascripts/boards/components/modal/filters/label.js
diff --git a/app/assets/javascripts/boards/components/modal/filters/milestone.js.es6 b/app/assets/javascripts/boards/components/modal/filters/milestone.js
index d555599d300..d555599d300 100644
--- a/app/assets/javascripts/boards/components/modal/filters/milestone.js.es6
+++ b/app/assets/javascripts/boards/components/modal/filters/milestone.js
diff --git a/app/assets/javascripts/boards/components/modal/filters/user.js.es6 b/app/assets/javascripts/boards/components/modal/filters/user.js
index 8523028c29c..8523028c29c 100644
--- a/app/assets/javascripts/boards/components/modal/filters/user.js.es6
+++ b/app/assets/javascripts/boards/components/modal/filters/user.js
diff --git a/app/assets/javascripts/boards/components/modal/footer.js.es6 b/app/assets/javascripts/boards/components/modal/footer.js
index 1cbc422c961..1cbc422c961 100644
--- a/app/assets/javascripts/boards/components/modal/footer.js.es6
+++ b/app/assets/javascripts/boards/components/modal/footer.js
diff --git a/app/assets/javascripts/boards/components/modal/header.js.es6 b/app/assets/javascripts/boards/components/modal/header.js
index 70c088f9054..70c088f9054 100644
--- a/app/assets/javascripts/boards/components/modal/header.js.es6
+++ b/app/assets/javascripts/boards/components/modal/header.js
diff --git a/app/assets/javascripts/boards/components/modal/index.js.es6 b/app/assets/javascripts/boards/components/modal/index.js
index f290cd13763..f290cd13763 100644
--- a/app/assets/javascripts/boards/components/modal/index.js.es6
+++ b/app/assets/javascripts/boards/components/modal/index.js
diff --git a/app/assets/javascripts/boards/components/modal/list.js.es6 b/app/assets/javascripts/boards/components/modal/list.js
index 3730c1ecaeb..3730c1ecaeb 100644
--- a/app/assets/javascripts/boards/components/modal/list.js.es6
+++ b/app/assets/javascripts/boards/components/modal/list.js
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6 b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
index 3c05120a2da..3c05120a2da 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.js.es6
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.js
diff --git a/app/assets/javascripts/boards/components/modal/tabs.js.es6 b/app/assets/javascripts/boards/components/modal/tabs.js
index e8cb43f3503..e8cb43f3503 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.js.es6
+++ b/app/assets/javascripts/boards/components/modal/tabs.js
diff --git a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6 b/app/assets/javascripts/boards/components/new_list_dropdown.js
index 556826a9148..556826a9148 100644
--- a/app/assets/javascripts/boards/components/new_list_dropdown.js.es6
+++ b/app/assets/javascripts/boards/components/new_list_dropdown.js
diff --git a/app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6 b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
index e74935e1cb0..e74935e1cb0 100644
--- a/app/assets/javascripts/boards/components/sidebar/remove_issue.js.es6
+++ b/app/assets/javascripts/boards/components/sidebar/remove_issue.js
diff --git a/app/assets/javascripts/boards/filters/due_date_filters.js.es6 b/app/assets/javascripts/boards/filters/due_date_filters.js
index 03425bb145b..03425bb145b 100644
--- a/app/assets/javascripts/boards/filters/due_date_filters.js.es6
+++ b/app/assets/javascripts/boards/filters/due_date_filters.js
diff --git a/app/assets/javascripts/boards/mixins/modal_mixins.js.es6 b/app/assets/javascripts/boards/mixins/modal_mixins.js
index d378b7d4baf..d378b7d4baf 100644
--- a/app/assets/javascripts/boards/mixins/modal_mixins.js.es6
+++ b/app/assets/javascripts/boards/mixins/modal_mixins.js
diff --git a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6 b/app/assets/javascripts/boards/mixins/sortable_default_options.js
index b6c6d17274f..b6c6d17274f 100644
--- a/app/assets/javascripts/boards/mixins/sortable_default_options.js.es6
+++ b/app/assets/javascripts/boards/mixins/sortable_default_options.js
diff --git a/app/assets/javascripts/boards/models/issue.js.es6 b/app/assets/javascripts/boards/models/issue.js
index 2d0a295ae4d..2d0a295ae4d 100644
--- a/app/assets/javascripts/boards/models/issue.js.es6
+++ b/app/assets/javascripts/boards/models/issue.js
diff --git a/app/assets/javascripts/boards/models/label.js.es6 b/app/assets/javascripts/boards/models/label.js
index 9af88d167d6..9af88d167d6 100644
--- a/app/assets/javascripts/boards/models/label.js.es6
+++ b/app/assets/javascripts/boards/models/label.js
diff --git a/app/assets/javascripts/boards/models/list.js.es6 b/app/assets/javascripts/boards/models/list.js
index 8158ed4ec2c..8158ed4ec2c 100644
--- a/app/assets/javascripts/boards/models/list.js.es6
+++ b/app/assets/javascripts/boards/models/list.js
diff --git a/app/assets/javascripts/boards/models/milestone.js.es6 b/app/assets/javascripts/boards/models/milestone.js
index c867b06d320..c867b06d320 100644
--- a/app/assets/javascripts/boards/models/milestone.js.es6
+++ b/app/assets/javascripts/boards/models/milestone.js
diff --git a/app/assets/javascripts/boards/models/user.js.es6 b/app/assets/javascripts/boards/models/user.js
index 8e9de4d4cbb..8e9de4d4cbb 100644
--- a/app/assets/javascripts/boards/models/user.js.es6
+++ b/app/assets/javascripts/boards/models/user.js
diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js
index 065e90518df..065e90518df 100644
--- a/app/assets/javascripts/boards/services/board_service.js.es6
+++ b/app/assets/javascripts/boards/services/board_service.js
diff --git a/app/assets/javascripts/boards/stores/boards_store.js.es6 b/app/assets/javascripts/boards/stores/boards_store.js
index 56436c8fdc7..56436c8fdc7 100644
--- a/app/assets/javascripts/boards/stores/boards_store.js.es6
+++ b/app/assets/javascripts/boards/stores/boards_store.js
diff --git a/app/assets/javascripts/boards/stores/modal_store.js.es6 b/app/assets/javascripts/boards/stores/modal_store.js
index 15fc6c79e8d..15fc6c79e8d 100644
--- a/app/assets/javascripts/boards/stores/modal_store.js.es6
+++ b/app/assets/javascripts/boards/stores/modal_store.js
diff --git a/app/assets/javascripts/build_variables.js.es6 b/app/assets/javascripts/build_variables.js
index 99082b412e2..99082b412e2 100644
--- a/app/assets/javascripts/build_variables.js.es6
+++ b/app/assets/javascripts/build_variables.js
diff --git a/app/assets/javascripts/ci_lint_editor.js.es6 b/app/assets/javascripts/ci_lint_editor.js
index 56ffaa765a8..56ffaa765a8 100644
--- a/app/assets/javascripts/ci_lint_editor.js.es6
+++ b/app/assets/javascripts/ci_lint_editor.js
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
index b5a988df897..b5a988df897 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_service.js
index 8ae98f9bf97..8ae98f9bf97 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_service.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_service.js
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_store.js
index f1b80e45444..f1b80e45444 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_store.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_store.js
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6 b/app/assets/javascripts/commit/pipelines/pipelines_table.js
index 631ed34851c..631ed34851c 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.js.es6
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.js
diff --git a/app/assets/javascripts/commons/bootstrap.js b/app/assets/javascripts/commons/bootstrap.js
new file mode 100644
index 00000000000..db0cbfd87c3
--- /dev/null
+++ b/app/assets/javascripts/commons/bootstrap.js
@@ -0,0 +1,10 @@
+import 'jquery';
+
+// bootstrap jQuery plugins
+import 'bootstrap-sass/assets/javascripts/bootstrap/affix';
+import 'bootstrap-sass/assets/javascripts/bootstrap/alert';
+import 'bootstrap-sass/assets/javascripts/bootstrap/dropdown';
+import 'bootstrap-sass/assets/javascripts/bootstrap/modal';
+import 'bootstrap-sass/assets/javascripts/bootstrap/tab';
+import 'bootstrap-sass/assets/javascripts/bootstrap/transition';
+import 'bootstrap-sass/assets/javascripts/bootstrap/tooltip';
diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js
new file mode 100644
index 00000000000..72ede1d621a
--- /dev/null
+++ b/app/assets/javascripts/commons/index.js
@@ -0,0 +1,2 @@
+import './jquery';
+import './bootstrap';
diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js
new file mode 100644
index 00000000000..b53f6284afc
--- /dev/null
+++ b/app/assets/javascripts/commons/jquery.js
@@ -0,0 +1,11 @@
+import 'jquery';
+
+// common jQuery plugins
+import 'jquery-ujs';
+import 'vendor/jquery.endless-scroll';
+import 'vendor/jquery.caret';
+import 'vendor/jquery.atwho';
+import 'vendor/jquery.scrollTo';
+import 'vendor/jquery.nicescroll';
+import 'vendor/jquery.waitforimages';
+import 'select2/select2';
diff --git a/app/assets/javascripts/compare_autocomplete.js.es6 b/app/assets/javascripts/compare_autocomplete.js
index 1eca973e069..1eca973e069 100644
--- a/app/assets/javascripts/compare_autocomplete.js.es6
+++ b/app/assets/javascripts/compare_autocomplete.js
diff --git a/app/assets/javascripts/copy_as_gfm.js.es6 b/app/assets/javascripts/copy_as_gfm.js
index 2bc3d85fba4..2bc3d85fba4 100644
--- a/app/assets/javascripts/copy_as_gfm.js.es6
+++ b/app/assets/javascripts/copy_as_gfm.js
diff --git a/app/assets/javascripts/create_label.js.es6 b/app/assets/javascripts/create_label.js
index 85384d98126..85384d98126 100644
--- a/app/assets/javascripts/create_label.js.es6
+++ b/app/assets/javascripts/create_label.js
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
index b83a4c63fad..b83a4c63fad 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_code_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/stage_code_component.js
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
index cb1687dcc7a..cb1687dcc7a 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/stage_issue_component.js
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
index 42e1bbce744..42e1bbce744 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.js
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
index 73f4205b578..73f4205b578 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_production_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/stage_production_component.js
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
index 501ffb1fac9..501ffb1fac9 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_review_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/stage_review_component.js
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
index 8fa63734cf1..8fa63734cf1 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/stage_staging_component.js
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
index 0015249cfaa..0015249cfaa 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_test_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/stage_test_component.js
diff --git a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6 b/app/assets/javascripts/cycle_analytics/components/total_time_component.js
index 0d85e1a4678..0d85e1a4678 100644
--- a/app/assets/javascripts/cycle_analytics/components/total_time_component.js.es6
+++ b/app/assets/javascripts/cycle_analytics/components/total_time_component.js
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index beff293b587..beff293b587 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js.es6
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
index 9f74b14c4b9..9f74b14c4b9 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js.es6
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6 b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
index 7ae9de7297c..7ae9de7297c 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js.es6
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_store.js
diff --git a/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6 b/app/assets/javascripts/cycle_analytics/default_event_objects.js
index cfaf9835bf8..cfaf9835bf8 100644
--- a/app/assets/javascripts/cycle_analytics/default_event_objects.js.es6
+++ b/app/assets/javascripts/cycle_analytics/default_event_objects.js
diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js
index 6829e8aeaea..6829e8aeaea 100644
--- a/app/assets/javascripts/diff.js.es6
+++ b/app/assets/javascripts/diff.js
diff --git a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
index d948dff58ec..d948dff58ec 100644
--- a/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/comment_resolve_btn.js
diff --git a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6 b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
index 283dc330cad..283dc330cad 100644
--- a/app/assets/javascripts/diff_notes/components/jump_to_discussion.js.es6
+++ b/app/assets/javascripts/diff_notes/components/jump_to_discussion.js
diff --git a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_btn.js
index d1873d6c7a2..d1873d6c7a2 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_btn.js
diff --git a/app/assets/javascripts/diff_notes/components/resolve_count.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_count.js
index de9367f2136..de9367f2136 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_count.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_count.js
diff --git a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6 b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
index 7c5fcd04d2d..7c5fcd04d2d 100644
--- a/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js.es6
+++ b/app/assets/javascripts/diff_notes/components/resolve_discussion_btn.js
diff --git a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6 b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
index cadf8b96b87..cadf8b96b87 100644
--- a/app/assets/javascripts/diff_notes/diff_notes_bundle.js.es6
+++ b/app/assets/javascripts/diff_notes/diff_notes_bundle.js
diff --git a/app/assets/javascripts/diff_notes/mixins/discussion.js.es6 b/app/assets/javascripts/diff_notes/mixins/discussion.js
index 3c08c222f46..3c08c222f46 100644
--- a/app/assets/javascripts/diff_notes/mixins/discussion.js.es6
+++ b/app/assets/javascripts/diff_notes/mixins/discussion.js
diff --git a/app/assets/javascripts/diff_notes/models/discussion.js.es6 b/app/assets/javascripts/diff_notes/models/discussion.js
index fa518ba4d33..fa518ba4d33 100644
--- a/app/assets/javascripts/diff_notes/models/discussion.js.es6
+++ b/app/assets/javascripts/diff_notes/models/discussion.js
diff --git a/app/assets/javascripts/diff_notes/models/note.js.es6 b/app/assets/javascripts/diff_notes/models/note.js
index f3a7cba5ef6..f3a7cba5ef6 100644
--- a/app/assets/javascripts/diff_notes/models/note.js.es6
+++ b/app/assets/javascripts/diff_notes/models/note.js
diff --git a/app/assets/javascripts/diff_notes/services/resolve.js.es6 b/app/assets/javascripts/diff_notes/services/resolve.js
index 090c454e9e4..090c454e9e4 100644
--- a/app/assets/javascripts/diff_notes/services/resolve.js.es6
+++ b/app/assets/javascripts/diff_notes/services/resolve.js
diff --git a/app/assets/javascripts/diff_notes/stores/comments.js.es6 b/app/assets/javascripts/diff_notes/stores/comments.js
index c80d979b977..c80d979b977 100644
--- a/app/assets/javascripts/diff_notes/stores/comments.js.es6
+++ b/app/assets/javascripts/diff_notes/stores/comments.js
diff --git a/app/assets/javascripts/dispatcher.js.es6 b/app/assets/javascripts/dispatcher.js
index ef5785b5532..ef5785b5532 100644
--- a/app/assets/javascripts/dispatcher.js.es6
+++ b/app/assets/javascripts/dispatcher.js
diff --git a/app/assets/javascripts/due_date_select.js.es6 b/app/assets/javascripts/due_date_select.js
index 9169fcd7328..9169fcd7328 100644
--- a/app/assets/javascripts/due_date_select.js.es6
+++ b/app/assets/javascripts/due_date_select.js
diff --git a/app/assets/javascripts/environments/components/environment.js.es6 b/app/assets/javascripts/environments/components/environment.js
index 2cb48dde628..2cb48dde628 100644
--- a/app/assets/javascripts/environments/components/environment.js.es6
+++ b/app/assets/javascripts/environments/components/environment.js
diff --git a/app/assets/javascripts/environments/components/environment_actions.js.es6 b/app/assets/javascripts/environments/components/environment_actions.js
index 15e3f8823d2..15e3f8823d2 100644
--- a/app/assets/javascripts/environments/components/environment_actions.js.es6
+++ b/app/assets/javascripts/environments/components/environment_actions.js
diff --git a/app/assets/javascripts/environments/components/environment_external_url.js.es6 b/app/assets/javascripts/environments/components/environment_external_url.js
index 2599bba3c59..2599bba3c59 100644
--- a/app/assets/javascripts/environments/components/environment_external_url.js.es6
+++ b/app/assets/javascripts/environments/components/environment_external_url.js
diff --git a/app/assets/javascripts/environments/components/environment_item.js.es6 b/app/assets/javascripts/environments/components/environment_item.js
index 7f4e070b229..7f4e070b229 100644
--- a/app/assets/javascripts/environments/components/environment_item.js.es6
+++ b/app/assets/javascripts/environments/components/environment_item.js
diff --git a/app/assets/javascripts/environments/components/environment_rollback.js.es6 b/app/assets/javascripts/environments/components/environment_rollback.js
index daf126eb4e8..daf126eb4e8 100644
--- a/app/assets/javascripts/environments/components/environment_rollback.js.es6
+++ b/app/assets/javascripts/environments/components/environment_rollback.js
diff --git a/app/assets/javascripts/environments/components/environment_stop.js.es6 b/app/assets/javascripts/environments/components/environment_stop.js
index 96983a19568..96983a19568 100644
--- a/app/assets/javascripts/environments/components/environment_stop.js.es6
+++ b/app/assets/javascripts/environments/components/environment_stop.js
diff --git a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6 b/app/assets/javascripts/environments/components/environment_terminal_button.js
index e86607e78f4..e86607e78f4 100644
--- a/app/assets/javascripts/environments/components/environment_terminal_button.js.es6
+++ b/app/assets/javascripts/environments/components/environment_terminal_button.js
diff --git a/app/assets/javascripts/environments/components/environments_table.js.es6 b/app/assets/javascripts/environments/components/environments_table.js
index 4088d63be80..4088d63be80 100644
--- a/app/assets/javascripts/environments/components/environments_table.js.es6
+++ b/app/assets/javascripts/environments/components/environments_table.js
diff --git a/app/assets/javascripts/environments/environments_bundle.js.es6 b/app/assets/javascripts/environments/environments_bundle.js
index 7bbba91bc10..7bbba91bc10 100644
--- a/app/assets/javascripts/environments/environments_bundle.js.es6
+++ b/app/assets/javascripts/environments/environments_bundle.js
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index d2ca465351a..d2ca465351a 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js.es6
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6 b/app/assets/javascripts/environments/folder/environments_folder_view.js
index 2a9d0492d7a..2a9d0492d7a 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.js.es6
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.js
diff --git a/app/assets/javascripts/environments/services/environments_service.js.es6 b/app/assets/javascripts/environments/services/environments_service.js
index effc6c4c838..effc6c4c838 100644
--- a/app/assets/javascripts/environments/services/environments_service.js.es6
+++ b/app/assets/javascripts/environments/services/environments_service.js
diff --git a/app/assets/javascripts/environments/stores/environments_store.js.es6 b/app/assets/javascripts/environments/stores/environments_store.js
index 15cd9bde08e..15cd9bde08e 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js.es6
+++ b/app/assets/javascripts/environments/stores/environments_store.js
diff --git a/app/assets/javascripts/extensions/array.js.es6 b/app/assets/javascripts/extensions/array.js
index f8256a8d26d..f8256a8d26d 100644
--- a/app/assets/javascripts/extensions/array.js.es6
+++ b/app/assets/javascripts/extensions/array.js
diff --git a/app/assets/javascripts/extensions/custom_event.js.es6 b/app/assets/javascripts/extensions/custom_event.js
index abedae4c1c7..abedae4c1c7 100644
--- a/app/assets/javascripts/extensions/custom_event.js.es6
+++ b/app/assets/javascripts/extensions/custom_event.js
diff --git a/app/assets/javascripts/extensions/element.js.es6 b/app/assets/javascripts/extensions/element.js
index 90ab79305a7..90ab79305a7 100644
--- a/app/assets/javascripts/extensions/element.js.es6
+++ b/app/assets/javascripts/extensions/element.js
diff --git a/app/assets/javascripts/extensions/object.js.es6 b/app/assets/javascripts/extensions/object.js
index 70a2d765abd..70a2d765abd 100644
--- a/app/assets/javascripts/extensions/object.js.es6
+++ b/app/assets/javascripts/extensions/object.js
diff --git a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6 b/app/assets/javascripts/filtered_search/dropdown_hint.js
index 9e92d544bef..9e92d544bef 100644
--- a/app/assets/javascripts/filtered_search/dropdown_hint.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_hint.js
diff --git a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_non_user.js
index b3dc3e502c5..b3dc3e502c5 100644
--- a/app/assets/javascripts/filtered_search/dropdown_non_user.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_non_user.js
diff --git a/app/assets/javascripts/filtered_search/dropdown_user.js.es6 b/app/assets/javascripts/filtered_search/dropdown_user.js
index 7e9c6f74aa5..7e9c6f74aa5 100644
--- a/app/assets/javascripts/filtered_search/dropdown_user.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_user.js
diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js
index de3fa116717..de3fa116717 100644
--- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6
+++ b/app/assets/javascripts/filtered_search/dropdown_utils.js
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
index dd565da507e..dd565da507e 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown.js
diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
index cecd3518ce3..cecd3518ce3 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index bbafead0305..bbafead0305 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
index e6b53cd4b55..e6b53cd4b55 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js
diff --git a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
index 9bf1b1ced88..9bf1b1ced88 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js.es6
+++ b/app/assets/javascripts/filtered_search/filtered_search_tokenizer.js
diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js
index 60d6658dc16..60d6658dc16 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.es6
+++ b/app/assets/javascripts/gfm_auto_complete.js
diff --git a/app/assets/javascripts/gl_field_error.js.es6 b/app/assets/javascripts/gl_field_error.js
index f7cbecc0385..f7cbecc0385 100644
--- a/app/assets/javascripts/gl_field_error.js.es6
+++ b/app/assets/javascripts/gl_field_error.js
diff --git a/app/assets/javascripts/gl_field_errors.js.es6 b/app/assets/javascripts/gl_field_errors.js
index e9add115429..e9add115429 100644
--- a/app/assets/javascripts/gl_field_errors.js.es6
+++ b/app/assets/javascripts/gl_field_errors.js
diff --git a/app/assets/javascripts/gl_form.js.es6 b/app/assets/javascripts/gl_form.js
index 0b446ff364a..0b446ff364a 100644
--- a/app/assets/javascripts/gl_form.js.es6
+++ b/app/assets/javascripts/gl_form.js
diff --git a/app/assets/javascripts/graphs/graphs_bundle.js b/app/assets/javascripts/graphs/graphs_bundle.js
index ea5afbd9d29..a433c7ba8f0 100644
--- a/app/assets/javascripts/graphs/graphs_bundle.js
+++ b/app/assets/javascripts/graphs/graphs_bundle.js
@@ -1,4 +1,6 @@
+import Chart from 'vendor/Chart';
import ContributorsStatGraph from './stat_graph_contributors';
// export to global scope
+window.Chart = Chart;
window.ContributorsStatGraph = ContributorsStatGraph;
diff --git a/app/assets/javascripts/group_label_subscription.js.es6 b/app/assets/javascripts/group_label_subscription.js
index 15e695e81cf..15e695e81cf 100644
--- a/app/assets/javascripts/group_label_subscription.js.es6
+++ b/app/assets/javascripts/group_label_subscription.js
diff --git a/app/assets/javascripts/issuable.js.es6 b/app/assets/javascripts/issuable.js
index 3bfce32768a..3bfce32768a 100644
--- a/app/assets/javascripts/issuable.js.es6
+++ b/app/assets/javascripts/issuable.js
diff --git a/app/assets/javascripts/issuable/issuable_bundle.js.es6 b/app/assets/javascripts/issuable/issuable_bundle.js
index e927cc0077c..e927cc0077c 100644
--- a/app/assets/javascripts/issuable/issuable_bundle.js.es6
+++ b/app/assets/javascripts/issuable/issuable_bundle.js
diff --git a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js
index 357b3487ca9..357b3487ca9 100644
--- a/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/components/collapsed_state.js
diff --git a/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js
index 750468c679b..750468c679b 100644
--- a/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/components/comparison_pane.js
diff --git a/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js
index 309e9f2f9ef..309e9f2f9ef 100644
--- a/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/components/estimate_only_pane.js
diff --git a/app/assets/javascripts/issuable/time_tracking/components/help_state.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/help_state.js
index d7ced6d7151..d7ced6d7151 100644
--- a/app/assets/javascripts/issuable/time_tracking/components/help_state.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/components/help_state.js
diff --git a/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js
index 1d2ca643b5b..1d2ca643b5b 100644
--- a/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/components/no_tracking_pane.js
diff --git a/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js
index ed283fec3c3..ed283fec3c3 100644
--- a/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/components/spent_only_pane.js
diff --git a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6 b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js
index 1fae2d62b14..1fae2d62b14 100644
--- a/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/components/time_tracker.js
diff --git a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6 b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js
index 958a0cc6d50..0134b7cb6f3 100644
--- a/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js.es6
+++ b/app/assets/javascripts/issuable/time_tracking/time_tracking_bundle.js
@@ -1,5 +1,7 @@
/* global Vue */
+window.Vue = require('vue');
+window.Vue.use(require('vue-resource'));
require('./components/time_tracker');
require('../../smart_interval');
require('../../subbable_resource');
diff --git a/app/assets/javascripts/issues_bulk_assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js
index e0ebd36a65c..e0ebd36a65c 100644
--- a/app/assets/javascripts/issues_bulk_assignment.js.es6
+++ b/app/assets/javascripts/issues_bulk_assignment.js
diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js
index 38b2eb9ff14..38b2eb9ff14 100644
--- a/app/assets/javascripts/label_manager.js.es6
+++ b/app/assets/javascripts/label_manager.js
diff --git a/app/assets/javascripts/lib/chart.js b/app/assets/javascripts/lib/chart.js
deleted file mode 100644
index 9b011d89e93..00000000000
--- a/app/assets/javascripts/lib/chart.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren */
-
-window.Chart = require('vendor/Chart');
diff --git a/app/assets/javascripts/lib/cropper.js b/app/assets/javascripts/lib/cropper.js
deleted file mode 100644
index 7862c6797c3..00000000000
--- a/app/assets/javascripts/lib/cropper.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren */
-
-/*= require cropper */
-
-(function() {
-
-}).call(window);
diff --git a/app/assets/javascripts/lib/d3.js b/app/assets/javascripts/lib/d3.js
deleted file mode 100644
index a9dd32edbed..00000000000
--- a/app/assets/javascripts/lib/d3.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren */
-
-window.d3 = require('d3');
diff --git a/app/assets/javascripts/lib/raphael.js b/app/assets/javascripts/lib/raphael.js
deleted file mode 100644
index ebe1e2ae98d..00000000000
--- a/app/assets/javascripts/lib/raphael.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/* eslint-disable func-names, space-before-function-paren */
-
-/*= require raphael */
-/*= require g.raphael */
-/*= require g.bar */
-
-(function() {
-
-}).call(window);
diff --git a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6 b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
index 2955bda1a36..2955bda1a36 100644
--- a/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js.es6
+++ b/app/assets/javascripts/lib/utils/bootstrap_linked_tabs.js
diff --git a/app/assets/javascripts/lib/utils/common_utils.js.es6 b/app/assets/javascripts/lib/utils/common_utils.js
index a1423b6fda5..a1423b6fda5 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js.es6
+++ b/app/assets/javascripts/lib/utils/common_utils.js
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js.es6 b/app/assets/javascripts/lib/utils/datetime_utility.js
index 82dcbdc26c8..82dcbdc26c8 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js.es6
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
diff --git a/app/assets/javascripts/lib/utils/pretty_time.js.es6 b/app/assets/javascripts/lib/utils/pretty_time.js
index ae397212e55..ae397212e55 100644
--- a/app/assets/javascripts/lib/utils/pretty_time.js.es6
+++ b/app/assets/javascripts/lib/utils/pretty_time.js
diff --git a/app/assets/javascripts/lib/utils/url_utility.js.es6 b/app/assets/javascripts/lib/utils/url_utility.js
index 1bc81d2e4a4..1bc81d2e4a4 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js.es6
+++ b/app/assets/javascripts/lib/utils/url_utility.js
diff --git a/app/assets/javascripts/lib/vue_resource.js.es6 b/app/assets/javascripts/lib/vue_resource.js.es6
deleted file mode 100644
index 49babdea2e1..00000000000
--- a/app/assets/javascripts/lib/vue_resource.js.es6
+++ /dev/null
@@ -1,2 +0,0 @@
-window.Vue = require('vue');
-window.Vue.use(require('vue-resource'));
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/main.js
index 4c24d35b5bb..798553c16ac 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/main.js
@@ -6,35 +6,34 @@
/* global AwardsHandler */
/* global Aside */
-window.$ = window.jQuery = require('jquery');
-require('jquery-ujs');
-require('vendor/jquery.endless-scroll');
-require('vendor/jquery.waitforimages');
-require('vendor/jquery.caret');
-require('vendor/jquery.atwho');
-require('vendor/jquery.scrollTo');
-window.Cookies = require('js-cookie');
-require('./autosave');
-require('bootstrap/js/affix');
-require('bootstrap/js/alert');
-require('bootstrap/js/dropdown');
-require('bootstrap/js/modal');
-require('bootstrap/js/tab');
-require('bootstrap/js/transition');
-require('bootstrap/js/tooltip');
-require('select2/select2.js');
-window.Pikaday = require('pikaday');
-window._ = require('underscore');
-window.Dropzone = require('dropzone');
-window.Sortable = require('vendor/Sortable');
+import jQuery from 'jquery';
+import _ from 'underscore';
+import Cookies from 'js-cookie';
+import Pikaday from 'pikaday';
+import Dropzone from 'dropzone';
+import Sortable from 'vendor/Sortable';
+
+// libraries with import side-effects
require('mousetrap');
require('mousetrap/plugins/pause/mousetrap-pause');
+require('vendor/fuzzaldrin-plus');
+require('es6-promise').polyfill();
+
+// expose common libraries as globals (TODO: remove these)
+window.jQuery = jQuery;
+window.$ = jQuery;
+window._ = _;
+window.Cookies = Cookies;
+window.Pikaday = Pikaday;
+window.Dropzone = Dropzone;
+window.Sortable = Sortable;
+
+// shortcuts
require('./shortcuts');
require('./shortcuts_navigation');
require('./shortcuts_dashboard_navigation');
require('./shortcuts_issuable');
require('./shortcuts_network');
-require('vendor/jquery.nicescroll');
// behaviors
require('./behaviors/autosize');
@@ -205,9 +204,6 @@ require('./visibility_select');
require('./wikis');
require('./zen_mode');
-require('vendor/fuzzaldrin-plus');
-require('es6-promise').polyfill();
-
(function () {
document.addEventListener('beforeunload', function () {
// Unbind scroll events
@@ -285,7 +281,7 @@ require('es6-promise').polyfill();
$.fn.tooltip.Constructor.DEFAULTS.trigger = 'hover';
$body.tooltip({
selector: '.has-tooltip, [data-toggle="tooltip"]',
- placement: function (_, el) {
+ placement: function (tip, el) {
return $(el).data('placement') || 'bottom';
}
});
diff --git a/app/assets/javascripts/member_expiration_date.js.es6 b/app/assets/javascripts/member_expiration_date.js
index 129d2dc5f0a..129d2dc5f0a 100644
--- a/app/assets/javascripts/member_expiration_date.js.es6
+++ b/app/assets/javascripts/member_expiration_date.js
diff --git a/app/assets/javascripts/members.js.es6 b/app/assets/javascripts/members.js
index e3f367a11eb..e3f367a11eb 100644
--- a/app/assets/javascripts/members.js.es6
+++ b/app/assets/javascripts/members.js
diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6 b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
index c7e78fed8fe..c7e78fed8fe 100644
--- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js.es6
+++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.js
diff --git a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js
index 240c8f98932..240c8f98932 100644
--- a/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js.es6
+++ b/app/assets/javascripts/merge_conflicts/components/inline_conflict_lines.js
diff --git a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6 b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
index 97753c50b60..97753c50b60 100644
--- a/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js.es6
+++ b/app/assets/javascripts/merge_conflicts/components/parallel_conflict_lines.js
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
index c012b77e0bf..c012b77e0bf 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_service.js.es6
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_service.js
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
index 74587df22c5..74587df22c5 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_store.js.es6
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_store.js
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6 b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
index 653e52fb6bf..653e52fb6bf 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js.es6
+++ b/app/assets/javascripts/merge_conflicts/merge_conflicts_bundle.js
diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js
index 53e000d7e9e..53e000d7e9e 100644
--- a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js.es6
+++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_actions.js
diff --git a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6 b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js
index 0f475f62ee6..0f475f62ee6 100644
--- a/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js.es6
+++ b/app/assets/javascripts/merge_conflicts/mixins/line_conflict_utils.js
diff --git a/app/assets/javascripts/merge_request_tabs.js.es6 b/app/assets/javascripts/merge_request_tabs.js
index 190336dbd20..190336dbd20 100644
--- a/app/assets/javascripts/merge_request_tabs.js.es6
+++ b/app/assets/javascripts/merge_request_tabs.js
diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js
index 5f1bd474a0c..5f1bd474a0c 100644
--- a/app/assets/javascripts/merge_request_widget.js.es6
+++ b/app/assets/javascripts/merge_request_widget.js
diff --git a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6 b/app/assets/javascripts/merge_request_widget/ci_bundle.js
index 21d7c3e168e..21d7c3e168e 100644
--- a/app/assets/javascripts/merge_request_widget/ci_bundle.js.es6
+++ b/app/assets/javascripts/merge_request_widget/ci_bundle.js
diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6 b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
index 2145e531331..2145e531331 100644
--- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js.es6
+++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js
diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js
index 43dc9838977..5aad3908eb6 100644
--- a/app/assets/javascripts/network/branch_graph.js
+++ b/app/assets/javascripts/network/branch_graph.js
@@ -1,424 +1,347 @@
-/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, new-cap, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */
-/* global Raphael */
+/* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */
-(function() {
- var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
+import Raphael from './raphael';
- this.BranchGraph = (function() {
- function BranchGraph(element1, options1) {
- this.element = element1;
- this.options = options1;
- this.scrollTop = bind(this.scrollTop, this);
- this.scrollBottom = bind(this.scrollBottom, this);
- this.scrollRight = bind(this.scrollRight, this);
- this.scrollLeft = bind(this.scrollLeft, this);
- this.scrollUp = bind(this.scrollUp, this);
- this.scrollDown = bind(this.scrollDown, this);
- this.preparedCommits = {};
- this.mtime = 0;
- this.mspace = 0;
- this.parents = {};
- this.colors = ["#000"];
- this.offsetX = 150;
- this.offsetY = 20;
- this.unitTime = 30;
- this.unitSpace = 10;
- this.prev_start = -1;
- this.load();
- }
-
- BranchGraph.prototype.load = function() {
- return $.ajax({
- url: this.options.url,
- method: "get",
- dataType: "json",
- success: $.proxy(function(data) {
- $(".loading", this.element).hide();
- this.prepareData(data.days, data.commits);
- return this.buildGraph();
- }, this)
- });
- };
+export default (function() {
+ function BranchGraph(element1, options1) {
+ this.element = element1;
+ this.options = options1;
+ this.scrollTop = this.scrollTop.bind(this);
+ this.scrollBottom = this.scrollBottom.bind(this);
+ this.scrollRight = this.scrollRight.bind(this);
+ this.scrollLeft = this.scrollLeft.bind(this);
+ this.scrollUp = this.scrollUp.bind(this);
+ this.scrollDown = this.scrollDown.bind(this);
+ this.preparedCommits = {};
+ this.mtime = 0;
+ this.mspace = 0;
+ this.parents = {};
+ this.colors = ["#000"];
+ this.offsetX = 150;
+ this.offsetY = 20;
+ this.unitTime = 30;
+ this.unitSpace = 10;
+ this.prev_start = -1;
+ this.load();
+ }
- BranchGraph.prototype.prepareData = function(days, commits) {
- var c, ch, cw, j, len, ref;
- this.days = days;
- this.commits = commits;
- this.collectParents();
- this.graphHeight = $(this.element).height();
- this.graphWidth = $(this.element).width();
- ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150);
- cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300);
- this.r = Raphael(this.element.get(0), cw, ch);
- this.top = this.r.set();
- this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320);
- ref = this.commits;
- for (j = 0, len = ref.length; j < len; j += 1) {
- c = ref[j];
- if (c.id in this.parents) {
- c.isParent = true;
- }
- this.preparedCommits[c.id] = c;
- this.markCommit(c);
- }
- return this.collectColors();
- };
-
- BranchGraph.prototype.collectParents = function() {
- var c, j, len, p, ref, results;
- ref = this.commits;
- results = [];
- for (j = 0, len = ref.length; j < len; j += 1) {
- c = ref[j];
- this.mtime = Math.max(this.mtime, c.time);
- this.mspace = Math.max(this.mspace, c.space);
- results.push((function() {
- var l, len1, ref1, results1;
- ref1 = c.parents;
- results1 = [];
- for (l = 0, len1 = ref1.length; l < len1; l += 1) {
- p = ref1[l];
- this.parents[p[0]] = true;
- results1.push(this.mspace = Math.max(this.mspace, p[1]));
- }
- return results1;
- }).call(this));
- }
- return results;
- };
+ BranchGraph.prototype.load = function() {
+ return $.ajax({
+ url: this.options.url,
+ method: "get",
+ dataType: "json",
+ success: $.proxy(function(data) {
+ $(".loading", this.element).hide();
+ this.prepareData(data.days, data.commits);
+ return this.buildGraph();
+ }, this)
+ });
+ };
- BranchGraph.prototype.collectColors = function() {
- var k, results;
- k = 0;
- results = [];
- while (k < this.mspace) {
- this.colors.push(Raphael.getColor(.8));
- // Skipping a few colors in the spectrum to get more contrast between colors
- Raphael.getColor();
- Raphael.getColor();
- results.push(k += 1);
+ BranchGraph.prototype.prepareData = function(days, commits) {
+ var c, ch, cw, j, len, ref;
+ this.days = days;
+ this.commits = commits;
+ this.collectParents();
+ this.graphHeight = $(this.element).height();
+ this.graphWidth = $(this.element).width();
+ ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150);
+ cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300);
+ this.r = Raphael(this.element.get(0), cw, ch);
+ this.top = this.r.set();
+ this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320);
+ ref = this.commits;
+ for (j = 0, len = ref.length; j < len; j += 1) {
+ c = ref[j];
+ if (c.id in this.parents) {
+ c.isParent = true;
}
- return results;
- };
+ this.preparedCommits[c.id] = c;
+ this.markCommit(c);
+ }
+ return this.collectColors();
+ };
- BranchGraph.prototype.buildGraph = function() {
- var cuday, cumonth, day, j, len, mm, r, ref;
- r = this.r;
- cuday = 0;
- cumonth = "";
- r.rect(0, 0, 40, this.barHeight).attr({
- fill: "#222"
- });
- r.rect(40, 0, 30, this.barHeight).attr({
- fill: "#444"
- });
- ref = this.days;
- for (mm = j = 0, len = ref.length; j < len; mm = (j += 1)) {
- day = ref[mm];
- if (cuday !== day[0] || cumonth !== day[1]) {
- // Dates
- r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
- font: "12px Monaco, monospace",
- fill: "#BBB"
- });
- cuday = day[0];
- }
- if (cumonth !== day[1]) {
- // Months
- r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
- font: "12px Monaco, monospace",
- fill: "#EEE"
- });
- cumonth = day[1];
+ BranchGraph.prototype.collectParents = function() {
+ var c, j, len, p, ref, results;
+ ref = this.commits;
+ results = [];
+ for (j = 0, len = ref.length; j < len; j += 1) {
+ c = ref[j];
+ this.mtime = Math.max(this.mtime, c.time);
+ this.mspace = Math.max(this.mspace, c.space);
+ results.push((function() {
+ var l, len1, ref1, results1;
+ ref1 = c.parents;
+ results1 = [];
+ for (l = 0, len1 = ref1.length; l < len1; l += 1) {
+ p = ref1[l];
+ this.parents[p[0]] = true;
+ results1.push(this.mspace = Math.max(this.mspace, p[1]));
}
- }
- this.renderPartialGraph();
- return this.bindEvents();
- };
+ return results1;
+ }).call(this));
+ }
+ return results;
+ };
- BranchGraph.prototype.renderPartialGraph = function() {
- var commit, end, i, isGraphEdge, start, x, y;
- start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10;
- if (start < 0) {
- isGraphEdge = true;
- start = 0;
+ BranchGraph.prototype.collectColors = function() {
+ var k, results;
+ k = 0;
+ results = [];
+ while (k < this.mspace) {
+ this.colors.push(Raphael.getColor(.8));
+ // Skipping a few colors in the spectrum to get more contrast between colors
+ Raphael.getColor();
+ Raphael.getColor();
+ results.push(k += 1);
+ }
+ return results;
+ };
+
+ BranchGraph.prototype.buildGraph = function() {
+ var cuday, cumonth, day, j, len, mm, r, ref;
+ r = this.r;
+ cuday = 0;
+ cumonth = "";
+ r.rect(0, 0, 40, this.barHeight).attr({
+ fill: "#222"
+ });
+ r.rect(40, 0, 30, this.barHeight).attr({
+ fill: "#444"
+ });
+ ref = this.days;
+ for (mm = j = 0, len = ref.length; j < len; mm = (j += 1)) {
+ day = ref[mm];
+ if (cuday !== day[0] || cumonth !== day[1]) {
+ // Dates
+ r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({
+ font: "12px Monaco, monospace",
+ fill: "#BBB"
+ });
+ cuday = day[0];
}
- end = start + 40;
- if (this.commits.length < end) {
- isGraphEdge = true;
- end = this.commits.length;
+ if (cumonth !== day[1]) {
+ // Months
+ r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({
+ font: "12px Monaco, monospace",
+ fill: "#EEE"
+ });
+ cumonth = day[1];
}
- if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) {
- i = start;
- this.prev_start = start;
- while (i < end) {
- commit = this.commits[i];
- i += 1;
- if (commit.hasDrawn !== true) {
- x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
- y = this.offsetY + this.unitTime * commit.time;
- this.drawDot(x, y, commit);
- this.drawLines(x, y, commit);
- this.appendLabel(x, y, commit);
- this.appendAnchor(x, y, commit);
- commit.hasDrawn = true;
- }
+ }
+ this.renderPartialGraph();
+ return this.bindEvents();
+ };
+
+ BranchGraph.prototype.renderPartialGraph = function() {
+ var commit, end, i, isGraphEdge, start, x, y;
+ start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10;
+ if (start < 0) {
+ isGraphEdge = true;
+ start = 0;
+ }
+ end = start + 40;
+ if (this.commits.length < end) {
+ isGraphEdge = true;
+ end = this.commits.length;
+ }
+ if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) {
+ i = start;
+ this.prev_start = start;
+ while (i < end) {
+ commit = this.commits[i];
+ i += 1;
+ if (commit.hasDrawn !== true) {
+ x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
+ y = this.offsetY + this.unitTime * commit.time;
+ this.drawDot(x, y, commit);
+ this.drawLines(x, y, commit);
+ this.appendLabel(x, y, commit);
+ this.appendAnchor(x, y, commit);
+ commit.hasDrawn = true;
}
- return this.top.toFront();
}
- };
-
- BranchGraph.prototype.bindEvents = function() {
- var element;
- element = this.element;
- return $(element).scroll((function(_this) {
- return function(event) {
- return _this.renderPartialGraph();
- };
- })(this));
- };
-
- BranchGraph.prototype.scrollDown = function() {
- this.element.scrollTop(this.element.scrollTop() + 50);
- return this.renderPartialGraph();
- };
-
- BranchGraph.prototype.scrollUp = function() {
- this.element.scrollTop(this.element.scrollTop() - 50);
- return this.renderPartialGraph();
- };
-
- BranchGraph.prototype.scrollLeft = function() {
- this.element.scrollLeft(this.element.scrollLeft() - 50);
- return this.renderPartialGraph();
- };
-
- BranchGraph.prototype.scrollRight = function() {
- this.element.scrollLeft(this.element.scrollLeft() + 50);
- return this.renderPartialGraph();
- };
-
- BranchGraph.prototype.scrollBottom = function() {
- return this.element.scrollTop(this.element.find('svg').height());
- };
+ return this.top.toFront();
+ }
+ };
- BranchGraph.prototype.scrollTop = function() {
- return this.element.scrollTop(0);
- };
+ BranchGraph.prototype.bindEvents = function() {
+ var element;
+ element = this.element;
+ return $(element).scroll((function(_this) {
+ return function(event) {
+ return _this.renderPartialGraph();
+ };
+ })(this));
+ };
- BranchGraph.prototype.appendLabel = function(x, y, commit) {
- var label, r, rect, shortrefs, text, textbox, triangle;
- if (!commit.refs) {
- return;
- }
- r = this.r;
- shortrefs = commit.refs;
- // Truncate if longer than 15 chars
- if (shortrefs.length > 17) {
- shortrefs = shortrefs.substr(0, 15) + "…";
- }
- text = r.text(x + 4, y, shortrefs).attr({
- "text-anchor": "start",
- font: "10px Monaco, monospace",
- fill: "#FFF",
- title: commit.refs
- });
- textbox = text.getBBox();
- // Create rectangle based on the size of the textbox
- rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
- fill: "#000",
- "fill-opacity": .5,
- stroke: "none"
- });
- triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({
- fill: "#000",
- "fill-opacity": .5,
- stroke: "none"
- });
- label = r.set(rect, text);
- label.transform(["t", -rect.getBBox().width - 15, 0]);
- // Set text to front
- return text.toFront();
- };
+ BranchGraph.prototype.scrollDown = function() {
+ this.element.scrollTop(this.element.scrollTop() + 50);
+ return this.renderPartialGraph();
+ };
- BranchGraph.prototype.appendAnchor = function(x, y, commit) {
- var anchor, options, r, top;
- r = this.r;
- top = this.top;
- options = this.options;
- anchor = r.circle(x, y, 10).attr({
- fill: "#000",
- opacity: 0,
- cursor: "pointer"
- }).click(function() {
- return window.open(options.commit_url.replace("%s", commit.id), "_blank");
- }).hover(function() {
- this.tooltip = r.commitTooltip(x + 5, y, commit);
- return top.push(this.tooltip.insertBefore(this));
- }, function() {
- return this.tooltip && this.tooltip.remove() && delete this.tooltip;
- });
- return top.push(anchor);
- };
+ BranchGraph.prototype.scrollUp = function() {
+ this.element.scrollTop(this.element.scrollTop() - 50);
+ return this.renderPartialGraph();
+ };
- BranchGraph.prototype.drawDot = function(x, y, commit) {
- var avatar_box_x, avatar_box_y, r;
- r = this.r;
- r.circle(x, y, 3).attr({
- fill: this.colors[commit.space],
- stroke: "none"
- });
- avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10;
- avatar_box_y = y - 10;
- r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({
- stroke: this.colors[commit.space],
- "stroke-width": 2
- });
- r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20);
- return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({
- "text-anchor": "start",
- font: "14px Monaco, monospace"
- });
- };
+ BranchGraph.prototype.scrollLeft = function() {
+ this.element.scrollLeft(this.element.scrollLeft() - 50);
+ return this.renderPartialGraph();
+ };
- BranchGraph.prototype.drawLines = function(x, y, commit) {
- var arrow, color, i, j, len, offset, parent, parentCommit, parentX1, parentX2, parentY, r, ref, results, route;
- r = this.r;
- ref = commit.parents;
- results = [];
- for (i = j = 0, len = ref.length; j < len; i = (j += 1)) {
- parent = ref[i];
- parentCommit = this.preparedCommits[parent[0]];
- parentY = this.offsetY + this.unitTime * parentCommit.time;
- parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
- parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
- // Set line color
- if (parentCommit.space <= commit.space) {
- color = this.colors[commit.space];
- } else {
- color = this.colors[parentCommit.space];
- }
- // Build line shape
- if (parent[1] === commit.space) {
- offset = [0, 5];
- arrow = "l-2,5,4,0,-2,-5,0,5";
- } else if (parent[1] < commit.space) {
- offset = [3, 3];
- arrow = "l5,0,-2,4,-3,-4,4,2";
- } else {
- offset = [-3, 3];
- arrow = "l-5,0,2,4,3,-4,-4,2";
- }
- // Start point
- route = ["M", x + offset[0], y + offset[1]];
- // Add arrow if not first parent
- if (i > 0) {
- route.push(arrow);
- }
- // Circumvent if overlap
- if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
- route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5);
- }
- // End point
- route.push("L", parentX1, parentY);
- results.push(r.path(route).attr({
- stroke: color,
- "stroke-width": 2
- }));
- }
- return results;
- };
+ BranchGraph.prototype.scrollRight = function() {
+ this.element.scrollLeft(this.element.scrollLeft() + 50);
+ return this.renderPartialGraph();
+ };
- BranchGraph.prototype.markCommit = function(commit) {
- var r, x, y;
- if (commit.id === this.options.commit_id) {
- r = this.r;
- x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
- y = this.offsetY + this.unitTime * commit.time;
- r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({
- fill: "#000",
- "fill-opacity": .5,
- stroke: "none"
- });
- // Displayed in the center
- return this.element.scrollTop(y - this.graphHeight / 2);
- }
- };
+ BranchGraph.prototype.scrollBottom = function() {
+ return this.element.scrollTop(this.element.find('svg').height());
+ };
- return BranchGraph;
- })();
+ BranchGraph.prototype.scrollTop = function() {
+ return this.element.scrollTop(0);
+ };
- Raphael.prototype.commitTooltip = function(x, y, commit) {
- var boxHeight, boxWidth, icon, idText, messageText, nameText, rect, textSet, tooltip;
- boxWidth = 300;
- boxHeight = 200;
- icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20);
- nameText = this.text(x + 25, y + 10, commit.author.name);
- idText = this.text(x, y + 35, commit.id);
- messageText = this.text(x, y + 50, commit.message.replace(/\r?\n/g, " \n "));
- textSet = this.set(icon, nameText, idText, messageText).attr({
+ BranchGraph.prototype.appendLabel = function(x, y, commit) {
+ var label, r, rect, shortrefs, text, textbox, triangle;
+ if (!commit.refs) {
+ return;
+ }
+ r = this.r;
+ shortrefs = commit.refs;
+ // Truncate if longer than 15 chars
+ if (shortrefs.length > 17) {
+ shortrefs = shortrefs.substr(0, 15) + "…";
+ }
+ text = r.text(x + 4, y, shortrefs).attr({
"text-anchor": "start",
- font: "12px Monaco, monospace"
- });
- nameText.attr({
- font: "14px Arial",
- "font-weight": "bold"
+ font: "10px Monaco, monospace",
+ fill: "#FFF",
+ title: commit.refs
});
- idText.attr({
- fill: "#AAA"
+ textbox = text.getBBox();
+ // Create rectangle based on the size of the textbox
+ rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({
+ fill: "#000",
+ "fill-opacity": .5,
+ stroke: "none"
});
- messageText.node.style["white-space"] = "pre";
- this.textWrap(messageText, boxWidth - 50);
- rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({
- fill: "#FFF",
- stroke: "#000",
- "stroke-linecap": "round",
- "stroke-width": 2
+ triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({
+ fill: "#000",
+ "fill-opacity": .5,
+ stroke: "none"
});
- tooltip = this.set(rect, textSet);
- rect.attr({
- height: tooltip.getBBox().height + 10,
- width: tooltip.getBBox().width + 10
+ label = r.set(rect, text);
+ label.transform(["t", -rect.getBBox().width - 15, 0]);
+ // Set text to front
+ return text.toFront();
+ };
+
+ BranchGraph.prototype.appendAnchor = function(x, y, commit) {
+ var anchor, options, r, top;
+ r = this.r;
+ top = this.top;
+ options = this.options;
+ anchor = r.circle(x, y, 10).attr({
+ fill: "#000",
+ opacity: 0,
+ cursor: "pointer"
+ }).click(function() {
+ return window.open(options.commit_url.replace("%s", commit.id), "_blank");
+ }).hover(function() {
+ this.tooltip = r.commitTooltip(x + 5, y, commit);
+ return top.push(this.tooltip.insertBefore(this));
+ }, function() {
+ return this.tooltip && this.tooltip.remove() && delete this.tooltip;
});
- tooltip.transform(["t", 20, 20]);
- return tooltip;
+ return top.push(anchor);
};
- Raphael.prototype.textWrap = function(t, width) {
- var abc, b, content, h, j, len, letterWidth, s, word, words, x;
- content = t.attr("text");
- abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
- t.attr({
- text: abc
+ BranchGraph.prototype.drawDot = function(x, y, commit) {
+ var avatar_box_x, avatar_box_y, r;
+ r = this.r;
+ r.circle(x, y, 3).attr({
+ fill: this.colors[commit.space],
+ stroke: "none"
});
- letterWidth = t.getBBox().width / abc.length;
- t.attr({
- text: content
+ avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10;
+ avatar_box_y = y - 10;
+ r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({
+ stroke: this.colors[commit.space],
+ "stroke-width": 2
});
- words = content.split(" ");
- x = 0;
- s = [];
- for (j = 0, len = words.length; j < len; j += 1) {
- word = words[j];
- if (x + (word.length * letterWidth) > width) {
- s.push("\n");
- x = 0;
+ r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20);
+ return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({
+ "text-anchor": "start",
+ font: "14px Monaco, monospace"
+ });
+ };
+
+ BranchGraph.prototype.drawLines = function(x, y, commit) {
+ var arrow, color, i, j, len, offset, parent, parentCommit, parentX1, parentX2, parentY, r, ref, results, route;
+ r = this.r;
+ ref = commit.parents;
+ results = [];
+ for (i = j = 0, len = ref.length; j < len; i = (j += 1)) {
+ parent = ref[i];
+ parentCommit = this.preparedCommits[parent[0]];
+ parentY = this.offsetY + this.unitTime * parentCommit.time;
+ parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space);
+ parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]);
+ // Set line color
+ if (parentCommit.space <= commit.space) {
+ color = this.colors[commit.space];
+ } else {
+ color = this.colors[parentCommit.space];
}
- if (word === "\n") {
- s.push("\n");
- x = 0;
+ // Build line shape
+ if (parent[1] === commit.space) {
+ offset = [0, 5];
+ arrow = "l-2,5,4,0,-2,-5,0,5";
+ } else if (parent[1] < commit.space) {
+ offset = [3, 3];
+ arrow = "l5,0,-2,4,-3,-4,4,2";
} else {
- s.push(word + " ");
- x += word.length * letterWidth;
+ offset = [-3, 3];
+ arrow = "l-5,0,2,4,3,-4,-4,2";
+ }
+ // Start point
+ route = ["M", x + offset[0], y + offset[1]];
+ // Add arrow if not first parent
+ if (i > 0) {
+ route.push(arrow);
+ }
+ // Circumvent if overlap
+ if (commit.space !== parentCommit.space || commit.space !== parent[1]) {
+ route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5);
}
+ // End point
+ route.push("L", parentX1, parentY);
+ results.push(r.path(route).attr({
+ stroke: color,
+ "stroke-width": 2
+ }));
+ }
+ return results;
+ };
+
+ BranchGraph.prototype.markCommit = function(commit) {
+ var r, x, y;
+ if (commit.id === this.options.commit_id) {
+ r = this.r;
+ x = this.offsetX + this.unitSpace * (this.mspace - commit.space);
+ y = this.offsetY + this.unitTime * commit.time;
+ r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({
+ fill: "#000",
+ "fill-opacity": .5,
+ stroke: "none"
+ });
+ // Displayed in the center
+ return this.element.scrollTop(y - this.graphHeight / 2);
}
- t.attr({
- text: s.join("").trim()
- });
- b = t.getBBox();
- h = Math.abs(b.y2) + 1;
- return t.attr({
- y: h
- });
};
-}).call(window);
+
+ return BranchGraph;
+})();
diff --git a/app/assets/javascripts/network/network.js b/app/assets/javascripts/network/network.js
index 8e7027b44e7..a3fd22aff2a 100644
--- a/app/assets/javascripts/network/network.js
+++ b/app/assets/javascripts/network/network.js
@@ -1,20 +1,19 @@
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */
-/* global BranchGraph */
-(function() {
- this.Network = (function() {
- function Network(opts) {
- var vph;
- $("#filter_ref").click(function() {
- return $(this).closest('form').submit();
- });
- this.branch_graph = new BranchGraph($(".network-graph"), opts);
- vph = $(window).height() - 250;
- $('.network-graph').css({
- 'height': vph + 'px'
- });
- }
+import BranchGraph from './branch_graph';
- return Network;
- })();
-}).call(window);
+export default (function() {
+ function Network(opts) {
+ var vph;
+ $("#filter_ref").click(function() {
+ return $(this).closest('form').submit();
+ });
+ this.branch_graph = new BranchGraph($(".network-graph"), opts);
+ vph = $(window).height() - 250;
+ $('.network-graph').css({
+ 'height': vph + 'px'
+ });
+ }
+
+ return Network;
+})();
diff --git a/app/assets/javascripts/network/network_bundle.js b/app/assets/javascripts/network/network_bundle.js
index e5947586583..8aae2ad201c 100644
--- a/app/assets/javascripts/network/network_bundle.js
+++ b/app/assets/javascripts/network/network_bundle.js
@@ -1,21 +1,17 @@
/* eslint-disable func-names, space-before-function-paren, prefer-arrow-callback, quotes, no-var, vars-on-top, camelcase, comma-dangle, consistent-return, max-len */
-/* global Network */
/* global ShortcutsNetwork */
-require('./branch_graph');
-require('./network');
+import Network from './network';
-(function() {
- $(function() {
- if (!$(".network-graph").length) return;
+$(function() {
+ if (!$(".network-graph").length) return;
- var network_graph;
- network_graph = new Network({
- url: $(".network-graph").attr('data-url'),
- commit_url: $(".network-graph").attr('data-commit-url'),
- ref: $(".network-graph").attr('data-ref'),
- commit_id: $(".network-graph").attr('data-commit-id')
- });
- return new ShortcutsNetwork(network_graph.branch_graph);
+ var network_graph;
+ network_graph = new Network({
+ url: $(".network-graph").attr('data-url'),
+ commit_url: $(".network-graph").attr('data-commit-url'),
+ ref: $(".network-graph").attr('data-ref'),
+ commit_id: $(".network-graph").attr('data-commit-id')
});
-}).call(window);
+ return new ShortcutsNetwork(network_graph.branch_graph);
+});
diff --git a/app/assets/javascripts/network/raphael.js b/app/assets/javascripts/network/raphael.js
new file mode 100644
index 00000000000..09dcf716148
--- /dev/null
+++ b/app/assets/javascripts/network/raphael.js
@@ -0,0 +1,74 @@
+import Raphael from 'raphael/raphael';
+
+Raphael.prototype.commitTooltip = function commitTooltip(x, y, commit) {
+ const boxWidth = 300;
+ const icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20);
+ const nameText = this.text(x + 25, y + 10, commit.author.name);
+ const idText = this.text(x, y + 35, commit.id);
+ const messageText = this.text(x, y + 50, commit.message.replace(/\r?\n/g, ' \n '));
+ const textSet = this.set(icon, nameText, idText, messageText).attr({
+ 'text-anchor': 'start',
+ font: '12px Monaco, monospace',
+ });
+ nameText.attr({
+ font: '14px Arial',
+ 'font-weight': 'bold',
+ });
+ idText.attr({
+ fill: '#AAA',
+ });
+ messageText.node.style['white-space'] = 'pre';
+ this.textWrap(messageText, boxWidth - 50);
+ const rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({
+ fill: '#FFF',
+ stroke: '#000',
+ 'stroke-linecap': 'round',
+ 'stroke-width': 2,
+ });
+ const tooltip = this.set(rect, textSet);
+ rect.attr({
+ height: tooltip.getBBox().height + 10,
+ width: tooltip.getBBox().width + 10,
+ });
+ tooltip.transform(['t', 20, 20]);
+ return tooltip;
+};
+
+Raphael.prototype.textWrap = function testWrap(t, width) {
+ const content = t.attr('text');
+ const abc = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ t.attr({
+ text: abc,
+ });
+ const letterWidth = t.getBBox().width / abc.length;
+ t.attr({
+ text: content,
+ });
+ const words = content.split(' ');
+ let x = 0;
+ const s = [];
+ for (let j = 0, len = words.length; j < len; j += 1) {
+ const word = words[j];
+ if (x + (word.length * letterWidth) > width) {
+ s.push('\n');
+ x = 0;
+ }
+ if (word === '\n') {
+ s.push('\n');
+ x = 0;
+ } else {
+ s.push(`${word} `);
+ x += word.length * letterWidth;
+ }
+ }
+ t.attr({
+ text: s.join('').trim(),
+ });
+ const b = t.getBBox();
+ const h = Math.abs(b.y2) + 1;
+ return t.attr({
+ y: h,
+ });
+};
+
+export default Raphael;
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index 47fa0f2eb96..df7a7d2a459 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -198,7 +198,7 @@ require('./task_list');
this.refreshing = true;
return $.ajax({
url: this.notes_url,
- data: "last_fetched_at=" + this.last_fetched_at,
+ headers: { "X-Last-Fetched-At": this.last_fetched_at },
dataType: "json",
success: (function(_this) {
return function(data) {
diff --git a/app/assets/javascripts/pager.js.es6 b/app/assets/javascripts/pager.js
index e35cf6d295e..e35cf6d295e 100644
--- a/app/assets/javascripts/pager.js.es6
+++ b/app/assets/javascripts/pager.js
diff --git a/app/assets/javascripts/pipelines.js.es6 b/app/assets/javascripts/pipelines.js
index 9203abefbbc..9203abefbbc 100644
--- a/app/assets/javascripts/pipelines.js.es6
+++ b/app/assets/javascripts/pipelines.js
diff --git a/app/assets/javascripts/profile/gl_crop.js.es6 b/app/assets/javascripts/profile/gl_crop.js
index 192b1192d07..cf1566eeb87 100644
--- a/app/assets/javascripts/profile/gl_crop.js.es6
+++ b/app/assets/javascripts/profile/gl_crop.js
@@ -1,5 +1,7 @@
/* eslint-disable no-useless-escape, max-len, quotes, no-var, no-underscore-dangle, func-names, space-before-function-paren, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */
+import 'vendor/cropper';
+
((global) => {
// Matches everything but the file name
const FILENAMEREGEX = /^.*[\\\/]/;
diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js
index 4ccea0624ee..4ccea0624ee 100644
--- a/app/assets/javascripts/profile/profile.js.es6
+++ b/app/assets/javascripts/profile/profile.js
diff --git a/app/assets/javascripts/project_label_subscription.js.es6 b/app/assets/javascripts/project_label_subscription.js
index 0a811627600..0a811627600 100644
--- a/app/assets/javascripts/project_label_subscription.js.es6
+++ b/app/assets/javascripts/project_label_subscription.js
diff --git a/app/assets/javascripts/project_variables.js.es6 b/app/assets/javascripts/project_variables.js
index 4ee2e49306d..4ee2e49306d 100644
--- a/app/assets/javascripts/project_variables.js.es6
+++ b/app/assets/javascripts/project_variables.js
diff --git a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
index e7fff57ff45..e7fff57ff45 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_access_dropdown.js
diff --git a/app/assets/javascripts/protected_branches/protected_branch_create.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_create.js
index 57ea2f52814..57ea2f52814 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_create.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_create.js
diff --git a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
index 5cf28aa7a73..5cf28aa7a73 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_dropdown.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_dropdown.js
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit.js
index 6ef59e94384..6ef59e94384 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit.js
diff --git a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6 b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
index 336fa6c57a7..336fa6c57a7 100644
--- a/app/assets/javascripts/protected_branches/protected_branch_edit_list.js.es6
+++ b/app/assets/javascripts/protected_branches/protected_branch_edit_list.js
diff --git a/app/assets/javascripts/search_autocomplete.js.es6 b/app/assets/javascripts/search_autocomplete.js
index 6fd5345a0a6..6fd5345a0a6 100644
--- a/app/assets/javascripts/search_autocomplete.js.es6
+++ b/app/assets/javascripts/search_autocomplete.js
diff --git a/app/assets/javascripts/shortcuts_blob.js.es6 b/app/assets/javascripts/shortcuts_blob.js
index bfe90aef71e..bfe90aef71e 100644
--- a/app/assets/javascripts/shortcuts_blob.js.es6
+++ b/app/assets/javascripts/shortcuts_blob.js
diff --git a/app/assets/javascripts/signin_tabs_memoizer.js.es6 b/app/assets/javascripts/signin_tabs_memoizer.js
index d811d1cd53a..d811d1cd53a 100644
--- a/app/assets/javascripts/signin_tabs_memoizer.js.es6
+++ b/app/assets/javascripts/signin_tabs_memoizer.js
diff --git a/app/assets/javascripts/smart_interval.js.es6 b/app/assets/javascripts/smart_interval.js
index d1bdc353be2..d1bdc353be2 100644
--- a/app/assets/javascripts/smart_interval.js.es6
+++ b/app/assets/javascripts/smart_interval.js
diff --git a/app/assets/javascripts/snippets_list.js.es6 b/app/assets/javascripts/snippets_list.js
index 2128007113f..2128007113f 100644
--- a/app/assets/javascripts/snippets_list.js.es6
+++ b/app/assets/javascripts/snippets_list.js
diff --git a/app/assets/javascripts/subbable_resource.js.es6 b/app/assets/javascripts/subbable_resource.js
index d8191605128..d8191605128 100644
--- a/app/assets/javascripts/subbable_resource.js.es6
+++ b/app/assets/javascripts/subbable_resource.js
diff --git a/app/assets/javascripts/subscription.js.es6 b/app/assets/javascripts/subscription.js
index 62d1604fe9e..62d1604fe9e 100644
--- a/app/assets/javascripts/subscription.js.es6
+++ b/app/assets/javascripts/subscription.js
diff --git a/app/assets/javascripts/templates/issuable_template_selector.js.es6 b/app/assets/javascripts/templates/issuable_template_selector.js
index e9e9aafd71a..e9e9aafd71a 100644
--- a/app/assets/javascripts/templates/issuable_template_selector.js.es6
+++ b/app/assets/javascripts/templates/issuable_template_selector.js
diff --git a/app/assets/javascripts/templates/issuable_template_selectors.js.es6 b/app/assets/javascripts/templates/issuable_template_selectors.js
index 97f6d37364d..97f6d37364d 100644
--- a/app/assets/javascripts/templates/issuable_template_selectors.js.es6
+++ b/app/assets/javascripts/templates/issuable_template_selectors.js
diff --git a/app/assets/javascripts/terminal/terminal.js.es6 b/app/assets/javascripts/terminal/terminal.js
index 6b9422b1816..6b9422b1816 100644
--- a/app/assets/javascripts/terminal/terminal.js.es6
+++ b/app/assets/javascripts/terminal/terminal.js
diff --git a/app/assets/javascripts/terminal/terminal_bundle.js.es6 b/app/assets/javascripts/terminal/terminal_bundle.js
index 13cf3a10a38..13cf3a10a38 100644
--- a/app/assets/javascripts/terminal/terminal_bundle.js.es6
+++ b/app/assets/javascripts/terminal/terminal_bundle.js
diff --git a/app/assets/javascripts/todos.js.es6 b/app/assets/javascripts/todos.js
index e9513725d9d..e9513725d9d 100644
--- a/app/assets/javascripts/todos.js.es6
+++ b/app/assets/javascripts/todos.js
diff --git a/app/assets/javascripts/u2f/authenticate.js.es6 b/app/assets/javascripts/u2f/authenticate.js
index 500b78fc5d8..500b78fc5d8 100644
--- a/app/assets/javascripts/u2f/authenticate.js.es6
+++ b/app/assets/javascripts/u2f/authenticate.js
diff --git a/app/assets/javascripts/user.js.es6 b/app/assets/javascripts/user.js
index 059e6c628b3..059e6c628b3 100644
--- a/app/assets/javascripts/user.js.es6
+++ b/app/assets/javascripts/user.js
diff --git a/app/assets/javascripts/user_tabs.js.es6 b/app/assets/javascripts/user_tabs.js
index 465618e3d53..465618e3d53 100644
--- a/app/assets/javascripts/user_tabs.js.es6
+++ b/app/assets/javascripts/user_tabs.js
diff --git a/app/assets/javascripts/username_validator.js.es6 b/app/assets/javascripts/username_validator.js
index 137cefa3b8e..137cefa3b8e 100644
--- a/app/assets/javascripts/username_validator.js.es6
+++ b/app/assets/javascripts/username_validator.js
diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js
index 5111b260e1c..754d448564f 100644
--- a/app/assets/javascripts/users/calendar.js
+++ b/app/assets/javascripts/users/calendar.js
@@ -1,5 +1,6 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, camelcase, vars-on-top, object-shorthand, comma-dangle, eqeqeq, no-mixed-operators, no-return-assign, newline-per-chained-call, prefer-arrow-callback, consistent-return, one-var, one-var-declaration-per-line, prefer-template, quotes, no-unused-vars, no-else-return, max-len */
-/* global d3 */
+
+import d3 from 'd3';
(function() {
var bind = function(fn, me) { return function() { return fn.apply(me, arguments); }; };
diff --git a/app/assets/javascripts/version_check_image.js.es6 b/app/assets/javascripts/version_check_image.js
index d4f716acb72..d4f716acb72 100644
--- a/app/assets/javascripts/version_check_image.js.es6
+++ b/app/assets/javascripts/version_check_image.js
diff --git a/app/assets/javascripts/visibility_select.js.es6 b/app/assets/javascripts/visibility_select.js
index f712d7ba930..f712d7ba930 100644
--- a/app/assets/javascripts/visibility_select.js.es6
+++ b/app/assets/javascripts/visibility_select.js
diff --git a/app/assets/javascripts/vue_pipelines_index/index.js.es6 b/app/assets/javascripts/vue_pipelines_index/index.js
index a90bd1518e9..a90bd1518e9 100644
--- a/app/assets/javascripts/vue_pipelines_index/index.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/index.js
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js
index 891f1f17fb3..891f1f17fb3 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_actions.js
diff --git a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js
index ae5649f0519..ae5649f0519 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipeline_url.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipeline_url.js
diff --git a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6 b/app/assets/javascripts/vue_pipelines_index/pipelines.js
index 601ef41e917..601ef41e917 100644
--- a/app/assets/javascripts/vue_pipelines_index/pipelines.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/pipelines.js
diff --git a/app/assets/javascripts/vue_pipelines_index/stage.js.es6 b/app/assets/javascripts/vue_pipelines_index/stage.js
index f67ebd6a265..f67ebd6a265 100644
--- a/app/assets/javascripts/vue_pipelines_index/stage.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/stage.js
diff --git a/app/assets/javascripts/vue_pipelines_index/status.js.es6 b/app/assets/javascripts/vue_pipelines_index/status.js
index 8d9f83ac113..8d9f83ac113 100644
--- a/app/assets/javascripts/vue_pipelines_index/status.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/status.js
diff --git a/app/assets/javascripts/vue_pipelines_index/store.js.es6 b/app/assets/javascripts/vue_pipelines_index/store.js
index 909007267b9..909007267b9 100644
--- a/app/assets/javascripts/vue_pipelines_index/store.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/store.js
diff --git a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6 b/app/assets/javascripts/vue_pipelines_index/time_ago.js
index a383570857d..a383570857d 100644
--- a/app/assets/javascripts/vue_pipelines_index/time_ago.js.es6
+++ b/app/assets/javascripts/vue_pipelines_index/time_ago.js
diff --git a/app/assets/javascripts/vue_realtime_listener/index.js.es6 b/app/assets/javascripts/vue_realtime_listener/index.js
index 30f6680a673..30f6680a673 100644
--- a/app/assets/javascripts/vue_realtime_listener/index.js.es6
+++ b/app/assets/javascripts/vue_realtime_listener/index.js
diff --git a/app/assets/javascripts/vue_shared/components/commit.js.es6 b/app/assets/javascripts/vue_shared/components/commit.js
index 4381487b79e..4381487b79e 100644
--- a/app/assets/javascripts/vue_shared/components/commit.js.es6
+++ b/app/assets/javascripts/vue_shared/components/commit.js
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table.js
index 0d8f85db965..0d8f85db965 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table.js.es6
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table.js
diff --git a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6 b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
index e5e88186a85..e5e88186a85 100644
--- a/app/assets/javascripts/vue_shared/components/pipelines_table_row.js.es6
+++ b/app/assets/javascripts/vue_shared/components/pipelines_table_row.js
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.js.es6 b/app/assets/javascripts/vue_shared/components/table_pagination.js
index 8943b850a72..8943b850a72 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.js.es6
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.js
diff --git a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6 b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
index d3229f9f730..d3229f9f730 100644
--- a/app/assets/javascripts/vue_shared/vue_resource_interceptor.js.es6
+++ b/app/assets/javascripts/vue_shared/vue_resource_interceptor.js
diff --git a/app/assets/javascripts/wikis.js.es6 b/app/assets/javascripts/wikis.js
index 75fd1394a03..75fd1394a03 100644
--- a/app/assets/javascripts/wikis.js.es6
+++ b/app/assets/javascripts/wikis.js
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 6e8a5cc688b..2a403589a53 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -96,7 +96,7 @@
.dropdown-menu-toggle {
@extend .dropdown-toggle;
- padding-right: 20px;
+ padding-right: 25px;
position: relative;
width: 163px;
text-overflow: ellipsis;
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index d2be8dc7a39..0ba00cea8b5 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -1,5 +1,4 @@
.filter-item {
- margin-right: 6px;
vertical-align: top;
&.reset-filters {
@@ -14,6 +13,20 @@
width: 132px;
}
}
+
+ .filter-item:not(:last-child) {
+ margin-right: 6px;
+ }
+
+ .sort-filter {
+ display: inline-block;
+ float: right;
+ }
+
+ .dropdown-menu-sort {
+ left: auto;
+ right: 0;
+ }
}
@media (max-width: $screen-xs-max) {
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index f4316ec7022..5d1aba4e529 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -260,24 +260,34 @@ header {
font-size: 18px;
.navbar-nav {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
margin: 0;
- float: none !important;
-
- .visible-xs,
- .visible-sm {
- display: table-cell !important;
- }
+ text-align: right;
}
.navbar-collapse {
padding-left: 5px;
- .nav > li {
- display: table-cell;
- width: 1%;
+ .nav > li:not(.hidden-xs) {
+ display: table-cell!important;
+ width: 25%;
+
+ a {
+ margin-right: 8px;
+ }
}
}
}
+
+ .header-user-dropdown-toggle {
+ text-align: center;
+ }
+
+ .header-user-avatar {
+ float: none;
+ }
}
.header-user {
diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss
index 29d55c44699..0a42b17c1f5 100644
--- a/app/assets/stylesheets/framework/layout.scss
+++ b/app/assets/stylesheets/framework/layout.scss
@@ -8,6 +8,19 @@ body {
&.navless {
background-color: $white-light !important;
}
+
+ &.card-content {
+ background-color: $gray-darker;
+
+ .content-wrapper {
+ padding: 0;
+
+ .container-fluid,
+ .container-limited {
+ background-color: $gray-darker;
+ }
+ }
+ }
}
.container {
diff --git a/app/assets/stylesheets/framework/zen.scss b/app/assets/stylesheets/framework/zen.scss
index 97ade638db6..0c226ff7598 100644
--- a/app/assets/stylesheets/framework/zen.scss
+++ b/app/assets/stylesheets/framework/zen.scss
@@ -20,8 +20,9 @@
outline: none;
resize: none;
height: 100vh;
+ max-height: calc(100vh - 10px);
max-width: 900px;
- margin: 0 auto;
+ margin: 0 auto 10px;
}
.zen-control-leave {
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index af9ddb9ff80..5f0aede4f5e 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -170,7 +170,11 @@
@media (max-width: $screen-sm-max) {
.todos-filters {
.dropdown-menu-toggle {
- width: 135px;
+ width: 130px;
+ }
+
+ .dropdown-menu-toggle-sort {
+ width: auto;
}
}
}
@@ -200,10 +204,6 @@
}
.todos-filters {
- .row-content-block {
- padding-bottom: 50px;
- }
-
.dropdown-menu-toggle {
width: 100%;
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index d807e6263ee..8d831ffdd70 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -138,6 +138,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:two_factor_grace_period,
:user_default_external,
:user_oauth_applications,
+ :unique_ips_limit_per_user,
+ :unique_ips_limit_time_window,
+ :unique_ips_limit_enabled,
:version_check_enabled,
:terminal_max_session_time,
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index cc7b7f247e8..1c66c530cd2 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -40,6 +40,10 @@ class ApplicationController < ActionController::Base
render_403
end
+ rescue_from Gitlab::Auth::TooManyIps do |e|
+ head :forbidden, retry_after: Gitlab::Auth::UniqueIpsLimiter.config.unique_ips_limit_time_window
+ end
+
def redirect_back_or_default(default: root_path, options: {})
redirect_to request.referer.present? ? :back : default, options
end
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index c8663a3c38e..e4452f46056 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -10,11 +10,6 @@ class Profiles::KeysController < Profiles::ApplicationController
@key = current_user.keys.find(params[:id])
end
- # Back-compat: We need to support this URL since git-annex webapp points to it
- def new
- redirect_to profile_keys_path
- end
-
def create
@key = current_user.keys.new(key_params)
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index f0c71725ea8..987b95e89b9 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -47,11 +47,14 @@ class ProfilesController < Profiles::ApplicationController
end
def update_username
- @user.update_attributes(username: user_params[:username])
-
- respond_to do |format|
- format.js
+ if @user.update_attributes(username: user_params[:username])
+ options = { notice: "Username successfully changed" }
+ else
+ message = @user.errors.full_messages.uniq.join('. ')
+ options = { alert: "Username change failed - #{message}" }
end
+
+ redirect_back_or_default(default: { action: 'show' }, options: options)
end
private
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index a01c0caa959..c40f9b7f75f 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -20,7 +20,7 @@ class Projects::BranchesController < Projects::ApplicationController
respond_to do |format|
format.html
format.json do
- render json: @repository.branch_names
+ render json: @branches.map(&:name)
end
end
end
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 5cf3a7f593b..d00177e7612 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -211,6 +211,11 @@ class Projects::NotesController < Projects::ApplicationController
end
def find_current_user_notes
- @notes = NotesFinder.new(project, current_user, params).execute.inc_author
+ @notes = NotesFinder.new(project, current_user, params.merge(last_fetched_at: last_fetched_at))
+ .execute.inc_author
+ end
+
+ def last_fetched_at
+ request.headers['X-Last-Fetched-At']
end
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index f49301e2631..2fca012252e 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -33,15 +33,17 @@ class IssuableFinder
items = by_scope(items)
items = by_state(items)
items = by_group(items)
- items = by_project(items)
items = by_search(items)
- items = by_milestone(items)
items = by_assignee(items)
items = by_author(items)
- items = by_label(items)
items = by_due_date(items)
items = by_non_archived(items)
items = by_iids(items)
+ items = by_milestone(items)
+ items = by_label(items)
+
+ # Filtering by project HAS TO be the last because we use the project IDs yielded by the issuable query thus far
+ items = by_project(items)
sort(items)
end
@@ -107,8 +109,7 @@ class IssuableFinder
@project = project
end
- def projects
- return @projects if defined?(@projects)
+ def projects(items = nil)
return @projects = project if project?
projects =
@@ -117,7 +118,7 @@ class IssuableFinder
elsif group
GroupProjectsFinder.new(group).execute(current_user)
else
- ProjectsFinder.new.execute(current_user)
+ projects_finder.execute(current_user, item_project_ids(items))
end
@projects = projects.with_feature_available_for_user(klass, current_user).reorder(nil)
@@ -257,9 +258,9 @@ class IssuableFinder
def by_project(items)
items =
if project?
- items.of_projects(projects).references_project
- elsif projects
- items.merge(projects.reorder(nil)).join_project
+ items.of_projects(projects(items)).references_project
+ elsif projects(items)
+ items.merge(projects(items).reorder(nil)).join_project
else
items.none
end
@@ -314,13 +315,14 @@ class IssuableFinder
if filter_by_no_milestone?
items = items.left_joins_milestones.where(milestone_id: [-1, nil])
elsif filter_by_upcoming_milestone?
- upcoming_ids = Milestone.upcoming_ids_by_projects(projects)
+ upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items))
items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
else
items = items.with_milestone(params[:milestone_title])
+ items_projects = projects(items)
- if projects
- items = items.where(milestones: { project_id: projects })
+ if items_projects
+ items = items.where(milestones: { project_id: items_projects })
end
end
end
@@ -334,9 +336,10 @@ class IssuableFinder
items = items.without_label
else
items = items.with_label(label_names, params[:sort])
+ items_projects = projects(items)
- if projects
- label_ids = LabelsFinder.new(current_user, project_ids: projects).execute(skip_authorization: true).select(:id)
+ if items_projects
+ label_ids = LabelsFinder.new(current_user, project_ids: items_projects).execute(skip_authorization: true).select(:id)
items = items.where(labels: { id: label_ids })
end
end
@@ -396,4 +399,8 @@ class IssuableFinder
def current_user_related?
params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
end
+
+ def projects_finder
+ @projects_finder ||= ProjectsFinder.new
+ end
end
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index f542f72a386..08713272947 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -41,4 +41,8 @@ class IssuesFinder < IssuableFinder
user_id: user.id,
project_ids: user.authorized_projects(Gitlab::Access::REPORTER).select(:id))
end
+
+ def item_project_ids(items)
+ items&.reorder(nil)&.select(:project_id)
+ end
end
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index b76ca389f38..1eec45d9cb5 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -20,4 +20,10 @@ class MergeRequestsFinder < IssuableFinder
def klass
MergeRequest
end
+
+ private
+
+ def item_project_ids(items)
+ items&.reorder(nil)&.select(:target_project_id)
+ end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 70419eb4bde..a3213581498 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -167,7 +167,7 @@ module ApplicationHelper
css_classes = short_format ? 'js-short-timeago' : 'js-timeago'
css_classes << " #{html_class}" unless html_class.blank?
- element = content_tag :time, time.to_s,
+ element = content_tag :time, time.strftime("%b %d, %Y"),
class: css_classes,
title: time.to_time.in_time_zone.to_s(:medium),
datetime: time.to_time.getutc.iso8601,
diff --git a/app/helpers/mattermost_helper.rb b/app/helpers/mattermost_helper.rb
index 49ac12db832..27ff4051c8d 100644
--- a/app/helpers/mattermost_helper.rb
+++ b/app/helpers/mattermost_helper.rb
@@ -1,9 +1,7 @@
module MattermostHelper
def mattermost_teams_options(teams)
- teams_options = teams.map do |id, options|
- [options['display_name'] || options['name'], id]
+ teams.map do |team|
+ [team['display_name'] || team['name'], team['id']]
end
-
- teams_options.compact.unshift(['Select team...', '0'])
end
end
diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb
index b0135ea2e95..a48d4475e97 100644
--- a/app/helpers/triggers_helper.rb
+++ b/app/helpers/triggers_helper.rb
@@ -1,9 +1,9 @@
module TriggersHelper
def builds_trigger_url(project_id, ref: nil)
if ref.nil?
- "#{Settings.gitlab.url}/api/v3/projects/#{project_id}/trigger/builds"
+ "#{Settings.gitlab.url}/api/v4/projects/#{project_id}/trigger/pipeline"
else
- "#{Settings.gitlab.url}/api/v3/projects/#{project_id}/ref/#{ref}/trigger/builds"
+ "#{Settings.gitlab.url}/api/v4/projects/#{project_id}/ref/#{ref}/trigger/pipeline"
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 255e8c4ff78..be632930895 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -64,6 +64,16 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
if: :akismet_enabled
+ validates :unique_ips_limit_per_user,
+ numericality: { greater_than_or_equal_to: 1 },
+ presence: true,
+ if: :unique_ips_limit_enabled
+
+ validates :unique_ips_limit_time_window,
+ numericality: { greater_than_or_equal_to: 0 },
+ presence: true,
+ if: :unique_ips_limit_enabled
+
validates :koding_url,
presence: true,
if: :koding_enabled
@@ -184,6 +194,9 @@ class ApplicationSetting < ActiveRecord::Base
domain_whitelist: Settings.gitlab['domain_whitelist'],
gravatar_enabled: Settings.gravatar['enabled'],
help_page_text: nil,
+ unique_ips_limit_per_user: 10,
+ unique_ips_limit_time_window: 3600,
+ unique_ips_limit_enabled: false,
housekeeping_bitmaps_enabled: true,
housekeeping_enabled: true,
housekeeping_full_repack_period: 50,
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 39a1dd86241..8aa45b2f02e 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -5,10 +5,11 @@ module Ci
acts_as_paranoid
belongs_to :project, foreign_key: :gl_project_id
+ belongs_to :owner, class_name: "User"
+
has_many :trigger_requests, dependent: :destroy
- validates :token, presence: true
- validates :token, uniqueness: true
+ validates :token, presence: true, uniqueness: true
before_validation :set_default_values
@@ -25,7 +26,11 @@ module Ci
end
def short_token
- token[0...10]
+ token[0...4]
+ end
+
+ def can_show_token?(user)
+ owner.blank? || owner == user
end
end
end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index b973bbcd8da..e63f89a9f85 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -24,6 +24,11 @@ class ExternalIssue
def ==(other)
other.is_a?(self.class) && (to_s == other.to_s)
end
+ alias_method :eql?, :==
+
+ def hash
+ [self.class, to_s].hash
+ end
def project
@project
diff --git a/app/models/note.rb b/app/models/note.rb
index 4c97e4a986c..e22e96aec6f 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -85,6 +85,7 @@ class Note < ActiveRecord::Base
before_validation :nullify_blank_type, :nullify_blank_line_code
before_validation :set_discussion_id
after_save :keep_around_commit, unless: :for_personal_snippet?
+ after_save :expire_etag_cache
class << self
def model_name
@@ -272,4 +273,16 @@ class Note < ActiveRecord::Base
self.class.build_discussion_id(noteable_type, noteable_id || commit_id)
end
end
+
+ def expire_etag_cache
+ return unless for_issue?
+
+ key = Gitlab::Routing.url_helpers.namespace_project_noteable_notes_path(
+ noteable.project.namespace,
+ noteable.project,
+ target_type: noteable_type.underscore,
+ target_id: noteable.id
+ )
+ Gitlab::EtagCaching::Store.new.touch(key)
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index d3bb04060bf..dfba51d3b00 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -95,6 +95,7 @@ class User < ActiveRecord::Base
has_many :todos, dependent: :destroy
has_many :notification_settings, dependent: :destroy
has_many :award_emoji, dependent: :destroy
+ has_many :triggers, dependent: :destroy, class_name: 'Ci::Trigger', foreign_key: :owner_id
has_many :assigned_issues, dependent: :nullify, foreign_key: :assignee_id, class_name: "Issue"
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest"
diff --git a/app/services/ci/create_trigger_request_service.rb b/app/services/ci/create_trigger_request_service.rb
index 6af3c1ca5b1..dca5aa9f5d7 100644
--- a/app/services/ci/create_trigger_request_service.rb
+++ b/app/services/ci/create_trigger_request_service.rb
@@ -3,7 +3,7 @@ module Ci
def execute(project, trigger, ref, variables = nil)
trigger_request = trigger.trigger_requests.create(variables: variables)
- pipeline = Ci::CreatePipelineService.new(project, nil, ref: ref).
+ pipeline = Ci::CreatePipelineService.new(project, trigger.owner, ref: ref).
execute(ignore_skip_ci: true, trigger_request: trigger_request)
if pipeline.persisted?
trigger_request
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 2d42c4fc04a..523b9f41916 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -34,6 +34,8 @@ module Projects
end
rescue => e
error(e.message)
+ ensure
+ build.erase_artifacts! unless build.has_expiring_artifacts?
end
private
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 057b584e1bc..00366b0a8c9 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -360,6 +360,29 @@
Generate API key at
%a{ href: 'http://www.akismet.com', target: 'blank' } http://www.akismet.com
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :unique_ips_limit_enabled do
+ = f.check_box :unique_ips_limit_enabled
+ Limit sign in from multiple ips
+ %span.help-block#unique_ip_help_block
+ Helps prevent malicious users hide their activity
+
+ .form-group
+ = f.label :unique_ips_limit_per_user, 'IPs per user', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :unique_ips_limit_per_user, class: 'form-control'
+ .help-block
+ Maximum number of unique IPs per user
+
+ .form-group
+ = f.label :unique_ips_limit_time_window, 'IP expiration time', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :unique_ips_limit_time_window, class: 'form-control'
+ .help-block
+ How many seconds an IP will be counted towards the limit
+
%fieldset
%legend Abuse reports
.form-group
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 16a5713948a..d7e0a8e4b2c 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -46,16 +46,16 @@
= 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', dropdown_class: 'dropdown-menu-selectable dropdown-menu-action js-filter-submit',
data: { data: todo_actions_options, default_label: 'Action' } })
- .pull-right
- .dropdown.inline.prepend-left-10
- %button.dropdown-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
+ .filter-item.sort-filter
+ .dropdown
+ %button.dropdown-menu-toggle.dropdown-menu-toggle-sort{ type: 'button', 'data-toggle' => 'dropdown' }
%span.light
- if @sort.present?
= sort_options_hash[@sort]
- else
= sort_title_recently_created
= icon('chevron-down')
- %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-sort
+ %ul.dropdown-menu.dropdown-menu-sort
%li
= link_to todos_filter_path(sort: sort_value_priority) do
= sort_title_priority
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 302c1794628..f6d8bb08a64 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -28,7 +28,9 @@
= stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print"
- = javascript_include_tag(*webpack_asset_paths("application"))
+ = javascript_include_tag(*webpack_asset_paths("runtime"))
+ = javascript_include_tag(*webpack_asset_paths("common"))
+ = javascript_include_tag(*webpack_asset_paths("main"))
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index 19bd9b6d5c9..36543edc040 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -1,7 +1,7 @@
!!! 5
%html{ lang: "en", class: "#{page_class}" }
= render "layouts/head"
- %body{ data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
+ %body{ class: @body_class, data: { page: body_data_page, project: "#{@project.path if @project}", group: "#{@group.path if @group}" } }
= Gon::Base.render_data
= render "layouts/header/default", title: header_title
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 555ec8ad079..6f4f2dbea3a 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -19,7 +19,7 @@
%ul.nav.navbar-nav
%li.hidden-sm.hidden-xs
= render 'layouts/search' unless current_controller?(:search)
- %li.visible-sm.visible-xs
+ %li.visible-sm-inline-block.visible-xs-inline-block
= link_to search_path, title: 'Search', aria: { label: "Search" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
= icon('search')
- if current_user
diff --git a/app/views/profiles/_head.html.haml b/app/views/profiles/_head.html.haml
index 1df04ea614e..83ae9129807 100644
--- a/app/views/profiles/_head.html.haml
+++ b/app/views/profiles/_head.html.haml
@@ -1,3 +1,2 @@
- content_for :page_specific_javascripts do
- = page_specific_javascript_tag('lib/cropper.js')
= page_specific_javascript_bundle_tag('profile')
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 02fb47ec981..8a994f6d600 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -93,7 +93,7 @@
%p
Changing your username will change path to all personal projects!
.col-lg-9
- = form_for @user, url: update_username_profile_path, method: :put, remote: true, html: {class: "update-username"} do |f|
+ = form_for @user, url: update_username_profile_path, method: :put, html: {class: "update-username"} do |f|
.form-group
= f.label :username, "Path", class: "label-light"
.input-group
diff --git a/app/views/profiles/update_username.js.haml b/app/views/profiles/update_username.js.haml
deleted file mode 100644
index 5307e0b48cb..00000000000
--- a/app/views/profiles/update_username.js.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-- if @user.valid?
- :plain
- new Flash("Username successfully changed", "notice")
-- else
- - error = @user.errors.full_messages.first
- :plain
- new Flash("Username change failed - #{escape_javascript error.html_safe}", "alert")
diff --git a/app/views/projects/boards/_show.html.haml b/app/views/projects/boards/_show.html.haml
index b3bc6010efb..3ae78387938 100644
--- a/app/views/projects/boards/_show.html.haml
+++ b/app/views/projects/boards/_show.html.haml
@@ -3,6 +3,7 @@
- page_title "Boards"
- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('boards')
= page_specific_javascript_bundle_tag('simulate_drag') if Rails.env.test?
diff --git a/app/views/projects/commit/_pipelines_list.haml b/app/views/projects/commit/_pipelines_list.haml
index 6792b3f7a83..da5a676274f 100644
--- a/app/views/projects/commit/_pipelines_list.haml
+++ b/app/views/projects/commit/_pipelines_list.haml
@@ -4,4 +4,5 @@
} }
- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('commit_pipelines')
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index be17f2c764e..dd3fa814716 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -1,6 +1,7 @@
- @no_container = true
- page_title "Cycle Analytics"
- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('cycle_analytics')
= render "projects/head"
diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml
index d9cb7bc0331..4b101447bc0 100644
--- a/app/views/projects/environments/folder.html.haml
+++ b/app/views/projects/environments/folder.html.haml
@@ -3,6 +3,7 @@
= render "projects/pipelines/head"
- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag("environments_folder")
#environments-folder-list-view{ data: { "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index d6366b57957..80d2b6f5d95 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -3,6 +3,7 @@
= render "projects/pipelines/head"
- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag("environments")
#environments-list-view{ data: { environments_data: environments_list_data,
diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml
index d3bce45e974..464ac34d961 100644
--- a/app/views/projects/graphs/charts.html.haml
+++ b/app/views/projects/graphs/charts.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
- page_title "Charts"
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('lib_chart')
+ = page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
= render "projects/commits/head"
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index d89dfe31e47..680f8ae6c8f 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
- page_title "Contributors"
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('lib_chart')
+ = page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
= render 'projects/commits/head'
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 069f3d97943..d39f36e94c7 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -2,8 +2,6 @@
- page_title "#{@issue.title} (#{@issue.to_reference})", "Issues"
- page_description @issue.description
- page_card_attributes @issue.card_attributes
-- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('lib_vue')
.clearfix.detail-page-header
.issuable-header
diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml
index a80f9aa4c4a..04bd4e8b683 100644
--- a/app/views/projects/mattermosts/_team_selection.html.haml
+++ b/app/views/projects/mattermosts/_team_selection.html.haml
@@ -2,16 +2,15 @@
This service will be installed on the Mattermost instance at
%strong= link_to Gitlab.config.mattermost.host, Gitlab.config.mattermost.host
%hr
-= form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project)) do |f|
+= form_for(:mattermost, method: :post, url: namespace_project_mattermost_path(@project.namespace, @project), html: { class: 'js-requires-input'} ) do |f|
%h4 Team
%p
= @teams.one? ? 'The team' : 'Select the team'
where the slash commands will be used in
- - selected_id = @teams.one? ? @teams.keys.first : 0
- - options = mattermost_teams_options(@teams)
- - options = options_for_select(options, selected_id)
- = f.select(:team_id, options, {}, { class: 'form-control', disabled: @teams.one?, selected: selected_id })
- = f.hidden_field(:team_id, value: selected_id) if @teams.one?
+ - selected_id = @teams.one? ? @teams.first['id'] : nil
+ - options = options_for_select(mattermost_teams_options(@teams), selected_id)
+ = f.select(:team_id, options, { include_blank: 'Select team...'}, { class: 'form-control', disabled: @teams.one?, selected: selected_id, required: true })
+ = f.hidden_field(:team_id, value: selected_id, required: true) if @teams.one?
.help-block
- if @teams.one?
This is the only available team.
@@ -25,7 +24,7 @@
%hr
%h4 Command trigger word
%p Choose the word that will trigger commands
- = f.text_field(:trigger, value: @project.path, class: 'form-control')
+ = f.text_field(:trigger, value: @project.path, class: 'form-control', required: true)
.help-block
%p
Trigger word must be unique, and can't begin with a slash or contain any spaces.
diff --git a/app/views/projects/mattermosts/new.html.haml b/app/views/projects/mattermosts/new.html.haml
index 96b1d2aee61..15829a3f143 100644
--- a/app/views/projects/mattermosts/new.html.haml
+++ b/app/views/projects/mattermosts/new.html.haml
@@ -1,3 +1,5 @@
+- @body_class = 'card-content'
+
.service-installation
.inline.pull-right
= custom_icon('mattermost_logo', size: 48)
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 521b0694ca9..03d618327d4 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -3,6 +3,7 @@
- page_description @merge_request.description
- page_card_attributes @merge_request.card_attributes
- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('diff_notes')
.merge-request{ 'data-url' => merge_request_path(@merge_request), 'data-project-path' => project_path(@merge_request.project) }
diff --git a/app/views/projects/merge_requests/conflicts.html.haml b/app/views/projects/merge_requests/conflicts.html.haml
index 1ecd9924d88..51d59280be8 100644
--- a/app/views/projects/merge_requests/conflicts.html.haml
+++ b/app/views/projects/merge_requests/conflicts.html.haml
@@ -1,6 +1,6 @@
- page_title "Merge Conflicts", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests"
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('lib_vue')
+ = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('merge_conflicts')
= page_specific_javascript_tag('lib/ace.js')
= render "projects/merge_requests/show/mr_title"
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index a4a24a217d3..ed6077f6c6b 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -1,6 +1,5 @@
- page_title "Graph", @ref
- content_for :page_specific_javascripts do
- = page_specific_javascript_tag('lib/raphael.js')
= page_specific_javascript_bundle_tag('network')
= render "projects/commits/head"
= render "head"
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index 08c73d94a09..90a150aa74c 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -23,4 +23,4 @@
to post a comment
:javascript
- var notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, project_id: @project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
+ var notes = new Notes("#{namespace_project_noteable_notes_path(namespace_id: @project.namespace, project_id: @project, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}")
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index 8ffdfa1a2cf..4a5043aac3c 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
- page_title "Charts", "Pipelines"
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('lib_chart')
+ = page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('graphs')
= render 'head'
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index acb61aa2490..5d59ce06612 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -50,4 +50,5 @@
.content-list.pipelines{ data: { url: namespace_project_pipelines_path(@project.namespace, @project, format: :json) } }
.vue-pipelines-index
+= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('vue_pipelines')
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 37a0c63e514..048fc488207 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -1,5 +1,6 @@
- todo = issuable_todo(issuable)
- content_for :page_specific_javascripts do
+ = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('issuable')
%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "101", "spy" => "affix" }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index af091f9ab88..76cd330e80a 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -1,7 +1,7 @@
- page_title @user.name
- page_description @user.bio
- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('lib_d3')
+ = page_specific_javascript_bundle_tag('common_d3')
= page_specific_javascript_bundle_tag('users')
- header_title @user.name, user_path(@user)
- @no_container = true
diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb
deleted file mode 100644
index b70df5a1afa..00000000000
--- a/app/workers/stuck_ci_builds_worker.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class StuckCiBuildsWorker
- include Sidekiq::Worker
- include CronjobQueue
-
- BUILD_STUCK_TIMEOUT = 1.day
-
- def perform
- Rails.logger.info 'Cleaning stuck builds'
-
- builds = Ci::Build.joins(:project).running_or_pending.where('ci_builds.updated_at < ?', BUILD_STUCK_TIMEOUT.ago)
- builds.find_each(batch_size: 50).each do |build|
- Rails.logger.debug "Dropping stuck #{build.status} build #{build.id} for runner #{build.runner_id}"
- build.drop
- end
-
- # Update builds that failed to drop
- builds.update_all(status: 'failed')
- end
-end
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
new file mode 100644
index 00000000000..ae8c980c9e4
--- /dev/null
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -0,0 +1,59 @@
+class StuckCiJobsWorker
+ include Sidekiq::Worker
+ include CronjobQueue
+
+ EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease'.freeze
+
+ BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour
+ BUILD_PENDING_OUTDATED_TIMEOUT = 1.day
+ BUILD_PENDING_STUCK_TIMEOUT = 1.hour
+
+ def perform
+ return unless try_obtain_lease
+
+ Rails.logger.info "#{self.class}: Cleaning stuck builds"
+
+ drop :running, BUILD_RUNNING_OUTDATED_TIMEOUT
+ drop :pending, BUILD_PENDING_OUTDATED_TIMEOUT
+ drop_stuck :pending, BUILD_PENDING_STUCK_TIMEOUT
+
+ remove_lease
+ end
+
+ private
+
+ def try_obtain_lease
+ @uuid = Gitlab::ExclusiveLease.new(EXCLUSIVE_LEASE_KEY, timeout: 30.minutes).try_obtain
+ end
+
+ def remove_lease
+ Gitlab::ExclusiveLease.cancel(EXCLUSIVE_LEASE_KEY, @uuid)
+ end
+
+ def drop(status, timeout)
+ search(status, timeout) do |build|
+ drop_build :outdated, build, status, timeout
+ end
+ end
+
+ def drop_stuck(status, timeout)
+ search(status, timeout) do |build|
+ return unless build.stuck?
+ drop_build :stuck, build, status, timeout
+ end
+ end
+
+ def search(status, timeout)
+ builds = Ci::Build.where(status: status).where('ci_builds.updated_at < ?', timeout.ago)
+ builds.joins(:project).includes(:tags, :runner, project: :namespace).find_each(batch_size: 50).each do |build|
+ yield(build)
+ end
+ end
+
+ def drop_build(type, build, status, timeout)
+ Rails.logger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{status}, timeout: #{timeout})"
+ Gitlab::OptimisticLocking.retry_lock(build, 3) do |b|
+ b.drop
+ end
+ end
+end
diff --git a/changelogs/unreleased/1648-remove-remnants-of-git-annex-from-ce.yml b/changelogs/unreleased/1648-remove-remnants-of-git-annex-from-ce.yml
new file mode 100644
index 00000000000..f247fe35439
--- /dev/null
+++ b/changelogs/unreleased/1648-remove-remnants-of-git-annex-from-ce.yml
@@ -0,0 +1,4 @@
+---
+title: Remove remnants of git annex support.
+merge_request:
+author:
diff --git a/changelogs/unreleased/22562-todos-filters.yml b/changelogs/unreleased/22562-todos-filters.yml
new file mode 100644
index 00000000000..9cca138744a
--- /dev/null
+++ b/changelogs/unreleased/22562-todos-filters.yml
@@ -0,0 +1,4 @@
+---
+title: Fix Sort dropdown reflow issue
+merge_request: 9533
+author: Jarkko Tuunanen
diff --git a/changelogs/unreleased/25503_issues_finder_performance.yml b/changelogs/unreleased/25503_issues_finder_performance.yml
new file mode 100644
index 00000000000..87964269c6d
--- /dev/null
+++ b/changelogs/unreleased/25503_issues_finder_performance.yml
@@ -0,0 +1,4 @@
+---
+title: Filter by projects in the end of search
+merge_request: 9030
+author:
diff --git a/changelogs/unreleased/27520-option-to-prevent-signing-in-from-multiple-ips.yml b/changelogs/unreleased/27520-option-to-prevent-signing-in-from-multiple-ips.yml
new file mode 100644
index 00000000000..3050b072863
--- /dev/null
+++ b/changelogs/unreleased/27520-option-to-prevent-signing-in-from-multiple-ips.yml
@@ -0,0 +1,4 @@
+---
+title: Option to prevent signing in from multiple ips
+merge_request: 8998
+author:
diff --git a/changelogs/unreleased/27523-make-stuck-build-detection-more-performant.yml b/changelogs/unreleased/27523-make-stuck-build-detection-more-performant.yml
new file mode 100644
index 00000000000..a4ef2b23aaa
--- /dev/null
+++ b/changelogs/unreleased/27523-make-stuck-build-detection-more-performant.yml
@@ -0,0 +1,4 @@
+---
+title: Make stuck builds detection more performant
+merge_request: 9025
+author:
diff --git a/changelogs/unreleased/28655-current-path-text-is-not-updated-after-setting-the-new-username.yml b/changelogs/unreleased/28655-current-path-text-is-not-updated-after-setting-the-new-username.yml
new file mode 100644
index 00000000000..bff996172f3
--- /dev/null
+++ b/changelogs/unreleased/28655-current-path-text-is-not-updated-after-setting-the-new-username.yml
@@ -0,0 +1,4 @@
+---
+title: Update account view to display new username
+merge_request:
+author:
diff --git a/changelogs/unreleased/28704-fullscreen-zen-mode-is-broken.yml b/changelogs/unreleased/28704-fullscreen-zen-mode-is-broken.yml
new file mode 100644
index 00000000000..b8dba0b5993
--- /dev/null
+++ b/changelogs/unreleased/28704-fullscreen-zen-mode-is-broken.yml
@@ -0,0 +1,4 @@
+---
+title: Set max height to screen height for Zen mode
+merge_request: 9667
+author:
diff --git a/changelogs/unreleased/28898-fix-search-branches-in-cherry-picking.yml b/changelogs/unreleased/28898-fix-search-branches-in-cherry-picking.yml
new file mode 100644
index 00000000000..48e62f8f70d
--- /dev/null
+++ b/changelogs/unreleased/28898-fix-search-branches-in-cherry-picking.yml
@@ -0,0 +1,4 @@
+---
+title: Fix json response in branches controller
+merge_request: 9710
+author: George Andrinopoulos
diff --git a/changelogs/unreleased/api-drop-subscribed.yml b/changelogs/unreleased/api-drop-subscribed.yml
new file mode 100644
index 00000000000..2a39026b519
--- /dev/null
+++ b/changelogs/unreleased/api-drop-subscribed.yml
@@ -0,0 +1,5 @@
+---
+title: Remove "subscribed" field from API responses returning list of issues or merge
+ requests
+merge_request: 9661
+author:
diff --git a/changelogs/unreleased/backup_storage_class.yml b/changelogs/unreleased/backup_storage_class.yml
new file mode 100644
index 00000000000..fc9989fc251
--- /dev/null
+++ b/changelogs/unreleased/backup_storage_class.yml
@@ -0,0 +1,4 @@
+---
+title: Add storage class configuration option for Amazon S3 remote backups
+merge_request:
+author: Jon Keys
diff --git a/changelogs/unreleased/commons-chunk-plugin.yml b/changelogs/unreleased/commons-chunk-plugin.yml
new file mode 100644
index 00000000000..5c11ea3bbb2
--- /dev/null
+++ b/changelogs/unreleased/commons-chunk-plugin.yml
@@ -0,0 +1,5 @@
+---
+title: Use webpack CommonsChunkPlugin to place common javascript libraries in their
+ own bundles
+merge_request: 9647
+author:
diff --git a/changelogs/unreleased/delete-artifacts-for-pages.yml b/changelogs/unreleased/delete-artifacts-for-pages.yml
new file mode 100644
index 00000000000..50b3dd81d60
--- /dev/null
+++ b/changelogs/unreleased/delete-artifacts-for-pages.yml
@@ -0,0 +1,4 @@
+---
+title: Delete artifacts for pages unless expiry date is specified
+merge_request: 9716
+author:
diff --git a/changelogs/unreleased/etag-notes-polling.yml b/changelogs/unreleased/etag-notes-polling.yml
new file mode 100644
index 00000000000..53990821d25
--- /dev/null
+++ b/changelogs/unreleased/etag-notes-polling.yml
@@ -0,0 +1,4 @@
+---
+title: Use ETag to improve performance of issue notes polling
+merge_request: 9036
+author:
diff --git a/changelogs/unreleased/fix-mentioned-issues-for-external-trackers.yml b/changelogs/unreleased/fix-mentioned-issues-for-external-trackers.yml
new file mode 100644
index 00000000000..ee827b7c939
--- /dev/null
+++ b/changelogs/unreleased/fix-mentioned-issues-for-external-trackers.yml
@@ -0,0 +1,4 @@
+---
+title: Fix issues mentioned but not closed for external issue trackers
+merge_request:
+author:
diff --git a/changelogs/unreleased/format-timeago-date.yml b/changelogs/unreleased/format-timeago-date.yml
new file mode 100644
index 00000000000..f331c34abbc
--- /dev/null
+++ b/changelogs/unreleased/format-timeago-date.yml
@@ -0,0 +1,4 @@
+---
+title: Format timeago date to short format
+merge_request:
+author:
diff --git a/changelogs/unreleased/introduce-pipeline-triggers.yml b/changelogs/unreleased/introduce-pipeline-triggers.yml
new file mode 100644
index 00000000000..ce5a230d48f
--- /dev/null
+++ b/changelogs/unreleased/introduce-pipeline-triggers.yml
@@ -0,0 +1,4 @@
+---
+title: Introduce Pipeline Triggers that are user-aware
+merge_request:
+author:
diff --git a/changelogs/unreleased/remove-es6-extension.yml b/changelogs/unreleased/remove-es6-extension.yml
new file mode 100644
index 00000000000..65f4a7a7867
--- /dev/null
+++ b/changelogs/unreleased/remove-es6-extension.yml
@@ -0,0 +1,4 @@
+---
+title: Remove es6 file extension from JavaScript files
+merge_request: 9241
+author: winniehell
diff --git a/changelogs/unreleased/workhorse-1-4-0.yml b/changelogs/unreleased/workhorse-1-4-0.yml
new file mode 100644
index 00000000000..b55fabddb0f
--- /dev/null
+++ b/changelogs/unreleased/workhorse-1-4-0.yml
@@ -0,0 +1,4 @@
+---
+title: Use gitlab-workhorse 1.4.0
+merge_request: 9724
+author:
diff --git a/changelogs/unreleased/zj-builds-to-jobs-api.yml b/changelogs/unreleased/zj-builds-to-jobs-api.yml
new file mode 100644
index 00000000000..473dd9bc8ed
--- /dev/null
+++ b/changelogs/unreleased/zj-builds-to-jobs-api.yml
@@ -0,0 +1,4 @@
+---
+title: Rename builds to job for the v4 API
+merge_request: 9463
+author:
diff --git a/config/application.rb b/config/application.rb
index 45f3b20d214..fce3d5bcd22 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -7,6 +7,7 @@ Bundler.require(:default, Rails.env)
module Gitlab
class Application < Rails::Application
require_dependency Rails.root.join('lib/gitlab/redis')
+ require_dependency Rails.root.join('lib/gitlab/request_context')
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
@@ -100,8 +101,6 @@ module Gitlab
config.assets.precompile << "katex.js"
config.assets.precompile << "xterm/xterm.css"
config.assets.precompile << "lib/ace.js"
- config.assets.precompile << "lib/cropper.js"
- config.assets.precompile << "lib/raphael.js"
config.assets.precompile << "u2f.js"
config.assets.precompile << "vendor/assets/fonts/*"
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 79d551fc4d8..be34a4000fa 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -177,9 +177,9 @@ production: &base
# Periodically executed jobs, to self-heal Gitlab, do external synchronizations, etc.
# Please read here for more information: https://github.com/ondrejbartas/sidekiq-cron#adding-cron-job
cron_jobs:
- # Flag stuck CI builds as failed
- stuck_ci_builds_worker:
- cron: "0 0 * * *"
+ # Flag stuck CI jobs as failed
+ stuck_ci_jobs_worker:
+ cron: "0 * * * *"
# Remove expired build artifacts
expire_build_artifacts_worker:
cron: "50 * * * *"
@@ -483,6 +483,8 @@ production: &base
# multipart_chunk_size: 104857600
# # Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
# # encryption: 'AES256'
+ # # Specifies Amazon S3 storage class to use for backups, this is optional
+ # # storage_class: 'STANDARD'
## GitLab Shell settings
gitlab_shell:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index c64ae15fa92..933844e4ea6 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -308,9 +308,9 @@ Settings.gravatar['host'] = Settings.host_without_www(Settings.gravatar[
# Cron Jobs
#
Settings['cron_jobs'] ||= Settingslogic.new({})
-Settings.cron_jobs['stuck_ci_builds_worker'] ||= Settingslogic.new({})
-Settings.cron_jobs['stuck_ci_builds_worker']['cron'] ||= '0 0 * * *'
-Settings.cron_jobs['stuck_ci_builds_worker']['job_class'] = 'StuckCiBuildsWorker'
+Settings.cron_jobs['stuck_ci_jobs_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['stuck_ci_jobs_worker']['cron'] ||= '0 * * * *'
+Settings.cron_jobs['stuck_ci_jobs_worker']['job_class'] = 'StuckCiJobsWorker'
Settings.cron_jobs['expire_build_artifacts_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['expire_build_artifacts_worker']['cron'] ||= '50 * * * *'
Settings.cron_jobs['expire_build_artifacts_worker']['job_class'] = 'ExpireBuildArtifactsWorker'
@@ -399,6 +399,7 @@ if Settings.backup['upload']['connection']
end
Settings.backup['upload']['multipart_chunk_size'] ||= 104857600
Settings.backup['upload']['encryption'] ||= nil
+Settings.backup['upload']['storage_class'] ||= nil
#
# Git
diff --git a/config/initializers/metrics.rb b/config/initializers/8_metrics.rb
index a1517e6afc8..a1517e6afc8 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/8_metrics.rb
diff --git a/config/initializers/etag_caching.rb b/config/initializers/etag_caching.rb
new file mode 100644
index 00000000000..eba88801141
--- /dev/null
+++ b/config/initializers/etag_caching.rb
@@ -0,0 +1,4 @@
+# This middleware has to come after Gitlab::Metrics::RackMiddleware
+# in the middleware stack, because it tracks events with
+# GitLab Performance Monitoring
+Rails.application.config.middleware.use(Gitlab::EtagCaching::Middleware)
diff --git a/config/initializers/request_context.rb b/config/initializers/request_context.rb
new file mode 100644
index 00000000000..0b485fc1adc
--- /dev/null
+++ b/config/initializers/request_context.rb
@@ -0,0 +1,3 @@
+Rails.application.configure do |config|
+ config.middleware.insert_after RequestStore::Middleware, Gitlab::RequestContext
+end
diff --git a/config/initializers/warden.rb b/config/initializers/warden.rb
new file mode 100644
index 00000000000..3d83fb92d56
--- /dev/null
+++ b/config/initializers/warden.rb
@@ -0,0 +1,5 @@
+Rails.application.configure do |config|
+ Warden::Manager.after_set_user do |user, auth, opts|
+ Gitlab::Auth::UniqueIpsLimiter.limit_user!(user)
+ end
+end
diff --git a/config/karma.config.js b/config/karma.config.js
index 2f3cc932413..a23e62f5022 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -1,9 +1,10 @@
var path = require('path');
+var webpack = require('webpack');
var webpackConfig = require('./webpack.config.js');
var ROOT_PATH = path.resolve(__dirname, '..');
// add coverage instrumentation to babel config
-if (webpackConfig && webpackConfig.module && webpackConfig.module.rules) {
+if (webpackConfig.module && webpackConfig.module.rules) {
var babelConfig = webpackConfig.module.rules.find(function (rule) {
return rule.loader === 'babel-loader';
});
@@ -13,6 +14,16 @@ if (webpackConfig && webpackConfig.module && webpackConfig.module.rules) {
babelConfig.options.plugins.push('istanbul');
}
+// remove problematic plugins
+if (webpackConfig.plugins) {
+ webpackConfig.plugins = webpackConfig.plugins.filter(function (plugin) {
+ return !(
+ plugin instanceof webpack.optimize.CommonsChunkPlugin ||
+ plugin instanceof webpack.DefinePlugin
+ );
+ });
+}
+
// Karma configuration
module.exports = function(config) {
var progressReporter = process.env.CI ? 'mocha' : 'progress';
diff --git a/config/routes/profile.rb b/config/routes/profile.rb
index 6b91485da9e..07c341999ea 100644
--- a/config/routes/profile.rb
+++ b/config/routes/profile.rb
@@ -21,7 +21,7 @@ resource :profile, only: [:show, :update] do
end
end
resource :preferences, only: [:show, :update]
- resources :keys, only: [:index, :show, :new, :create, :destroy]
+ resources :keys, only: [:index, :show, :create, :destroy]
resources :emails, only: [:index, :create, :destroy]
resources :chat_names, only: [:index, :new, :create, :destroy] do
collection do
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 2703bf4ab46..7dc7963ab88 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -267,7 +267,7 @@ constraints(ProjectUrlConstrainer.new) do
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
- resources :notes, only: [:index, :create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
+ resources :notes, only: [:create, :destroy, :update], concerns: :awardable, constraints: { id: /\d+/ } do
member do
delete :delete_attachment
post :resolve
@@ -275,6 +275,8 @@ constraints(ProjectUrlConstrainer.new) do
end
end
+ get 'noteable/:target_type/:target_id/notes' => 'notes#index', as: 'noteable_notes'
+
resources :boards, only: [:index, :show] do
scope module: :boards do
resources :issues, only: [:index, :update]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 13273902b0e..d9fa70c29fb 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -17,7 +17,10 @@ var WEBPACK_REPORT = process.env.WEBPACK_REPORT;
var config = {
context: path.join(ROOT_PATH, 'app/assets/javascripts'),
entry: {
- application: './application.js',
+ common: './commons/index.js',
+ common_vue: ['vue', 'vue-resource'],
+ common_d3: ['d3'],
+ main: './main.js',
blob_edit: './blob_edit/blob_edit_bundle.js',
boards: './boards/boards_bundle.js',
simulate_drag: './test_utils/simulate_drag.js',
@@ -38,16 +41,13 @@ var config = {
snippet: './snippet/snippet_bundle.js',
terminal: './terminal/terminal_bundle.js',
users: './users/users_bundle.js',
- lib_chart: './lib/chart.js',
- lib_d3: './lib/d3.js',
- lib_vue: './lib/vue_resource.js',
vue_pipelines: './vue_pipelines_index/index.js',
},
output: {
path: path.join(ROOT_PATH, 'public/assets/webpack'),
publicPath: '/assets/webpack/',
- filename: IS_PRODUCTION ? '[name]-[chunkhash].js' : '[name].js'
+ filename: IS_PRODUCTION ? '[name].[chunkhash].bundle.js' : '[name].bundle.js'
},
devtool: 'inline-source-map',
@@ -82,14 +82,56 @@ var config = {
modules: false,
assets: true
}),
+
+ // prevent pikaday from including moment.js
new webpack.IgnorePlugin(/moment/, /pikaday/),
+
+ // fix legacy jQuery plugins which depend on globals
+ new webpack.ProvidePlugin({
+ $: 'jquery',
+ jQuery: 'jquery',
+ }),
+
+ // use deterministic module ids in all environments
+ IS_PRODUCTION ?
+ new webpack.HashedModuleIdsPlugin() :
+ new webpack.NamedModulesPlugin(),
+
+ // create cacheable common library bundle for all vue chunks
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'common_vue',
+ chunks: [
+ 'boards',
+ 'commit_pipelines',
+ 'cycle_analytics',
+ 'diff_notes',
+ 'environments',
+ 'environments_folder',
+ 'issuable',
+ 'merge_conflicts',
+ 'vue_pipelines',
+ ],
+ minChunks: function(module, count) {
+ return module.resource && (/vue_shared/).test(module.resource);
+ },
+ }),
+
+ // create cacheable common library bundle for all d3 chunks
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'common_d3',
+ chunks: ['graphs', 'users'],
+ }),
+
+ // create cacheable common library bundles
+ new webpack.optimize.CommonsChunkPlugin({
+ names: ['main', 'common', 'runtime'],
+ }),
],
resolve: {
extensions: ['.js', '.es6', '.js.es6'],
alias: {
'~': path.join(ROOT_PATH, 'app/assets/javascripts'),
- 'bootstrap/js': 'bootstrap-sass/assets/javascripts/bootstrap',
'emoji-aliases$': path.join(ROOT_PATH, 'fixtures/emojis/aliases.json'),
'icons': path.join(ROOT_PATH, 'app/views/shared/icons'),
'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'),
diff --git a/db/migrate/20170210131347_add_unique_ips_limit_to_application_settings.rb b/db/migrate/20170210131347_add_unique_ips_limit_to_application_settings.rb
new file mode 100644
index 00000000000..9ab970134be
--- /dev/null
+++ b/db/migrate/20170210131347_add_unique_ips_limit_to_application_settings.rb
@@ -0,0 +1,17 @@
+class AddUniqueIpsLimitToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ DOWNTIME = false
+ disable_ddl_transaction!
+
+ def up
+ add_column :application_settings, :unique_ips_limit_per_user, :integer
+ add_column :application_settings, :unique_ips_limit_time_window, :integer
+ add_column_with_default :application_settings, :unique_ips_limit_enabled, :boolean, default: false
+ end
+
+ def down
+ remove_column :application_settings, :unique_ips_limit_per_user
+ remove_column :application_settings, :unique_ips_limit_time_window
+ remove_column :application_settings, :unique_ips_limit_enabled
+ end
+end
diff --git a/db/migrate/20170217151948_add_owner_id_to_triggers.rb b/db/migrate/20170217151948_add_owner_id_to_triggers.rb
new file mode 100644
index 00000000000..16d7cc5bed6
--- /dev/null
+++ b/db/migrate/20170217151948_add_owner_id_to_triggers.rb
@@ -0,0 +1,9 @@
+class AddOwnerIdToTriggers < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_triggers, :owner_id, :integer
+ end
+end
diff --git a/db/migrate/20170217151949_add_description_to_triggers.rb b/db/migrate/20170217151949_add_description_to_triggers.rb
new file mode 100644
index 00000000000..1dca0e37412
--- /dev/null
+++ b/db/migrate/20170217151949_add_description_to_triggers.rb
@@ -0,0 +1,9 @@
+class AddDescriptionToTriggers < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :ci_triggers, :description, :string
+ end
+end
diff --git a/db/migrate/20170305203726_add_owner_id_foreign_key.rb b/db/migrate/20170305203726_add_owner_id_foreign_key.rb
new file mode 100644
index 00000000000..3eece0e2eb5
--- /dev/null
+++ b/db/migrate/20170305203726_add_owner_id_foreign_key.rb
@@ -0,0 +1,11 @@
+class AddOwnerIdForeignKey < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_foreign_key :ci_triggers, :users, column: :owner_id, on_delete: :cascade
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index cd5aa339269..911cb22c8e5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20170217151947) do
+ActiveRecord::Schema.define(version: 20170305203726) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -111,7 +111,10 @@ ActiveRecord::Schema.define(version: 20170217151947) do
t.boolean "plantuml_enabled"
t.integer "max_pages_size", default: 100, null: false
t.integer "terminal_max_session_time", default: 0, null: false
- t.string "default_artifacts_expire_in", default: '0', null: false
+ t.string "default_artifacts_expire_in", default: "0", null: false
+ t.integer "unique_ips_limit_per_user"
+ t.integer "unique_ips_limit_time_window"
+ t.boolean "unique_ips_limit_enabled", default: false, null: false
end
create_table "audit_events", force: :cascade do |t|
@@ -377,6 +380,8 @@ ActiveRecord::Schema.define(version: 20170217151947) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "gl_project_id"
+ t.integer "owner_id"
+ t.string "description"
end
add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree
@@ -581,9 +586,9 @@ ActiveRecord::Schema.define(version: 20170217151947) do
end
add_index "labels", ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true, using: :btree
- add_index "labels", ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
add_index "labels", ["title"], name: "index_labels_on_title", using: :btree
+ add_index "labels", ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree
create_table "lfs_objects", force: :cascade do |t|
t.string "oid", null: false
@@ -1333,6 +1338,7 @@ ActiveRecord::Schema.define(version: 20170217151947) do
add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
add_foreign_key "boards", "projects"
+ add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "issue_metrics", "issues", on_delete: :cascade
add_foreign_key "label_priorities", "labels", on_delete: :cascade
add_foreign_key "label_priorities", "projects", on_delete: :cascade
diff --git a/doc/api/README.md b/doc/api/README.md
index 3399e2bb5f6..285cd2435ac 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -12,7 +12,6 @@ following locations:
- [Branches](branches.md)
- [Broadcast Messages](broadcast_messages.md)
- [Builds](builds.md)
-- [Build Triggers](build_triggers.md)
- [Build Variables](build_variables.md)
- [Commits](commits.md)
- [Deployments](deployments.md)
@@ -33,6 +32,7 @@ following locations:
- [Notes](notes.md) (comments)
- [Notification settings](notification_settings.md)
- [Pipelines](pipelines.md)
+- [Pipeline Triggers](pipeline_triggers.md)
- [Projects](projects.md) including setting Webhooks
- [Project Access Requests](access_requests.md)
- [Project Members](members.md)
diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md
index 28befba69d6..20d924ab35e 100644
--- a/doc/api/build_triggers.md
+++ b/doc/api/build_triggers.md
@@ -1,108 +1 @@
-# Build triggers
-
-You can read more about [triggering builds through the API](../ci/triggers/README.md).
-
-## List project triggers
-
-Get a list of project's build triggers.
-
-```
-GET /projects/:id/triggers
-```
-
-| Attribute | Type | required | Description |
-|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
-
-```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
-```
-
-```json
-[
- {
- "created_at": "2015-12-23T16:24:34.716Z",
- "deleted_at": null,
- "last_used": "2016-01-04T15:41:21.986Z",
- "token": "fbdb730c2fbdb095a0862dbd8ab88b",
- "updated_at": "2015-12-23T16:24:34.716Z"
- },
- {
- "created_at": "2015-12-23T16:25:56.760Z",
- "deleted_at": null,
- "last_used": null,
- "token": "7b9148c158980bbd9bcea92c17522d",
- "updated_at": "2015-12-23T16:25:56.760Z"
- }
-]
-```
-
-## Get trigger details
-
-Get details of project's build trigger.
-
-```
-GET /projects/:id/triggers/:token
-```
-
-| Attribute | Type | required | Description |
-|-----------|---------|----------|--------------------------|
-| `id` | integer | yes | The ID of a project |
-| `token` | string | yes | The `token` of a trigger |
-
-```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
-```
-
-```json
-{
- "created_at": "2015-12-23T16:25:56.760Z",
- "deleted_at": null,
- "last_used": null,
- "token": "7b9148c158980bbd9bcea92c17522d",
- "updated_at": "2015-12-23T16:25:56.760Z"
-}
-```
-
-## Create a project trigger
-
-Create a build trigger for a project.
-
-```
-POST /projects/:id/triggers
-```
-
-| Attribute | Type | required | Description |
-|-----------|---------|----------|--------------------------|
-| `id` | integer | yes | The ID of a project |
-
-```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
-```
-
-```json
-{
- "created_at": "2016-01-07T09:53:58.235Z",
- "deleted_at": null,
- "last_used": null,
- "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
- "updated_at": "2016-01-07T09:53:58.235Z"
-}
-```
-
-## Remove a project trigger
-
-Remove a project's build trigger.
-
-```
-DELETE /projects/:id/triggers/:token
-```
-
-| Attribute | Type | required | Description |
-|-----------|---------|----------|--------------------------|
-| `id` | integer | yes | The ID of a project |
-| `token` | string | yes | The `token` of a trigger |
-
-```
-curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/7b9148c158980bbd9bcea92c17522d"
-```
+This document was moved to [Pipeline Triggers](pipeline_triggers.md).
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 80c08096dea..dfc6b80bfd9 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -84,7 +84,7 @@ Example response:
"issues_enabled": true,
"merge_requests_enabled": true,
"wiki_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"snippets_enabled": true,
"created_at": "2016-04-05T21:40:50.169Z",
"last_activity_at": "2016-04-06T16:52:08.432Z",
@@ -100,7 +100,7 @@ Example response:
"star_count": 1,
"forks_count": 0,
"open_issues_count": 3,
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"request_access_enabled": false
}
@@ -158,7 +158,7 @@ Example response:
"issues_enabled": true,
"merge_requests_enabled": true,
"wiki_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": true,
"created_at": "2016-06-17T07:47:25.578Z",
@@ -175,7 +175,7 @@ Example response:
"star_count": 0,
"forks_count": 0,
"open_issues_count": 3,
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"request_access_enabled": false
},
@@ -196,7 +196,7 @@ Example response:
"issues_enabled": true,
"merge_requests_enabled": true,
"wiki_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": true,
"created_at": "2016-06-17T07:47:24.661Z",
@@ -213,7 +213,7 @@ Example response:
"star_count": 0,
"forks_count": 0,
"open_issues_count": 8,
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"request_access_enabled": false
}
@@ -236,7 +236,7 @@ Example response:
"issues_enabled": true,
"merge_requests_enabled": true,
"wiki_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": true,
"created_at": "2016-06-17T07:47:27.089Z",
@@ -253,7 +253,7 @@ Example response:
"star_count": 0,
"forks_count": 0,
"open_issues_count": 4,
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [
{
"group_id": 4,
@@ -359,7 +359,7 @@ Example response:
"issues_enabled": true,
"merge_requests_enabled": true,
"wiki_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"snippets_enabled": true,
"created_at": "2016-04-05T21:40:50.169Z",
"last_activity_at": "2016-04-06T16:52:08.432Z",
@@ -375,7 +375,7 @@ Example response:
"star_count": 1,
"forks_count": 0,
"open_issues_count": 3,
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"request_access_enabled": false
}
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 0f22d0b7f94..4047ff14af2 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -84,7 +84,6 @@ Example response:
"created_at" : "2016-01-04T15:31:51.081Z",
"iid" : 6,
"labels" : [],
- "subscribed" : false,
"user_notes_count": 1,
"due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/6",
@@ -167,7 +166,6 @@ Example response:
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
- "subscribed" : false,
"user_notes_count": 1,
"due_date": null,
"web_url": "http://example.com/example/example/issues/1",
@@ -250,7 +248,6 @@ Example response:
"title" : "Ut commodi ullam eos dolores perferendis nihil sunt.",
"updated_at" : "2016-01-04T15:31:46.176Z",
"created_at" : "2016-01-04T15:31:46.176Z",
- "subscribed" : false,
"user_notes_count": 1,
"due_date": "2016-07-22",
"web_url": "http://example.com/example/example/issues/1",
diff --git a/doc/api/builds.md b/doc/api/jobs.md
index 84214e4708f..296f1d025dd 100644
--- a/doc/api/builds.md
+++ b/doc/api/jobs.md
@@ -1,20 +1,20 @@
-# Builds API
+# Jobs API
-## List project builds
+## List project jobs
-Get a list of builds in a project.
+Get a list of jobs in a project.
```
-GET /projects/:id/builds
+GET /projects/:id/jobs
```
| Attribute | Type | Required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
-| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all builds if none provided |
+| `scope` | string **or** array of strings | no | The scope of jobs to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all jobs if none provided |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/builds?scope%5B0%5D=pending&scope%5B1%5D=running'
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/jobs?scope%5B0%5D=pending&scope%5B1%5D=running'
```
Example of response
@@ -115,125 +115,21 @@ Example of response
]
```
-## List commit builds
+## Get a single job
-Get a list of builds for specific commit in a project.
-
-This endpoint will return all builds, from all pipelines for a given commit.
-If the commit SHA is not found, it will respond with 404, otherwise it will
-return an array of builds (an empty array if there are no builds for this
-particular commit).
-
-```
-GET /projects/:id/repository/commits/:sha/builds
-```
-
-| Attribute | Type | Required | Description |
-|-----------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
-| `sha` | string | yes | The SHA id of a commit |
-| `scope` | string **or** array of strings | no | The scope of builds to show, one or array of: `created`, `pending`, `running`, `failed`, `success`, `canceled`, `skipped`; showing all builds if none provided |
-
-```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" 'https://gitlab.example.com/api/v4/projects/1/repository/commits/0ff3ae198f8601a285adcf5c0fff204ee6fba5fd/builds?scope%5B0%5D=pending&scope%5B1%5D=running'
-```
-
-Example of response
-
-```json
-[
- {
- "commit": {
- "author_email": "admin@example.com",
- "author_name": "Administrator",
- "created_at": "2015-12-24T16:51:14.000+01:00",
- "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
- "message": "Test the CI integration.",
- "short_id": "0ff3ae19",
- "title": "Test the CI integration."
- },
- "coverage": null,
- "created_at": "2016-01-11T10:13:33.506Z",
- "artifacts_file": null,
- "finished_at": "2016-01-11T10:14:09.526Z",
- "id": 69,
- "name": "rubocop",
- "pipeline": {
- "id": 6,
- "ref": "master",
- "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
- "status": "pending"
- },
- "ref": "master",
- "runner": null,
- "stage": "test",
- "started_at": null,
- "status": "canceled",
- "tag": false,
- "user": null
- },
- {
- "commit": {
- "author_email": "admin@example.com",
- "author_name": "Administrator",
- "created_at": "2015-12-24T16:51:14.000+01:00",
- "id": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
- "message": "Test the CI integration.",
- "short_id": "0ff3ae19",
- "title": "Test the CI integration."
- },
- "coverage": null,
- "created_at": "2015-12-24T15:51:21.957Z",
- "artifacts_file": null,
- "finished_at": "2015-12-24T17:54:33.913Z",
- "id": 9,
- "name": "brakeman",
- "pipeline": {
- "id": 6,
- "ref": "master",
- "sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
- "status": "pending"
- },
- "ref": "master",
- "runner": null,
- "stage": "test",
- "started_at": "2015-12-24T17:54:33.727Z",
- "status": "failed",
- "tag": false,
- "user": {
- "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
- "bio": null,
- "created_at": "2015-12-21T13:14:24.077Z",
- "id": 1,
- "is_admin": true,
- "linkedin": "",
- "name": "Administrator",
- "skype": "",
- "state": "active",
- "twitter": "",
- "username": "root",
- "web_url": "http://gitlab.dev/root",
- "website_url": ""
- }
- }
-]
-```
-
-## Get a single build
-
-Get a single build of a project
+Get a single job of a project
```
-GET /projects/:id/builds/:build_id
+GET /projects/:id/jobs/:job_id
```
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
-| `build_id` | integer | yes | The ID of a build |
+| `job_id` | integer | yes | The ID of a job |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/8"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8"
```
Example of response
@@ -285,23 +181,23 @@ Example of response
}
```
-## Get build artifacts
+## Get job artifacts
> [Introduced][ce-2893] in GitLab 8.5
-Get build artifacts of a project
+Get job artifacts of a project
```
-GET /projects/:id/builds/:build_id/artifacts
+GET /projects/:id/jobs/:job_id/artifacts
```
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
-| `build_id` | integer | yes | The ID of a build |
+| `job_id` | integer | yes | The ID of a job |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/8/artifacts"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/artifacts"
```
Response:
@@ -318,10 +214,10 @@ Response:
> [Introduced][ce-5347] in GitLab 8.10.
Download the artifacts file from the given reference name and job provided the
-build finished successfully.
+job finished successfully.
```
-GET /projects/:id/builds/artifacts/:ref_name/download?job=name
+GET /projects/:id/jobs/artifacts/:ref_name/download?job=name
```
Parameters
@@ -335,7 +231,7 @@ Parameters
Example request:
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/artifacts/master/download?job=test"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/download?job=test"
```
Example response:
@@ -349,19 +245,19 @@ Example response:
## Get a trace file
-Get a trace of a specific build of a project
+Get a trace of a specific job of a project
```
-GET /projects/:id/builds/:build_id/trace
+GET /projects/:id/jobs/:job_id/trace
```
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| id | integer | yes | The ID of a project |
-| build_id | integer | yes | The ID of a build |
+| job_id | integer | yes | The ID of a job |
```
-curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/8/trace"
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace"
```
Response:
@@ -371,21 +267,21 @@ Response:
| 200 | Serves the trace file |
| 404 | Build not found or no trace file |
-## Cancel a build
+## Cancel a job
-Cancel a single build of a project
+Cancel a single job of a project
```
-POST /projects/:id/builds/:build_id/cancel
+POST /projects/:id/jobs/:job_id/cancel
```
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
-| `build_id` | integer | yes | The ID of a build |
+| `job_id` | integer | yes | The ID of a job |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/cancel"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/cancel"
```
Example of response
@@ -417,21 +313,21 @@ Example of response
}
```
-## Retry a build
+## Retry a job
-Retry a single build of a project
+Retry a single job of a project
```
-POST /projects/:id/builds/:build_id/retry
+POST /projects/:id/jobs/:job_id/retry
```
| Attribute | Type | Required | Description |
|------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
-| `build_id` | integer | yes | The ID of a build |
+| `job_id` | integer | yes | The ID of a job |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/retry"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/retry"
```
Example of response
@@ -463,12 +359,12 @@ Example of response
}
```
-## Erase a build
+## Erase a job
-Erase a single build of a project (remove build artifacts and a build trace)
+Erase a single job of a project (remove job artifacts and a job trace)
```
-POST /projects/:id/builds/:build_id/erase
+POST /projects/:id/jobs/:job_id/erase
```
Parameters
@@ -476,12 +372,12 @@ Parameters
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
-| `build_id` | integer | yes | The ID of a build |
+| `job_id` | integer | yes | The ID of a job |
Example of request
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/erase"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/erase"
```
Example of response
@@ -518,7 +414,7 @@ Example of response
Prevents artifacts from being deleted when expiration is set.
```
-POST /projects/:id/builds/:build_id/artifacts/keep
+POST /projects/:id/jobs/:job_id/artifacts/keep
```
Parameters
@@ -526,12 +422,12 @@ Parameters
| Attribute | Type | Required | Description |
|-------------|---------|----------|---------------------|
| `id` | integer | yes | The ID of a project |
-| `build_id` | integer | yes | The ID of a build |
+| `job_id` | integer | yes | The ID of a job |
Example request:
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/artifacts/keep"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/artifacts/keep"
```
Example response:
@@ -563,21 +459,21 @@ Example response:
}
```
-## Play a build
+## Play a job
-Triggers a manual action to start a build.
+Triggers a manual action to start a job.
```
-POST /projects/:id/builds/:build_id/play
+POST /projects/:id/jobs/:job_id/play
```
-| Attribute | Type | Required | Description |
-|------------|---------|----------|---------------------|
-| `id` | integer | yes | The ID of a project |
-| `build_id` | integer | yes | The ID of a build |
+| Attribute | Type | Required | Description |
+|-----------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+| `job_id` | integer | yes | The ID of a job |
```
-curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/builds/1/play"
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/jobs/1/play"
```
Example of response
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index e178d5c1629..09d23cd2ff6 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -67,7 +67,6 @@ Parameters:
},
"merge_when_pipeline_succeeds": true,
"merge_status": "can_be_merged",
- "subscribed" : false,
"sha": "8888888888888888888888888888888888888888",
"merge_commit_sha": null,
"user_notes_count": 1,
diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md
new file mode 100644
index 00000000000..fdb41a1d615
--- /dev/null
+++ b/doc/api/pipeline_triggers.md
@@ -0,0 +1,170 @@
+# Pipeline triggers
+
+You can read more about [triggering pipelines through the API](../ci/triggers/README.md).
+
+## List project triggers
+
+Get a list of project's build triggers.
+
+```
+GET /projects/:id/triggers
+```
+
+| Attribute | Type | required | Description |
+|-----------|---------|----------|---------------------|
+| `id` | integer | yes | The ID of a project |
+
+```
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers"
+```
+
+```json
+[
+ {
+ "id": 10,
+ "description": "my trigger",
+ "created_at": "2016-01-07T09:53:58.235Z",
+ "deleted_at": null,
+ "last_used": null,
+ "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
+ "updated_at": "2016-01-07T09:53:58.235Z",
+ "owner": null
+ }
+]
+```
+
+## Get trigger details
+
+Get details of project's build trigger.
+
+```
+GET /projects/:id/triggers/:trigger_id
+```
+
+| Attribute | Type | required | Description |
+|-----------|---------|----------|--------------------------|
+| `id` | integer | yes | The ID of a project |
+| `token` | string | yes | The `token` of a trigger |
+
+```
+curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
+```
+
+```json
+{
+ "id": 10,
+ "description": "my trigger",
+ "created_at": "2016-01-07T09:53:58.235Z",
+ "deleted_at": null,
+ "last_used": null,
+ "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
+ "updated_at": "2016-01-07T09:53:58.235Z",
+ "owner": null
+}
+```
+
+## Create a project trigger
+
+Create a trigger for a project.
+
+```
+POST /projects/:id/triggers
+```
+
+| Attribute | Type | required | Description |
+|---------------|---------|----------|--------------------------|
+| `id` | integer | yes | The ID of a project |
+| `description` | string | yes | The trigger name |
+
+```
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers"
+```
+
+```json
+{
+ "id": 10,
+ "description": "my trigger",
+ "created_at": "2016-01-07T09:53:58.235Z",
+ "deleted_at": null,
+ "last_used": null,
+ "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
+ "updated_at": "2016-01-07T09:53:58.235Z",
+ "owner": null
+}
+```
+
+## Update a project trigger
+
+Update a trigger for a project.
+
+```
+PUT /projects/:id/triggers/:trigger_id
+```
+
+| Attribute | Type | required | Description |
+|---------------|---------|----------|--------------------------|
+| `trigger_id` | integer | yes | The trigger id |
+| `description` | string | no | The trigger name |
+
+```
+curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form description="my description" "https://gitlab.example.com/api/v4/projects/1/triggers/10"
+```
+
+```json
+{
+ "id": 10,
+ "description": "my trigger",
+ "created_at": "2016-01-07T09:53:58.235Z",
+ "deleted_at": null,
+ "last_used": null,
+ "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
+ "updated_at": "2016-01-07T09:53:58.235Z",
+ "owner": null
+}
+```
+
+## Take ownership of a project trigger
+
+Update an owner of a project trigger.
+
+```
+POST /projects/:id/triggers/:trigger_id/take_ownership
+```
+
+| Attribute | Type | required | Description |
+|---------------|---------|----------|--------------------------|
+| `trigger_id` | integer | yes | The trigger id |
+
+```
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/10/take_ownership"
+```
+
+```json
+{
+ "id": 10,
+ "description": "my trigger",
+ "created_at": "2016-01-07T09:53:58.235Z",
+ "deleted_at": null,
+ "last_used": null,
+ "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
+ "updated_at": "2016-01-07T09:53:58.235Z",
+ "owner": null
+}
+```
+
+## Remove a project trigger
+
+Remove a project's build trigger.
+
+```
+DELETE /projects/:id/triggers/:trigger_id
+```
+
+| Attribute | Type | required | Description |
+|----------------|---------|----------|--------------------------|
+| `id` | integer | yes | The ID of a project |
+| `trigger_id` | integer | yes | The trigger id |
+
+```
+curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/1/triggers/5"
+```
diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md
index d809ec032f8..574a8bacb25 100644
--- a/doc/api/pipelines.md
+++ b/doc/api/pipelines.md
@@ -127,7 +127,7 @@ Example of response
}
```
-## Retry builds in a pipeline
+## Retry jobs in a pipeline
> [Introduced][ce-5837] in GitLab 8.11
@@ -173,7 +173,7 @@ Response:
}
```
-## Cancel a pipelines builds
+## Cancel a pipelines jobs
> [Introduced][ce-5837] in GitLab 8.11
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 6062c5ccd71..28e4bfe39dc 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -66,7 +66,7 @@ Parameters:
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
@@ -86,7 +86,7 @@ Parameters:
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02",
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
@@ -116,7 +116,7 @@ Parameters:
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
@@ -146,7 +146,7 @@ Parameters:
"forks_count": 0,
"star_count": 0,
"runners_token": "b8547b1dc37721d05889db52fa2f02",
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
@@ -196,7 +196,7 @@ Parameters:
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
@@ -226,7 +226,7 @@ Parameters:
"forks_count": 0,
"star_count": 0,
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [
{
"group_id": 4,
@@ -439,15 +439,15 @@ Parameters:
| `description` | string | no | Short project description |
| `issues_enabled` | boolean | no | Enable issues for this project |
| `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
-| `builds_enabled` | boolean | no | Enable builds for this project |
+| `jobs_enabled` | boolean | no | Enable jobs for this project |
| `wiki_enabled` | boolean | no | Enable wiki for this project |
| `snippets_enabled` | boolean | no | Enable snippets for this project |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | String | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
-| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
-| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
+| `public_jobs` | boolean | no | If `true`, jobs can be viewed by non-project-members |
+| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
@@ -472,15 +472,15 @@ Parameters:
| `description` | string | no | Short project description |
| `issues_enabled` | boolean | no | Enable issues for this project |
| `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
-| `builds_enabled` | boolean | no | Enable builds for this project |
+| `jobs_enabled` | boolean | no | Enable jobs for this project |
| `wiki_enabled` | boolean | no | Enable wiki for this project |
| `snippets_enabled` | boolean | no | Enable snippets for this project |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
-| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
-| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
+| `public_jobs` | boolean | no | If `true`, jobs can be viewed by non-project-members |
+| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
@@ -504,15 +504,15 @@ Parameters:
| `description` | string | no | Short project description |
| `issues_enabled` | boolean | no | Enable issues for this project |
| `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
-| `builds_enabled` | boolean | no | Enable builds for this project |
+| `jobs_enabled` | boolean | no | Enable jobs for this project |
| `wiki_enabled` | boolean | no | Enable wiki for this project |
| `snippets_enabled` | boolean | no | Enable snippets for this project |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
| `visibility` | string | no | See [project visibility level](#project-visibility-level) |
| `import_url` | string | no | URL to import repository from |
-| `public_builds` | boolean | no | If `true`, builds can be viewed by non-project-members |
-| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful builds |
+| `public_jobs` | boolean | no | If `true`, jobs can be viewed by non-project-members |
+| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
@@ -572,7 +572,7 @@ Example response:
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
@@ -591,7 +591,7 @@ Example response:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 1,
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
@@ -637,7 +637,7 @@ Example response:
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
@@ -656,7 +656,7 @@ Example response:
"shared_runners_enabled": true,
"forks_count": 0,
"star_count": 0,
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
@@ -708,7 +708,7 @@ Example response:
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
@@ -738,7 +738,7 @@ Example response:
"forks_count": 0,
"star_count": 0,
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
@@ -790,7 +790,7 @@ Example response:
"issues_enabled": true,
"open_issues_count": 1,
"merge_requests_enabled": true,
- "builds_enabled": true,
+ "jobs_enabled": true,
"wiki_enabled": true,
"snippets_enabled": false,
"container_registry_enabled": false,
@@ -820,7 +820,7 @@ Example response:
"forks_count": 0,
"star_count": 0,
"runners_token": "b8bc4a7a29eb76ea83cf79e4908c2b",
- "public_builds": true,
+ "public_jobs": true,
"shared_with_groups": [],
"only_allow_merge_if_pipeline_succeeds": false,
"only_allow_merge_if_all_discussions_are_resolved": false,
@@ -955,7 +955,7 @@ Parameters:
"merge_requests_events": true,
"tag_push_events": true,
"note_events": true,
- "build_events": true,
+ "job_events": true,
"pipeline_events": true,
"wiki_page_events": true,
"enable_ssl_verification": true,
@@ -982,7 +982,7 @@ Parameters:
| `merge_requests_events` | boolean | no | Trigger hook on merge requests events |
| `tag_push_events` | boolean | no | Trigger hook on tag push events |
| `note_events` | boolean | no | Trigger hook on note events |
-| `build_events` | boolean | no | Trigger hook on build events |
+| `job_events` | boolean | no | Trigger hook on job events |
| `pipeline_events` | boolean | no | Trigger hook on pipeline events |
| `wiki_events` | boolean | no | Trigger hook on wiki events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
@@ -1008,7 +1008,7 @@ Parameters:
| `merge_requests_events` | boolean | no | Trigger hook on merge requests events |
| `tag_push_events` | boolean | no | Trigger hook on tag push events |
| `note_events` | boolean | no | Trigger hook on note events |
-| `build_events` | boolean | no | Trigger hook on build events |
+| `job_events` | boolean | no | Trigger hook on job events |
| `pipeline_events` | boolean | no | Trigger hook on pipeline events |
| `wiki_events` | boolean | no | Trigger hook on wiki events |
| `enable_ssl_verification` | boolean | no | Do SSL verification when triggering the hook |
diff --git a/doc/api/services.md b/doc/api/services.md
index b030a425a7a..8e7afe41b0c 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -148,7 +148,7 @@ Get emails for GitLab CI builds.
Set Build-Emails service for a project.
```
-PUT /projects/:id/services/builds-email
+PUT /projects/:id/services/jobs-email
```
Parameters:
@@ -157,23 +157,23 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `recipients` | string | yes | Comma-separated list of recipient email addresses |
| `add_pusher` | boolean | no | Add pusher to recipients list |
-| `notify_only_broken_builds` | boolean | no | Notify only broken builds |
+| `notify_only_broken_jobs` | boolean | no | Notify only broken jobs |
-### Delete Build-Emails service
+### Delete Job-Emails service
Delete Build-Emails service for a project.
```
-DELETE /projects/:id/services/builds-email
+DELETE /projects/:id/services/jobs-email
```
-### Get Build-Emails service settings
+### Get Job-Emails service settings
Get Build-Emails service settings for a project.
```
-GET /projects/:id/services/builds-email
+GET /projects/:id/services/jobs-email
```
## Campfire
@@ -580,7 +580,7 @@ Parameters:
| --------- | ---- | -------- | ----------- |
| `recipients` | string | yes | Comma-separated list of recipient email addresses |
| `add_pusher` | boolean | no | Add pusher to recipients list |
-| `notify_only_broken_builds` | boolean | no | Notify only broken pipelines |
+| `notify_only_broken_jobs` | boolean | no | Notify only broken pipelines |
### Delete Pipeline-Emails service
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 39dc6d98e7b..e5ef64fa8dc 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -52,6 +52,10 @@ changes are in V4:
- PUT `projects/:id`
- Renamed `branch_name` to `branch` on DELETE `id/repository/branches/:branch` response [!8936](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8936)
- Remove `public` param from create and edit actions of projects [!8736](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8736)
+- Remove `subscribed` field from responses returning list of issues or merge
+ requests. Fetch individual issues or merge requests to obtain the value
+ of `subscribed`
+ [!9661](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9661)
- Use `visibility` as string parameter everywhere [!9337](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9337)
- Notes do not return deprecated field `upvote` and `downvote` [!9384](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9384)
- Return HTTP status code `400` for all validation errors when creating or updating a member instead of sometimes `422` error. [!9523](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9523)
@@ -59,3 +63,8 @@ changes are in V4:
- Return 202 with JSON body on async removals on V4 API (DELETE `/projects/:id/repository/merged_branches` and DELETE `/projects/:id`) [!9449](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9449)
- `projects/:id/milestones?iid[]=x&iid[]=y` array filter has been renamed to `iids` [!9096](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9096)
- Return basic info about pipeline in `GET /projects/:id/pipelines` [!8875](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8875)
+- Renamed all `build` references to `job` [!9463](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9463)
+- Drop GET '/projects/:id/repository/commits/:sha/jobs' [!9463](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9463)
+- Rename Build Triggers to be Pipeline Triggers API [!9713](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9713)
+ - `POST /projects/:id/trigger/builds` to `POST /projects/:id/trigger/pipeline`
+ - Require description when creating a new trigger `POST /projects/:id/triggers`
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 1ad9621c8a0..ccaee33dc92 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -36,7 +36,7 @@ it will not trigger a job.
To trigger a job you need to send a `POST` request to GitLab's API endpoint:
```
-POST /projects/:id/trigger/builds
+POST /projects/:id/trigger/pipeline
```
The required parameters are the trigger's `token` and the Git `ref` on which
@@ -71,7 +71,7 @@ To trigger a job from webhook of another project you need to add the following
webhook url for Push and Tag push events:
```
-https://gitlab.example.com/api/v4/projects/:id/ref/:ref/trigger/builds?token=TOKEN
+https://gitlab.example.com/api/v4/projects/:id/ref/:ref/trigger/pipeline?token=TOKEN
```
> **Note**:
@@ -105,7 +105,7 @@ Using cURL you can trigger a rebuild with minimal effort, for example:
curl --request POST \
--form token=TOKEN \
--form ref=master \
- https://gitlab.example.com/api/v4/projects/9/trigger/builds
+ https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
```
In this case, the project with ID `9` will get rebuilt on `master` branch.
@@ -114,7 +114,7 @@ Alternatively, you can pass the `token` and `ref` arguments in the query string:
```bash
curl --request POST \
- "https://gitlab.example.com/api/v4/projects/9/trigger/builds?token=TOKEN&ref=master"
+ "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline?token=TOKEN&ref=master"
```
### Triggering a job within `.gitlab-ci.yml`
@@ -128,7 +128,7 @@ need to add in project's A `.gitlab-ci.yml`:
build_docs:
stage: deploy
script:
- - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/builds"
+ - "curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
only:
- tags
```
@@ -187,7 +187,7 @@ curl --request POST \
--form token=TOKEN \
--form ref=master \
--form "variables[UPLOAD_TO_S3]=true" \
- https://gitlab.example.com/api/v4/projects/9/trigger/builds
+ https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
```
### Using webhook to trigger job
@@ -195,7 +195,7 @@ curl --request POST \
You can add the following webhook to another project in order to trigger a job:
```
-https://gitlab.example.com/api/v4/projects/9/ref/master/trigger/builds?token=TOKEN&variables[UPLOAD_TO_S3]=true
+https://gitlab.example.com/api/v4/projects/9/ref/master/trigger/pipeline?token=TOKEN&variables[UPLOAD_TO_S3]=true
```
### Using cron to trigger nightly jobs
@@ -205,7 +205,7 @@ in conjunction with cron. The example below triggers a job on the `master`
branch of project with ID `9` every night at `00:30`:
```bash
-30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/builds
+30 0 * * * curl --request POST --form token=TOKEN --form ref=master https://gitlab.example.com/api/v4/projects/9/trigger/pipeline
```
[ci-229]: https://gitlab.com/gitlab-org/gitlab-ci/merge_requests/229
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 04c0af44237..a9e25187b88 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -308,8 +308,8 @@ Running on runner-8a2f473d-project-1796893-concurrent-0 via runner-8a2f473d-mach
++ CI_RUNNER_ID=1337
++ export CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com
++ CI_RUNNER_DESCRIPTION=shared-runners-manager-1.example.com
-++ export 'CI_RUNNER_TAGS=shared, docker, linux, ruby, mysql, postgres, mongo, git-annex'
-++ CI_RUNNER_TAGS='shared, docker, linux, ruby, mysql, postgres, mongo, git-annex'
+++ export 'CI_RUNNER_TAGS=shared, docker, linux, ruby, mysql, postgres, mongo'
+++ CI_RUNNER_TAGS='shared, docker, linux, ruby, mysql, postgres, mongo'
++ export CI_REGISTRY=registry.example.com
++ CI_REGISTRY=registry.example.com
++ export CI_DEBUG_TRACE=true
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index b8669964c84..a14c0752366 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -35,7 +35,7 @@ Using this method is in general preferred over directly calling the various
instrumentation methods.
Method instrumentation should be added in the initializer
-`config/initializers/metrics.rb`.
+`config/initializers/8_metrics.rb`.
### Examples
diff --git a/doc/downgrade_ee_to_ce/README.md b/doc/downgrade_ee_to_ce/README.md
index a6d22e5a04a..fe4b6d73771 100644
--- a/doc/downgrade_ee_to_ce/README.md
+++ b/doc/downgrade_ee_to_ce/README.md
@@ -15,13 +15,6 @@ Kerberos and Atlassian Crowd are only available on the Enterprise Edition, so
you should disable these mechanisms before downgrading and you should provide
alternative authentication methods to your users.
-### Git Annex
-
-Git Annex is also only available on the Enterprise Edition. This means that if
-you have repositories that use Git Annex to store large files, these files will
-no longer be easily available via Git. You should consider migrating these
-repositories to use Git LFS before downgrading to the Community Edition.
-
### Remove Jenkins CI Service entries from the database
The `JenkinsService` class is only available on the Enterprise Edition codebase,
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 96ec1b178b6..65fcfc77ab1 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -159,6 +159,8 @@ For installations from source:
remote_directory: 'my.s3.bucket'
# Turns on AWS Server-Side Encryption with Amazon S3-Managed Keys for backups, this is optional
# encryption: 'AES256'
+ # Specifies Amazon S3 storage class to use for backups, this is optional
+ # storage_class: 'STANDARD'
```
If you are uploading your backups to S3 you will probably want to create a new
diff --git a/doc/university/README.md b/doc/university/README.md
index 8d4e7eff115..c1661f0b52b 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -165,7 +165,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project
#### 3.4. Large Files
-1. [Big files in Git (Git LFS, Annex) - Video](https://www.youtube.com/watch?v=DawznUxYDe4)
+1. [Big files in Git (Git LFS) - Video](https://www.youtube.com/watch?v=DawznUxYDe4)
#### 3.5. LDAP and Active Directory
diff --git a/doc/university/support/README.md b/doc/university/support/README.md
index ca538ef6dc3..567dadb3b47 100644
--- a/doc/university/support/README.md
+++ b/doc/university/support/README.md
@@ -167,7 +167,6 @@ Some tickets need specific knowledge or a deep understanding of a particular com
Move on to understanding some of GitLab's more advanced features. You can make use of GitLab.com to understand the features from an end-user perspective and then use your own instance to understand setup and configuration of the feature from an Administrative perspective
-- Set up and try [Git Annex](https://docs.gitlab.com/ee/workflow/git_annex.html)
- Set up and try [Git LFS](https://docs.gitlab.com/ee/workflow/lfs/manage_large_binaries_with_git_lfs.html)
- Get to know the [GitLab API](https://docs.gitlab.com/ee/api/README.html), its capabilities and shortcomings
- Learn how to [migrate from SVN to Git](https://docs.gitlab.com/ee/workflow/importing/migrating_from_svn.html)
diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md
index 6edf99239ea..35af48724f2 100644
--- a/doc/user/project/pages/getting_started_part_four.md
+++ b/doc/user/project/pages/getting_started_part_four.md
@@ -133,6 +133,9 @@ your Jekyll 3.4.0 site with GitLab Pages. This is the minimum
configuration for our example. On the steps below, we'll refine
the script by adding extra options to our GitLab CI.
+Artifacts will be automatically deleted once GitLab Pages got deployed.
+You can preserve artifacts for limited time by specifying the expiry time.
+
### Image
At this point, you probably ask yourself: "okay, but to install Jekyll
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 9cc45065eb2..6adde447975 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -4,13 +4,6 @@ Managing large files such as audio, video and graphics files has always been one
of the shortcomings of Git. The general recommendation is to not have Git repositories
larger than 1GB to preserve performance.
-GitLab already supports [managing large files with git annex](http://docs.gitlab.com/ee/workflow/git_annex.html)
-(EE only), however in certain environments it is not always convenient to use
-different commands to differentiate between the large files and regular ones.
-
-Git LFS makes this simpler for the end user by removing the requirement to
-learn new commands.
-
## How it works
Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 89449ce8813..8dbe8875fe8 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -9,6 +9,7 @@ module API
mount ::API::V3::Boards
mount ::API::V3::Branches
mount ::API::V3::BroadcastMessages
+ mount ::API::V3::Builds
mount ::API::V3::Commits
mount ::API::V3::DeployKeys
mount ::API::V3::Environments
@@ -60,6 +61,10 @@ module API
error! e.message, e.status, e.headers
end
+ rescue_from Gitlab::Auth::TooManyIps do |e|
+ rack_response({ 'message' => '403 Forbidden' }.to_json, 403)
+ end
+
rescue_from :all do |exception|
handle_api_exception(exception)
end
@@ -77,7 +82,6 @@ module API
mount ::API::Boards
mount ::API::Branches
mount ::API::BroadcastMessages
- mount ::API::Builds
mount ::API::Commits
mount ::API::CommitStatuses
mount ::API::DeployKeys
@@ -87,6 +91,7 @@ module API
mount ::API::Groups
mount ::API::Internal
mount ::API::Issues
+ mount ::API::Jobs
mount ::API::Keys
mount ::API::Labels
mount ::API::Lint
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index d2d21f5d03a..965f8fbab8f 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -49,7 +49,8 @@ module API
class ProjectHook < Hook
expose :project_id, :issues_events, :merge_requests_events
- expose :note_events, :build_events, :pipeline_events, :wiki_page_events
+ expose :note_events, :pipeline_events, :wiki_page_events
+ expose :build_events, as: :job_events
end
class BasicProjectDetails < Grape::Entity
@@ -80,7 +81,7 @@ module API
expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
- expose(:builds_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
+ expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
expose :created_at, :last_activity_at
@@ -93,7 +94,7 @@ module API
expose :star_count, :forks_count
expose :open_issues_count, if: lambda { |project, options| project.feature_available?(:issues, options[:current_user]) && project.default_issues_tracker? }
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
- expose :public_builds
+ expose :public_builds, as: :public_jobs
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links.all, options)
end
@@ -109,7 +110,7 @@ module API
expose :storage_size
expose :repository_size
expose :lfs_objects_size
- expose :build_artifacts_size
+ expose :build_artifacts_size, as: :job_artifacts_size
end
class Member < UserBasic
@@ -144,7 +145,7 @@ module API
expose :storage_size
expose :repository_size
expose :lfs_objects_size
- expose :build_artifacts_size
+ expose :build_artifacts_size, as: :job_artifacts_size
end
end
end
@@ -249,14 +250,11 @@ module API
expose :start_date
end
- class Issue < ProjectEntity
+ class IssueBasic < ProjectEntity
expose :label_names, as: :labels
expose :milestone, using: Entities::Milestone
expose :assignee, :author, using: Entities::UserBasic
- expose :subscribed do |issue, options|
- issue.subscribed?(options[:current_user], options[:project] || issue.project)
- end
expose :user_notes_count
expose :upvotes, :downvotes
expose :due_date
@@ -267,6 +265,12 @@ module API
end
end
+ class Issue < IssueBasic
+ expose :subscribed do |issue, options|
+ issue.subscribed?(options[:current_user], options[:project] || issue.project)
+ end
+ end
+
class IssuableTimeStats < Grape::Entity
expose :time_estimate
expose :total_time_spent
@@ -279,7 +283,7 @@ module API
expose :id
end
- class MergeRequest < ProjectEntity
+ class MergeRequestBasic < ProjectEntity
expose :target_branch, :source_branch
expose :upvotes, :downvotes
expose :author, :assignee, using: Entities::UserBasic
@@ -291,9 +295,6 @@ module API
expose :merge_status
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
- expose :subscribed do |merge_request, options|
- merge_request.subscribed?(options[:current_user], options[:project])
- end
expose :user_notes_count
expose :should_remove_source_branch?, as: :should_remove_source_branch
expose :force_remove_source_branch?, as: :force_remove_source_branch
@@ -303,6 +304,12 @@ module API
end
end
+ class MergeRequest < MergeRequestBasic
+ expose :subscribed do |merge_request, options|
+ merge_request.subscribed?(options[:current_user], options[:project])
+ end
+ end
+
class MergeRequestChanges < MergeRequest
expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _|
compare.raw_diffs(all_diffs: true).to_a
@@ -448,7 +455,8 @@ module API
class ProjectService < Grape::Entity
expose :id, :title, :created_at, :updated_at, :active
expose :push_events, :issues_events, :merge_requests_events
- expose :tag_push_events, :note_events, :build_events, :pipeline_events
+ expose :tag_push_events, :note_events, :pipeline_events
+ expose :build_events, as: :job_events
# Expose serialized properties
expose :properties do |service, options|
field_names = service.fields.
@@ -592,10 +600,6 @@ module API
end
end
- class TriggerRequest < Grape::Entity
- expose :id, :variables
- end
-
class Runner < Grape::Entity
expose :id
expose :description
@@ -624,7 +628,7 @@ module API
expose :id, :token
end
- class BuildArtifactFile < Grape::Entity
+ class JobArtifactFile < Grape::Entity
expose :filename, :size
end
@@ -632,18 +636,21 @@ module API
expose :id, :sha, :ref, :status
end
- class Build < Grape::Entity
+ class Job < Grape::Entity
expose :id, :status, :stage, :name, :ref, :tag, :coverage
expose :created_at, :started_at, :finished_at
expose :user, with: User
- expose :artifacts_file, using: BuildArtifactFile, if: -> (build, opts) { build.artifacts? }
+ expose :artifacts_file, using: JobArtifactFile, if: -> (job, opts) { job.artifacts? }
expose :commit, with: RepoCommit
expose :runner, with: Runner
expose :pipeline, with: PipelineBasic
end
class Trigger < Grape::Entity
- expose :token, :created_at, :updated_at, :deleted_at, :last_used
+ expose :id
+ expose :token, :description
+ expose :created_at, :updated_at, :deleted_at, :last_used
+ expose :owner, using: Entities::UserBasic
end
class Variable < Grape::Entity
@@ -671,7 +678,7 @@ module API
expose :id, :iid, :ref, :sha, :created_at
expose :user, using: Entities::UserBasic
expose :environment, using: Entities::EnvironmentBasic
- expose :deployable, using: Entities::Build
+ expose :deployable, using: Entities::Job
end
class RepoLicense < Grape::Entity
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 9c41146f1e3..f325f0a3050 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -336,16 +336,17 @@ module API
def initial_current_user
return @initial_current_user if defined?(@initial_current_user)
+ Gitlab::Auth::UniqueIpsLimiter.limit_user! do
+ @initial_current_user ||= find_user_by_private_token(scopes: @scopes)
+ @initial_current_user ||= doorkeeper_guard(scopes: @scopes)
+ @initial_current_user ||= find_user_from_warden
- @initial_current_user ||= find_user_by_private_token(scopes: @scopes)
- @initial_current_user ||= doorkeeper_guard(scopes: @scopes)
- @initial_current_user ||= find_user_from_warden
+ unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed?
+ @initial_current_user = nil
+ end
- unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed?
- @initial_current_user = nil
+ @initial_current_user
end
-
- @initial_current_user
end
def sudo!
@@ -388,14 +389,6 @@ module API
header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
end
- def issue_entity(project)
- if project.has_external_issue_tracker?
- Entities::ExternalIssue
- else
- Entities::Issue
- end
- end
-
# The Grape Error Middleware only has access to env but no params. We workaround this by
# defining a method that returns the right value.
def define_params_for_grape_middleware
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index 1d6d0b05750..bda74069ad5 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -41,7 +41,7 @@ module API
resource :issues do
desc "Get currently authenticated user's issues" do
- success Entities::Issue
+ success Entities::IssueBasic
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
@@ -51,7 +51,7 @@ module API
get do
issues = find_issues(scope: 'authored')
- present paginate(issues), with: Entities::Issue, current_user: current_user
+ present paginate(issues), with: Entities::IssueBasic, current_user: current_user
end
end
@@ -60,7 +60,7 @@ module API
end
resource :groups do
desc 'Get a list of group issues' do
- success Entities::Issue
+ success Entities::IssueBasic
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'opened',
@@ -72,7 +72,7 @@ module API
issues = find_issues(group_id: group.id, state: params[:state] || 'opened')
- present paginate(issues), with: Entities::Issue, current_user: current_user
+ present paginate(issues), with: Entities::IssueBasic, current_user: current_user
end
end
@@ -83,7 +83,7 @@ module API
include TimeTrackingEndpoints
desc 'Get a list of project issues' do
- success Entities::Issue
+ success Entities::IssueBasic
end
params do
optional :state, type: String, values: %w[opened closed all], default: 'all',
@@ -95,7 +95,7 @@ module API
issues = find_issues(project_id: project.id)
- present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
+ present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
end
desc 'Get a single project issue' do
diff --git a/lib/api/builds.rb b/lib/api/jobs.rb
index 5b76913fe45..33c05e8aa63 100644
--- a/lib/api/builds.rb
+++ b/lib/api/jobs.rb
@@ -1,5 +1,5 @@
module API
- class Builds < Grape::API
+ class Jobs < Grape::API
include PaginationParams
before { authenticate! }
@@ -13,9 +13,10 @@ module API
optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
values: ::CommitStatus::AVAILABLE_STATUSES,
coerce_with: ->(scope) {
- if scope.is_a?(String)
+ case scope
+ when String
[scope]
- elsif scope.is_a?(Hashie::Mash)
+ when Hashie::Mash
scope.values
else
['unknown']
@@ -24,79 +25,58 @@ module API
end
end
- desc 'Get a project builds' do
- success Entities::Build
+ desc 'Get a projects jobs' do
+ success Entities::Job
end
params do
use :optional_scope
use :pagination
end
- get ':id/builds' do
+ get ':id/jobs' do
builds = user_project.builds.order('id DESC')
builds = filter_builds(builds, params[:scope])
- present paginate(builds), with: Entities::Build,
+ present paginate(builds), with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
- desc 'Get builds for a specific commit of a project' do
- success Entities::Build
+ desc 'Get a specific job of a project' do
+ success Entities::Job
end
params do
- requires :sha, type: String, desc: 'The SHA id of a commit'
- use :optional_scope
- use :pagination
- end
- get ':id/repository/commits/:sha/builds' do
- authorize_read_builds!
-
- return not_found! unless user_project.commit(params[:sha])
-
- pipelines = user_project.pipelines.where(sha: params[:sha])
- builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
- builds = filter_builds(builds, params[:scope])
-
- present paginate(builds), with: Entities::Build,
- user_can_download_artifacts: can?(current_user, :read_build, user_project)
- end
-
- desc 'Get a specific build of a project' do
- success Entities::Build
- end
- params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- get ':id/builds/:build_id' do
+ get ':id/jobs/:job_id' do
authorize_read_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
- desc 'Download the artifacts file from build' do
+ desc 'Download the artifacts file from a job' do
detail 'This feature was introduced in GitLab 8.5'
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- get ':id/builds/:build_id/artifacts' do
+ get ':id/jobs/:job_id/artifacts' do
authorize_read_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
present_artifacts!(build.artifacts_file)
end
- desc 'Download the artifacts file from build' do
+ desc 'Download the artifacts file from a job' do
detail 'This feature was introduced in GitLab 8.10'
end
params do
requires :ref_name, type: String, desc: 'The ref from repository'
- requires :job, type: String, desc: 'The name for the build'
+ requires :job, type: String, desc: 'The name for the job'
end
- get ':id/builds/artifacts/:ref_name/download',
+ get ':id/jobs/artifacts/:ref_name/download',
requirements: { ref_name: /.+/ } do
authorize_read_builds!
@@ -109,14 +89,14 @@ module API
# TODO: We should use `present_file!` and leave this implementation for backward compatibility (when build trace
# is saved in the DB instead of file). But before that, we need to consider how to replace the value of
# `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
- desc 'Get a trace of a specific build of a project'
+ desc 'Get a trace of a specific job of a project'
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- get ':id/builds/:build_id/trace' do
+ get ':id/jobs/:job_id/trace' do
authorize_read_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
content_type 'text/plain'
@@ -126,95 +106,95 @@ module API
body trace
end
- desc 'Cancel a specific build of a project' do
- success Entities::Build
+ desc 'Cancel a specific job of a project' do
+ success Entities::Job
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- post ':id/builds/:build_id/cancel' do
+ post ':id/jobs/:job_id/cancel' do
authorize_update_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
build.cancel
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
desc 'Retry a specific build of a project' do
- success Entities::Build
+ success Entities::Job
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a build'
end
- post ':id/builds/:build_id/retry' do
+ post ':id/jobs/:job_id/retry' do
authorize_update_builds!
- build = get_build!(params[:build_id])
- return forbidden!('Build is not retryable') unless build.retryable?
+ build = get_build!(params[:job_id])
+ return forbidden!('Job is not retryable') unless build.retryable?
build = Ci::Build.retry(build, current_user)
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
- desc 'Erase build (remove artifacts and build trace)' do
- success Entities::Build
+ desc 'Erase job (remove artifacts and the trace)' do
+ success Entities::Job
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a build'
end
- post ':id/builds/:build_id/erase' do
+ post ':id/jobs/:job_id/erase' do
authorize_update_builds!
- build = get_build!(params[:build_id])
- return forbidden!('Build is not erasable!') unless build.erasable?
+ build = get_build!(params[:job_id])
+ return forbidden!('Job is not erasable!') unless build.erasable?
build.erase(erased_by: current_user)
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
end
desc 'Keep the artifacts to prevent them from being deleted' do
- success Entities::Build
+ success Entities::Job
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a build'
+ requires :job_id, type: Integer, desc: 'The ID of a job'
end
- post ':id/builds/:build_id/artifacts/keep' do
+ post ':id/jobs/:job_id/artifacts/keep' do
authorize_update_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
return not_found!(build) unless build.artifacts?
build.keep_artifacts!
status 200
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
- desc 'Trigger a manual build' do
- success Entities::Build
+ desc 'Trigger a manual job' do
+ success Entities::Job
detail 'This feature was added in GitLab 8.11'
end
params do
- requires :build_id, type: Integer, desc: 'The ID of a Build'
+ requires :job_id, type: Integer, desc: 'The ID of a Job'
end
- post ":id/builds/:build_id/play" do
+ post ":id/jobs/:job_id/play" do
authorize_read_builds!
- build = get_build!(params[:build_id])
+ build = get_build!(params[:job_id])
bad_request!("Unplayable Job") unless build.playable?
build.play(current_user)
status 200
- present build, with: Entities::Build,
+ present build, with: Entities::Job,
user_can_download_artifacts: can?(current_user, :read_build, user_project)
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 4638a66811d..6fc33a7a54a 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -25,6 +25,14 @@ module API
render_api_error!(errors, 400)
end
+ def issue_entity(project)
+ if project.has_external_issue_tracker?
+ Entities::ExternalIssue
+ else
+ Entities::IssueBasic
+ end
+ end
+
params :optional_params do
optional :description, type: String, desc: 'The description of the merge request'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
@@ -35,7 +43,7 @@ module API
end
desc 'List merge requests' do
- success Entities::MergeRequest
+ success Entities::MergeRequestBasic
end
params do
optional :state, type: String, values: %w[opened closed merged all], default: 'all',
@@ -62,7 +70,7 @@ module API
end
merge_requests = merge_requests.reorder(params[:order_by] => params[:sort])
- present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project
+ present paginate(merge_requests), with: Entities::MergeRequestBasic, current_user: current_user, project: user_project
end
desc 'Create a merge request' do
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index bd74174c655..e7f7edd95c7 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -103,7 +103,7 @@ module API
end
desc 'Get all issues for a single project milestone' do
- success Entities::Issue
+ success Entities::IssueBasic
end
params do
requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
@@ -120,12 +120,12 @@ module API
}
issues = IssuesFinder.new(current_user, finder_params).execute
- present paginate(issues), with: Entities::Issue, current_user: current_user, project: user_project
+ present paginate(issues), with: Entities::IssueBasic, current_user: current_user, project: user_project
end
desc 'Get all merge requests for a single project milestone' do
detail 'This feature was introduced in GitLab 9.'
- success Entities::MergeRequest
+ success Entities::MergeRequestBasic
end
params do
requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
@@ -142,7 +142,10 @@ module API
}
merge_requests = MergeRequestsFinder.new(current_user, finder_params).execute
- present paginate(merge_requests), with: Entities::MergeRequest, current_user: current_user, project: user_project
+ present paginate(merge_requests),
+ with: Entities::MergeRequestBasic,
+ current_user: current_user,
+ project: user_project
end
end
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index 79a5f27dc4d..1cf29d9a1a3 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -122,9 +122,9 @@ module API
},
{
required: false,
- name: :notify_only_broken_builds,
+ name: :notify_only_broken_jobs,
type: Boolean,
- desc: 'Notify only broken builds'
+ desc: 'Notify only broken jobs'
}
],
'campfire' => [
@@ -403,9 +403,9 @@ module API
},
{
required: false,
- name: :notify_only_broken_builds,
+ name: :notify_only_broken_jobs,
type: Boolean,
- desc: 'Notify only broken builds'
+ desc: 'Notify only broken jobs'
}
],
'pivotaltracker' => [
@@ -611,7 +611,7 @@ module API
desc "Set #{service_slug} service for project"
params do
service_classes.each do |service|
- event_names = service.try(:event_names) || []
+ event_names = service.try(:event_names) || next
event_names.each do |event_name|
services[service.to_param.tr("_", "-")] << {
required: false,
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index b7c9c5f2b7f..119e9024712 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -6,15 +6,15 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
- desc 'Trigger a GitLab project build' do
- success Entities::TriggerRequest
+ desc 'Trigger a GitLab project pipeline' do
+ success Entities::Pipeline
end
params do
requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
requires :token, type: String, desc: 'The unique token of trigger'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
- post ":id/(ref/:ref/)trigger/builds" do
+ post ":id/(ref/:ref/)trigger/pipeline" do
project = find_project(params[:id])
trigger = Ci::Trigger.find_by_token(params[:token].to_s)
not_found! unless project && trigger
@@ -29,9 +29,9 @@ module API
# create request and trigger builds
trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
if trigger_request
- present trigger_request, with: Entities::TriggerRequest
+ present trigger_request.pipeline, with: Entities::Pipeline
else
- errors = 'No builds created'
+ errors = 'No pipeline created'
render_api_error!(errors, 400)
end
end
@@ -55,13 +55,13 @@ module API
success Entities::Trigger
end
params do
- requires :token, type: String, desc: 'The unique token of trigger'
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
- get ':id/triggers/:token' do
+ get ':id/triggers/:trigger_id' do
authenticate!
authorize! :admin_build, user_project
- trigger = user_project.triggers.find_by(token: params[:token].to_s)
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
present trigger, with: Entities::Trigger
@@ -70,26 +70,76 @@ module API
desc 'Create a trigger' do
success Entities::Trigger
end
+ params do
+ requires :description, type: String, desc: 'The trigger description'
+ end
post ':id/triggers' do
authenticate!
authorize! :admin_build, user_project
- trigger = user_project.triggers.create
+ trigger = user_project.triggers.create(
+ declared_params(include_missing: false).merge(owner: current_user))
- present trigger, with: Entities::Trigger
+ if trigger.valid?
+ present trigger, with: Entities::Trigger
+ else
+ render_validation_error!(trigger)
+ end
+ end
+
+ desc 'Update a trigger' do
+ success Entities::Trigger
+ end
+ params do
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ optional :description, type: String, desc: 'The trigger description'
+ end
+ put ':id/triggers/:trigger_id' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
+ return not_found!('Trigger') unless trigger
+
+ if trigger.update(declared_params(include_missing: false))
+ present trigger, with: Entities::Trigger
+ else
+ render_validation_error!(trigger)
+ end
+ end
+
+ desc 'Take ownership of trigger' do
+ success Entities::Trigger
+ end
+ params do
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
+ end
+ post ':id/triggers/:trigger_id/take_ownership' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
+ return not_found!('Trigger') unless trigger
+
+ if trigger.update(owner: current_user)
+ status :ok
+ present trigger, with: Entities::Trigger
+ else
+ render_validation_error!(trigger)
+ end
end
desc 'Delete a trigger' do
success Entities::Trigger
end
params do
- requires :token, type: String, desc: 'The unique token of trigger'
+ requires :trigger_id, type: Integer, desc: 'The trigger ID'
end
- delete ':id/triggers/:token' do
+ delete ':id/triggers/:trigger_id' do
authenticate!
authorize! :admin_build, user_project
- trigger = user_project.triggers.find_by(token: params[:token].to_s)
+ trigger = user_project.triggers.find(params.delete(:trigger_id))
return not_found!('Trigger') unless trigger
trigger.destroy
diff --git a/lib/api/v3/builds.rb b/lib/api/v3/builds.rb
new file mode 100644
index 00000000000..c8feba13527
--- /dev/null
+++ b/lib/api/v3/builds.rb
@@ -0,0 +1,263 @@
+module API
+ module V3
+ class Builds < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ helpers do
+ params :optional_scope do
+ optional :scope, types: [String, Array[String]], desc: 'The scope of builds to show',
+ values: %w(pending running failed success canceled skipped),
+ coerce_with: ->(scope) {
+ if scope.is_a?(String)
+ [scope]
+ elsif scope.is_a?(Hashie::Mash)
+ scope.values
+ else
+ ['unknown']
+ end
+ }
+ end
+ end
+
+ desc 'Get a project builds' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ use :optional_scope
+ use :pagination
+ end
+ get ':id/builds' do
+ builds = user_project.builds.order('id DESC')
+ builds = filter_builds(builds, params[:scope])
+
+ present paginate(builds), with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Get builds for a specific commit of a project' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :sha, type: String, desc: 'The SHA id of a commit'
+ use :optional_scope
+ use :pagination
+ end
+ get ':id/repository/commits/:sha/builds' do
+ authorize_read_builds!
+
+ return not_found! unless user_project.commit(params[:sha])
+
+ pipelines = user_project.pipelines.where(sha: params[:sha])
+ builds = user_project.builds.where(pipeline: pipelines).order('id DESC')
+ builds = filter_builds(builds, params[:scope])
+
+ present paginate(builds), with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Get a specific build of a project' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ get ':id/builds/:build_id' do
+ authorize_read_builds!
+
+ build = get_build!(params[:build_id])
+
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Download the artifacts file from build' do
+ detail 'This feature was introduced in GitLab 8.5'
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ get ':id/builds/:build_id/artifacts' do
+ authorize_read_builds!
+
+ build = get_build!(params[:build_id])
+
+ present_artifacts!(build.artifacts_file)
+ end
+
+ desc 'Download the artifacts file from build' do
+ detail 'This feature was introduced in GitLab 8.10'
+ end
+ params do
+ requires :ref_name, type: String, desc: 'The ref from repository'
+ requires :job, type: String, desc: 'The name for the build'
+ end
+ get ':id/builds/artifacts/:ref_name/download',
+ requirements: { ref_name: /.+/ } do
+ authorize_read_builds!
+
+ builds = user_project.latest_successful_builds_for(params[:ref_name])
+ latest_build = builds.find_by!(name: params[:job])
+
+ present_artifacts!(latest_build.artifacts_file)
+ end
+
+ # TODO: We should use `present_file!` and leave this implementation for backward compatibility (when build trace
+ # is saved in the DB instead of file). But before that, we need to consider how to replace the value of
+ # `runners_token` with some mask (like `xxxxxx`) when sending trace file directly by workhorse.
+ desc 'Get a trace of a specific build of a project'
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ get ':id/builds/:build_id/trace' do
+ authorize_read_builds!
+
+ build = get_build!(params[:build_id])
+
+ header 'Content-Disposition', "infile; filename=\"#{build.id}.log\""
+ content_type 'text/plain'
+ env['api.format'] = :binary
+
+ trace = build.trace
+ body trace
+ end
+
+ desc 'Cancel a specific build of a project' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/builds/:build_id/cancel' do
+ authorize_update_builds!
+
+ build = get_build!(params[:build_id])
+
+ build.cancel
+
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Retry a specific build of a project' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/builds/:build_id/retry' do
+ authorize_update_builds!
+
+ build = get_build!(params[:build_id])
+ return forbidden!('Build is not retryable') unless build.retryable?
+
+ build = Ci::Build.retry(build, current_user)
+
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Erase build (remove artifacts and build trace)' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/builds/:build_id/erase' do
+ authorize_update_builds!
+
+ build = get_build!(params[:build_id])
+ return forbidden!('Build is not erasable!') unless build.erasable?
+
+ build.erase(erased_by: current_user)
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :download_build_artifacts, user_project)
+ end
+
+ desc 'Keep the artifacts to prevent them from being deleted' do
+ success ::API::V3::Entities::Build
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a build'
+ end
+ post ':id/builds/:build_id/artifacts/keep' do
+ authorize_update_builds!
+
+ build = get_build!(params[:build_id])
+ return not_found!(build) unless build.artifacts?
+
+ build.keep_artifacts!
+
+ status 200
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+
+ desc 'Trigger a manual build' do
+ success ::API::V3::Entities::Build
+ detail 'This feature was added in GitLab 8.11'
+ end
+ params do
+ requires :build_id, type: Integer, desc: 'The ID of a Build'
+ end
+ post ":id/builds/:build_id/play" do
+ authorize_read_builds!
+
+ build = get_build!(params[:build_id])
+
+ bad_request!("Unplayable Job") unless build.playable?
+
+ build.play(current_user)
+
+ status 200
+ present build, with: ::API::V3::Entities::Build,
+ user_can_download_artifacts: can?(current_user, :read_build, user_project)
+ end
+ end
+
+ helpers do
+ def get_build(id)
+ user_project.builds.find_by(id: id.to_i)
+ end
+
+ def get_build!(id)
+ get_build(id) || not_found!
+ end
+
+ def present_artifacts!(artifacts_file)
+ if !artifacts_file.file_storage?
+ redirect_to(build.artifacts_file.url)
+ elsif artifacts_file.exists?
+ present_file!(artifacts_file.path, artifacts_file.filename)
+ else
+ not_found!
+ end
+ end
+
+ def filter_builds(builds, scope)
+ return builds if scope.nil? || scope.empty?
+
+ available_statuses = ::CommitStatus::AVAILABLE_STATUSES
+
+ unknown = scope - available_statuses
+ render_api_error!('Scope contains invalid value(s)', 400) unless unknown.empty?
+
+ builds.where(status: available_statuses && scope)
+ end
+
+ def authorize_read_builds!
+ authorize! :read_build, user_project
+ end
+
+ def authorize_update_builds!
+ authorize! :update_build, user_project
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/deployments.rb b/lib/api/v3/deployments.rb
new file mode 100644
index 00000000000..545485fac0a
--- /dev/null
+++ b/lib/api/v3/deployments.rb
@@ -0,0 +1,41 @@
+module API
+ # Deployments RESTfull API endpoints
+ class Deployments < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The project ID'
+ end
+ resource :projects do
+ desc 'Get all deployments of the project' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success ::API::V3::Deployments
+ end
+ params do
+ use :pagination
+ end
+ get ':id/deployments' do
+ authorize! :read_deployment, user_project
+
+ present paginate(user_project.deployments), with: ::API::V3::Deployments
+ end
+
+ desc 'Gets a specific deployment' do
+ detail 'This feature was introduced in GitLab 8.11.'
+ success ::API::V3::Deployments
+ end
+ params do
+ requires :deployment_id, type: Integer, desc: 'The deployment ID'
+ end
+ get ':id/deployments/:deployment_id' do
+ authorize! :read_deployment, user_project
+
+ deployment = user_project.deployments.find(params[:deployment_id])
+
+ present deployment, with: ::API::V3::Deployments
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb
index 270d99a2348..832b4bdeb4f 100644
--- a/lib/api/v3/entities.rb
+++ b/lib/api/v3/entities.rb
@@ -81,7 +81,7 @@ module API
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
- expose :statistics, using: 'API::Entities::ProjectStatistics', if: :statistics
+ expose :statistics, using: '::API::V3::Entities::ProjectStatistics', if: :statistics
end
class ProjectWithAccess < Project
@@ -186,6 +186,68 @@ module API
class Environment < ::API::Entities::EnvironmentBasic
expose :project, using: Entities::Project
end
+
+ class Trigger < Grape::Entity
+ expose :token, :created_at, :updated_at, :deleted_at, :last_used
+ expose :owner, using: ::API::Entities::UserBasic
+ end
+
+ class TriggerRequest < Grape::Entity
+ expose :id, :variables
+ end
+
+ class Build < Grape::Entity
+ expose :id, :status, :stage, :name, :ref, :tag, :coverage
+ expose :created_at, :started_at, :finished_at
+ expose :user, with: ::API::Entities::User
+ expose :artifacts_file, using: ::API::Entities::JobArtifactFile, if: -> (build, opts) { build.artifacts? }
+ expose :commit, with: ::API::Entities::RepoCommit
+ expose :runner, with: ::API::Entities::Runner
+ expose :pipeline, with: ::API::Entities::PipelineBasic
+ end
+
+ class BuildArtifactFile < Grape::Entity
+ expose :filename, :size
+ end
+
+ class Deployment < Grape::Entity
+ expose :id, :iid, :ref, :sha, :created_at
+ expose :user, using: ::API::Entities::UserBasic
+ expose :environment, using: ::API::Entities::EnvironmentBasic
+ expose :deployable, using: Entities::Build
+ end
+
+ class MergeRequestChanges < MergeRequest
+ expose :diffs, as: :changes, using: ::API::Entities::RepoDiff do |compare, _|
+ compare.raw_diffs(all_diffs: true).to_a
+ end
+ end
+
+ class ProjectStatistics < Grape::Entity
+ expose :commit_count
+ expose :storage_size
+ expose :repository_size
+ expose :lfs_objects_size
+ expose :build_artifacts_size
+ end
+
+ class ProjectService < Grape::Entity
+ expose :id, :title, :created_at, :updated_at, :active
+ expose :push_events, :issues_events, :merge_requests_events
+ expose :tag_push_events, :note_events, :build_events, :pipeline_events
+ # Expose serialized properties
+ expose :properties do |service, options|
+ field_names = service.fields.
+ select { |field| options[:include_passwords] || field[:type] != 'password' }.
+ map { |field| field[:name] }
+ service.properties.slice(*field_names)
+ end
+ end
+
+ class ProjectHook < ::API::Entities::Hook
+ expose :project_id, :issues_events, :merge_requests_events
+ expose :note_events, :build_events, :pipeline_events, :wiki_page_events
+ end
end
end
end
diff --git a/lib/api/v3/merge_request_diffs.rb b/lib/api/v3/merge_request_diffs.rb
new file mode 100644
index 00000000000..a462803e26c
--- /dev/null
+++ b/lib/api/v3/merge_request_diffs.rb
@@ -0,0 +1,43 @@
+module API
+ module V3
+ # MergeRequestDiff API
+ class MergeRequestDiffs < Grape::API
+ before { authenticate! }
+
+ resource :projects do
+ desc 'Get a list of merge request diff versions' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success ::API::Entities::MergeRequestDiff
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ end
+
+ get ":id/merge_requests/:merge_request_id/versions" do
+ merge_request = find_merge_request_with_access(params[:merge_request_id])
+
+ present merge_request.merge_request_diffs, with: ::API::Entities::MergeRequestDiff
+ end
+
+ desc 'Get a single merge request diff version' do
+ detail 'This feature was introduced in GitLab 8.12.'
+ success ::API::Entities::MergeRequestDiffFull
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ requires :merge_request_id, type: Integer, desc: 'The ID of a merge request'
+ requires :version_id, type: Integer, desc: 'The ID of a merge request diff version'
+ end
+
+ get ":id/merge_requests/:merge_request_id/versions/:version_id" do
+ merge_request = find_merge_request_with_access(params[:merge_request_id])
+
+ present merge_request.merge_request_diffs.find(params[:version_id]), with: ::API::Entities::MergeRequestDiffFull
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb
index 654e818e1b5..7dbd4691a94 100644
--- a/lib/api/v3/merge_requests.rb
+++ b/lib/api/v3/merge_requests.rb
@@ -28,6 +28,14 @@ module API
render_api_error!(errors, 400)
end
+ def issue_entity(project)
+ if project.has_external_issue_tracker?
+ ::API::Entities::ExternalIssue
+ else
+ ::API::Entities::Issue
+ end
+ end
+
params :optional_params do
optional :description, type: String, desc: 'The description of the merge request'
optional :assignee_id, type: Integer, desc: 'The ID of a user to assign the merge request'
diff --git a/lib/api/v3/milestones.rb b/lib/api/v3/milestones.rb
index bbc29c40ee2..2a850a08a8a 100644
--- a/lib/api/v3/milestones.rb
+++ b/lib/api/v3/milestones.rb
@@ -37,6 +37,27 @@ module API
present paginate(milestones), with: ::API::Entities::Milestone
end
+
+ desc 'Get all issues for a single project milestone' do
+ success ::API::Entities::Issue
+ end
+ params do
+ requires :milestone_id, type: Integer, desc: 'The ID of a project milestone'
+ use :pagination
+ end
+ get ':id/milestones/:milestone_id/issues' do
+ authorize! :read_milestone, user_project
+
+ milestone = user_project.milestones.find(params[:milestone_id])
+
+ finder_params = {
+ project_id: user_project.id,
+ milestone_title: milestone.title
+ }
+
+ issues = IssuesFinder.new(current_user, finder_params).execute
+ present paginate(issues), with: ::API::Entities::Issue, current_user: current_user, project: user_project
+ end
end
end
end
diff --git a/lib/api/v3/project_hooks.rb b/lib/api/v3/project_hooks.rb
new file mode 100644
index 00000000000..861b991b8e1
--- /dev/null
+++ b/lib/api/v3/project_hooks.rb
@@ -0,0 +1,106 @@
+module API
+ module V3
+ class ProjectHooks < Grape::API
+ include PaginationParams
+
+ before { authenticate! }
+ before { authorize_admin_project }
+
+ helpers do
+ params :project_hook_properties do
+ requires :url, type: String, desc: "The URL to send the request to"
+ optional :push_events, type: Boolean, desc: "Trigger hook on push events"
+ optional :issues_events, type: Boolean, desc: "Trigger hook on issues events"
+ optional :merge_requests_events, type: Boolean, desc: "Trigger hook on merge request events"
+ optional :tag_push_events, type: Boolean, desc: "Trigger hook on tag push events"
+ optional :note_events, type: Boolean, desc: "Trigger hook on note(comment) events"
+ optional :build_events, type: Boolean, desc: "Trigger hook on build events"
+ optional :pipeline_events, type: Boolean, desc: "Trigger hook on pipeline events"
+ optional :wiki_page_events, type: Boolean, desc: "Trigger hook on wiki events"
+ optional :enable_ssl_verification, type: Boolean, desc: "Do SSL verification when triggering the hook"
+ optional :token, type: String, desc: "Secret token to validate received payloads; this will not be returned in the response"
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc 'Get project hooks' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ use :pagination
+ end
+ get ":id/hooks" do
+ hooks = paginate user_project.hooks
+
+ present hooks, with: ::API::V3::Entities::ProjectHook
+ end
+
+ desc 'Get a project hook' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ requires :hook_id, type: Integer, desc: 'The ID of a project hook'
+ end
+ get ":id/hooks/:hook_id" do
+ hook = user_project.hooks.find(params[:hook_id])
+ present hook, with: ::API::V3::Entities::ProjectHook
+ end
+
+ desc 'Add hook to project' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ use :project_hook_properties
+ end
+ post ":id/hooks" do
+ hook = user_project.hooks.new(declared_params(include_missing: false))
+
+ if hook.save
+ present hook, with: ::API::V3::Entities::ProjectHook
+ else
+ error!("Invalid url given", 422) if hook.errors[:url].present?
+
+ not_found!("Project hook #{hook.errors.messages}")
+ end
+ end
+
+ desc 'Update an existing project hook' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ requires :hook_id, type: Integer, desc: "The ID of the hook to update"
+ use :project_hook_properties
+ end
+ put ":id/hooks/:hook_id" do
+ hook = user_project.hooks.find(params.delete(:hook_id))
+
+ if hook.update_attributes(declared_params(include_missing: false))
+ present hook, with: ::API::V3::Entities::ProjectHook
+ else
+ error!("Invalid url given", 422) if hook.errors[:url].present?
+
+ not_found!("Project hook #{hook.errors.messages}")
+ end
+ end
+
+ desc 'Deletes project hook' do
+ success ::API::V3::Entities::ProjectHook
+ end
+ params do
+ requires :hook_id, type: Integer, desc: 'The ID of the hook to delete'
+ end
+ delete ":id/hooks/:hook_id" do
+ begin
+ present user_project.hooks.destroy(params[:hook_id]), with: ::API::V3::Entities::ProjectHook
+ rescue
+ # ProjectHook can raise Error if hook_id not found
+ not_found!("Error deleting hook #{params[:hook_id]}")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb
index af0a058f69b..d77185ffe5a 100644
--- a/lib/api/v3/services.rb
+++ b/lib/api/v3/services.rb
@@ -537,6 +537,23 @@ module API
]
}
+ trigger_services = {
+ 'mattermost-slash-commands' => [
+ {
+ name: :token,
+ type: String,
+ desc: 'The Mattermost token'
+ }
+ ],
+ 'slack-slash-commands' => [
+ {
+ name: :token,
+ type: String,
+ desc: 'The Slack token'
+ }
+ ]
+ }.freeze
+
resource :projects do
before { authenticate! }
before { authorize_admin_project }
@@ -567,6 +584,57 @@ module API
render_api_error!('400 Bad Request', 400)
end
end
+
+ desc 'Get the service settings for project' do
+ success Entities::ProjectService
+ end
+ params do
+ requires :service_slug, type: String, values: services.keys, desc: 'The name of the service'
+ end
+ get ":id/services/:service_slug" do
+ service = user_project.find_or_initialize_service(params[:service_slug].underscore)
+ present service, with: Entities::ProjectService, include_passwords: current_user.is_admin?
+ end
+ end
+
+ trigger_services.each do |service_slug, settings|
+ helpers do
+ def chat_command_service(project, service_slug, params)
+ project.services.active.where(template: false).find do |service|
+ service.try(:token) == params[:token] && service.to_param == service_slug.underscore
+ end
+ end
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects do
+ desc "Trigger a slash command for #{service_slug}" do
+ detail 'Added in GitLab 8.13'
+ end
+ params do
+ settings.each do |setting|
+ requires setting[:name], type: setting[:type], desc: setting[:desc]
+ end
+ end
+ post ":id/services/#{service_slug.underscore}/trigger" do
+ project = find_project(params[:id])
+
+ # This is not accurate, but done to prevent leakage of the project names
+ not_found!('Service') unless project
+
+ service = chat_command_service(project, service_slug, params)
+ result = service.try(:trigger, params)
+
+ if result
+ status result[:status] || 200
+ present result
+ else
+ not_found!('Service')
+ end
+ end
+ end
end
end
end
diff --git a/lib/api/v3/triggers.rb b/lib/api/v3/triggers.rb
index 4051d4bca8d..1dfdb6a5956 100644
--- a/lib/api/v3/triggers.rb
+++ b/lib/api/v3/triggers.rb
@@ -7,8 +7,81 @@ module API
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects do
+ desc 'Trigger a GitLab project build' do
+ success ::API::V3::Entities::TriggerRequest
+ end
+ params do
+ requires :ref, type: String, desc: 'The commit sha or name of a branch or tag'
+ requires :token, type: String, desc: 'The unique token of trigger'
+ optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
+ end
+ post ":id/(ref/:ref/)trigger/builds" do
+ project = find_project(params[:id])
+ trigger = Ci::Trigger.find_by_token(params[:token].to_s)
+ not_found! unless project && trigger
+ unauthorized! unless trigger.project == project
+
+ # validate variables
+ variables = params[:variables].to_h
+ unless variables.all? { |key, value| key.is_a?(String) && value.is_a?(String) }
+ render_api_error!('variables needs to be a map of key-valued strings', 400)
+ end
+
+ # create request and trigger builds
+ trigger_request = Ci::CreateTriggerRequestService.new.execute(project, trigger, params[:ref].to_s, variables)
+ if trigger_request
+ present trigger_request, with: ::API::V3::Entities::TriggerRequest
+ else
+ errors = 'No builds created'
+ render_api_error!(errors, 400)
+ end
+ end
+
+ desc 'Get triggers list' do
+ success ::API::V3::Entities::Trigger
+ end
+ params do
+ use :pagination
+ end
+ get ':id/triggers' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ triggers = user_project.triggers.includes(:trigger_requests)
+
+ present paginate(triggers), with: ::API::V3::Entities::Trigger
+ end
+
+ desc 'Get specific trigger of a project' do
+ success ::API::V3::Entities::Trigger
+ end
+ params do
+ requires :token, type: String, desc: 'The unique token of trigger'
+ end
+ get ':id/triggers/:token' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.find_by(token: params[:token].to_s)
+ return not_found!('Trigger') unless trigger
+
+ present trigger, with: ::API::V3::Entities::Trigger
+ end
+
+ desc 'Create a trigger' do
+ success ::API::V3::Entities::Trigger
+ end
+ post ':id/triggers' do
+ authenticate!
+ authorize! :admin_build, user_project
+
+ trigger = user_project.triggers.create
+
+ present trigger, with: ::API::V3::Entities::Trigger
+ end
+
desc 'Delete a trigger' do
- success ::API::Entities::Trigger
+ success ::API::V3::Entities::Trigger
end
params do
requires :token, type: String, desc: 'The unique token of trigger'
@@ -22,7 +95,7 @@ module API
trigger.destroy
- present trigger, with: ::API::Entities::Trigger
+ present trigger, with: ::API::V3::Entities::Trigger
end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 5cc164a6325..7b4476fa4db 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -51,7 +51,8 @@ module Backup
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size,
- encryption: Gitlab.config.backup.upload.encryption)
+ encryption: Gitlab.config.backup.upload.encryption,
+ storage_class: Gitlab.config.backup.upload.storage_class)
$progress.puts "done".color(:green)
else
puts "uploading backup to #{remote_directory} failed".color(:red)
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index d16d5ba4960..3c4ba5d50e6 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -180,9 +180,8 @@ module Backup
return unless Dir.exist?(path)
dir_entries = Dir.entries(path)
- %w[annex custom_hooks].each do |entry|
- yield(entry) if dir_entries.include?(entry)
- end
+
+ yield('custom_hooks') if dir_entries.include?('custom_hooks')
end
def prepare
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 0a5abc92190..0a0bd0e781c 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -23,22 +23,25 @@ module Gitlab
Gitlab::Auth::Result.new
rate_limit!(ip, success: result.success?, login: login)
+ Gitlab::Auth::UniqueIpsLimiter.limit_user!(result.actor)
result
end
def find_with_user_password(login, password)
- user = User.by_login(login)
+ Gitlab::Auth::UniqueIpsLimiter.limit_user! do
+ user = User.by_login(login)
- # If no user is found, or it's an LDAP server, try LDAP.
- # LDAP users are only authenticated via LDAP
- if user.nil? || user.ldap_user?
- # Second chance - try LDAP authentication
- return nil unless Gitlab::LDAP::Config.enabled?
+ # If no user is found, or it's an LDAP server, try LDAP.
+ # LDAP users are only authenticated via LDAP
+ if user.nil? || user.ldap_user?
+ # Second chance - try LDAP authentication
+ return nil unless Gitlab::LDAP::Config.enabled?
- Gitlab::LDAP::Authentication.login(login, password)
- else
- user if user.valid_password?(password)
+ Gitlab::LDAP::Authentication.login(login, password)
+ else
+ user if user.valid_password?(password)
+ end
end
end
diff --git a/lib/gitlab/auth/too_many_ips.rb b/lib/gitlab/auth/too_many_ips.rb
new file mode 100644
index 00000000000..ed862791551
--- /dev/null
+++ b/lib/gitlab/auth/too_many_ips.rb
@@ -0,0 +1,17 @@
+module Gitlab
+ module Auth
+ class TooManyIps < StandardError
+ attr_reader :user_id, :ip, :unique_ips_count
+
+ def initialize(user_id, ip, unique_ips_count)
+ @user_id = user_id
+ @ip = ip
+ @unique_ips_count = unique_ips_count
+ end
+
+ def message
+ "User #{user_id} from IP: #{ip} tried logging from too many ips: #{unique_ips_count}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/unique_ips_limiter.rb b/lib/gitlab/auth/unique_ips_limiter.rb
new file mode 100644
index 00000000000..bf2239ca150
--- /dev/null
+++ b/lib/gitlab/auth/unique_ips_limiter.rb
@@ -0,0 +1,43 @@
+module Gitlab
+ module Auth
+ class UniqueIpsLimiter
+ USER_UNIQUE_IPS_PREFIX = 'user_unique_ips'.freeze
+
+ class << self
+ def limit_user_id!(user_id)
+ if config.unique_ips_limit_enabled
+ ip = RequestContext.client_ip
+ unique_ips = update_and_return_ips_count(user_id, ip)
+
+ raise TooManyIps.new(user_id, ip, unique_ips) if unique_ips > config.unique_ips_limit_per_user
+ end
+ end
+
+ def limit_user!(user = nil)
+ user ||= yield if block_given?
+ limit_user_id!(user.id) unless user.nil?
+ user
+ end
+
+ def config
+ Gitlab::CurrentSettings.current_application_settings
+ end
+
+ def update_and_return_ips_count(user_id, ip)
+ time = Time.now.utc.to_i
+ key = "#{USER_UNIQUE_IPS_PREFIX}:#{user_id}"
+
+ Gitlab::Redis.with do |redis|
+ unique_ips_count = nil
+ redis.multi do |r|
+ r.zadd(key, time, ip)
+ r.zremrangebyscore(key, 0, time - config.unique_ips_limit_time_window)
+ unique_ips_count = r.zcard(key)
+ end
+ unique_ips_count.value
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index d160cadc2d0..f3f417c1a63 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -24,7 +24,7 @@ module Gitlab
def self.nulls_last_order(field, direction = 'ASC')
order = "#{field} #{direction}"
- if Gitlab::Database.postgresql?
+ if postgresql?
order << ' NULLS LAST'
else
# `field IS NULL` will be `0` for non-NULL columns and `1` for NULL
@@ -38,7 +38,7 @@ module Gitlab
def self.nulls_first_order(field, direction = 'ASC')
order = "#{field} #{direction}"
- if Gitlab::Database.postgresql?
+ if postgresql?
order << ' NULLS FIRST'
else
# `field IS NULL` will be `0` for non-NULL columns and `1` for NULL
@@ -50,7 +50,7 @@ module Gitlab
end
def self.random
- Gitlab::Database.postgresql? ? "RANDOM()" : "RAND()"
+ postgresql? ? "RANDOM()" : "RAND()"
end
def true_value
diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb
new file mode 100644
index 00000000000..0f24f9bbfde
--- /dev/null
+++ b/lib/gitlab/etag_caching/middleware.rb
@@ -0,0 +1,66 @@
+module Gitlab
+ module EtagCaching
+ class Middleware
+ RESERVED_WORDS = ProjectPathValidator::RESERVED.map { |word| "/#{word}/" }.join('|')
+ ROUTE_REGEXP = Regexp.union(
+ %r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z)
+ )
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ return @app.call(env) unless enabled_for_current_route?(env)
+ Gitlab::Metrics.add_event(:etag_caching_middleware_used)
+
+ etag, cached_value_present = get_etag(env)
+ if_none_match = env['HTTP_IF_NONE_MATCH']
+
+ if if_none_match == etag
+ Gitlab::Metrics.add_event(:etag_caching_cache_hit)
+ [304, { 'ETag' => etag }, ['']]
+ else
+ track_cache_miss(if_none_match, cached_value_present)
+
+ status, headers, body = @app.call(env)
+ headers['ETag'] = etag
+ [status, headers, body]
+ end
+ end
+
+ private
+
+ def enabled_for_current_route?(env)
+ ROUTE_REGEXP.match(env['PATH_INFO'])
+ end
+
+ def get_etag(env)
+ cache_key = env['PATH_INFO']
+ store = Store.new
+ current_value = store.get(cache_key)
+ cached_value_present = current_value.present?
+
+ unless cached_value_present
+ current_value = store.touch(cache_key, only_if_missing: true)
+ end
+
+ [weak_etag_format(current_value), cached_value_present]
+ end
+
+ def weak_etag_format(value)
+ %Q{W/"#{value}"}
+ end
+
+ def track_cache_miss(if_none_match, cached_value_present)
+ if if_none_match.blank?
+ Gitlab::Metrics.add_event(:etag_caching_header_missing)
+ elsif !cached_value_present
+ Gitlab::Metrics.add_event(:etag_caching_key_not_found)
+ else
+ Gitlab::Metrics.add_event(:etag_caching_resource_changed)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb
new file mode 100644
index 00000000000..9532e432f78
--- /dev/null
+++ b/lib/gitlab/etag_caching/store.rb
@@ -0,0 +1,32 @@
+module Gitlab
+ module EtagCaching
+ class Store
+ EXPIRY_TIME = 10.minutes
+ REDIS_NAMESPACE = 'etag:'.freeze
+
+ def get(key)
+ Gitlab::Redis.with { |redis| redis.get(redis_key(key)) }
+ end
+
+ def touch(key, only_if_missing: false)
+ etag = generate_etag
+
+ Gitlab::Redis.with do |redis|
+ redis.set(redis_key(key), etag, ex: EXPIRY_TIME, nx: only_if_missing)
+ end
+
+ etag
+ end
+
+ private
+
+ def generate_etag
+ SecureRandom.hex
+ end
+
+ def redis_key(key)
+ "#{REDIS_NAMESPACE}#{key}"
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/request_context.rb b/lib/gitlab/request_context.rb
new file mode 100644
index 00000000000..fef536ecb0b
--- /dev/null
+++ b/lib/gitlab/request_context.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ class RequestContext
+ class << self
+ def client_ip
+ RequestStore[:client_ip]
+ end
+ end
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ req = Rack::Request.new(env)
+
+ RequestStore[:client_ip] = req.ip
+
+ @app.call(env)
+ end
+ end
+end
diff --git a/lib/mattermost/team.rb b/lib/mattermost/team.rb
index 09dfd082b3a..afc152aa02e 100644
--- a/lib/mattermost/team.rb
+++ b/lib/mattermost/team.rb
@@ -1,7 +1,7 @@
module Mattermost
class Team < Client
def all
- session_get('/api/v3/teams/all')
+ session_get('/api/v3/teams/all').values
end
end
end
diff --git a/package.json b/package.json
index 6b2991f9608..6c6a490b0f9 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"js-cookie": "^2.1.3",
"mousetrap": "^1.4.6",
"pikaday": "^1.5.1",
+ "raphael": "^2.2.7",
"raw-loader": "^0.5.1",
"select2": "3.5.2-browserify",
"stats-webpack-plugin": "^0.4.3",
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index f7219690722..61e4fae46fb 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -3,16 +3,6 @@ require 'spec_helper'
describe Profiles::KeysController do
let(:user) { create(:user) }
- describe '#new' do
- before { sign_in(user) }
-
- it 'redirects to #index' do
- get :new
-
- expect(response).to redirect_to(profile_keys_path)
- end
- end
-
describe "#get_keys" do
describe "non existant user" do
it "does not generally work" do
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index e70737376af..298a7ff179c 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -244,4 +244,27 @@ describe Projects::BranchesController do
end
end
end
+
+ describe "GET index" do
+ render_views
+
+ before do
+ sign_in(user)
+ end
+
+ context 'when rendering a JSON format' do
+ it 'filters branches by name' do
+ get :index,
+ namespace_id: project.namespace,
+ project_id: project,
+ format: :json,
+ search: 'master'
+
+ parsed_response = JSON.parse(response.body)
+
+ expect(parsed_response.length).to eq 1
+ expect(parsed_response.first).to eq 'master'
+ end
+ end
+ end
end
diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb
index 049bae1899d..e0de62e4454 100644
--- a/spec/controllers/projects/graphs_controller_spec.rb
+++ b/spec/controllers/projects/graphs_controller_spec.rb
@@ -30,18 +30,18 @@ describe Projects::GraphsController do
double(languages: {
'Ruby' => 1000,
'CoffeeScript' => 350,
- 'PowerShell' => 15
+ 'NSIS' => 15
})
end
let(:expected_values) do
- ps_color = "##{Digest::SHA256.hexdigest('PowerShell')[0...6]}"
+ nsis_color = "##{Digest::SHA256.hexdigest('NSIS')[0...6]}"
[
# colors from Linguist:
- { label: "Ruby", color: "#701516", highlight: "#701516" },
- { label: "CoffeeScript", color: "#244776", highlight: "#244776" },
+ { label: "Ruby", color: "#701516", highlight: "#701516" },
+ { label: "CoffeeScript", color: "#244776", highlight: "#244776" },
# colors from SHA256 fallback:
- { label: "PowerShell", color: ps_color, highlight: ps_color }
+ { label: "NSIS", color: nsis_color, highlight: nsis_color }
]
end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index dc597202050..d80780b1d90 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -200,4 +200,31 @@ describe Projects::NotesController do
end
end
end
+
+ describe 'GET index' do
+ let(:last_fetched_at) { '1487756246' }
+ let(:request_params) do
+ {
+ namespace_id: project.namespace,
+ project_id: project,
+ target_type: 'issue',
+ target_id: issue.id
+ }
+ end
+
+ before do
+ sign_in(user)
+ project.team << [user, :developer]
+ end
+
+ it 'passes last_fetched_at from headers to NotesFinder' do
+ request.headers['X-Last-Fetched-At'] = last_fetched_at
+
+ expect(NotesFinder).to receive(:new)
+ .with(anything, anything, hash_including(last_fetched_at: last_fetched_at))
+ .and_call_original
+
+ get :index, request_params
+ end
+ end
end
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
index b56c7880b64..a06c29dd91a 100644
--- a/spec/controllers/sessions_controller_spec.rb
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -25,9 +25,17 @@ describe SessionsController do
expect(subject.current_user). to eq user
end
- it "creates an audit log record" do
+ it 'creates an audit log record' do
expect { post(:create, user: { login: user.username, password: user.password }) }.to change { SecurityEvent.count }.by(1)
- expect(SecurityEvent.last.details[:with]).to eq("standard")
+ expect(SecurityEvent.last.details[:with]).to eq('standard')
+ end
+
+ include_examples 'user login request with unique ip limit', 302 do
+ def request
+ post(:create, user: { login: user.username, password: user.password })
+ expect(subject.current_user).to eq user
+ subject.sign_out user
+ end
end
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 04de3512125..70c65bc693a 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -189,29 +189,10 @@ FactoryGirl.define do
factory :jira_project, parent: :project do
has_external_issue_tracker true
-
- after :create do |project|
- project.create_jira_service(
- active: true,
- properties: {
- title: 'JIRA tracker',
- url: 'http://jira.example.net',
- project_key: 'JIRA'
- }
- )
- end
+ jira_service
end
factory :kubernetes_project, parent: :empty_project do
- after :create do |project|
- project.create_kubernetes_service(
- active: true,
- properties: {
- namespace: project.path,
- api_url: 'https://kubernetes.example.com',
- token: 'a' * 40,
- }
- )
- end
+ kubernetes_service
end
end
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index 51335bdcf1d..88f6c265505 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -12,4 +12,13 @@ FactoryGirl.define do
token: 'a' * 40,
})
end
+
+ factory :jira_service do
+ project factory: :empty_project
+ active true
+ properties(
+ url: 'https://jira.example.com',
+ project_key: 'jira-key'
+ )
+ end
end
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index 324ede798fe..0e305c52358 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -192,7 +192,7 @@ describe 'Commits' do
commits = project.repository.commits(branch_name)
commits.each do |commit|
- expect(page).to have_content("committed #{commit.committed_date}")
+ expect(page).to have_content("committed #{commit.committed_date.strftime("%b %d, %Y")}")
end
end
end
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 406d7cf791c..e63feb14b7e 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -61,4 +61,18 @@ describe 'Profile account page', feature: true do
expect(find('#incoming-email-token').value).not_to eq(previous_token)
end
end
+
+ describe 'when I change my username' do
+ before do
+ visit profile_account_path
+ end
+
+ it 'changes my username' do
+ fill_in 'user_username', with: 'new-username'
+
+ click_button('Update username')
+
+ expect(page).to have_content('new-username')
+ end
+ end
end
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index f5adb53a2dc..24d22a092d4 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Setup Mattermost slash commands', feature: true do
+feature 'Setup Mattermost slash commands', :feature, :js do
let(:user) { create(:user) }
let(:project) { create(:empty_project) }
let(:service) { project.create_mattermost_slash_commands_service }
@@ -62,11 +62,11 @@ feature 'Setup Mattermost slash commands', feature: true do
click_link 'Add to Mattermost'
- team_name = teams.first[1]['display_name']
- select_element = find('select#mattermost_team_id')
+ team_name = teams.first['display_name']
+ select_element = find('#mattermost_team_id')
selected_option = select_element.find('option[selected]')
- expect(select_element['disabled']).to eq('disabled')
+ expect(select_element['disabled']).to be(true)
expect(selected_option).to have_content(team_name.to_s)
end
@@ -75,7 +75,7 @@ feature 'Setup Mattermost slash commands', feature: true do
click_link 'Add to Mattermost'
- expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first[0].to_s)
+ expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first['id'])
end
it 'shows an explanation user is a member of multiple teams' do
@@ -92,12 +92,9 @@ feature 'Setup Mattermost slash commands', feature: true do
click_link 'Add to Mattermost'
- select_element = find('select#mattermost_team_id')
- selected_option = select_element.find('option[selected]')
+ select_element = find('#mattermost_team_id')
- expect(select_element['disabled']).to be(nil)
- expect(selected_option).to have_content('Select team...')
- # The 'Select team...' placeholder is item `0`.
+ expect(select_element['disabled']).to be(false)
expect(select_element.all('option').count).to eq(3)
end
@@ -110,20 +107,37 @@ feature 'Setup Mattermost slash commands', feature: true do
expect(page).to have_content('test mattermost error message')
end
+ it 'enables the submit button if the required fields are provided', :js do
+ stub_teams(count: 1)
+
+ click_link 'Add to Mattermost'
+
+ expect(find('input[type="submit"]')['disabled']).not_to be(true)
+ end
+
+ it 'disables the submit button if the required fields are not provided', :js do
+ stub_teams(count: 1)
+
+ click_link 'Add to Mattermost'
+
+ fill_in('mattermost_trigger', with: '')
+
+ expect(find('input[type="submit"]')['disabled']).to be(true)
+ end
+
def stub_teams(count: 0)
teams = create_teams(count)
- allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { teams }
+ allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { [teams, nil] }
teams
end
def create_teams(count = 0)
- teams = {}
+ teams = []
count.times do |i|
- i += 1
- teams[i] = { id: i, display_name: i }
+ teams.push({ "id" => "x#{i}", "display_name" => "x#{i}-name" })
end
teams
diff --git a/spec/fixtures/api/schemas/public_api/v3/issues.json b/spec/fixtures/api/schemas/public_api/v3/issues.json
new file mode 100644
index 00000000000..f2ee9c925ae
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v3/issues.json
@@ -0,0 +1,77 @@
+{
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "labels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "milestone": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "due_date": { "type": "date" },
+ "start_date": { "type": "date" }
+ },
+ "additionalProperties": false
+ },
+ "assignee": {
+ "type": ["object", "null"],
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "author": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "user_notes_count": { "type": "integer" },
+ "upvotes": { "type": "integer" },
+ "downvotes": { "type": "integer" },
+ "due_date": { "type": ["date", "null"] },
+ "confidential": { "type": "boolean" },
+ "web_url": { "type": "uri" },
+ "subscribed": { "type": ["boolean"] }
+ },
+ "required": [
+ "id", "iid", "project_id", "title", "description",
+ "state", "created_at", "updated_at", "labels",
+ "milestone", "assignee", "author", "user_notes_count",
+ "upvotes", "downvotes", "due_date", "confidential",
+ "web_url", "subscribed"
+ ],
+ "additionalProperties": false
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v3/merge_requests.json b/spec/fixtures/api/schemas/public_api/v3/merge_requests.json
new file mode 100644
index 00000000000..01f9fbb2c89
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v3/merge_requests.json
@@ -0,0 +1,89 @@
+{
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "target_branch": { "type": "string" },
+ "source_branch": { "type": "string" },
+ "upvotes": { "type": "integer" },
+ "downvotes": { "type": "integer" },
+ "author": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "assignee": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "source_project_id": { "type": "integer" },
+ "target_project_id": { "type": "integer" },
+ "labels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "work_in_progress": { "type": "boolean" },
+ "milestone": {
+ "type": ["object", "null"],
+ "properties": {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "due_date": { "type": "date" },
+ "start_date": { "type": "date" }
+ },
+ "additionalProperties": false
+ },
+ "merge_when_build_succeeds": { "type": "boolean" },
+ "merge_status": { "type": "string" },
+ "sha": { "type": "string" },
+ "merge_commit_sha": { "type": ["string", "null"] },
+ "user_notes_count": { "type": "integer" },
+ "should_remove_source_branch": { "type": ["boolean", "null"] },
+ "force_remove_source_branch": { "type": ["boolean", "null"] },
+ "web_url": { "type": "uri" },
+ "subscribed": { "type": ["boolean"] }
+ },
+ "required": [
+ "id", "iid", "project_id", "title", "description",
+ "state", "created_at", "updated_at", "target_branch",
+ "source_branch", "upvotes", "downvotes", "author",
+ "assignee", "source_project_id", "target_project_id",
+ "labels", "work_in_progress", "milestone", "merge_when_build_succeeds",
+ "merge_status", "sha", "merge_commit_sha", "user_notes_count",
+ "should_remove_source_branch", "force_remove_source_branch",
+ "web_url", "subscribed"
+ ],
+ "additionalProperties": false
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/issues.json b/spec/fixtures/api/schemas/public_api/v4/issues.json
new file mode 100644
index 00000000000..52199e75734
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/issues.json
@@ -0,0 +1,76 @@
+{
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "labels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "milestone": {
+ "type": "object",
+ "properties": {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "due_date": { "type": "date" },
+ "start_date": { "type": "date" }
+ },
+ "additionalProperties": false
+ },
+ "assignee": {
+ "type": ["object", "null"],
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "author": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "user_notes_count": { "type": "integer" },
+ "upvotes": { "type": "integer" },
+ "downvotes": { "type": "integer" },
+ "due_date": { "type": ["date", "null"] },
+ "confidential": { "type": "boolean" },
+ "web_url": { "type": "uri" }
+ },
+ "required": [
+ "id", "iid", "project_id", "title", "description",
+ "state", "created_at", "updated_at", "labels",
+ "milestone", "assignee", "author", "user_notes_count",
+ "upvotes", "downvotes", "due_date", "confidential",
+ "web_url"
+ ],
+ "additionalProperties": false
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
new file mode 100644
index 00000000000..51642e8cbb8
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
@@ -0,0 +1,88 @@
+{
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "target_branch": { "type": "string" },
+ "source_branch": { "type": "string" },
+ "upvotes": { "type": "integer" },
+ "downvotes": { "type": "integer" },
+ "author": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "assignee": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "source_project_id": { "type": "integer" },
+ "target_project_id": { "type": "integer" },
+ "labels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "work_in_progress": { "type": "boolean" },
+ "milestone": {
+ "type": ["object", "null"],
+ "properties": {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "due_date": { "type": "date" },
+ "start_date": { "type": "date" }
+ },
+ "additionalProperties": false
+ },
+ "merge_when_pipeline_succeeds": { "type": "boolean" },
+ "merge_status": { "type": "string" },
+ "sha": { "type": "string" },
+ "merge_commit_sha": { "type": ["string", "null"] },
+ "user_notes_count": { "type": "integer" },
+ "should_remove_source_branch": { "type": ["boolean", "null"] },
+ "force_remove_source_branch": { "type": ["boolean", "null"] },
+ "web_url": { "type": "uri" }
+ },
+ "required": [
+ "id", "iid", "project_id", "title", "description",
+ "state", "created_at", "updated_at", "target_branch",
+ "source_branch", "upvotes", "downvotes", "author",
+ "assignee", "source_project_id", "target_project_id",
+ "labels", "work_in_progress", "milestone", "merge_when_pipeline_succeeds",
+ "merge_status", "sha", "merge_commit_sha", "user_notes_count",
+ "should_remove_source_branch", "force_remove_source_branch",
+ "web_url"
+ ],
+ "additionalProperties": false
+ }
+}
diff --git a/spec/fixtures/api/schemas/user/login.json b/spec/fixtures/api/schemas/public_api/v4/user/login.json
index 6181b3ccc86..6181b3ccc86 100644
--- a/spec/fixtures/api/schemas/user/login.json
+++ b/spec/fixtures/api/schemas/public_api/v4/user/login.json
diff --git a/spec/fixtures/api/schemas/user/public.json b/spec/fixtures/api/schemas/public_api/v4/user/public.json
index 5587cfec61a..5587cfec61a 100644
--- a/spec/fixtures/api/schemas/user/public.json
+++ b/spec/fixtures/api/schemas/public_api/v4/user/public.json
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 4ffdd530171..5c07ea8a872 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -193,8 +193,8 @@ describe ApplicationHelper do
describe 'time_ago_with_tooltip' do
def element(*arguments)
Time.zone = 'UTC'
- time = Time.zone.parse('2015-07-02 08:23')
- element = helper.time_ago_with_tooltip(time, *arguments)
+ @time = Time.zone.parse('2015-07-02 08:23')
+ element = helper.time_ago_with_tooltip(@time, *arguments)
Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
end
@@ -204,7 +204,7 @@ describe ApplicationHelper do
end
it 'includes the date string' do
- expect(element.text).to eq '2015-07-02 08:23:00 UTC'
+ expect(element.text).to eq @time.strftime("%b %d, %Y")
end
it 'has a datetime attribute' do
diff --git a/spec/initializers/metrics_spec.rb b/spec/initializers/8_metrics_spec.rb
index bb595162370..570754621f3 100644
--- a/spec/initializers/metrics_spec.rb
+++ b/spec/initializers/8_metrics_spec.rb
@@ -1,5 +1,5 @@
require 'spec_helper'
-require_relative '../../config/initializers/metrics'
+require_relative '../../config/initializers/8_metrics'
describe 'instrument_classes', lib: true do
let(:config) { double(:config) }
diff --git a/spec/javascripts/abuse_reports_spec.js.es6 b/spec/javascripts/abuse_reports_spec.js
index 76b370b345b..76b370b345b 100644
--- a/spec/javascripts/abuse_reports_spec.js.es6
+++ b/spec/javascripts/abuse_reports_spec.js
diff --git a/spec/javascripts/activities_spec.js.es6 b/spec/javascripts/activities_spec.js
index e6a6fc36ca1..e6a6fc36ca1 100644
--- a/spec/javascripts/activities_spec.js.es6
+++ b/spec/javascripts/activities_spec.js
diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js
index 9dd741a680b..9dd741a680b 100644
--- a/spec/javascripts/boards/boards_store_spec.js.es6
+++ b/spec/javascripts/boards/boards_store_spec.js
diff --git a/spec/javascripts/boards/issue_card_spec.js.es6 b/spec/javascripts/boards/issue_card_spec.js
index 4340a571017..4340a571017 100644
--- a/spec/javascripts/boards/issue_card_spec.js.es6
+++ b/spec/javascripts/boards/issue_card_spec.js
diff --git a/spec/javascripts/boards/issue_spec.js.es6 b/spec/javascripts/boards/issue_spec.js
index aab4d9c501e..aab4d9c501e 100644
--- a/spec/javascripts/boards/issue_spec.js.es6
+++ b/spec/javascripts/boards/issue_spec.js
diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js
index c8a18af7198..c8a18af7198 100644
--- a/spec/javascripts/boards/list_spec.js.es6
+++ b/spec/javascripts/boards/list_spec.js
diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js
index 7a399b307ad..7a399b307ad 100644
--- a/spec/javascripts/boards/mock_data.js.es6
+++ b/spec/javascripts/boards/mock_data.js
diff --git a/spec/javascripts/boards/modal_store_spec.js.es6 b/spec/javascripts/boards/modal_store_spec.js
index 1815847f3fa..1815847f3fa 100644
--- a/spec/javascripts/boards/modal_store_spec.js.es6
+++ b/spec/javascripts/boards/modal_store_spec.js
diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 b/spec/javascripts/bootstrap_linked_tabs_spec.js
index fa9f95e16cd..fa9f95e16cd 100644
--- a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6
+++ b/spec/javascripts/bootstrap_linked_tabs_spec.js
diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js
index 0bd50588f5a..0bd50588f5a 100644
--- a/spec/javascripts/build_spec.js.es6
+++ b/spec/javascripts/build_spec.js
diff --git a/spec/javascripts/commit/pipelines/mock_data.js.es6 b/spec/javascripts/commit/pipelines/mock_data.js
index 188908d66bd..188908d66bd 100644
--- a/spec/javascripts/commit/pipelines/mock_data.js.es6
+++ b/spec/javascripts/commit/pipelines/mock_data.js
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_spec.js
index f09c57978a1..f09c57978a1 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js.es6
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
diff --git a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6 b/spec/javascripts/commit/pipelines/pipelines_store_spec.js
index 94973419979..94973419979 100644
--- a/spec/javascripts/commit/pipelines/pipelines_store_spec.js.es6
+++ b/spec/javascripts/commit/pipelines/pipelines_store_spec.js
diff --git a/spec/javascripts/commits_spec.js.es6 b/spec/javascripts/commits_spec.js
index 05260760c43..05260760c43 100644
--- a/spec/javascripts/commits_spec.js.es6
+++ b/spec/javascripts/commits_spec.js
diff --git a/spec/javascripts/datetime_utility_spec.js.es6 b/spec/javascripts/datetime_utility_spec.js
index d5eec10be42..d5eec10be42 100644
--- a/spec/javascripts/datetime_utility_spec.js.es6
+++ b/spec/javascripts/datetime_utility_spec.js
diff --git a/spec/javascripts/diff_comments_store_spec.js.es6 b/spec/javascripts/diff_comments_store_spec.js
index f956394ef53..f956394ef53 100644
--- a/spec/javascripts/diff_comments_store_spec.js.es6
+++ b/spec/javascripts/diff_comments_store_spec.js
diff --git a/spec/javascripts/environments/environment_actions_spec.js.es6 b/spec/javascripts/environments/environment_actions_spec.js
index d50d45d295e..d50d45d295e 100644
--- a/spec/javascripts/environments/environment_actions_spec.js.es6
+++ b/spec/javascripts/environments/environment_actions_spec.js
diff --git a/spec/javascripts/environments/environment_external_url_spec.js.es6 b/spec/javascripts/environments/environment_external_url_spec.js
index 393dbb5aae0..393dbb5aae0 100644
--- a/spec/javascripts/environments/environment_external_url_spec.js.es6
+++ b/spec/javascripts/environments/environment_external_url_spec.js
diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js
index 7fea80ed799..7fea80ed799 100644
--- a/spec/javascripts/environments/environment_item_spec.js.es6
+++ b/spec/javascripts/environments/environment_item_spec.js
diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js
index 4a596baad09..4a596baad09 100644
--- a/spec/javascripts/environments/environment_rollback_spec.js.es6
+++ b/spec/javascripts/environments/environment_rollback_spec.js
diff --git a/spec/javascripts/environments/environment_spec.js.es6 b/spec/javascripts/environments/environment_spec.js
index edd0cad32d0..edd0cad32d0 100644
--- a/spec/javascripts/environments/environment_spec.js.es6
+++ b/spec/javascripts/environments/environment_spec.js
diff --git a/spec/javascripts/environments/environment_stop_spec.js.es6 b/spec/javascripts/environments/environment_stop_spec.js
index 5ca65b1debc..5ca65b1debc 100644
--- a/spec/javascripts/environments/environment_stop_spec.js.es6
+++ b/spec/javascripts/environments/environment_stop_spec.js
diff --git a/spec/javascripts/environments/environment_table_spec.js.es6 b/spec/javascripts/environments/environment_table_spec.js
index be4330b5012..be4330b5012 100644
--- a/spec/javascripts/environments/environment_table_spec.js.es6
+++ b/spec/javascripts/environments/environment_table_spec.js
diff --git a/spec/javascripts/environments/environments_store_spec.js.es6 b/spec/javascripts/environments/environments_store_spec.js
index 77e182b3830..77e182b3830 100644
--- a/spec/javascripts/environments/environments_store_spec.js.es6
+++ b/spec/javascripts/environments/environments_store_spec.js
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6 b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index d1335b5b304..d1335b5b304 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js.es6
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
diff --git a/spec/javascripts/environments/mock_data.js.es6 b/spec/javascripts/environments/mock_data.js
index 5c395c6b2d8..5c395c6b2d8 100644
--- a/spec/javascripts/environments/mock_data.js.es6
+++ b/spec/javascripts/environments/mock_data.js
diff --git a/spec/javascripts/extensions/array_spec.js.es6 b/spec/javascripts/extensions/array_spec.js
index 60f6b9b78e3..60f6b9b78e3 100644
--- a/spec/javascripts/extensions/array_spec.js.es6
+++ b/spec/javascripts/extensions/array_spec.js
diff --git a/spec/javascripts/extensions/element_spec.js.es6 b/spec/javascripts/extensions/element_spec.js
index 2d8a128ed33..2d8a128ed33 100644
--- a/spec/javascripts/extensions/element_spec.js.es6
+++ b/spec/javascripts/extensions/element_spec.js
diff --git a/spec/javascripts/extensions/object_spec.js.es6 b/spec/javascripts/extensions/object_spec.js
index 2467ed78459..2467ed78459 100644
--- a/spec/javascripts/extensions/object_spec.js.es6
+++ b/spec/javascripts/extensions/object_spec.js
diff --git a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_user_spec.js
index fa9d03c8a9a..fa9d03c8a9a 100644
--- a/spec/javascripts/filtered_search/dropdown_user_spec.js.es6
+++ b/spec/javascripts/filtered_search/dropdown_user_spec.js
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_utils_spec.js
index 1e2d7582d5b..1e2d7582d5b 100644
--- a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js
diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
index ed0b0196ec4..ed0b0196ec4 100644
--- a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js
diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
index 98959dda242..98959dda242 100644
--- a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js
diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
index cf409a7e509..cf409a7e509 100644
--- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
index a91801cfc89..a91801cfc89 100644
--- a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
+++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js
diff --git a/spec/javascripts/gfm_auto_complete_spec.js.es6 b/spec/javascripts/gfm_auto_complete_spec.js
index 5dfa4008fbd..5dfa4008fbd 100644
--- a/spec/javascripts/gfm_auto_complete_spec.js.es6
+++ b/spec/javascripts/gfm_auto_complete_spec.js
diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js
index c207fb00a47..c207fb00a47 100644
--- a/spec/javascripts/gl_dropdown_spec.js.es6
+++ b/spec/javascripts/gl_dropdown_spec.js
diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js
index 733023481f5..733023481f5 100644
--- a/spec/javascripts/gl_field_errors_spec.js.es6
+++ b/spec/javascripts/gl_field_errors_spec.js
diff --git a/spec/javascripts/gl_form_spec.js.es6 b/spec/javascripts/gl_form_spec.js
index 71d6e2a7e22..71d6e2a7e22 100644
--- a/spec/javascripts/gl_form_spec.js.es6
+++ b/spec/javascripts/gl_form_spec.js
diff --git a/spec/javascripts/helpers/class_spec_helper.js.es6 b/spec/javascripts/helpers/class_spec_helper.js
index 61db27a8fcc..61db27a8fcc 100644
--- a/spec/javascripts/helpers/class_spec_helper.js.es6
+++ b/spec/javascripts/helpers/class_spec_helper.js
diff --git a/spec/javascripts/helpers/class_spec_helper_spec.js.es6 b/spec/javascripts/helpers/class_spec_helper_spec.js
index 0a61e561640..0a61e561640 100644
--- a/spec/javascripts/helpers/class_spec_helper_spec.js.es6
+++ b/spec/javascripts/helpers/class_spec_helper_spec.js
diff --git a/spec/javascripts/issuable_spec.js.es6 b/spec/javascripts/issuable_spec.js
index 26d87cc5931..26d87cc5931 100644
--- a/spec/javascripts/issuable_spec.js.es6
+++ b/spec/javascripts/issuable_spec.js
diff --git a/spec/javascripts/issuable_time_tracker_spec.js.es6 b/spec/javascripts/issuable_time_tracker_spec.js
index cb068a4f879..cb068a4f879 100644
--- a/spec/javascripts/issuable_time_tracker_spec.js.es6
+++ b/spec/javascripts/issuable_time_tracker_spec.js
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js.es6 b/spec/javascripts/labels_issue_sidebar_spec.js
index 37e038c16da..37e038c16da 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js.es6
+++ b/spec/javascripts/labels_issue_sidebar_spec.js
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js
index f4d3e77e515..f4d3e77e515 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js.es6
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js.es6 b/spec/javascripts/lib/utils/text_utility_spec.js
index 4200e943121..4200e943121 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js.es6
+++ b/spec/javascripts/lib/utils/text_utility_spec.js
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6 b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
index 7cdade01e00..7cdade01e00 100644
--- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js
diff --git a/spec/javascripts/pipelines_spec.js.es6 b/spec/javascripts/pipelines_spec.js
index 72770a702d3..72770a702d3 100644
--- a/spec/javascripts/pipelines_spec.js.es6
+++ b/spec/javascripts/pipelines_spec.js
diff --git a/spec/javascripts/pretty_time_spec.js.es6 b/spec/javascripts/pretty_time_spec.js
index a4662cfb557..a4662cfb557 100644
--- a/spec/javascripts/pretty_time_spec.js.es6
+++ b/spec/javascripts/pretty_time_spec.js
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js.es6 b/spec/javascripts/signin_tabs_memoizer_spec.js
index d83d9a57b42..d83d9a57b42 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js.es6
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js
diff --git a/spec/javascripts/smart_interval_spec.js.es6 b/spec/javascripts/smart_interval_spec.js
index 4366ec2a5b8..4366ec2a5b8 100644
--- a/spec/javascripts/smart_interval_spec.js.es6
+++ b/spec/javascripts/smart_interval_spec.js
diff --git a/spec/javascripts/subbable_resource_spec.js.es6 b/spec/javascripts/subbable_resource_spec.js
index 454386697f5..454386697f5 100644
--- a/spec/javascripts/subbable_resource_spec.js.es6
+++ b/spec/javascripts/subbable_resource_spec.js
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index ca707d872a4..fae462561e9 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -5,23 +5,12 @@ jasmine.getFixtures().fixturesPath = 'base/spec/javascripts/fixtures';
jasmine.getJSONFixtures().fixturesPath = 'base/spec/javascripts/fixtures';
// include common libraries
+require('~/commons/index.js');
window.$ = window.jQuery = require('jquery');
window._ = require('underscore');
window.Cookies = require('js-cookie');
window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
-require('jquery-ujs');
-require('bootstrap/js/affix');
-require('bootstrap/js/alert');
-require('bootstrap/js/button');
-require('bootstrap/js/collapse');
-require('bootstrap/js/dropdown');
-require('bootstrap/js/modal');
-require('bootstrap/js/scrollspy');
-require('bootstrap/js/tab');
-require('bootstrap/js/transition');
-require('bootstrap/js/tooltip');
-require('bootstrap/js/popover');
// stub expected globals
window.gl = window.gl || {};
diff --git a/spec/javascripts/user_callout_spec.js.es6 b/spec/javascripts/user_callout_spec.js
index 205e72af600..205e72af600 100644
--- a/spec/javascripts/user_callout_spec.js.es6
+++ b/spec/javascripts/user_callout_spec.js
diff --git a/spec/javascripts/version_check_image_spec.js.es6 b/spec/javascripts/version_check_image_spec.js
index 464c1fce210..464c1fce210 100644
--- a/spec/javascripts/version_check_image_spec.js.es6
+++ b/spec/javascripts/version_check_image_spec.js
diff --git a/spec/javascripts/visibility_select_spec.js.es6 b/spec/javascripts/visibility_select_spec.js
index 9727c03c91e..9727c03c91e 100644
--- a/spec/javascripts/visibility_select_spec.js.es6
+++ b/spec/javascripts/visibility_select_spec.js
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js.es6 b/spec/javascripts/vue_shared/components/commit_spec.js
index 15ab10b9b69..15ab10b9b69 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js.es6
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6 b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
index 412abfd5e41..412abfd5e41 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js.es6
+++ b/spec/javascripts/vue_shared/components/pipelines_table_row_spec.js
diff --git a/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6 b/spec/javascripts/vue_shared/components/pipelines_table_spec.js
index 54d81e2ea7d..54d81e2ea7d 100644
--- a/spec/javascripts/vue_shared/components/pipelines_table_spec.js.es6
+++ b/spec/javascripts/vue_shared/components/pipelines_table_spec.js
diff --git a/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6 b/spec/javascripts/vue_shared/components/table_pagination_spec.js
index 9cb067921a7..9cb067921a7 100644
--- a/spec/javascripts/vue_shared/components/table_pagination_spec.js.es6
+++ b/spec/javascripts/vue_shared/components/table_pagination_spec.js
diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
new file mode 100644
index 00000000000..94dcddcc30c
--- /dev/null
+++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Gitlab::Auth::UniqueIpsLimiter, :redis, lib: true do
+ include_context 'unique ips sign in limit'
+ let(:user) { create(:user) }
+
+ describe '#count_unique_ips' do
+ context 'non unique IPs' do
+ it 'properly counts them' do
+ expect(described_class.update_and_return_ips_count(user.id, 'ip1')).to eq(1)
+ expect(described_class.update_and_return_ips_count(user.id, 'ip1')).to eq(1)
+ end
+ end
+
+ context 'unique IPs' do
+ it 'properly counts them' do
+ expect(described_class.update_and_return_ips_count(user.id, 'ip2')).to eq(1)
+ expect(described_class.update_and_return_ips_count(user.id, 'ip3')).to eq(2)
+ end
+ end
+
+ it 'resets count after specified time window' do
+ Timecop.freeze do
+ expect(described_class.update_and_return_ips_count(user.id, 'ip2')).to eq(1)
+ expect(described_class.update_and_return_ips_count(user.id, 'ip3')).to eq(2)
+
+ Timecop.travel(Time.now.utc + described_class.config.unique_ips_limit_time_window) do
+ expect(described_class.update_and_return_ips_count(user.id, 'ip4')).to eq(1)
+ expect(described_class.update_and_return_ips_count(user.id, 'ip5')).to eq(2)
+ end
+ end
+ end
+ end
+
+ describe '#limit_user!' do
+ include_examples 'user login operation with unique ip limit' do
+ def operation
+ described_class.limit_user! { user }
+ end
+ end
+
+ context 'allow 2 unique ips' do
+ before { current_application_settings.update!(unique_ips_limit_per_user: 2) }
+
+ it 'blocks user trying to login from third ip' do
+ change_ip('ip1')
+ expect(described_class.limit_user! { user }).to eq(user)
+
+ change_ip('ip2')
+ expect(described_class.limit_user! { user }).to eq(user)
+
+ change_ip('ip3')
+ expect { described_class.limit_user! { user } }.to raise_error(Gitlab::Auth::TooManyIps)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index b234de4c772..daf8f5c1d6c 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -58,6 +58,14 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
end
+ include_examples 'user login operation with unique ip limit' do
+ let(:user) { create(:user, password: 'password') }
+
+ def operation
+ expect(gl_auth.find_for_git_client(user.username, 'password', project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities))
+ end
+ end
+
context 'while using LFS authenticate' do
it 'recognizes user lfs tokens' do
user = create(:user)
@@ -196,6 +204,12 @@ describe Gitlab::Auth, lib: true do
expect( gl_auth.find_with_user_password(username, password) ).not_to eql user
end
+ include_examples 'user login operation with unique ip limit' do
+ def operation
+ expect(gl_auth.find_with_user_password(username, password)).to eq(user)
+ end
+ end
+
context "with ldap enabled" do
before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
new file mode 100644
index 00000000000..8b5bfc4dbb0
--- /dev/null
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -0,0 +1,163 @@
+require 'spec_helper'
+
+describe Gitlab::EtagCaching::Middleware do
+ let(:app) { double(:app) }
+ let(:middleware) { described_class.new(app) }
+ let(:app_status_code) { 200 }
+ let(:if_none_match) { nil }
+ let(:enabled_path) { '/gitlab-org/gitlab-ce/noteable/issue/1/notes' }
+
+ context 'when ETag caching is not enabled for current route' do
+ let(:path) { '/gitlab-org/gitlab-ce/tree/master/noteable/issue/1/notes' }
+
+ before do
+ mock_app_response
+ end
+
+ it 'does not add ETag header' do
+ _, headers, _ = middleware.call(build_env(path, if_none_match))
+
+ expect(headers['ETag']).to be_nil
+ end
+
+ it 'passes status code from app' do
+ status, _, _ = middleware.call(build_env(path, if_none_match))
+
+ expect(status).to eq app_status_code
+ end
+ end
+
+ context 'when there is no ETag in store for given resource' do
+ let(:path) { enabled_path }
+
+ before do
+ mock_app_response
+ mock_value_in_store(nil)
+ end
+
+ it 'generates ETag' do
+ expect_any_instance_of(Gitlab::EtagCaching::Store)
+ .to receive(:touch).and_return('123')
+
+ middleware.call(build_env(path, if_none_match))
+ end
+
+ context 'when If-None-Match header was specified' do
+ let(:if_none_match) { 'W/"abc"' }
+
+ it 'tracks "etag_caching_key_not_found" event' do
+ expect(Gitlab::Metrics).to receive(:add_event)
+ .with(:etag_caching_middleware_used)
+ expect(Gitlab::Metrics).to receive(:add_event)
+ .with(:etag_caching_key_not_found)
+
+ middleware.call(build_env(path, if_none_match))
+ end
+ end
+ end
+
+ context 'when there is ETag in store for given resource' do
+ let(:path) { enabled_path }
+
+ before do
+ mock_app_response
+ mock_value_in_store('123')
+ end
+
+ it 'returns this value as header' do
+ _, headers, _ = middleware.call(build_env(path, if_none_match))
+
+ expect(headers['ETag']).to eq 'W/"123"'
+ end
+ end
+
+ context 'when If-None-Match header matches ETag in store' do
+ let(:path) { enabled_path }
+ let(:if_none_match) { 'W/"123"' }
+
+ before do
+ mock_value_in_store('123')
+ end
+
+ it 'does not call app' do
+ expect(app).not_to receive(:call)
+
+ middleware.call(build_env(path, if_none_match))
+ end
+
+ it 'returns status code 304' do
+ status, _, _ = middleware.call(build_env(path, if_none_match))
+
+ expect(status).to eq 304
+ end
+
+ it 'tracks "etag_caching_cache_hit" event' do
+ expect(Gitlab::Metrics).to receive(:add_event)
+ .with(:etag_caching_middleware_used)
+ expect(Gitlab::Metrics).to receive(:add_event)
+ .with(:etag_caching_cache_hit)
+
+ middleware.call(build_env(path, if_none_match))
+ end
+ end
+
+ context 'when If-None-Match header does not match ETag in store' do
+ let(:path) { enabled_path }
+ let(:if_none_match) { 'W/"abc"' }
+
+ before do
+ mock_value_in_store('123')
+ end
+
+ it 'calls app' do
+ expect(app).to receive(:call).and_return([app_status_code, {}, ['body']])
+
+ middleware.call(build_env(path, if_none_match))
+ end
+
+ it 'tracks "etag_caching_resource_changed" event' do
+ mock_app_response
+
+ expect(Gitlab::Metrics).to receive(:add_event)
+ .with(:etag_caching_middleware_used)
+ expect(Gitlab::Metrics).to receive(:add_event)
+ .with(:etag_caching_resource_changed)
+
+ middleware.call(build_env(path, if_none_match))
+ end
+ end
+
+ context 'when If-None-Match header is not specified' do
+ let(:path) { enabled_path }
+
+ before do
+ mock_value_in_store('123')
+ mock_app_response
+ end
+
+ it 'tracks "etag_caching_header_missing" event' do
+ expect(Gitlab::Metrics).to receive(:add_event)
+ .with(:etag_caching_middleware_used)
+ expect(Gitlab::Metrics).to receive(:add_event)
+ .with(:etag_caching_header_missing)
+
+ middleware.call(build_env(path, if_none_match))
+ end
+ end
+
+ def mock_app_response
+ allow(app).to receive(:call).and_return([app_status_code, {}, ['body']])
+ end
+
+ def mock_value_in_store(value)
+ allow_any_instance_of(Gitlab::EtagCaching::Store)
+ .to receive(:get).and_return(value)
+ end
+
+ def build_env(path, if_none_match)
+ {
+ 'PATH_INFO' => path,
+ 'HTTP_IF_NONE_MATCH' => if_none_match
+ }
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index eef283c2460..f20b6be51e1 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -97,6 +97,7 @@ variables:
triggers:
- project
- trigger_requests
+- owner
deploy_keys:
- user
- deploy_keys_projects
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 6534902b52d..3bd1f335a89 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -240,6 +240,8 @@ Ci::Trigger:
- created_at
- updated_at
- gl_project_id
+- owner_id
+- description
DeployKey:
- id
- user_id
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
new file mode 100644
index 00000000000..a91c8655cdd
--- /dev/null
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -0,0 +1,30 @@
+require 'spec_helper'
+
+describe Gitlab::RequestContext, lib: true do
+ describe '#client_ip' do
+ subject { Gitlab::RequestContext.client_ip }
+ let(:app) { -> (env) {} }
+ let(:env) { Hash.new }
+
+ context 'when RequestStore::Middleware is used' do
+ around(:each) do |example|
+ RequestStore::Middleware.new(-> (env) { example.run }).call({})
+ end
+
+ context 'request' do
+ let(:ip) { '192.168.1.11' }
+
+ before do
+ allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
+ Gitlab::RequestContext.new(app).call(env)
+ end
+
+ it { is_expected.to eq(ip) }
+ end
+
+ context 'before RequestContext middleware run' do
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+end
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index 6a9c7cebfff..ac493fdb20f 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -13,7 +13,7 @@ describe Mattermost::Team do
context 'for valid request' do
let(:response) do
- [{
+ { "xiyro8huptfhdndadpz8r3wnbo" => {
"id" => "xiyro8huptfhdndadpz8r3wnbo",
"create_at" => 1482174222155,
"update_at" => 1482174222155,
@@ -26,7 +26,7 @@ describe Mattermost::Team do
"allowed_domains" => "",
"invite_id" => "o4utakb9jtb7imctdfzbf9r5ro",
"allow_open_invite" => false
- }]
+ } }
end
before do
@@ -39,7 +39,7 @@ describe Mattermost::Team do
end
it 'returns a token' do
- is_expected.to eq(response)
+ is_expected.to eq(response.values)
end
end
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index 3ca9231f58e..074cf1a0bd7 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -1,16 +1,24 @@
require 'spec_helper'
describe Ci::Trigger, models: true do
- let(:project) { FactoryGirl.create :empty_project }
+ let(:project) { create :empty_project }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to belong_to(:owner) }
+ it { is_expected.to have_many(:trigger_requests) }
+ end
describe 'before_validation' do
it 'sets an random token if none provided' do
- trigger = FactoryGirl.create :ci_trigger_without_token, project: project
+ trigger = create(:ci_trigger_without_token, project: project)
+
expect(trigger.token).not_to be_nil
end
it 'does not set an random token if one provided' do
- trigger = FactoryGirl.create :ci_trigger, project: project
+ trigger = create(:ci_trigger, project: project)
+
expect(trigger.token).to eq('token')
end
end
diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb
index 2debe1289a3..cd50bda8996 100644
--- a/spec/models/external_issue_spec.rb
+++ b/spec/models/external_issue_spec.rb
@@ -42,4 +42,12 @@ describe ExternalIssue, models: true do
expect(issue.project_id).to eq(project.id)
end
end
+
+ describe '#hash' do
+ it 'returns the hash of its [class, to_s] pair' do
+ issue_2 = described_class.new(issue.to_s, project)
+
+ expect(issue.hash).to eq(issue_2.hash)
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index e000d0d38b3..fcaf4c71182 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -346,6 +346,23 @@ describe MergeRequest, models: true do
expect(subject.issues_mentioned_but_not_closing(subject.author)).to match_array([mentioned_issue])
end
+
+ context 'when the project has an external issue tracker' do
+ before do
+ subject.project.team << [subject.author, :developer]
+ commit = double(:commit, safe_message: 'Fixes TEST-3')
+
+ create(:jira_service, project: subject.project)
+
+ allow(subject).to receive(:commits).and_return([commit])
+ allow(subject).to receive(:description).and_return('Is related to TEST-2 and TEST-3')
+ allow(subject.project).to receive(:default_branch).and_return(subject.target_branch)
+ end
+
+ it 'detects issues mentioned in description but not closed' do
+ expect(subject.issues_mentioned_but_not_closing(subject.author).map(&:to_s)).to match_array(['TEST-2'])
+ end
+ end
end
describe "#work_in_progress?" do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 1cde9e04951..33536487c41 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -387,4 +387,16 @@ describe Note, models: true do
end
end
end
+
+ describe 'expiring ETag cache' do
+ let(:note) { build(:note_on_issue) }
+
+ it "expires cache for note's issue when note is saved" do
+ expect_any_instance_of(Gitlab::EtagCaching::Store)
+ .to receive(:touch)
+ .with("/#{note.project.namespace.to_param}/#{note.project.to_param}/noteable/issue/#{note.noteable.id}/notes")
+
+ note.save!
+ end
+ end
end
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 598ff232c82..f9531be5d25 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -92,7 +92,7 @@ describe MattermostSlashCommandsService, :models do
to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
- body: ['list'].to_json
+ body: { 'list' => true }.to_json
)
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index e86b4a761d9..b99cde64675 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -32,6 +32,7 @@ describe User, models: true do
it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
it { is_expected.to have_many(:todos).dependent(:destroy) }
it { is_expected.to have_many(:award_emoji).dependent(:destroy) }
+ it { is_expected.to have_many(:triggers).dependent(:destroy) }
it { is_expected.to have_many(:builds).dependent(:nullify) }
it { is_expected.to have_many(:pipelines).dependent(:nullify) }
it { is_expected.to have_many(:chat_names).dependent(:destroy) }
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index bd9ecaf2685..2974875510a 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -1,17 +1,23 @@
require 'spec_helper'
-describe API::API, api: true do
+describe API::API, api: true do
include ApiHelpers
let!(:user) { create(:user) }
let!(:application) { Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user) }
let!(:token) { Doorkeeper::AccessToken.create! application_id: application.id, resource_owner_id: user.id, scopes: "api" }
- describe "when unauthenticated" do
+ describe "unauthenticated" do
it "returns authentication success" do
get api("/user"), access_token: token.token
expect(response).to have_http_status(200)
end
+
+ include_examples 'user login request with unique ip limit' do
+ def request
+ get api('/user'), access_token: token.token
+ end
+ end
end
describe "when token invalid" do
@@ -26,5 +32,11 @@ describe API::API, api: true do
get api("/user", user)
expect(response).to have_http_status(200)
end
+
+ include_examples 'user login request with unique ip limit' do
+ def request
+ get api('/user', user)
+ end
+ end
end
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 2b8fd7e31a1..2545da7b1db 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -76,6 +76,8 @@ describe API::Groups, api: true do
lfs_objects_size: 234,
build_artifacts_size: 345,
}.stringify_keys
+ exposed_attributes = attributes.dup
+ exposed_attributes['job_artifacts_size'] = exposed_attributes.delete('build_artifacts_size')
project1.statistics.update!(attributes)
@@ -85,7 +87,7 @@ describe API::Groups, api: true do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response)
- .to satisfy_one { |group| group['statistics'] == attributes }
+ .to satisfy_one { |group| group['statistics'] == exposed_attributes }
end
end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 710e4320fd1..aca7c6a0734 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -270,6 +270,13 @@ describe API::Issues, api: true do
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
+
+ it 'matches V4 response schema' do
+ get api('/issues', user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/issues')
+ end
end
end
diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb
new file mode 100644
index 00000000000..a4d27734cc2
--- /dev/null
+++ b/spec/requests/api/jobs_spec.rb
@@ -0,0 +1,408 @@
+require 'spec_helper'
+
+describe API::Jobs, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:api_user) { user }
+ let!(:project) { create(:project, :repository, creator: user, public_builds: false) }
+ let!(:developer) { create(:project_member, :developer, user: user, project: project) }
+ let(:reporter) { create(:project_member, :reporter, project: project) }
+ let(:guest) { create(:project_member, :guest, project: project) }
+ let!(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id, ref: project.default_branch) }
+ let!(:build) { create(:ci_build, pipeline: pipeline) }
+
+ describe 'GET /projects/:id/jobs' do
+ let(:query) { Hash.new }
+
+ before do
+ get api("/projects/#{project.id}/jobs", api_user), query
+ end
+
+ context 'authorized user' do
+ it 'returns project jobs' do
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ end
+
+ it 'returns correct values' do
+ expect(json_response).not_to be_empty
+ expect(json_response.first['commit']['id']).to eq project.commit.id
+ end
+
+ it 'returns pipeline data' do
+ json_build = json_response.first
+
+ expect(json_build['pipeline']).not_to be_empty
+ expect(json_build['pipeline']['id']).to eq build.pipeline.id
+ expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
+ expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
+ expect(json_build['pipeline']['status']).to eq build.pipeline.status
+ end
+
+ context 'filter project with one scope element' do
+ let(:query) { { 'scope' => 'pending' } }
+
+ it do
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'filter project with array of scope elements' do
+ let(:query) { { 'scope[0]' => 'pending', 'scope[1]' => 'running' } }
+
+ it do
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'respond 400 when scope contains invalid state' do
+ let(:query) { { 'scope[0]' => 'unknown', 'scope[1]' => 'running' } }
+
+ it { expect(response).to have_http_status(400) }
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not return project builds' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/jobs/:job_id' do
+ before do
+ get api("/projects/#{project.id}/jobs/#{build.id}", api_user)
+ end
+
+ context 'authorized user' do
+ it 'returns specific job data' do
+ expect(response).to have_http_status(200)
+ expect(json_response['name']).to eq('test')
+ end
+
+ it 'returns pipeline data' do
+ json_build = json_response
+ expect(json_build['pipeline']).not_to be_empty
+ expect(json_build['pipeline']['id']).to eq build.pipeline.id
+ expect(json_build['pipeline']['ref']).to eq build.pipeline.ref
+ expect(json_build['pipeline']['sha']).to eq build.pipeline.sha
+ expect(json_build['pipeline']['status']).to eq build.pipeline.status
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not return specific job data' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/jobs/:job_id/artifacts' do
+ before do
+ get api("/projects/#{project.id}/jobs/#{build.id}/artifacts", api_user)
+ end
+
+ context 'job with artifacts' do
+ let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ context 'authorized user' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
+ end
+
+ it 'returns specific job artifacts' do
+ expect(response).to have_http_status(200)
+ expect(response.headers).to include(download_headers)
+ expect(response.body).to match_file(build.artifacts_file.file.file)
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not return specific job artifacts' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ it 'does not return job artifacts if not uploaded' do
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe 'GET /projects/:id/artifacts/:ref_name/download?job=name' do
+ let(:api_user) { reporter.user }
+ let(:build) { create(:ci_build, :artifacts, pipeline: pipeline) }
+
+ before do
+ build.success
+ end
+
+ def get_for_ref(ref = pipeline.ref, job = build.name)
+ get api("/projects/#{project.id}/jobs/artifacts/#{ref}/download", api_user), job: job
+ end
+
+ context 'when not logged in' do
+ let(:api_user) { nil }
+
+ before do
+ get_for_ref
+ end
+
+ it 'gives 401' do
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'when logging as guest' do
+ let(:api_user) { guest.user }
+
+ before do
+ get_for_ref
+ end
+
+ it 'gives 403' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'non-existing job' do
+ shared_examples 'not found' do
+ it { expect(response).to have_http_status(:not_found) }
+ end
+
+ context 'has no such ref' do
+ before do
+ get_for_ref('TAIL')
+ end
+
+ it_behaves_like 'not found'
+ end
+
+ context 'has no such job' do
+ before do
+ get_for_ref(pipeline.ref, 'NOBUILD')
+ end
+
+ it_behaves_like 'not found'
+ end
+ end
+
+ context 'find proper job' do
+ shared_examples 'a valid file' do
+ let(:download_headers) do
+ { 'Content-Transfer-Encoding' => 'binary',
+ 'Content-Disposition' =>
+ "attachment; filename=#{build.artifacts_file.filename}" }
+ end
+
+ it { expect(response).to have_http_status(200) }
+ it { expect(response.headers).to include(download_headers) }
+ end
+
+ context 'with regular branch' do
+ before do
+ pipeline.reload
+ pipeline.update(ref: 'master',
+ sha: project.commit('master').sha)
+
+ get_for_ref('master')
+ end
+
+ it_behaves_like 'a valid file'
+ end
+
+ context 'with branch name containing slash' do
+ before do
+ pipeline.reload
+ pipeline.update(ref: 'improve/awesome',
+ sha: project.commit('improve/awesome').sha)
+ end
+
+ before do
+ get_for_ref('improve/awesome')
+ end
+
+ it_behaves_like 'a valid file'
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/jobs/:job_id/trace' do
+ let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
+
+ before do
+ get api("/projects/#{project.id}/jobs/#{build.id}/trace", api_user)
+ end
+
+ context 'authorized user' do
+ it 'returns specific job trace' do
+ expect(response).to have_http_status(200)
+ expect(response.body).to eq(build.trace)
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not return specific job trace' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/jobs/:job_id/cancel' do
+ before do
+ post api("/projects/#{project.id}/jobs/#{build.id}/cancel", api_user)
+ end
+
+ context 'authorized user' do
+ context 'user with :update_build persmission' do
+ it 'cancels running or pending job' do
+ expect(response).to have_http_status(201)
+ expect(project.builds.first.status).to eq('canceled')
+ end
+ end
+
+ context 'user without :update_build permission' do
+ let(:api_user) { reporter.user }
+
+ it 'does not cancel job' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not cancel job' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/jobs/:job_id/retry' do
+ let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
+
+ before do
+ post api("/projects/#{project.id}/jobs/#{build.id}/retry", api_user)
+ end
+
+ context 'authorized user' do
+ context 'user with :update_build permission' do
+ it 'retries non-running job' do
+ expect(response).to have_http_status(201)
+ expect(project.builds.first.status).to eq('canceled')
+ expect(json_response['status']).to eq('pending')
+ end
+ end
+
+ context 'user without :update_build permission' do
+ let(:api_user) { reporter.user }
+
+ it 'does not retry job' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ context 'unauthorized user' do
+ let(:api_user) { nil }
+
+ it 'does not retry job' do
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/jobs/:job_id/erase' do
+ before do
+ post api("/projects/#{project.id}/jobs/#{build.id}/erase", user)
+ end
+
+ context 'job is erasable' do
+ let(:build) { create(:ci_build, :trace, :artifacts, :success, project: project, pipeline: pipeline) }
+
+ it 'erases job content' do
+ expect(response).to have_http_status(201)
+ expect(build.trace).to be_empty
+ expect(build.artifacts_file.exists?).to be_falsy
+ expect(build.artifacts_metadata.exists?).to be_falsy
+ end
+
+ it 'updates job' do
+ build.reload
+ expect(build.erased_at).to be_truthy
+ expect(build.erased_by).to eq(user)
+ end
+ end
+
+ context 'job is not erasable' do
+ let(:build) { create(:ci_build, :trace, project: project, pipeline: pipeline) }
+
+ it 'responds with forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/jobs/:build_id/artifacts/keep' do
+ before do
+ post api("/projects/#{project.id}/jobs/#{build.id}/artifacts/keep", user)
+ end
+
+ context 'artifacts did not expire' do
+ let(:build) do
+ create(:ci_build, :trace, :artifacts, :success,
+ project: project, pipeline: pipeline, artifacts_expire_at: Time.now + 7.days)
+ end
+
+ it 'keeps artifacts' do
+ expect(response).to have_http_status(200)
+ expect(build.reload.artifacts_expire_at).to be_nil
+ end
+ end
+
+ context 'no artifacts' do
+ let(:build) { create(:ci_build, project: project, pipeline: pipeline) }
+
+ it 'responds with not found' do
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/jobs/:job_id/play' do
+ before do
+ post api("/projects/#{project.id}/jobs/#{build.id}/play", user)
+ end
+
+ context 'on an playable job' do
+ let(:build) { create(:ci_build, :manual, project: project, pipeline: pipeline) }
+
+ it 'plays the job' do
+ expect(response).to have_http_status(200)
+ expect(json_response['user']['id']).to eq(user.id)
+ expect(json_response['id']).to eq(build.id)
+ end
+ end
+
+ context 'on a non-playable job' do
+ it 'returns a status code 400, Bad Request' do
+ expect(response).to have_http_status 400
+ expect(response.body).to match("Unplayable Job")
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index b3f0876c822..1083abf2ad3 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -93,6 +93,13 @@ describe API::MergeRequests, api: true do
expect(json_response.first['id']).to eq merge_request_closed.id
end
+ it 'matches V4 response schema' do
+ get api("/projects/#{project.id}/merge_requests", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/merge_requests')
+ end
+
context "with ordering" do
before do
@mr_later = mr_with_later_created_and_updated_at_time
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 78c230117b8..3bb8b6fdbeb 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -227,6 +227,13 @@ describe API::Milestones, api: true do
expect(json_response.first['milestone']['title']).to eq(milestone.title)
end
+ it 'matches V4 response schema for a list of issues' do
+ get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/issues')
+ end
+
it 'returns a 401 error if user not authenticated' do
get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index f286568547d..b1f8c249092 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -33,7 +33,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
expect(json_response.first['merge_requests_events']).to eq(true)
expect(json_response.first['tag_push_events']).to eq(true)
expect(json_response.first['note_events']).to eq(true)
- expect(json_response.first['build_events']).to eq(true)
+ expect(json_response.first['job_events']).to eq(true)
expect(json_response.first['pipeline_events']).to eq(true)
expect(json_response.first['wiki_page_events']).to eq(true)
expect(json_response.first['enable_ssl_verification']).to eq(true)
@@ -59,7 +59,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events)
- expect(json_response['build_events']).to eq(hook.build_events)
+ expect(json_response['job_events']).to eq(hook.build_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
@@ -98,7 +98,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
expect(json_response['merge_requests_events']).to eq(false)
expect(json_response['tag_push_events']).to eq(false)
expect(json_response['note_events']).to eq(false)
- expect(json_response['build_events']).to eq(false)
+ expect(json_response['job_events']).to eq(false)
expect(json_response['pipeline_events']).to eq(false)
expect(json_response['wiki_page_events']).to eq(true)
expect(json_response['enable_ssl_verification']).to eq(true)
@@ -144,7 +144,7 @@ describe API::ProjectHooks, 'ProjectHooks', api: true do
expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
expect(json_response['note_events']).to eq(hook.note_events)
- expect(json_response['build_events']).to eq(hook.build_events)
+ expect(json_response['job_events']).to eq(hook.build_events)
expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 03cae074803..77f79cd5bc7 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -594,7 +594,7 @@ describe API::Projects, api: true do
expect(json_response['issues_enabled']).to be_present
expect(json_response['merge_requests_enabled']).to be_present
expect(json_response['wiki_enabled']).to be_present
- expect(json_response['builds_enabled']).to be_present
+ expect(json_response['jobs_enabled']).to be_present
expect(json_response['snippets_enabled']).to be_present
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['created_at']).to be_present
@@ -605,7 +605,7 @@ describe API::Projects, api: true do
expect(json_response['avatar_url']).to be_nil
expect(json_response['star_count']).to be_present
expect(json_response['forks_count']).to be_present
- expect(json_response['public_builds']).to be_present
+ expect(json_response['public_jobs']).to be_present
expect(json_response['shared_with_groups']).to be_an Array
expect(json_response['shared_with_groups'].length).to eq(1)
expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index 153e2791cbe..424c02932ab 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -14,7 +14,7 @@ describe API::Triggers do
let!(:trigger2) { create(:ci_trigger, project: project, token: trigger_token_2) }
let!(:trigger_request) { create(:ci_trigger_request, trigger: trigger, created_at: '2015-01-01 12:13:14') }
- describe 'POST /projects/:project_id/trigger' do
+ describe 'POST /projects/:project_id/trigger/pipeline' do
let!(:project2) { create(:project) }
let(:options) do
{
@@ -28,17 +28,20 @@ describe API::Triggers do
context 'Handles errors' do
it 'returns bad request if token is missing' do
- post api("/projects/#{project.id}/trigger/builds"), ref: 'master'
+ post api("/projects/#{project.id}/trigger/pipeline"), ref: 'master'
+
expect(response).to have_http_status(400)
end
it 'returns not found if project is not found' do
- post api('/projects/0/trigger/builds'), options.merge(ref: 'master')
+ post api('/projects/0/trigger/pipeline'), options.merge(ref: 'master')
+
expect(response).to have_http_status(404)
end
it 'returns unauthorized if token is for different project' do
- post api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
+ post api("/projects/#{project2.id}/trigger/pipeline"), options.merge(ref: 'master')
+
expect(response).to have_http_status(401)
end
end
@@ -46,9 +49,11 @@ describe API::Triggers do
context 'Have a commit' do
let(:pipeline) { project.pipelines.last }
- it 'creates builds' do
- post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master')
+ it 'creates pipeline' do
+ post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'master')
+
expect(response).to have_http_status(201)
+ expect(json_response).to include('id' => pipeline.id)
pipeline.builds.reload
expect(pipeline.builds.pending.size).to eq(2)
expect(pipeline.builds.size).to eq(5)
@@ -56,15 +61,17 @@ describe API::Triggers do
it 'creates builds on webhook from other gitlab repository and branch' do
expect do
- post api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
+ post api("/projects/#{project.id}/ref/master/trigger/pipeline?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
end.to change(project.builds, :count).by(5)
+
expect(response).to have_http_status(201)
end
- it 'returns bad request with no builds created if there\'s no commit for that ref' do
- post api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
+ it 'returns bad request with no pipeline created if there\'s no commit for that ref' do
+ post api("/projects/#{project.id}/trigger/pipeline"), options.merge(ref: 'other-branch')
+
expect(response).to have_http_status(400)
- expect(json_response['message']).to eq('No builds created')
+ expect(json_response['message']).to eq('No pipeline created')
end
context 'Validates variables' do
@@ -73,22 +80,24 @@ describe API::Triggers do
end
it 'validates variables to be a hash' do
- post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master')
+ post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: 'value', ref: 'master')
+
expect(response).to have_http_status(400)
expect(json_response['error']).to eq('variables is invalid')
end
it 'validates variables needs to be a map of key-valued strings' do
- post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
+ post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
+
expect(response).to have_http_status(400)
expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
end
it 'creates trigger request with variables' do
- post api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
+ post api("/projects/#{project.id}/trigger/pipeline"), options.merge(variables: variables, ref: 'master')
+
expect(response).to have_http_status(201)
- pipeline.builds.reload
- expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
+ expect(pipeline.builds.reload.first.trigger_request.variables).to eq(variables)
end
end
end
@@ -123,17 +132,17 @@ describe API::Triggers do
end
end
- describe 'GET /projects/:id/triggers/:token' do
+ describe 'GET /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do
it 'returns trigger details' do
- get api("/projects/#{project.id}/triggers/#{trigger.token}", user)
+ get api("/projects/#{project.id}/triggers/#{trigger.id}", user)
expect(response).to have_http_status(200)
expect(json_response).to be_a(Hash)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
- get api("/projects/#{project.id}/triggers/abcdef012345", user)
+ get api("/projects/#{project.id}/triggers/-5", user)
expect(response).to have_http_status(404)
end
@@ -141,7 +150,7 @@ describe API::Triggers do
context 'authenticated user with invalid permissions' do
it 'does not return triggers list' do
- get api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
+ get api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_http_status(403)
end
@@ -149,7 +158,7 @@ describe API::Triggers do
context 'unauthenticated user' do
it 'does not return triggers list' do
- get api("/projects/#{project.id}/triggers/#{trigger.token}")
+ get api("/projects/#{project.id}/triggers/#{trigger.id}")
expect(response).to have_http_status(401)
end
@@ -158,19 +167,31 @@ describe API::Triggers do
describe 'POST /projects/:id/triggers' do
context 'authenticated user with valid permissions' do
- it 'creates trigger' do
- expect do
+ context 'with required parameters' do
+ it 'creates trigger' do
+ expect do
+ post api("/projects/#{project.id}/triggers", user),
+ description: 'trigger'
+ end.to change{project.triggers.count}.by(1)
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to include('description' => 'trigger')
+ end
+ end
+
+ context 'without required parameters' do
+ it 'does not create trigger' do
post api("/projects/#{project.id}/triggers", user)
- end.to change{project.triggers.count}.by(1)
- expect(response).to have_http_status(201)
- expect(json_response).to be_a(Hash)
+ expect(response).to have_http_status(:bad_request)
+ end
end
end
context 'authenticated user with invalid permissions' do
it 'does not create trigger' do
- post api("/projects/#{project.id}/triggers", user2)
+ post api("/projects/#{project.id}/triggers", user2),
+ description: 'trigger'
expect(response).to have_http_status(403)
end
@@ -178,25 +199,87 @@ describe API::Triggers do
context 'unauthenticated user' do
it 'does not create trigger' do
- post api("/projects/#{project.id}/triggers")
+ post api("/projects/#{project.id}/triggers"),
+ description: 'trigger'
+
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'PUT /projects/:id/triggers/:trigger_id' do
+ context 'authenticated user with valid permissions' do
+ let(:new_description) { 'new description' }
+
+ it 'updates description' do
+ put api("/projects/#{project.id}/triggers/#{trigger.id}", user),
+ description: new_description
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to include('description' => new_description)
+ expect(trigger.reload.description).to eq(new_description)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update trigger' do
+ put api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update trigger' do
+ put api("/projects/#{project.id}/triggers/#{trigger.id}")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/triggers/:trigger_id/take_ownership' do
+ context 'authenticated user with valid permissions' do
+ it 'updates owner' do
+ expect(trigger.owner).to be_nil
+
+ post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to include('owner')
+ expect(trigger.reload.owner).to eq(user)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user2)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not update owner' do
+ post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership")
expect(response).to have_http_status(401)
end
end
end
- describe 'DELETE /projects/:id/triggers/:token' do
+ describe 'DELETE /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do
it 'deletes trigger' do
expect do
- delete api("/projects/#{project.id}/triggers/#{trigger.token}", user)
+ delete api("/projects/#{project.id}/triggers/#{trigger.id}", user)
expect(response).to have_http_status(204)
end.to change{project.triggers.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
- delete api("/projects/#{project.id}/triggers/abcdef012345", user)
+ delete api("/projects/#{project.id}/triggers/-5", user)
expect(response).to have_http_status(404)
end
@@ -204,7 +287,7 @@ describe API::Triggers do
context 'authenticated user with invalid permissions' do
it 'does not delete trigger' do
- delete api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
+ delete api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_http_status(403)
end
@@ -212,7 +295,7 @@ describe API::Triggers do
context 'unauthenticated user' do
it 'does not delete trigger' do
- delete api("/projects/#{project.id}/triggers/#{trigger.token}")
+ delete api("/projects/#{project.id}/triggers/#{trigger.id}")
expect(response).to have_http_status(401)
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index e5e4c84755f..881c48c75e0 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -728,7 +728,7 @@ describe API::Users, api: true do
get api("/user", user)
expect(response).to have_http_status(200)
- expect(response).to match_response_schema('user/public')
+ expect(response).to match_response_schema('public_api/v4/user/public')
expect(json_response['id']).to eq(user.id)
end
end
@@ -747,7 +747,7 @@ describe API::Users, api: true do
get api("/user?private_token=#{admin_personal_access_token}")
expect(response).to have_http_status(200)
- expect(response).to match_response_schema('user/public')
+ expect(response).to match_response_schema('public_api/v4/user/public')
expect(json_response['id']).to eq(admin.id)
end
end
@@ -757,7 +757,7 @@ describe API::Users, api: true do
get api("/user?private_token=#{admin.private_token}&sudo=#{user.id}")
expect(response).to have_http_status(200)
- expect(response).to match_response_schema('user/login')
+ expect(response).to match_response_schema('public_api/v4/user/login')
expect(json_response['id']).to eq(user.id)
end
@@ -765,7 +765,7 @@ describe API::Users, api: true do
get api("/user?private_token=#{admin.private_token}")
expect(response).to have_http_status(200)
- expect(response).to match_response_schema('user/public')
+ expect(response).to match_response_schema('public_api/v4/user/public')
expect(json_response['id']).to eq(admin.id)
end
end
diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/v3/builds_spec.rb
index 76a10a2374c..a50c22a6dd1 100644
--- a/spec/requests/api/builds_spec.rb
+++ b/spec/requests/api/v3/builds_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe API::Builds, api: true do
+describe API::V3::Builds, api: true do
include ApiHelpers
let(:user) { create(:user) }
@@ -18,7 +18,7 @@ describe API::Builds, api: true do
before do
create(:ci_build, :skipped, pipeline: pipeline)
- get api("/projects/#{project.id}/builds?#{query}", api_user)
+ get v3_api("/projects/#{project.id}/builds?#{query}", api_user)
end
context 'authorized user' do
@@ -91,7 +91,7 @@ describe API::Builds, api: true do
describe 'GET /projects/:id/repository/commits/:sha/builds' do
context 'when commit does not exist in repository' do
before do
- get api("/projects/#{project.id}/repository/commits/1a271fd1/builds", api_user)
+ get v3_api("/projects/#{project.id}/repository/commits/1a271fd1/builds", api_user)
end
it 'responds with 404' do
@@ -107,7 +107,7 @@ describe API::Builds, api: true do
create(:ci_build, pipeline: pipeline)
create(:ci_build)
- get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user)
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", api_user)
end
it 'returns project jobs for specific commit' do
@@ -130,7 +130,7 @@ describe API::Builds, api: true do
context 'when pipeline has no jobs' do
before do
branch_head = project.commit('feature').id
- get api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user)
+ get v3_api("/projects/#{project.id}/repository/commits/#{branch_head}/builds", api_user)
end
it 'returns an empty array' do
@@ -146,7 +146,7 @@ describe API::Builds, api: true do
create(:ci_pipeline, project: project, sha: project.commit.id)
create(:ci_build, pipeline: pipeline)
- get api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil)
+ get v3_api("/projects/#{project.id}/repository/commits/#{project.commit.id}/builds", nil)
end
it 'does not return project jobs' do
@@ -159,7 +159,7 @@ describe API::Builds, api: true do
describe 'GET /projects/:id/builds/:build_id' do
before do
- get api("/projects/#{project.id}/builds/#{build.id}", api_user)
+ get v3_api("/projects/#{project.id}/builds/#{build.id}", api_user)
end
context 'authorized user' do
@@ -189,7 +189,7 @@ describe API::Builds, api: true do
describe 'GET /projects/:id/builds/:build_id/artifacts' do
before do
- get api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
+ get v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts", api_user)
end
context 'job with artifacts' do
@@ -231,7 +231,7 @@ describe API::Builds, api: true do
end
def path_for_ref(ref = pipeline.ref, job = build.name)
- api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", api_user)
+ v3_api("/projects/#{project.id}/builds/artifacts/#{ref}/download?job=#{job}", api_user)
end
context 'when not logged in' do
@@ -324,7 +324,7 @@ describe API::Builds, api: true do
let(:build) { create(:ci_build, :trace, pipeline: pipeline) }
before do
- get api("/projects/#{project.id}/builds/#{build.id}/trace", api_user)
+ get v3_api("/projects/#{project.id}/builds/#{build.id}/trace", api_user)
end
context 'authorized user' do
@@ -345,7 +345,7 @@ describe API::Builds, api: true do
describe 'POST /projects/:id/builds/:build_id/cancel' do
before do
- post api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user)
+ post v3_api("/projects/#{project.id}/builds/#{build.id}/cancel", api_user)
end
context 'authorized user' do
@@ -378,7 +378,7 @@ describe API::Builds, api: true do
let(:build) { create(:ci_build, :canceled, pipeline: pipeline) }
before do
- post api("/projects/#{project.id}/builds/#{build.id}/retry", api_user)
+ post v3_api("/projects/#{project.id}/builds/#{build.id}/retry", api_user)
end
context 'authorized user' do
@@ -410,7 +410,7 @@ describe API::Builds, api: true do
describe 'POST /projects/:id/builds/:build_id/erase' do
before do
- post api("/projects/#{project.id}/builds/#{build.id}/erase", user)
+ post v3_api("/projects/#{project.id}/builds/#{build.id}/erase", user)
end
context 'job is erasable' do
@@ -440,7 +440,7 @@ describe API::Builds, api: true do
describe 'POST /projects/:id/builds/:build_id/artifacts/keep' do
before do
- post api("/projects/#{project.id}/builds/#{build.id}/artifacts/keep", user)
+ post v3_api("/projects/#{project.id}/builds/#{build.id}/artifacts/keep", user)
end
context 'artifacts did not expire' do
@@ -466,7 +466,7 @@ describe API::Builds, api: true do
describe 'POST /projects/:id/builds/:build_id/play' do
before do
- post api("/projects/#{project.id}/builds/#{build.id}/play", user)
+ post v3_api("/projects/#{project.id}/builds/#{build.id}/play", user)
end
context 'on an playable job' do
diff --git a/spec/requests/api/v3/deployments_spec.rb b/spec/requests/api/v3/deployments_spec.rb
new file mode 100644
index 00000000000..3c5ce407b32
--- /dev/null
+++ b/spec/requests/api/v3/deployments_spec.rb
@@ -0,0 +1,71 @@
+require 'spec_helper'
+
+describe API::Deployments, api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:project) { deployment.environment.project }
+ let!(:deployment) { create(:deployment) }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ shared_examples 'a paginated resources' do
+ before do
+ # Fires the request
+ request
+ end
+
+ it 'has pagination headers' do
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ describe 'GET /projects/:id/deployments' do
+ context 'as member of the project' do
+ it_behaves_like 'a paginated resources' do
+ let(:request) { get api("/projects/#{project.id}/deployments", user) }
+ end
+
+ it 'returns projects deployments' do
+ get api("/projects/#{project.id}/deployments", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['iid']).to eq(deployment.iid)
+ expect(json_response.first['sha']).to match /\A\h{40}\z/
+ end
+ end
+
+ context 'as non member' do
+ it 'returns a 404 status code' do
+ get api("/projects/#{project.id}/deployments", non_member)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/deployments/:deployment_id' do
+ context 'as a member of the project' do
+ it 'returns the projects deployment' do
+ get api("/projects/#{project.id}/deployments/#{deployment.id}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response['sha']).to match /\A\h{40}\z/
+ expect(json_response['id']).to eq(deployment.id)
+ end
+ end
+
+ context 'as non member' do
+ it 'returns a 404 status code' do
+ get api("/projects/#{project.id}/deployments/#{deployment.id}", non_member)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/v3/issues_spec.rb b/spec/requests/api/v3/issues_spec.rb
index 803acd55470..2a8105d5a2b 100644
--- a/spec/requests/api/v3/issues_spec.rb
+++ b/spec/requests/api/v3/issues_spec.rb
@@ -232,6 +232,13 @@ describe API::V3::Issues, api: true do
expect(json_response).to be_an Array
expect(response_dates).to eq(response_dates.sort)
end
+
+ it 'matches V3 response schema' do
+ get v3_api('/issues', user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('public_api/v3/issues')
+ end
end
end
diff --git a/spec/requests/api/v3/merge_request_diffs_spec.rb b/spec/requests/api/v3/merge_request_diffs_spec.rb
new file mode 100644
index 00000000000..e1887138aab
--- /dev/null
+++ b/spec/requests/api/v3/merge_request_diffs_spec.rb
@@ -0,0 +1,49 @@
+require "spec_helper"
+
+describe API::MergeRequestDiffs, 'MergeRequestDiffs', api: true do
+ include ApiHelpers
+
+ let!(:user) { create(:user) }
+ let!(:merge_request) { create(:merge_request, importing: true) }
+ let!(:project) { merge_request.target_project }
+
+ before do
+ merge_request.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9')
+ merge_request.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e')
+ project.team << [user, :master]
+ end
+
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/versions' do
+ it 'returns 200 for a valid merge request' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions", user)
+ merge_request_diff = merge_request.merge_request_diffs.first
+
+ expect(response.status).to eq 200
+ expect(json_response.size).to eq(merge_request.merge_request_diffs.size)
+ expect(json_response.first['id']).to eq(merge_request_diff.id)
+ expect(json_response.first['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
+ end
+
+ it 'returns a 404 when merge_request_id not found' do
+ get api("/projects/#{project.id}/merge_requests/999/versions", user)
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe 'GET /projects/:id/merge_requests/:merge_request_id/versions/:version_id' do
+ it 'returns a 200 for a valid merge request' do
+ merge_request_diff = merge_request.merge_request_diffs.first
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/#{merge_request_diff.id}", user)
+
+ expect(response.status).to eq 200
+ expect(json_response['id']).to eq(merge_request_diff.id)
+ expect(json_response['head_commit_sha']).to eq(merge_request_diff.head_commit_sha)
+ expect(json_response['diffs'].size).to eq(merge_request_diff.diffs.size)
+ end
+
+ it 'returns a 404 when merge_request_id not found' do
+ get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/versions/999", user)
+ expect(response).to have_http_status(404)
+ end
+ end
+end
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index 51764d1000e..b7ed643bc21 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -73,6 +73,13 @@ describe API::MergeRequests, api: true do
expect(json_response.first['title']).to eq(merge_request_merged.title)
end
+ it 'matches V3 response schema' do
+ get v3_api("/projects/#{project.id}/merge_requests", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('public_api/v3/merge_requests')
+ end
+
context "with ordering" do
before do
@mr_later = mr_with_later_created_and_updated_at_time
diff --git a/spec/requests/api/v3/milestones_spec.rb b/spec/requests/api/v3/milestones_spec.rb
index 77705d8c839..127c0eec881 100644
--- a/spec/requests/api/v3/milestones_spec.rb
+++ b/spec/requests/api/v3/milestones_spec.rb
@@ -181,6 +181,13 @@ describe API::V3::Milestones, api: true do
expect(json_response.first['milestone']['title']).to eq(milestone.title)
end
+ it 'matches V3 response schema for a list of issues' do
+ get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to match_response_schema('public_api/v3/issues')
+ end
+
it 'returns a 401 error if user not authenticated' do
get v3_api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
diff --git a/spec/requests/api/v3/project_hooks_spec.rb b/spec/requests/api/v3/project_hooks_spec.rb
new file mode 100644
index 00000000000..a981119dc5a
--- /dev/null
+++ b/spec/requests/api/v3/project_hooks_spec.rb
@@ -0,0 +1,216 @@
+require 'spec_helper'
+
+describe API::ProjectHooks, 'ProjectHooks', api: true do
+ include ApiHelpers
+ let(:user) { create(:user) }
+ let(:user3) { create(:user) }
+ let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
+ let!(:hook) do
+ create(:project_hook,
+ :all_events_enabled,
+ project: project,
+ url: 'http://example.com',
+ enable_ssl_verification: true)
+ end
+
+ before do
+ project.team << [user, :master]
+ project.team << [user3, :developer]
+ end
+
+ describe "GET /projects/:id/hooks" do
+ context "authorized user" do
+ it "returns project hooks" do
+ get v3_api("/projects/#{project.id}/hooks", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.count).to eq(1)
+ expect(json_response.first['url']).to eq("http://example.com")
+ expect(json_response.first['issues_events']).to eq(true)
+ expect(json_response.first['push_events']).to eq(true)
+ expect(json_response.first['merge_requests_events']).to eq(true)
+ expect(json_response.first['tag_push_events']).to eq(true)
+ expect(json_response.first['note_events']).to eq(true)
+ expect(json_response.first['build_events']).to eq(true)
+ expect(json_response.first['pipeline_events']).to eq(true)
+ expect(json_response.first['wiki_page_events']).to eq(true)
+ expect(json_response.first['enable_ssl_verification']).to eq(true)
+ end
+ end
+
+ context "unauthorized user" do
+ it "does not access project hooks" do
+ get v3_api("/projects/#{project.id}/hooks", user3)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+ end
+
+ describe "GET /projects/:id/hooks/:hook_id" do
+ context "authorized user" do
+ it "returns a project hook" do
+ get v3_api("/projects/#{project.id}/hooks/#{hook.id}", user)
+ expect(response).to have_http_status(200)
+ expect(json_response['url']).to eq(hook.url)
+ expect(json_response['issues_events']).to eq(hook.issues_events)
+ expect(json_response['push_events']).to eq(hook.push_events)
+ expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
+ expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
+ expect(json_response['note_events']).to eq(hook.note_events)
+ expect(json_response['build_events']).to eq(hook.build_events)
+ expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
+ expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
+ expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
+ end
+
+ it "returns a 404 error if hook id is not available" do
+ get v3_api("/projects/#{project.id}/hooks/1234", user)
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context "unauthorized user" do
+ it "does not access an existing hook" do
+ get v3_api("/projects/#{project.id}/hooks/#{hook.id}", user3)
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ it "returns a 404 error if hook id is not available" do
+ get v3_api("/projects/#{project.id}/hooks/1234", user)
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ describe "POST /projects/:id/hooks" do
+ it "adds hook to project" do
+ expect do
+ post v3_api("/projects/#{project.id}/hooks", user),
+ url: "http://example.com", issues_events: true, wiki_page_events: true
+ end.to change {project.hooks.count}.by(1)
+
+ expect(response).to have_http_status(201)
+ expect(json_response['url']).to eq('http://example.com')
+ expect(json_response['issues_events']).to eq(true)
+ expect(json_response['push_events']).to eq(true)
+ expect(json_response['merge_requests_events']).to eq(false)
+ expect(json_response['tag_push_events']).to eq(false)
+ expect(json_response['note_events']).to eq(false)
+ expect(json_response['build_events']).to eq(false)
+ expect(json_response['pipeline_events']).to eq(false)
+ expect(json_response['wiki_page_events']).to eq(true)
+ expect(json_response['enable_ssl_verification']).to eq(true)
+ expect(json_response).not_to include('token')
+ end
+
+ it "adds the token without including it in the response" do
+ token = "secret token"
+
+ expect do
+ post v3_api("/projects/#{project.id}/hooks", user), url: "http://example.com", token: token
+ end.to change {project.hooks.count}.by(1)
+
+ expect(response).to have_http_status(201)
+ expect(json_response["url"]).to eq("http://example.com")
+ expect(json_response).not_to include("token")
+
+ hook = project.hooks.find(json_response["id"])
+
+ expect(hook.url).to eq("http://example.com")
+ expect(hook.token).to eq(token)
+ end
+
+ it "returns a 400 error if url not given" do
+ post v3_api("/projects/#{project.id}/hooks", user)
+ expect(response).to have_http_status(400)
+ end
+
+ it "returns a 422 error if url not valid" do
+ post v3_api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com"
+ expect(response).to have_http_status(422)
+ end
+ end
+
+ describe "PUT /projects/:id/hooks/:hook_id" do
+ it "updates an existing project hook" do
+ put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user),
+ url: 'http://example.org', push_events: false
+ expect(response).to have_http_status(200)
+ expect(json_response['url']).to eq('http://example.org')
+ expect(json_response['issues_events']).to eq(hook.issues_events)
+ expect(json_response['push_events']).to eq(false)
+ expect(json_response['merge_requests_events']).to eq(hook.merge_requests_events)
+ expect(json_response['tag_push_events']).to eq(hook.tag_push_events)
+ expect(json_response['note_events']).to eq(hook.note_events)
+ expect(json_response['build_events']).to eq(hook.build_events)
+ expect(json_response['pipeline_events']).to eq(hook.pipeline_events)
+ expect(json_response['wiki_page_events']).to eq(hook.wiki_page_events)
+ expect(json_response['enable_ssl_verification']).to eq(hook.enable_ssl_verification)
+ end
+
+ it "adds the token without including it in the response" do
+ token = "secret token"
+
+ put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user), url: "http://example.org", token: token
+
+ expect(response).to have_http_status(200)
+ expect(json_response["url"]).to eq("http://example.org")
+ expect(json_response).not_to include("token")
+
+ expect(hook.reload.url).to eq("http://example.org")
+ expect(hook.reload.token).to eq(token)
+ end
+
+ it "returns 404 error if hook id not found" do
+ put v3_api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org'
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns 400 error if url is not given" do
+ put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user)
+ expect(response).to have_http_status(400)
+ end
+
+ it "returns a 422 error if url is not valid" do
+ put v3_api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com'
+ expect(response).to have_http_status(422)
+ end
+ end
+
+ describe "DELETE /projects/:id/hooks/:hook_id" do
+ it "deletes hook from project" do
+ expect do
+ delete v3_api("/projects/#{project.id}/hooks/#{hook.id}", user)
+ end.to change {project.hooks.count}.by(-1)
+ expect(response).to have_http_status(200)
+ end
+
+ it "returns success when deleting hook" do
+ delete v3_api("/projects/#{project.id}/hooks/#{hook.id}", user)
+ expect(response).to have_http_status(200)
+ end
+
+ it "returns a 404 error when deleting non existent hook" do
+ delete v3_api("/projects/#{project.id}/hooks/42", user)
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns a 404 error if hook id not given" do
+ delete v3_api("/projects/#{project.id}/hooks", user)
+
+ expect(response).to have_http_status(404)
+ end
+
+ it "returns a 404 if a user attempts to delete project hooks he/she does not own" do
+ test_user = create(:user)
+ other_project = create(:project)
+ other_project.team << [test_user, :master]
+
+ delete v3_api("/projects/#{other_project.id}/hooks/#{hook.id}", test_user)
+ expect(response).to have_http_status(404)
+ expect(WebHook.exists?(hook.id)).to be_truthy
+ end
+ end
+end
diff --git a/spec/requests/api/v3/triggers_spec.rb b/spec/requests/api/v3/triggers_spec.rb
index 721ce4a361b..4819269d69f 100644
--- a/spec/requests/api/v3/triggers_spec.rb
+++ b/spec/requests/api/v3/triggers_spec.rb
@@ -11,6 +11,177 @@ describe API::V3::Triggers do
let!(:developer) { create(:project_member, :developer, user: user2, project: project) }
let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) }
+ describe 'POST /projects/:project_id/trigger' do
+ let!(:project2) { create(:project) }
+ let(:options) do
+ {
+ token: trigger_token
+ }
+ end
+
+ before do
+ stub_ci_pipeline_to_return_yaml_file
+ end
+
+ context 'Handles errors' do
+ it 'returns bad request if token is missing' do
+ post v3_api("/projects/#{project.id}/trigger/builds"), ref: 'master'
+ expect(response).to have_http_status(400)
+ end
+
+ it 'returns not found if project is not found' do
+ post v3_api('/projects/0/trigger/builds'), options.merge(ref: 'master')
+ expect(response).to have_http_status(404)
+ end
+
+ it 'returns unauthorized if token is for different project' do
+ post v3_api("/projects/#{project2.id}/trigger/builds"), options.merge(ref: 'master')
+ expect(response).to have_http_status(401)
+ end
+ end
+
+ context 'Have a commit' do
+ let(:pipeline) { project.pipelines.last }
+
+ it 'creates builds' do
+ post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'master')
+ expect(response).to have_http_status(201)
+ pipeline.builds.reload
+ expect(pipeline.builds.pending.size).to eq(2)
+ expect(pipeline.builds.size).to eq(5)
+ end
+
+ it 'creates builds on webhook from other gitlab repository and branch' do
+ expect do
+ post v3_api("/projects/#{project.id}/ref/master/trigger/builds?token=#{trigger_token}"), { ref: 'refs/heads/other-branch' }
+ end.to change(project.builds, :count).by(5)
+ expect(response).to have_http_status(201)
+ end
+
+ it 'returns bad request with no builds created if there\'s no commit for that ref' do
+ post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(ref: 'other-branch')
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq('No builds created')
+ end
+
+ context 'Validates variables' do
+ let(:variables) do
+ { 'TRIGGER_KEY' => 'TRIGGER_VALUE' }
+ end
+
+ it 'validates variables to be a hash' do
+ post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: 'value', ref: 'master')
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('variables is invalid')
+ end
+
+ it 'validates variables needs to be a map of key-valued strings' do
+ post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: { key: %w(1 2) }, ref: 'master')
+ expect(response).to have_http_status(400)
+ expect(json_response['message']).to eq('variables needs to be a map of key-valued strings')
+ end
+
+ it 'creates trigger request with variables' do
+ post v3_api("/projects/#{project.id}/trigger/builds"), options.merge(variables: variables, ref: 'master')
+ expect(response).to have_http_status(201)
+ pipeline.builds.reload
+ expect(pipeline.builds.first.trigger_request.variables).to eq(variables)
+ end
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/triggers' do
+ context 'authenticated user with valid permissions' do
+ it 'returns list of triggers' do
+ get v3_api("/projects/#{project.id}/triggers", user)
+
+ expect(response).to have_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_a(Array)
+ expect(json_response[0]).to have_key('token')
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return triggers list' do
+ get v3_api("/projects/#{project.id}/triggers", user2)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return triggers list' do
+ get v3_api("/projects/#{project.id}/triggers")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/triggers/:token' do
+ context 'authenticated user with valid permissions' do
+ it 'returns trigger details' do
+ get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_a(Hash)
+ end
+
+ it 'responds with 404 Not Found if requesting non-existing trigger' do
+ get v3_api("/projects/#{project.id}/triggers/abcdef012345", user)
+
+ expect(response).to have_http_status(404)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not return triggers list' do
+ get v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user2)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not return triggers list' do
+ get v3_api("/projects/#{project.id}/triggers/#{trigger.token}")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
+ describe 'POST /projects/:id/triggers' do
+ context 'authenticated user with valid permissions' do
+ it 'creates trigger' do
+ expect do
+ post v3_api("/projects/#{project.id}/triggers", user)
+ end.to change{project.triggers.count}.by(1)
+
+ expect(response).to have_http_status(201)
+ expect(json_response).to be_a(Hash)
+ end
+ end
+
+ context 'authenticated user with invalid permissions' do
+ it 'does not create trigger' do
+ post v3_api("/projects/#{project.id}/triggers", user2)
+
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ context 'unauthenticated user' do
+ it 'does not create trigger' do
+ post v3_api("/projects/#{project.id}/triggers")
+
+ expect(response).to have_http_status(401)
+ end
+ end
+ end
+
describe 'DELETE /projects/:id/triggers/:token' do
context 'authenticated user with valid permissions' do
it 'deletes trigger' do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index a5bc62ef6c2..d31f1bdfb7c 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -431,12 +431,22 @@ describe 'project routing' do
end
end
- # project_notes GET /:project_id/notes(.:format) notes#index
- # POST /:project_id/notes(.:format) notes#create
- # project_note DELETE /:project_id/notes/:id(.:format) notes#destroy
+ # project_noteable_notes GET /:project_id/noteable/:target_type/:target_id/notes notes#index
+ # POST /:project_id/notes(.:format) notes#create
+ # project_note DELETE /:project_id/notes/:id(.:format) notes#destroy
describe Projects::NotesController, 'routing' do
+ it 'to #index' do
+ expect(get('/gitlab/gitlabhq/noteable/issue/1/notes')).to route_to(
+ 'projects/notes#index',
+ namespace_id: 'gitlab',
+ project_id: 'gitlabhq',
+ target_type: 'issue',
+ target_id: '1'
+ )
+ end
+
it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :create, :destroy] }
+ let(:actions) { [:create, :destroy] }
let(:controller) { 'notes' }
end
end
diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb
index d8c443d29d5..5e68343784d 100644
--- a/spec/services/ci/create_trigger_request_service_spec.rb
+++ b/spec/services/ci/create_trigger_request_service_spec.rb
@@ -13,8 +13,22 @@ describe Ci::CreateTriggerRequestService, services: true do
context 'valid params' do
subject { service.execute(project, trigger, 'master') }
- it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
- it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
+ context 'without owner' do
+ it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
+ it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
+ end
+
+ context 'with owner' do
+ let(:owner) { create(:user) }
+ let(:trigger) { create(:ci_trigger, project: project, owner: owner) }
+
+ it { expect(subject).to be_kind_of(Ci::TriggerRequest) }
+ it { expect(subject.pipeline).to be_kind_of(Ci::Pipeline) }
+ it { expect(subject.pipeline.user).to eq(owner) }
+ it { expect(subject.builds.first).to be_kind_of(Ci::Build) }
+ it { expect(subject.builds.first.user).to eq(owner) }
+ end
end
context 'no commit for ref' do
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index 411b22a0fb8..f75fdd9e03f 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -26,6 +26,28 @@ describe Projects::UpdatePagesService do
build.update_attributes(artifacts_metadata: metadata)
end
+ describe 'pages artifacts' do
+ context 'with expiry date' do
+ before do
+ build.artifacts_expire_in = "2 days"
+ end
+
+ it "doesn't delete artifacts" do
+ expect(execute).to eq(:success)
+
+ expect(build.reload.artifacts_file?).to eq(true)
+ end
+ end
+
+ context 'without expiry date' do
+ it "does delete artifacts" do
+ expect(execute).to eq(:success)
+
+ expect(build.reload.artifacts_file?).to eq(false)
+ end
+ end
+ end
+
it 'succeeds' do
expect(project.pages_deployed?).to be_falsey
expect(execute).to eq(:success)
diff --git a/spec/support/unique_ip_check_shared_examples.rb b/spec/support/unique_ip_check_shared_examples.rb
new file mode 100644
index 00000000000..7cf5a65eeed
--- /dev/null
+++ b/spec/support/unique_ip_check_shared_examples.rb
@@ -0,0 +1,62 @@
+shared_context 'unique ips sign in limit' do
+ include StubENV
+ before(:each) do
+ Gitlab::Redis.with(&:flushall)
+ end
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+
+ current_application_settings.update!(
+ unique_ips_limit_enabled: true,
+ unique_ips_limit_time_window: 10000
+ )
+ end
+
+ def change_ip(ip)
+ allow(Gitlab::RequestContext).to receive(:client_ip).and_return(ip)
+ end
+
+ def request_from_ip(ip)
+ change_ip(ip)
+ request
+ response
+ end
+
+ def operation_from_ip(ip)
+ change_ip(ip)
+ operation
+ end
+end
+
+shared_examples 'user login operation with unique ip limit' do
+ include_context 'unique ips sign in limit' do
+ before { current_application_settings.update!(unique_ips_limit_per_user: 1) }
+
+ it 'allows user authenticating from the same ip' do
+ expect { operation_from_ip('ip') }.not_to raise_error
+ expect { operation_from_ip('ip') }.not_to raise_error
+ end
+
+ it 'blocks user authenticating from two distinct ips' do
+ expect { operation_from_ip('ip') }.not_to raise_error
+ expect { operation_from_ip('ip2') }.to raise_error(Gitlab::Auth::TooManyIps)
+ end
+ end
+end
+
+shared_examples 'user login request with unique ip limit' do |success_status = 200|
+ include_context 'unique ips sign in limit' do
+ before { current_application_settings.update!(unique_ips_limit_per_user: 1) }
+
+ it 'allows user authenticating from the same ip' do
+ expect(request_from_ip('ip')).to have_http_status(success_status)
+ expect(request_from_ip('ip')).to have_http_status(success_status)
+ end
+
+ it 'blocks user authenticating from two distinct ips' do
+ expect(request_from_ip('ip')).to have_http_status(success_status)
+ expect(request_from_ip('ip2')).to have_http_status(403)
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index df8a47893f9..dfbfbd05f43 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -108,7 +108,7 @@ describe 'gitlab:app namespace rake task' do
$stdout = orig_stdout
end
- describe 'backup creation and deletion using annex and custom_hooks' do
+ describe 'backup creation and deletion using custom_hooks' do
let(:project) { create(:project) }
let(:user_backup_path) { "repositories/#{project.path_with_namespace}" }
@@ -132,25 +132,6 @@ describe 'gitlab:app namespace rake task' do
Dir.chdir(@origin_cd)
end
- context 'project uses git-annex and successfully creates backup' do
- let(:filename) { "annex" }
-
- it 'creates annex.tar and project bundle' do
- tar_contents, exit_status = Gitlab::Popen.popen(%W{tar -tvf #{@backup_tar}})
-
- expect(exit_status).to eq(0)
- expect(tar_contents).to match(user_backup_path)
- expect(tar_contents).to match("#{user_backup_path}/annex.tar")
- expect(tar_contents).to match("#{user_backup_path}.bundle")
- end
-
- it 'restores files correctly' do
- restore_backup
-
- expect(Dir.entries(File.join(project.repository.path, "annex"))).to include("dummy.txt")
- end
- end
-
context 'project uses custom_hooks and successfully creates backup' do
let(:filename) { "custom_hooks" }
diff --git a/spec/workers/stuck_ci_builds_worker_spec.rb b/spec/workers/stuck_ci_builds_worker_spec.rb
deleted file mode 100644
index 801fa31b45d..00000000000
--- a/spec/workers/stuck_ci_builds_worker_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-require "spec_helper"
-
-describe StuckCiBuildsWorker do
- let!(:build) { create :ci_build }
- let(:worker) { described_class.new }
-
- subject do
- build.reload
- build.status
- end
-
- %w(pending running).each do |status|
- context "#{status} build" do
- before do
- build.update!(status: status)
- end
-
- it 'gets dropped if it was updated over 2 days ago' do
- build.update!(updated_at: 2.days.ago)
- worker.perform
- is_expected.to eq('failed')
- end
-
- it "is still #{status}" do
- build.update!(updated_at: 1.minute.ago)
- worker.perform
- is_expected.to eq(status)
- end
- end
- end
-
- %w(success failed canceled).each do |status|
- context "#{status} build" do
- before do
- build.update!(status: status)
- end
-
- it "is still #{status}" do
- build.update!(updated_at: 2.days.ago)
- worker.perform
- is_expected.to eq(status)
- end
- end
- end
-
- context "for deleted project" do
- before do
- build.update!(status: :running, updated_at: 2.days.ago)
- build.project.update(pending_delete: true)
- end
-
- it "does not drop build" do
- expect_any_instance_of(Ci::Build).not_to receive(:drop)
- worker.perform
- end
- end
-end
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
new file mode 100644
index 00000000000..8434b0c8e5b
--- /dev/null
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -0,0 +1,129 @@
+require 'spec_helper'
+
+describe StuckCiJobsWorker do
+ let!(:runner) { create :ci_runner }
+ let!(:job) { create :ci_build, runner: runner }
+ let(:worker) { described_class.new }
+ let(:exclusive_lease_uuid) { SecureRandom.uuid }
+
+ subject do
+ job.reload
+ job.status
+ end
+
+ before do
+ job.update!(status: status, updated_at: updated_at)
+ allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(exclusive_lease_uuid)
+ end
+
+ shared_examples 'job is dropped' do
+ it 'changes status' do
+ worker.perform
+ is_expected.to eq('failed')
+ end
+ end
+
+ shared_examples 'job is unchanged' do
+ it "doesn't change status" do
+ worker.perform
+ is_expected.to eq(status)
+ end
+ end
+
+ context 'when job is pending' do
+ let(:status) { 'pending' }
+
+ context 'when job is not stuck' do
+ before { allow_any_instance_of(Ci::Build).to receive(:stuck?).and_return(false) }
+
+ context 'when job was not updated for more than 1 day ago' do
+ let(:updated_at) { 2.days.ago }
+ it_behaves_like 'job is dropped'
+ end
+
+ context 'when job was updated in less than 1 day ago' do
+ let(:updated_at) { 6.hours.ago }
+ it_behaves_like 'job is unchanged'
+ end
+
+ context 'when job was not updated for more than 1 hour ago' do
+ let(:updated_at) { 2.hours.ago }
+ it_behaves_like 'job is unchanged'
+ end
+ end
+
+ context 'when job is stuck' do
+ before { allow_any_instance_of(Ci::Build).to receive(:stuck?).and_return(true) }
+
+ context 'when job was not updated for more than 1 hour ago' do
+ let(:updated_at) { 2.hours.ago }
+ it_behaves_like 'job is dropped'
+ end
+
+ context 'when job was updated in less than 1 hour ago' do
+ let(:updated_at) { 30.minutes.ago }
+ it_behaves_like 'job is unchanged'
+ end
+ end
+ end
+
+ context 'when job is running' do
+ let(:status) { 'running' }
+
+ context 'when job was not updated for more than 1 hour ago' do
+ let(:updated_at) { 2.hours.ago }
+ it_behaves_like 'job is dropped'
+ end
+
+ context 'when job was updated in less than 1 hour ago' do
+ let(:updated_at) { 30.minutes.ago }
+ it_behaves_like 'job is unchanged'
+ end
+ end
+
+ %w(success skipped failed canceled).each do |status|
+ context "when job is #{status}" do
+ let(:status) { status }
+ let(:updated_at) { 2.days.ago }
+ it_behaves_like 'job is unchanged'
+ end
+ end
+
+ context 'for deleted project' do
+ let(:status) { 'running' }
+ let(:updated_at) { 2.days.ago }
+
+ before { job.project.update(pending_delete: true) }
+
+ it 'does not drop job' do
+ expect_any_instance_of(Ci::Build).not_to receive(:drop)
+ worker.perform
+ end
+ end
+
+ describe 'exclusive lease' do
+ let(:status) { 'running' }
+ let(:updated_at) { 2.days.ago }
+ let(:worker2) { described_class.new }
+
+ it 'is guard by exclusive lease when executed concurrently' do
+ expect(worker).to receive(:drop).at_least(:once)
+ expect(worker2).not_to receive(:drop)
+ worker.perform
+ allow_any_instance_of(Gitlab::ExclusiveLease).to receive(:try_obtain).and_return(false)
+ worker2.perform
+ end
+
+ it 'can be executed in sequence' do
+ expect(worker).to receive(:drop).at_least(:once)
+ expect(worker2).to receive(:drop).at_least(:once)
+ worker.perform
+ worker2.perform
+ end
+
+ it 'cancels exclusive lease after worker perform' do
+ expect(Gitlab::ExclusiveLease).to receive(:cancel).with(described_class::EXCLUSIVE_LEASE_KEY, exclusive_lease_uuid)
+ worker.perform
+ end
+ end
+end
diff --git a/vendor/assets/javascripts/g.bar.js b/vendor/assets/javascripts/g.bar.js
deleted file mode 100644
index 166bd654d6e..00000000000
--- a/vendor/assets/javascripts/g.bar.js
+++ /dev/null
@@ -1,674 +0,0 @@
-/*!
- * g.Raphael 0.51 - Charting library, based on Raphaël
- *
- * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
- * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
- */
-(function () {
- var mmin = Math.min,
- mmax = Math.max;
-
- function finger(x, y, width, height, dir, ending, isPath, paper) {
- var path,
- ends = { round: 'round', sharp: 'sharp', soft: 'soft', square: 'square' };
-
- // dir 0 for horizontal and 1 for vertical
- if ((dir && !height) || (!dir && !width)) {
- return isPath ? "" : paper.path();
- }
-
- ending = ends[ending] || "square";
- height = Math.round(height);
- width = Math.round(width);
- x = Math.round(x);
- y = Math.round(y);
-
- switch (ending) {
- case "round":
- if (!dir) {
- var r = ~~(height / 2);
-
- if (width < r) {
- r = width;
- path = [
- "M", x + .5, y + .5 - ~~(height / 2),
- "l", 0, 0,
- "a", r, ~~(height / 2), 0, 0, 1, 0, height,
- "l", 0, 0,
- "z"
- ];
- } else {
- path = [
- "M", x + .5, y + .5 - r,
- "l", width - r, 0,
- "a", r, r, 0, 1, 1, 0, height,
- "l", r - width, 0,
- "z"
- ];
- }
- } else {
- r = ~~(width / 2);
-
- if (height < r) {
- r = height;
- path = [
- "M", x - ~~(width / 2), y,
- "l", 0, 0,
- "a", ~~(width / 2), r, 0, 0, 1, width, 0,
- "l", 0, 0,
- "z"
- ];
- } else {
- path = [
- "M", x - r, y,
- "l", 0, r - height,
- "a", r, r, 0, 1, 1, width, 0,
- "l", 0, height - r,
- "z"
- ];
- }
- }
- break;
- case "sharp":
- if (!dir) {
- var half = ~~(height / 2);
-
- path = [
- "M", x, y + half,
- "l", 0, -height, mmax(width - half, 0), 0, mmin(half, width), half, -mmin(half, width), half + (half * 2 < height),
- "z"
- ];
- } else {
- half = ~~(width / 2);
- path = [
- "M", x + half, y,
- "l", -width, 0, 0, -mmax(height - half, 0), half, -mmin(half, height), half, mmin(half, height), half,
- "z"
- ];
- }
- break;
- case "square":
- if (!dir) {
- path = [
- "M", x, y + ~~(height / 2),
- "l", 0, -height, width, 0, 0, height,
- "z"
- ];
- } else {
- path = [
- "M", x + ~~(width / 2), y,
- "l", 1 - width, 0, 0, -height, width - 1, 0,
- "z"
- ];
- }
- break;
- case "soft":
- if (!dir) {
- r = mmin(width, Math.round(height / 5));
- path = [
- "M", x + .5, y + .5 - ~~(height / 2),
- "l", width - r, 0,
- "a", r, r, 0, 0, 1, r, r,
- "l", 0, height - r * 2,
- "a", r, r, 0, 0, 1, -r, r,
- "l", r - width, 0,
- "z"
- ];
- } else {
- r = mmin(Math.round(width / 5), height);
- path = [
- "M", x - ~~(width / 2), y,
- "l", 0, r - height,
- "a", r, r, 0, 0, 1, r, -r,
- "l", width - 2 * r, 0,
- "a", r, r, 0, 0, 1, r, r,
- "l", 0, height - r,
- "z"
- ];
- }
- }
-
- if (isPath) {
- return path.join(",");
- } else {
- return paper.path(path);
- }
- }
-
-/*\
- * Paper.vbarchart
- [ method ]
- **
- * Creates a vertical bar chart
- **
- > Parameters
- **
- - x (number) x coordinate of the chart
- - y (number) y coordinate of the chart
- - width (number) width of the chart (respected by all elements in the set)
- - height (number) height of the chart (respected by all elements in the set)
- - values (array) values
- - opts (object) options for the chart
- o {
- o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'.
- o gutter (number)(string) default '20%' (WHAT DOES IT DO?)
- o vgutter (number)
- o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color.
- o stacked (boolean) whether or not to tread values as in a stacked bar chart
- o to
- o stretch (boolean)
- o }
- **
- = (object) path element of the popup
- > Usage
- | r.vbarchart(0, 0, 620, 260, [76, 70, 67, 71, 69], {})
- \*/
-
- function VBarchart(paper, x, y, width, height, values, opts) {
- opts = opts || {};
-
- var chartinst = this,
- type = opts.type || "square",
- gutter = parseFloat(opts.gutter || "20%"),
- chart = paper.set(),
- bars = paper.set(),
- covers = paper.set(),
- covers2 = paper.set(),
- total = Math.max.apply(Math, values),
- stacktotal = [],
- multi = 0,
- colors = opts.colors || chartinst.colors,
- len = values.length;
-
- if (Raphael.is(values[0], "array")) {
- total = [];
- multi = len;
- len = 0;
-
- for (var i = values.length; i--;) {
- bars.push(paper.set());
- total.push(Math.max.apply(Math, values[i]));
- len = Math.max(len, values[i].length);
- }
-
- if (opts.stacked) {
- for (var i = len; i--;) {
- var tot = 0;
-
- for (var j = values.length; j--;) {
- tot +=+ values[j][i] || 0;
- }
-
- stacktotal.push(tot);
- }
- }
-
- for (var i = values.length; i--;) {
- if (values[i].length < len) {
- for (var j = len; j--;) {
- values[i].push(0);
- }
- }
- }
-
- total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
- }
-
- total = (opts.to) || total;
-
- var barwidth = width / (len * (100 + gutter) + gutter) * 100,
- barhgutter = barwidth * gutter / 100,
- barvgutter = opts.vgutter == null ? 20 : opts.vgutter,
- stack = [],
- X = x + barhgutter,
- Y = (height - 2 * barvgutter) / total;
-
- if (!opts.stretch) {
- barhgutter = Math.round(barhgutter);
- barwidth = Math.floor(barwidth);
- }
-
- !opts.stacked && (barwidth /= multi || 1);
-
- for (var i = 0; i < len; i++) {
- stack = [];
-
- for (var j = 0; j < (multi || 1); j++) {
- var h = Math.round((multi ? values[j][i] : values[i]) * Y),
- top = y + height - barvgutter - h,
- bar = finger(Math.round(X + barwidth / 2), top + h, barwidth, h, true, type, null, paper).attr({ stroke: "none", fill: colors[multi ? j : i] });
-
- if (multi) {
- bars[j].push(bar);
- } else {
- bars.push(bar);
- }
-
- bar.y = top;
- bar.x = Math.round(X + barwidth / 2);
- bar.w = barwidth;
- bar.h = h;
- bar.value = multi ? values[j][i] : values[i];
-
- if (!opts.stacked) {
- X += barwidth;
- } else {
- stack.push(bar);
- }
- }
-
- if (opts.stacked) {
- var cvr;
-
- covers2.push(cvr = paper.rect(stack[0].x - stack[0].w / 2, y, barwidth, height).attr(chartinst.shim));
- cvr.bars = paper.set();
-
- var size = 0;
-
- for (var s = stack.length; s--;) {
- stack[s].toFront();
- }
-
- for (var s = 0, ss = stack.length; s < ss; s++) {
- var bar = stack[s],
- cover,
- h = (size + bar.value) * Y,
- path = finger(bar.x, y + height - barvgutter - !!size * .5, barwidth, h, true, type, 1, paper);
-
- cvr.bars.push(bar);
- size && bar.attr({path: path});
- bar.h = h;
- bar.y = y + height - barvgutter - !!size * .5 - h;
- covers.push(cover = paper.rect(bar.x - bar.w / 2, bar.y, barwidth, bar.value * Y).attr(chartinst.shim));
- cover.bar = bar;
- cover.value = bar.value;
- size += bar.value;
- }
-
- X += barwidth;
- }
-
- X += barhgutter;
- }
-
- covers2.toFront();
- X = x + barhgutter;
-
- if (!opts.stacked) {
- for (var i = 0; i < len; i++) {
- for (var j = 0; j < (multi || 1); j++) {
- var cover;
-
- covers.push(cover = paper.rect(Math.round(X), y + barvgutter, barwidth, height - barvgutter).attr(chartinst.shim));
- cover.bar = multi ? bars[j][i] : bars[i];
- cover.value = cover.bar.value;
- X += barwidth;
- }
-
- X += barhgutter;
- }
- }
-
- chart.label = function (labels, isBottom) {
- labels = labels || [];
- this.labels = paper.set();
-
- var L, l = -Infinity;
-
- if (opts.stacked) {
- for (var i = 0; i < len; i++) {
- var tot = 0;
-
- for (var j = 0; j < (multi || 1); j++) {
- tot += multi ? values[j][i] : values[i];
-
- if (j == multi - 1) {
- var label = paper.labelise(labels[i], tot, total);
-
- L = paper.text(bars[i * (multi || 1) + j].x, y + height - barvgutter / 2, label).attr(txtattr).insertBefore(covers[i * (multi || 1) + j]);
-
- var bb = L.getBBox();
-
- if (bb.x - 7 < l) {
- L.remove();
- } else {
- this.labels.push(L);
- l = bb.x + bb.width;
- }
- }
- }
- }
- } else {
- for (var i = 0; i < len; i++) {
- for (var j = 0; j < (multi || 1); j++) {
- var label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
-
- L = paper.text(bars[i * (multi || 1) + j].x, isBottom ? y + height - barvgutter / 2 : bars[i * (multi || 1) + j].y - 10, label).attr(txtattr).insertBefore(covers[i * (multi || 1) + j]);
-
- var bb = L.getBBox();
-
- if (bb.x - 7 < l) {
- L.remove();
- } else {
- this.labels.push(L);
- l = bb.x + bb.width;
- }
- }
- }
- }
- return this;
- };
-
- chart.hover = function (fin, fout) {
- covers2.hide();
- covers.show();
- covers.mouseover(fin).mouseout(fout);
- return this;
- };
-
- chart.hoverColumn = function (fin, fout) {
- covers.hide();
- covers2.show();
- fout = fout || function () {};
- covers2.mouseover(fin).mouseout(fout);
- return this;
- };
-
- chart.click = function (f) {
- covers2.hide();
- covers.show();
- covers.click(f);
- return this;
- };
-
- chart.each = function (f) {
- if (!Raphael.is(f, "function")) {
- return this;
- }
- for (var i = covers.length; i--;) {
- f.call(covers[i]);
- }
- return this;
- };
-
- chart.eachColumn = function (f) {
- if (!Raphael.is(f, "function")) {
- return this;
- }
- for (var i = covers2.length; i--;) {
- f.call(covers2[i]);
- }
- return this;
- };
-
- chart.clickColumn = function (f) {
- covers.hide();
- covers2.show();
- covers2.click(f);
- return this;
- };
-
- chart.push(bars, covers, covers2);
- chart.bars = bars;
- chart.covers = covers;
- return chart;
- };
-
- //inheritance
- var F = function() {};
- F.prototype = Raphael.g;
- HBarchart.prototype = VBarchart.prototype = new F; //prototype reused by hbarchart
-
- Raphael.fn.barchart = function(x, y, width, height, values, opts) {
- return new VBarchart(this, x, y, width, height, values, opts);
- };
-
-/*\
- * Paper.barchart
- [ method ]
- **
- * Creates a horizontal bar chart
- **
- > Parameters
- **
- - x (number) x coordinate of the chart
- - y (number) y coordinate of the chart
- - width (number) width of the chart (respected by all elements in the set)
- - height (number) height of the chart (respected by all elements in the set)
- - values (array) values
- - opts (object) options for the chart
- o {
- o type (string) type of endings of the bar. Default: 'square'. Other options are: 'round', 'sharp', 'soft'.
- o gutter (number)(string) default '20%' (WHAT DOES IT DO?)
- o vgutter (number)
- o colors (array) colors be used repeatedly to plot the bars. If multicolumn bar is used each sequence of bars with use a different color.
- o stacked (boolean) whether or not to tread values as in a stacked bar chart
- o to
- o stretch (boolean)
- o }
- **
- = (object) path element of the popup
- > Usage
- | r.barchart(0, 0, 620, 260, [76, 70, 67, 71, 69], {})
- \*/
-
- function HBarchart(paper, x, y, width, height, values, opts) {
- opts = opts || {};
-
- var chartinst = this,
- type = opts.type || "square",
- gutter = parseFloat(opts.gutter || "20%"),
- chart = paper.set(),
- bars = paper.set(),
- covers = paper.set(),
- covers2 = paper.set(),
- total = Math.max.apply(Math, values),
- stacktotal = [],
- multi = 0,
- colors = opts.colors || chartinst.colors,
- len = values.length;
-
- if (Raphael.is(values[0], "array")) {
- total = [];
- multi = len;
- len = 0;
-
- for (var i = values.length; i--;) {
- bars.push(paper.set());
- total.push(Math.max.apply(Math, values[i]));
- len = Math.max(len, values[i].length);
- }
-
- if (opts.stacked) {
- for (var i = len; i--;) {
- var tot = 0;
- for (var j = values.length; j--;) {
- tot +=+ values[j][i] || 0;
- }
- stacktotal.push(tot);
- }
- }
-
- for (var i = values.length; i--;) {
- if (values[i].length < len) {
- for (var j = len; j--;) {
- values[i].push(0);
- }
- }
- }
-
- total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
- }
-
- total = (opts.to) || total;
-
- var barheight = Math.floor(height / (len * (100 + gutter) + gutter) * 100),
- bargutter = Math.floor(barheight * gutter / 100),
- stack = [],
- Y = y + bargutter,
- X = (width - 1) / total;
-
- !opts.stacked && (barheight /= multi || 1);
-
- for (var i = 0; i < len; i++) {
- stack = [];
-
- for (var j = 0; j < (multi || 1); j++) {
- var val = multi ? values[j][i] : values[i],
- bar = finger(x, Y + barheight / 2, Math.round(val * X), barheight - 1, false, type, null, paper).attr({stroke: "none", fill: colors[multi ? j : i]});
-
- if (multi) {
- bars[j].push(bar);
- } else {
- bars.push(bar);
- }
-
- bar.x = x + Math.round(val * X);
- bar.y = Y + barheight / 2;
- bar.w = Math.round(val * X);
- bar.h = barheight;
- bar.value = +val;
-
- if (!opts.stacked) {
- Y += barheight;
- } else {
- stack.push(bar);
- }
- }
-
- if (opts.stacked) {
- var cvr = paper.rect(x, stack[0].y - stack[0].h / 2, width, barheight).attr(chartinst.shim);
-
- covers2.push(cvr);
- cvr.bars = paper.set();
-
- var size = 0;
-
- for (var s = stack.length; s--;) {
- stack[s].toFront();
- }
-
- for (var s = 0, ss = stack.length; s < ss; s++) {
- var bar = stack[s],
- cover,
- val = Math.round((size + bar.value) * X),
- path = finger(x, bar.y, val, barheight - 1, false, type, 1, paper);
-
- cvr.bars.push(bar);
- size && bar.attr({ path: path });
- bar.w = val;
- bar.x = x + val;
- covers.push(cover = paper.rect(x + size * X, bar.y - bar.h / 2, bar.value * X, barheight).attr(chartinst.shim));
- cover.bar = bar;
- size += bar.value;
- }
-
- Y += barheight;
- }
-
- Y += bargutter;
- }
-
- covers2.toFront();
- Y = y + bargutter;
-
- if (!opts.stacked) {
- for (var i = 0; i < len; i++) {
- for (var j = 0; j < (multi || 1); j++) {
- var cover = paper.rect(x, Y, width, barheight).attr(chartinst.shim);
-
- covers.push(cover);
- cover.bar = multi ? bars[j][i] : bars[i];
- cover.value = cover.bar.value;
- Y += barheight;
- }
-
- Y += bargutter;
- }
- }
-
- chart.label = function (labels, isRight) {
- labels = labels || [];
- this.labels = paper.set();
-
- for (var i = 0; i < len; i++) {
- for (var j = 0; j < multi; j++) {
- var label = paper.labelise(multi ? labels[j] && labels[j][i] : labels[i], multi ? values[j][i] : values[i], total),
- X = isRight ? bars[i * (multi || 1) + j].x - barheight / 2 + 3 : x + 5,
- A = isRight ? "end" : "start",
- L;
-
- this.labels.push(L = paper.text(X, bars[i * (multi || 1) + j].y, label).attr(txtattr).attr({ "text-anchor": A }).insertBefore(covers[0]));
-
- if (L.getBBox().x < x + 5) {
- L.attr({x: x + 5, "text-anchor": "start"});
- } else {
- bars[i * (multi || 1) + j].label = L;
- }
- }
- }
-
- return this;
- };
-
- chart.hover = function (fin, fout) {
- covers2.hide();
- covers.show();
- fout = fout || function () {};
- covers.mouseover(fin).mouseout(fout);
- return this;
- };
-
- chart.hoverColumn = function (fin, fout) {
- covers.hide();
- covers2.show();
- fout = fout || function () {};
- covers2.mouseover(fin).mouseout(fout);
- return this;
- };
-
- chart.each = function (f) {
- if (!Raphael.is(f, "function")) {
- return this;
- }
- for (var i = covers.length; i--;) {
- f.call(covers[i]);
- }
- return this;
- };
-
- chart.eachColumn = function (f) {
- if (!Raphael.is(f, "function")) {
- return this;
- }
- for (var i = covers2.length; i--;) {
- f.call(covers2[i]);
- }
- return this;
- };
-
- chart.click = function (f) {
- covers2.hide();
- covers.show();
- covers.click(f);
- return this;
- };
-
- chart.clickColumn = function (f) {
- covers.hide();
- covers2.show();
- covers2.click(f);
- return this;
- };
-
- chart.push(bars, covers, covers2);
- chart.bars = bars;
- chart.covers = covers;
- return chart;
- };
-
- Raphael.fn.hbarchart = function(x, y, width, height, values, opts) {
- return new HBarchart(this, x, y, width, height, values, opts);
- };
-
-})();
diff --git a/vendor/assets/javascripts/g.raphael.js b/vendor/assets/javascripts/g.raphael.js
deleted file mode 100644
index 27f27caf9f2..00000000000
--- a/vendor/assets/javascripts/g.raphael.js
+++ /dev/null
@@ -1,861 +0,0 @@
-/*!
- * g.Raphael 0.51 - Charting library, based on Raphaël
- *
- * Copyright (c) 2009-2012 Dmitry Baranovskiy (http://g.raphaeljs.com)
- * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
- */
-
-/*
- * Tooltips on Element prototype
- */
-/*\
- * Element.popup
- [ method ]
- **
- * Puts the context Element in a 'popup' tooltip. Can also be used on sets.
- **
- > Parameters
- **
- - dir (string) location of Element relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
- - size (number) amount of bevel/padding around the Element, as well as half the width and height of the tail [default: `5`]
- - x (number) x coordinate of the popup's tail [default: Element's `x` or `cx`]
- - y (number) y coordinate of the popup's tail [default: Element's `y` or `cy`]
- **
- = (object) path element of the popup
- \*/
-Raphael.el.popup = function (dir, size, x, y) {
- var paper = this.paper || this[0].paper,
- bb, xy, center, cw, ch;
-
- if (!paper) return;
-
- switch (this.type) {
- case 'text':
- case 'circle':
- case 'ellipse': center = true; break;
- default: center = false;
- }
-
- dir = dir == null ? 'up' : dir;
- size = size || 5;
- bb = this.getBBox();
-
- x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
- y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
- cw = Math.max(bb.width / 2 - size, 0);
- ch = Math.max(bb.height / 2 - size, 0);
-
- this.translate(x - bb.x - (center ? bb.width / 2 : 0), y - bb.y - (center ? bb.height / 2 : 0));
- bb = this.getBBox();
-
- var paths = {
- up: [
- 'M', x, y,
- 'l', -size, -size, -cw, 0,
- 'a', size, size, 0, 0, 1, -size, -size,
- 'l', 0, -bb.height,
- 'a', size, size, 0, 0, 1, size, -size,
- 'l', size * 2 + cw * 2, 0,
- 'a', size, size, 0, 0, 1, size, size,
- 'l', 0, bb.height,
- 'a', size, size, 0, 0, 1, -size, size,
- 'l', -cw, 0,
- 'z'
- ].join(','),
- down: [
- 'M', x, y,
- 'l', size, size, cw, 0,
- 'a', size, size, 0, 0, 1, size, size,
- 'l', 0, bb.height,
- 'a', size, size, 0, 0, 1, -size, size,
- 'l', -(size * 2 + cw * 2), 0,
- 'a', size, size, 0, 0, 1, -size, -size,
- 'l', 0, -bb.height,
- 'a', size, size, 0, 0, 1, size, -size,
- 'l', cw, 0,
- 'z'
- ].join(','),
- left: [
- 'M', x, y,
- 'l', -size, size, 0, ch,
- 'a', size, size, 0, 0, 1, -size, size,
- 'l', -bb.width, 0,
- 'a', size, size, 0, 0, 1, -size, -size,
- 'l', 0, -(size * 2 + ch * 2),
- 'a', size, size, 0, 0, 1, size, -size,
- 'l', bb.width, 0,
- 'a', size, size, 0, 0, 1, size, size,
- 'l', 0, ch,
- 'z'
- ].join(','),
- right: [
- 'M', x, y,
- 'l', size, -size, 0, -ch,
- 'a', size, size, 0, 0, 1, size, -size,
- 'l', bb.width, 0,
- 'a', size, size, 0, 0, 1, size, size,
- 'l', 0, size * 2 + ch * 2,
- 'a', size, size, 0, 0, 1, -size, size,
- 'l', -bb.width, 0,
- 'a', size, size, 0, 0, 1, -size, -size,
- 'l', 0, -ch,
- 'z'
- ].join(',')
- };
-
- xy = {
- up: { x: -!center * (bb.width / 2), y: -size * 2 - (center ? bb.height / 2 : bb.height) },
- down: { x: -!center * (bb.width / 2), y: size * 2 + (center ? bb.height / 2 : bb.height) },
- left: { x: -size * 2 - (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) },
- right: { x: size * 2 + (center ? bb.width / 2 : bb.width), y: -!center * (bb.height / 2) }
- }[dir];
-
- this.translate(xy.x, xy.y);
- return paper.path(paths[dir]).attr({ fill: "#000", stroke: "none" }).insertBefore(this.node ? this : this[0]);
-};
-
-/*\
- * Element.tag
- [ method ]
- **
- * Puts the context Element in a 'tag' tooltip. Can also be used on sets.
- **
- > Parameters
- **
- - angle (number) angle of orientation in degrees [default: `0`]
- - r (number) radius of the loop [default: `5`]
- - x (number) x coordinate of the center of the tag loop [default: Element's `x` or `cx`]
- - y (number) y coordinate of the center of the tag loop [default: Element's `x` or `cx`]
- **
- = (object) path element of the tag
- \*/
-Raphael.el.tag = function (angle, r, x, y) {
- var d = 3,
- paper = this.paper || this[0].paper;
-
- if (!paper) return;
-
- var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
- bb = this.getBBox(),
- dx, R, center, tmp;
-
- switch (this.type) {
- case 'text':
- case 'circle':
- case 'ellipse': center = true; break;
- default: center = false;
- }
-
- angle = angle || 0;
- x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
- y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
- r = r == null ? 5 : r;
- R = .5522 * r;
-
- if (bb.height >= r * 2) {
- p.attr({
- path: [
- "M", x, y + r,
- "a", r, r, 0, 1, 1, 0, -r * 2, r, r, 0, 1, 1, 0, r * 2,
- "m", 0, -r * 2 -d,
- "a", r + d, r + d, 0, 1, 0, 0, (r + d) * 2,
- "L", x + r + d, y + bb.height / 2 + d,
- "l", bb.width + 2 * d, 0, 0, -bb.height - 2 * d, -bb.width - 2 * d, 0,
- "L", x, y - r - d
- ].join(",")
- });
- } else {
- dx = Math.sqrt(Math.pow(r + d, 2) - Math.pow(bb.height / 2 + d, 2));
- p.attr({
- path: [
- "M", x, y + r,
- "c", -R, 0, -r, R - r, -r, -r, 0, -R, r - R, -r, r, -r, R, 0, r, r - R, r, r, 0, R, R - r, r, -r, r,
- "M", x + dx, y - bb.height / 2 - d,
- "a", r + d, r + d, 0, 1, 0, 0, bb.height + 2 * d,
- "l", r + d - dx + bb.width + 2 * d, 0, 0, -bb.height - 2 * d,
- "L", x + dx, y - bb.height / 2 - d
- ].join(",")
- });
- }
-
- angle = 360 - angle;
- p.rotate(angle, x, y);
-
- if (this.attrs) {
- //elements
- this.attr(this.attrs.x ? 'x' : 'cx', x + r + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
- this.rotate(angle, x, y);
- angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - r - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
- } else {
- //sets
- if (angle > 90 && angle < 270) {
- this.translate(x - bb.x - bb.width - r - d, y - bb.y - bb.height / 2);
- this.rotate(angle - 180, bb.x + bb.width + r + d, bb.y + bb.height / 2);
- } else {
- this.translate(x - bb.x + r + d, y - bb.y - bb.height / 2);
- this.rotate(angle, bb.x - r - d, bb.y + bb.height / 2);
- }
- }
-
- return p.insertBefore(this.node ? this : this[0]);
-};
-
-/*\
- * Element.drop
- [ method ]
- **
- * Puts the context Element in a 'drop' tooltip. Can also be used on sets.
- **
- > Parameters
- **
- - angle (number) angle of orientation in degrees [default: `0`]
- - x (number) x coordinate of the drop's point [default: Element's `x` or `cx`]
- - y (number) y coordinate of the drop's point [default: Element's `x` or `cx`]
- **
- = (object) path element of the drop
- \*/
-Raphael.el.drop = function (angle, x, y) {
- var bb = this.getBBox(),
- paper = this.paper || this[0].paper,
- center, size, p, dx, dy;
-
- if (!paper) return;
-
- switch (this.type) {
- case 'text':
- case 'circle':
- case 'ellipse': center = true; break;
- default: center = false;
- }
-
- angle = angle || 0;
-
- x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
- y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
- size = Math.max(bb.width, bb.height) + Math.min(bb.width, bb.height);
- p = paper.path([
- "M", x, y,
- "l", size, 0,
- "A", size * .4, size * .4, 0, 1, 0, x + size * .7, y - size * .7,
- "z"
- ]).attr({fill: "#000", stroke: "none"}).rotate(22.5 - angle, x, y);
-
- angle = (angle + 90) * Math.PI / 180;
- dx = (x + size * Math.sin(angle)) - (center ? 0 : bb.width / 2);
- dy = (y + size * Math.cos(angle)) - (center ? 0 : bb.height / 2);
-
- this.attrs ?
- this.attr(this.attrs.x ? 'x' : 'cx', dx).attr(this.attrs.y ? 'y' : 'cy', dy) :
- this.translate(dx - bb.x, dy - bb.y);
-
- return p.insertBefore(this.node ? this : this[0]);
-};
-
-/*\
- * Element.flag
- [ method ]
- **
- * Puts the context Element in a 'flag' tooltip. Can also be used on sets.
- **
- > Parameters
- **
- - angle (number) angle of orientation in degrees [default: `0`]
- - x (number) x coordinate of the flag's point [default: Element's `x` or `cx`]
- - y (number) y coordinate of the flag's point [default: Element's `x` or `cx`]
- **
- = (object) path element of the flag
- \*/
-Raphael.el.flag = function (angle, x, y) {
- var d = 3,
- paper = this.paper || this[0].paper;
-
- if (!paper) return;
-
- var p = paper.path().attr({ fill: '#000', stroke: '#000' }),
- bb = this.getBBox(),
- h = bb.height / 2,
- center;
-
- switch (this.type) {
- case 'text':
- case 'circle':
- case 'ellipse': center = true; break;
- default: center = false;
- }
-
- angle = angle || 0;
- x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
- y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2: bb.y);
-
- p.attr({
- path: [
- "M", x, y,
- "l", h + d, -h - d, bb.width + 2 * d, 0, 0, bb.height + 2 * d, -bb.width - 2 * d, 0,
- "z"
- ].join(",")
- });
-
- angle = 360 - angle;
- p.rotate(angle, x, y);
-
- if (this.attrs) {
- //elements
- this.attr(this.attrs.x ? 'x' : 'cx', x + h + d + (!center ? this.type == 'text' ? bb.width : 0 : bb.width / 2)).attr('y', center ? y : y - bb.height / 2);
- this.rotate(angle, x, y);
- angle > 90 && angle < 270 && this.attr(this.attrs.x ? 'x' : 'cx', x - h - d - (!center ? bb.width : bb.width / 2)).rotate(180, x, y);
- } else {
- //sets
- if (angle > 90 && angle < 270) {
- this.translate(x - bb.x - bb.width - h - d, y - bb.y - bb.height / 2);
- this.rotate(angle - 180, bb.x + bb.width + h + d, bb.y + bb.height / 2);
- } else {
- this.translate(x - bb.x + h + d, y - bb.y - bb.height / 2);
- this.rotate(angle, bb.x - h - d, bb.y + bb.height / 2);
- }
- }
-
- return p.insertBefore(this.node ? this : this[0]);
-};
-
-/*\
- * Element.label
- [ method ]
- **
- * Puts the context Element in a 'label' tooltip. Can also be used on sets.
- **
- = (object) path element of the label.
- \*/
-Raphael.el.label = function () {
- var bb = this.getBBox(),
- paper = this.paper || this[0].paper,
- r = Math.min(20, bb.width + 10, bb.height + 10) / 2;
-
- if (!paper) return;
-
- return paper.rect(bb.x - r / 2, bb.y - r / 2, bb.width + r, bb.height + r, r).attr({ stroke: 'none', fill: '#000' }).insertBefore(this.node ? this : this[0]);
-};
-
-/*\
- * Element.blob
- [ method ]
- **
- * Puts the context Element in a 'blob' tooltip. Can also be used on sets.
- **
- > Parameters
- **
- - angle (number) angle of orientation in degrees [default: `0`]
- - x (number) x coordinate of the blob's tail [default: Element's `x` or `cx`]
- - y (number) y coordinate of the blob's tail [default: Element's `x` or `cx`]
- **
- = (object) path element of the blob
- \*/
-Raphael.el.blob = function (angle, x, y) {
- var bb = this.getBBox(),
- rad = Math.PI / 180,
- paper = this.paper || this[0].paper,
- p, center, size;
-
- if (!paper) return;
-
- switch (this.type) {
- case 'text':
- case 'circle':
- case 'ellipse': center = true; break;
- default: center = false;
- }
-
- p = paper.path().attr({ fill: "#000", stroke: "none" });
- angle = (+angle + 1 ? angle : 45) + 90;
- size = Math.min(bb.height, bb.width);
- x = typeof x == 'number' ? x : (center ? bb.x + bb.width / 2 : bb.x);
- y = typeof y == 'number' ? y : (center ? bb.y + bb.height / 2 : bb.y);
-
- var w = Math.max(bb.width + size, size * 25 / 12),
- h = Math.max(bb.height + size, size * 25 / 12),
- x2 = x + size * Math.sin((angle - 22.5) * rad),
- y2 = y + size * Math.cos((angle - 22.5) * rad),
- x1 = x + size * Math.sin((angle + 22.5) * rad),
- y1 = y + size * Math.cos((angle + 22.5) * rad),
- dx = (x1 - x2) / 2,
- dy = (y1 - y2) / 2,
- rx = w / 2,
- ry = h / 2,
- k = -Math.sqrt(Math.abs(rx * rx * ry * ry - rx * rx * dy * dy - ry * ry * dx * dx) / (rx * rx * dy * dy + ry * ry * dx * dx)),
- cx = k * rx * dy / ry + (x1 + x2) / 2,
- cy = k * -ry * dx / rx + (y1 + y2) / 2;
-
- p.attr({
- x: cx,
- y: cy,
- path: [
- "M", x, y,
- "L", x1, y1,
- "A", rx, ry, 0, 1, 1, x2, y2,
- "z"
- ].join(",")
- });
-
- this.translate(cx - bb.x - bb.width / 2, cy - bb.y - bb.height / 2);
-
- return p.insertBefore(this.node ? this : this[0]);
-};
-
-/*
- * Tooltips on Paper prototype
- */
-/*\
- * Paper.label
- [ method ]
- **
- * Puts the given `text` into a 'label' tooltip. The text is given a default style according to @g.txtattr. See @Element.label
- **
- > Parameters
- **
- - x (number) x coordinate of the center of the label
- - y (number) y coordinate of the center of the label
- - text (string) text to place inside the label
- **
- = (object) set containing the label path and the text element
- > Usage
- | paper.label(50, 50, "$9.99");
- \*/
-Raphael.fn.label = function (x, y, text) {
- var set = this.set();
-
- text = this.text(x, y, text).attr(Raphael.g.txtattr);
- return set.push(text.label(), text);
-};
-
-/*\
- * Paper.popup
- [ method ]
- **
- * Puts the given `text` into a 'popup' tooltip. The text is given a default style according to @g.txtattr. See @Element.popup
- *
- * Note: The `dir` parameter has changed from g.Raphael 0.4.1 to 0.5. The options `0`, `1`, `2`, and `3` has been changed to `'down'`, `'left'`, `'up'`, and `'right'` respectively.
- **
- > Parameters
- **
- - x (number) x coordinate of the popup's tail
- - y (number) y coordinate of the popup's tail
- - text (string) text to place inside the popup
- - dir (string) location of the text relative to the tail: `'down'`, `'left'`, `'up'` [default], or `'right'`.
- - size (number) amount of padding around the Element [default: `5`]
- **
- = (object) set containing the popup path and the text element
- > Usage
- | paper.popup(50, 50, "$9.99", 'down');
- \*/
-Raphael.fn.popup = function (x, y, text, dir, size) {
- var set = this.set();
-
- text = this.text(x, y, text).attr(Raphael.g.txtattr);
- return set.push(text.popup(dir, size), text);
-};
-
-/*\
- * Paper.tag
- [ method ]
- **
- * Puts the given text into a 'tag' tooltip. The text is given a default style according to @g.txtattr. See @Element.tag
- **
- > Parameters
- **
- - x (number) x coordinate of the center of the tag loop
- - y (number) y coordinate of the center of the tag loop
- - text (string) text to place inside the tag
- - angle (number) angle of orientation in degrees [default: `0`]
- - r (number) radius of the loop [default: `5`]
- **
- = (object) set containing the tag path and the text element
- > Usage
- | paper.tag(50, 50, "$9.99", 60);
- \*/
-Raphael.fn.tag = function (x, y, text, angle, r) {
- var set = this.set();
-
- text = this.text(x, y, text).attr(Raphael.g.txtattr);
- return set.push(text.tag(angle, r), text);
-};
-
-/*\
- * Paper.flag
- [ method ]
- **
- * Puts the given `text` into a 'flag' tooltip. The text is given a default style according to @g.txtattr. See @Element.flag
- **
- > Parameters
- **
- - x (number) x coordinate of the flag's point
- - y (number) y coordinate of the flag's point
- - text (string) text to place inside the flag
- - angle (number) angle of orientation in degrees [default: `0`]
- **
- = (object) set containing the flag path and the text element
- > Usage
- | paper.flag(50, 50, "$9.99", 60);
- \*/
-Raphael.fn.flag = function (x, y, text, angle) {
- var set = this.set();
-
- text = this.text(x, y, text).attr(Raphael.g.txtattr);
- return set.push(text.flag(angle), text);
-};
-
-/*\
- * Paper.drop
- [ method ]
- **
- * Puts the given text into a 'drop' tooltip. The text is given a default style according to @g.txtattr. See @Element.drop
- **
- > Parameters
- **
- - x (number) x coordinate of the drop's point
- - y (number) y coordinate of the drop's point
- - text (string) text to place inside the drop
- - angle (number) angle of orientation in degrees [default: `0`]
- **
- = (object) set containing the drop path and the text element
- > Usage
- | paper.drop(50, 50, "$9.99", 60);
- \*/
-Raphael.fn.drop = function (x, y, text, angle) {
- var set = this.set();
-
- text = this.text(x, y, text).attr(Raphael.g.txtattr);
- return set.push(text.drop(angle), text);
-};
-
-/*\
- * Paper.blob
- [ method ]
- **
- * Puts the given text into a 'blob' tooltip. The text is given a default style according to @g.txtattr. See @Element.blob
- **
- > Parameters
- **
- - x (number) x coordinate of the blob's tail
- - y (number) y coordinate of the blob's tail
- - text (string) text to place inside the blob
- - angle (number) angle of orientation in degrees [default: `0`]
- **
- = (object) set containing the blob path and the text element
- > Usage
- | paper.blob(50, 50, "$9.99", 60);
- \*/
-Raphael.fn.blob = function (x, y, text, angle) {
- var set = this.set();
-
- text = this.text(x, y, text).attr(Raphael.g.txtattr);
- return set.push(text.blob(angle), text);
-};
-
-/**
- * Brightness functions on the Element prototype
- */
-/*\
- * Element.lighter
- [ method ]
- **
- * Makes the context element lighter by increasing the brightness and reducing the saturation by a given factor. Can be called on Sets.
- **
- > Parameters
- **
- - times (number) adjustment factor [default: `2`]
- **
- = (object) Element
- > Usage
- | paper.circle(50, 50, 20).attr({
- | fill: "#ff0000",
- | stroke: "#fff",
- | "stroke-width": 2
- | }).lighter(6);
- \*/
-Raphael.el.lighter = function (times) {
- times = times || 2;
-
- var fs = [this.attrs.fill, this.attrs.stroke];
-
- this.fs = this.fs || [fs[0], fs[1]];
-
- fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
- fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
- fs[0].b = Math.min(fs[0].b * times, 1);
- fs[0].s = fs[0].s / times;
- fs[1].b = Math.min(fs[1].b * times, 1);
- fs[1].s = fs[1].s / times;
-
- this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
- return this;
-};
-
-/*\
- * Element.darker
- [ method ]
- **
- * Makes the context element darker by decreasing the brightness and increasing the saturation by a given factor. Can be called on Sets.
- **
- > Parameters
- **
- - times (number) adjustment factor [default: `2`]
- **
- = (object) Element
- > Usage
- | paper.circle(50, 50, 20).attr({
- | fill: "#ff0000",
- | stroke: "#fff",
- | "stroke-width": 2
- | }).darker(6);
- \*/
-Raphael.el.darker = function (times) {
- times = times || 2;
-
- var fs = [this.attrs.fill, this.attrs.stroke];
-
- this.fs = this.fs || [fs[0], fs[1]];
-
- fs[0] = Raphael.rgb2hsb(Raphael.getRGB(fs[0]).hex);
- fs[1] = Raphael.rgb2hsb(Raphael.getRGB(fs[1]).hex);
- fs[0].s = Math.min(fs[0].s * times, 1);
- fs[0].b = fs[0].b / times;
- fs[1].s = Math.min(fs[1].s * times, 1);
- fs[1].b = fs[1].b / times;
-
- this.attr({fill: "hsb(" + [fs[0].h, fs[0].s, fs[0].b] + ")", stroke: "hsb(" + [fs[1].h, fs[1].s, fs[1].b] + ")"});
- return this;
-};
-
-/*\
- * Element.resetBrightness
- [ method ]
- **
- * Resets brightness and saturation levels to their original values. See @Element.lighter and @Element.darker. Can be called on Sets.
- **
- = (object) Element
- > Usage
- | paper.circle(50, 50, 20).attr({
- | fill: "#ff0000",
- | stroke: "#fff",
- | "stroke-width": 2
- | }).lighter(6).resetBrightness();
- \*/
-Raphael.el.resetBrightness = function () {
- if (this.fs) {
- this.attr({ fill: this.fs[0], stroke: this.fs[1] });
- delete this.fs;
- }
- return this;
-};
-
-//alias to set prototype
-(function () {
- var brightness = ['lighter', 'darker', 'resetBrightness'],
- tooltips = ['popup', 'tag', 'flag', 'label', 'drop', 'blob'];
-
- for (var f in tooltips) (function (name) {
- Raphael.st[name] = function () {
- return Raphael.el[name].apply(this, arguments);
- };
- })(tooltips[f]);
-
- for (var f in brightness) (function (name) {
- Raphael.st[name] = function () {
- for (var i = 0; i < this.length; i++) {
- this[i][name].apply(this[i], arguments);
- }
-
- return this;
- };
- })(brightness[f]);
-})();
-
-//chart prototype for storing common functions
-Raphael.g = {
- /*\
- * g.shim
- [ object ]
- **
- * An attribute object that charts will set on all generated shims (shims being the invisible objects that mouse events are bound to)
- **
- > Default value
- | { stroke: 'none', fill: '#000', 'fill-opacity': 0 }
- \*/
- shim: { stroke: 'none', fill: '#000', 'fill-opacity': 0 },
-
- /*\
- * g.txtattr
- [ object ]
- **
- * An attribute object that charts and tooltips will set on any generated text
- **
- > Default value
- | { font: '12px Arial, sans-serif', fill: '#fff' }
- \*/
- txtattr: { font: '12px Arial, sans-serif', fill: '#fff' },
-
- /*\
- * g.colors
- [ array ]
- **
- * An array of color values that charts will iterate through when drawing chart data values.
- **
- \*/
- colors: (function () {
- var hues = [.6, .2, .05, .1333, .75, 0],
- colors = [];
-
- for (var i = 0; i < 10; i++) {
- if (i < hues.length) {
- colors.push('hsb(' + hues[i] + ',.75, .75)');
- } else {
- colors.push('hsb(' + hues[i - hues.length] + ', 1, .5)');
- }
- }
-
- return colors;
- })(),
-
- snapEnds: function(from, to, steps) {
- var f = from,
- t = to;
-
- if (f == t) {
- return {from: f, to: t, power: 0};
- }
-
- function round(a) {
- return Math.abs(a - .5) < .25 ? ~~(a) + .5 : Math.round(a);
- }
-
- var d = (t - f) / steps,
- r = ~~(d),
- R = r,
- i = 0;
-
- if (r) {
- while (R) {
- i--;
- R = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
- }
-
- i ++;
- } else {
- if(d == 0 || !isFinite(d)) {
- i = 1;
- } else {
- while (!r) {
- i = i || 1;
- r = ~~(d * Math.pow(10, i)) / Math.pow(10, i);
- i++;
- }
- }
-
- i && i--;
- }
-
- t = round(to * Math.pow(10, i)) / Math.pow(10, i);
-
- if (t < to) {
- t = round((to + .5) * Math.pow(10, i)) / Math.pow(10, i);
- }
-
- f = round((from - (i > 0 ? 0 : .5)) * Math.pow(10, i)) / Math.pow(10, i);
- return { from: f, to: t, power: i };
- },
-
- axis: function (x, y, length, from, to, steps, orientation, labels, type, dashsize, paper) {
- dashsize = dashsize == null ? 2 : dashsize;
- type = type || "t";
- steps = steps || 10;
- paper = arguments[arguments.length-1] //paper is always last argument
-
- var path = type == "|" || type == " " ? ["M", x + .5, y, "l", 0, .001] : orientation == 1 || orientation == 3 ? ["M", x + .5, y, "l", 0, -length] : ["M", x, y + .5, "l", length, 0],
- ends = this.snapEnds(from, to, steps),
- f = ends.from,
- t = ends.to,
- i = ends.power,
- j = 0,
- txtattr = { font: "11px 'Fontin Sans', Fontin-Sans, sans-serif" },
- text = paper.set(),
- d;
-
- d = (t - f) / steps;
-
- var label = f,
- rnd = i > 0 ? i : 0;
- dx = length / steps;
-
- if (+orientation == 1 || +orientation == 3) {
- var Y = y,
- addon = (orientation - 1 ? 1 : -1) * (dashsize + 3 + !!(orientation - 1));
-
- while (Y >= y - length) {
- type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), Y + .5, "l", dashsize * 2 + 1, 0]));
- text.push(paper.text(x + addon, Y, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
- label += d;
- Y -= dx;
- }
-
- if (Math.round(Y + dx - (y - length))) {
- type != "-" && type != " " && (path = path.concat(["M", x - (type == "+" || type == "|" ? dashsize : !(orientation - 1) * dashsize * 2), y - length + .5, "l", dashsize * 2 + 1, 0]));
- text.push(paper.text(x + addon, y - length, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr).attr({ "text-anchor": orientation - 1 ? "start" : "end" }));
- }
- } else {
- label = f;
- rnd = (i > 0) * i;
- addon = (orientation ? -1 : 1) * (dashsize + 9 + !orientation);
-
- var X = x,
- dx = length / steps,
- txt = 0,
- prev = 0;
-
- while (X <= x + length) {
- type != "-" && type != " " && (path = path.concat(["M", X + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
- text.push(txt = paper.text(X, y + addon, (labels && labels[j++]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
-
- var bb = txt.getBBox();
-
- if (prev >= bb.x - 5) {
- text.pop(text.length - 1).remove();
- } else {
- prev = bb.x + bb.width;
- }
-
- label += d;
- X += dx;
- }
-
- if (Math.round(X - dx - x - length)) {
- type != "-" && type != " " && (path = path.concat(["M", x + length + .5, y - (type == "+" ? dashsize : !!orientation * dashsize * 2), "l", 0, dashsize * 2 + 1]));
- text.push(paper.text(x + length, y + addon, (labels && labels[j]) || (Math.round(label) == label ? label : +label.toFixed(rnd))).attr(txtattr));
- }
- }
-
- var res = paper.path(path);
-
- res.text = text;
- res.all = paper.set([res, text]);
- res.remove = function () {
- this.text.remove();
- this.constructor.prototype.remove.call(this);
- };
-
- return res;
- },
-
- labelise: function(label, val, total) {
- if (label) {
- return (label + "").replace(/(##+(?:\.#+)?)|(%%+(?:\.%+)?)/g, function (all, value, percent) {
- if (value) {
- return (+val).toFixed(value.replace(/^#+\.?/g, "").length);
- }
- if (percent) {
- return (val * 100 / total).toFixed(percent.replace(/^%+\.?/g, "").length) + "%";
- }
- });
- } else {
- return (+val).toFixed(0);
- }
- }
-}
diff --git a/vendor/assets/javascripts/raphael.js b/vendor/assets/javascripts/raphael.js
deleted file mode 100644
index 3f3f8a0b7f6..00000000000
--- a/vendor/assets/javascripts/raphael.js
+++ /dev/null
@@ -1,8239 +0,0 @@
-// ┌────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël 2.1.4 - JavaScript Vector Library │ \\
-// ├────────────────────────────────────────────────────────────────────┤ \\
-// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
-// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com) │ \\
-// ├────────────────────────────────────────────────────────────────────┤ \\
-// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
-// └────────────────────────────────────────────────────────────────────┘ \\
-// Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-// ┌────────────────────────────────────────────────────────────┐ \\
-// │ Eve 0.4.2 - JavaScript Events Library │ \\
-// ├────────────────────────────────────────────────────────────┤ \\
-// │ Author Dmitry Baranovskiy (http://dmitry.baranovskiy.com/) │ \\
-// └────────────────────────────────────────────────────────────┘ \\
-
-(function (glob) {
- var version = "0.4.2",
- has = "hasOwnProperty",
- separator = /[\.\/]/,
- wildcard = "*",
- fun = function () {},
- numsort = function (a, b) {
- return a - b;
- },
- current_event,
- stop,
- events = {n: {}},
- /*\
- * eve
- [ method ]
-
- * Fires event with given `name`, given scope and other parameters.
-
- > Arguments
-
- - name (string) name of the *event*, dot (`.`) or slash (`/`) separated
- - scope (object) context for the event handlers
- - varargs (...) the rest of arguments will be sent to event handlers
-
- = (object) array of returned values from the listeners
- \*/
- eve = function (name, scope) {
- name = String(name);
- var e = events,
- oldstop = stop,
- args = Array.prototype.slice.call(arguments, 2),
- listeners = eve.listeners(name),
- z = 0,
- f = false,
- l,
- indexed = [],
- queue = {},
- out = [],
- ce = current_event,
- errors = [];
- current_event = name;
- stop = 0;
- for (var i = 0, ii = listeners.length; i < ii; i++) if ("zIndex" in listeners[i]) {
- indexed.push(listeners[i].zIndex);
- if (listeners[i].zIndex < 0) {
- queue[listeners[i].zIndex] = listeners[i];
- }
- }
- indexed.sort(numsort);
- while (indexed[z] < 0) {
- l = queue[indexed[z++]];
- out.push(l.apply(scope, args));
- if (stop) {
- stop = oldstop;
- return out;
- }
- }
- for (i = 0; i < ii; i++) {
- l = listeners[i];
- if ("zIndex" in l) {
- if (l.zIndex == indexed[z]) {
- out.push(l.apply(scope, args));
- if (stop) {
- break;
- }
- do {
- z++;
- l = queue[indexed[z]];
- l && out.push(l.apply(scope, args));
- if (stop) {
- break;
- }
- } while (l)
- } else {
- queue[l.zIndex] = l;
- }
- } else {
- out.push(l.apply(scope, args));
- if (stop) {
- break;
- }
- }
- }
- stop = oldstop;
- current_event = ce;
- return out.length ? out : null;
- };
- // Undocumented. Debug only.
- eve._events = events;
- /*\
- * eve.listeners
- [ method ]
-
- * Internal method which gives you array of all event handlers that will be triggered by the given `name`.
-
- > Arguments
-
- - name (string) name of the event, dot (`.`) or slash (`/`) separated
-
- = (array) array of event handlers
- \*/
- eve.listeners = function (name) {
- var names = name.split(separator),
- e = events,
- item,
- items,
- k,
- i,
- ii,
- j,
- jj,
- nes,
- es = [e],
- out = [];
- for (i = 0, ii = names.length; i < ii; i++) {
- nes = [];
- for (j = 0, jj = es.length; j < jj; j++) {
- e = es[j].n;
- items = [e[names[i]], e[wildcard]];
- k = 2;
- while (k--) {
- item = items[k];
- if (item) {
- nes.push(item);
- out = out.concat(item.f || []);
- }
- }
- }
- es = nes;
- }
- return out;
- };
-
- /*\
- * eve.on
- [ method ]
- **
- * Binds given event handler with a given name. You can use wildcards “`*`” for the names:
- | eve.on("*.under.*", f);
- | eve("mouse.under.floor"); // triggers f
- * Use @eve to trigger the listener.
- **
- > Arguments
- **
- - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
- - f (function) event handler function
- **
- = (function) returned function accepts a single numeric parameter that represents z-index of the handler. It is an optional feature and only used when you need to ensure that some subset of handlers will be invoked in a given order, despite of the order of assignment.
- > Example:
- | eve.on("mouse", eatIt)(2);
- | eve.on("mouse", scream);
- | eve.on("mouse", catchIt)(1);
- * This will ensure that `catchIt()` function will be called before `eatIt()`.
- *
- * If you want to put your handler before non-indexed handlers, specify a negative value.
- * Note: I assume most of the time you don’t need to worry about z-index, but it’s nice to have this feature “just in case”.
- \*/
- eve.on = function (name, f) {
- name = String(name);
- if (typeof f != "function") {
- return function () {};
- }
- var names = name.split(separator),
- e = events;
- for (var i = 0, ii = names.length; i < ii; i++) {
- e = e.n;
- e = e.hasOwnProperty(names[i]) && e[names[i]] || (e[names[i]] = {n: {}});
- }
- e.f = e.f || [];
- for (i = 0, ii = e.f.length; i < ii; i++) if (e.f[i] == f) {
- return fun;
- }
- e.f.push(f);
- return function (zIndex) {
- if (+zIndex == +zIndex) {
- f.zIndex = +zIndex;
- }
- };
- };
- /*\
- * eve.f
- [ method ]
- **
- * Returns function that will fire given event with optional arguments.
- * Arguments that will be passed to the result function will be also
- * concated to the list of final arguments.
- | el.onclick = eve.f("click", 1, 2);
- | eve.on("click", function (a, b, c) {
- | console.log(a, b, c); // 1, 2, [event object]
- | });
- > Arguments
- - event (string) event name
- - varargs (…) and any other arguments
- = (function) possible event handler function
- \*/
- eve.f = function (event) {
- var attrs = [].slice.call(arguments, 1);
- return function () {
- eve.apply(null, [event, null].concat(attrs).concat([].slice.call(arguments, 0)));
- };
- };
- /*\
- * eve.stop
- [ method ]
- **
- * Is used inside an event handler to stop the event, preventing any subsequent listeners from firing.
- \*/
- eve.stop = function () {
- stop = 1;
- };
- /*\
- * eve.nt
- [ method ]
- **
- * Could be used inside event handler to figure out actual name of the event.
- **
- > Arguments
- **
- - subname (string) #optional subname of the event
- **
- = (string) name of the event, if `subname` is not specified
- * or
- = (boolean) `true`, if current event’s name contains `subname`
- \*/
- eve.nt = function (subname) {
- if (subname) {
- return new RegExp("(?:\\.|\\/|^)" + subname + "(?:\\.|\\/|$)").test(current_event);
- }
- return current_event;
- };
- /*\
- * eve.nts
- [ method ]
- **
- * Could be used inside event handler to figure out actual name of the event.
- **
- **
- = (array) names of the event
- \*/
- eve.nts = function () {
- return current_event.split(separator);
- };
- /*\
- * eve.off
- [ method ]
- **
- * Removes given function from the list of event listeners assigned to given name.
- * If no arguments specified all the events will be cleared.
- **
- > Arguments
- **
- - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
- - f (function) event handler function
- \*/
- /*\
- * eve.unbind
- [ method ]
- **
- * See @eve.off
- \*/
- eve.off = eve.unbind = function (name, f) {
- if (!name) {
- eve._events = events = {n: {}};
- return;
- }
- var names = name.split(separator),
- e,
- key,
- splice,
- i, ii, j, jj,
- cur = [events];
- for (i = 0, ii = names.length; i < ii; i++) {
- for (j = 0; j < cur.length; j += splice.length - 2) {
- splice = [j, 1];
- e = cur[j].n;
- if (names[i] != wildcard) {
- if (e[names[i]]) {
- splice.push(e[names[i]]);
- }
- } else {
- for (key in e) if (e[has](key)) {
- splice.push(e[key]);
- }
- }
- cur.splice.apply(cur, splice);
- }
- }
- for (i = 0, ii = cur.length; i < ii; i++) {
- e = cur[i];
- while (e.n) {
- if (f) {
- if (e.f) {
- for (j = 0, jj = e.f.length; j < jj; j++) if (e.f[j] == f) {
- e.f.splice(j, 1);
- break;
- }
- !e.f.length && delete e.f;
- }
- for (key in e.n) if (e.n[has](key) && e.n[key].f) {
- var funcs = e.n[key].f;
- for (j = 0, jj = funcs.length; j < jj; j++) if (funcs[j] == f) {
- funcs.splice(j, 1);
- break;
- }
- !funcs.length && delete e.n[key].f;
- }
- } else {
- delete e.f;
- for (key in e.n) if (e.n[has](key) && e.n[key].f) {
- delete e.n[key].f;
- }
- }
- e = e.n;
- }
- }
- };
- /*\
- * eve.once
- [ method ]
- **
- * Binds given event handler with a given name to only run once then unbind itself.
- | eve.once("login", f);
- | eve("login"); // triggers f
- | eve("login"); // no listeners
- * Use @eve to trigger the listener.
- **
- > Arguments
- **
- - name (string) name of the event, dot (`.`) or slash (`/`) separated, with optional wildcards
- - f (function) event handler function
- **
- = (function) same return function as @eve.on
- \*/
- eve.once = function (name, f) {
- var f2 = function () {
- eve.unbind(name, f2);
- return f.apply(this, arguments);
- };
- return eve.on(name, f2);
- };
- /*\
- * eve.version
- [ property (string) ]
- **
- * Current version of the library.
- \*/
- eve.version = version;
- eve.toString = function () {
- return "You are running Eve " + version;
- };
- (typeof module != "undefined" && module.exports) ? (module.exports = eve) : (typeof define != "undefined" ? (define("eve", [], function() { return eve; })) : (glob.eve = eve));
-})(window || this);
-// ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ "Raphaël 2.1.2" - JavaScript Vector Library │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
-// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com) │ \\
-// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
-// └─────────────────────────────────────────────────────────────────────┘ \\
-
-(function (glob, factory) {
- // AMD support
- if (typeof define === "function" && define.amd) {
- // Define as an anonymous module
- define(["eve"], function( eve ) {
- return factory(glob, eve);
- });
- } else {
- // Browser globals (glob is window)
- // Raphael adds itself to window
- factory(glob, glob.eve || (typeof require == "function" && require('eve')) );
- }
-}(this, function (window, eve) {
- /*\
- * Raphael
- [ method ]
- **
- * Creates a canvas object on which to draw.
- * You must do this first, as all future calls to drawing methods
- * from this instance will be bound to this canvas.
- > Parameters
- **
- - container (HTMLElement|string) DOM element or its ID which is going to be a parent for drawing surface
- - width (number)
- - height (number)
- - callback (function) #optional callback function which is going to be executed in the context of newly created paper
- * or
- - x (number)
- - y (number)
- - width (number)
- - height (number)
- - callback (function) #optional callback function which is going to be executed in the context of newly created paper
- * or
- - all (array) (first 3 or 4 elements in the array are equal to [containerID, width, height] or [x, y, width, height]. The rest are element descriptions in format {type: type, <attributes>}). See @Paper.add.
- - callback (function) #optional callback function which is going to be executed in the context of newly created paper
- * or
- - onReadyCallback (function) function that is going to be called on DOM ready event. You can also subscribe to this event via Eve’s “DOMLoad” event. In this case method returns `undefined`.
- = (object) @Paper
- > Usage
- | // Each of the following examples create a canvas
- | // that is 320px wide by 200px high.
- | // Canvas is created at the viewport’s 10,50 coordinate.
- | var paper = Raphael(10, 50, 320, 200);
- | // Canvas is created at the top left corner of the #notepad element
- | // (or its top right corner in dir="rtl" elements)
- | var paper = Raphael(document.getElementById("notepad"), 320, 200);
- | // Same as above
- | var paper = Raphael("notepad", 320, 200);
- | // Image dump
- | var set = Raphael(["notepad", 320, 200, {
- | type: "rect",
- | x: 10,
- | y: 10,
- | width: 25,
- | height: 25,
- | stroke: "#f00"
- | }, {
- | type: "text",
- | x: 30,
- | y: 40,
- | text: "Dump"
- | }]);
- \*/
- function R(first) {
- if (R.is(first, "function")) {
- return loaded ? first() : eve.on("raphael.DOMload", first);
- } else if (R.is(first, array)) {
- return R._engine.create[apply](R, first.splice(0, 3 + R.is(first[0], nu))).add(first);
- } else {
- var args = Array.prototype.slice.call(arguments, 0);
- if (R.is(args[args.length - 1], "function")) {
- var f = args.pop();
- return loaded ? f.call(R._engine.create[apply](R, args)) : eve.on("raphael.DOMload", function () {
- f.call(R._engine.create[apply](R, args));
- });
- } else {
- return R._engine.create[apply](R, arguments);
- }
- }
- }
- R.version = "2.1.2";
- R.eve = eve;
- var loaded,
- separator = /[, ]+/,
- elements = {circle: 1, rect: 1, path: 1, ellipse: 1, text: 1, image: 1},
- formatrg = /\{(\d+)\}/g,
- proto = "prototype",
- has = "hasOwnProperty",
- g = {
- doc: document,
- win: window
- },
- oldRaphael = {
- was: Object.prototype[has].call(g.win, "Raphael"),
- is: g.win.Raphael
- },
- Paper = function () {
- /*\
- * Paper.ca
- [ property (object) ]
- **
- * Shortcut for @Paper.customAttributes
- \*/
- /*\
- * Paper.customAttributes
- [ property (object) ]
- **
- * If you have a set of attributes that you would like to represent
- * as a function of some number you can do it easily with custom attributes:
- > Usage
- | paper.customAttributes.hue = function (num) {
- | num = num % 1;
- | return {fill: "hsb(" + num + ", 0.75, 1)"};
- | };
- | // Custom attribute “hue” will change fill
- | // to be given hue with fixed saturation and brightness.
- | // Now you can use it like this:
- | var c = paper.circle(10, 10, 10).attr({hue: .45});
- | // or even like this:
- | c.animate({hue: 1}, 1e3);
- |
- | // You could also create custom attribute
- | // with multiple parameters:
- | paper.customAttributes.hsb = function (h, s, b) {
- | return {fill: "hsb(" + [h, s, b].join(",") + ")"};
- | };
- | c.attr({hsb: "0.5 .8 1"});
- | c.animate({hsb: [1, 0, 0.5]}, 1e3);
- \*/
- this.ca = this.customAttributes = {};
- },
- paperproto,
- appendChild = "appendChild",
- apply = "apply",
- concat = "concat",
- supportsTouch = ('ontouchstart' in g.win) || g.win.DocumentTouch && g.doc instanceof DocumentTouch, //taken from Modernizr touch test
- E = "",
- S = " ",
- Str = String,
- split = "split",
- events = "click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[split](S),
- touchMap = {
- mousedown: "touchstart",
- mousemove: "touchmove",
- mouseup: "touchend"
- },
- lowerCase = Str.prototype.toLowerCase,
- math = Math,
- mmax = math.max,
- mmin = math.min,
- abs = math.abs,
- pow = math.pow,
- PI = math.PI,
- nu = "number",
- string = "string",
- array = "array",
- toString = "toString",
- fillString = "fill",
- objectToString = Object.prototype.toString,
- paper = {},
- push = "push",
- ISURL = R._ISURL = /^url\(['"]?(.+?)['"]?\)$/i,
- colourRegExp = /^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,
- isnan = {"NaN": 1, "Infinity": 1, "-Infinity": 1},
- bezierrg = /^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,
- round = math.round,
- setAttribute = "setAttribute",
- toFloat = parseFloat,
- toInt = parseInt,
- upperCase = Str.prototype.toUpperCase,
- availableAttrs = R._availableAttrs = {
- "arrow-end": "none",
- "arrow-start": "none",
- blur: 0,
- "clip-rect": "0 0 1e9 1e9",
- cursor: "default",
- cx: 0,
- cy: 0,
- fill: "#fff",
- "fill-opacity": 1,
- font: '10px "Arial"',
- "font-family": '"Arial"',
- "font-size": "10",
- "font-style": "normal",
- "font-weight": 400,
- gradient: 0,
- height: 0,
- href: "http://raphaeljs.com/",
- "letter-spacing": 0,
- opacity: 1,
- path: "M0,0",
- r: 0,
- rx: 0,
- ry: 0,
- src: "",
- stroke: "#000",
- "stroke-dasharray": "",
- "stroke-linecap": "butt",
- "stroke-linejoin": "butt",
- "stroke-miterlimit": 0,
- "stroke-opacity": 1,
- "stroke-width": 1,
- target: "_blank",
- "text-anchor": "middle",
- title: "Raphael",
- transform: "",
- width: 0,
- x: 0,
- y: 0
- },
- availableAnimAttrs = R._availableAnimAttrs = {
- blur: nu,
- "clip-rect": "csv",
- cx: nu,
- cy: nu,
- fill: "colour",
- "fill-opacity": nu,
- "font-size": nu,
- height: nu,
- opacity: nu,
- path: "path",
- r: nu,
- rx: nu,
- ry: nu,
- stroke: "colour",
- "stroke-opacity": nu,
- "stroke-width": nu,
- transform: "transform",
- width: nu,
- x: nu,
- y: nu
- },
- whitespace = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]/g,
- commaSpaces = /[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,
- hsrg = {hs: 1, rg: 1},
- p2s = /,?([achlmqrstvxz]),?/gi,
- pathCommand = /([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
- tCommand = /([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/ig,
- pathValues = /(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/ig,
- radial_gradient = R._radial_gradient = /^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,
- eldata = {},
- sortByKey = function (a, b) {
- return a.key - b.key;
- },
- sortByNumber = function (a, b) {
- return toFloat(a) - toFloat(b);
- },
- fun = function () {},
- pipe = function (x) {
- return x;
- },
- rectPath = R._rectPath = function (x, y, w, h, r) {
- if (r) {
- return [["M", x + r, y], ["l", w - r * 2, 0], ["a", r, r, 0, 0, 1, r, r], ["l", 0, h - r * 2], ["a", r, r, 0, 0, 1, -r, r], ["l", r * 2 - w, 0], ["a", r, r, 0, 0, 1, -r, -r], ["l", 0, r * 2 - h], ["a", r, r, 0, 0, 1, r, -r], ["z"]];
- }
- return [["M", x, y], ["l", w, 0], ["l", 0, h], ["l", -w, 0], ["z"]];
- },
- ellipsePath = function (x, y, rx, ry) {
- if (ry == null) {
- ry = rx;
- }
- return [["M", x, y], ["m", 0, -ry], ["a", rx, ry, 0, 1, 1, 0, 2 * ry], ["a", rx, ry, 0, 1, 1, 0, -2 * ry], ["z"]];
- },
- getPath = R._getPath = {
- path: function (el) {
- return el.attr("path");
- },
- circle: function (el) {
- var a = el.attrs;
- return ellipsePath(a.cx, a.cy, a.r);
- },
- ellipse: function (el) {
- var a = el.attrs;
- return ellipsePath(a.cx, a.cy, a.rx, a.ry);
- },
- rect: function (el) {
- var a = el.attrs;
- return rectPath(a.x, a.y, a.width, a.height, a.r);
- },
- image: function (el) {
- var a = el.attrs;
- return rectPath(a.x, a.y, a.width, a.height);
- },
- text: function (el) {
- var bbox = el._getBBox();
- return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
- },
- set : function(el) {
- var bbox = el._getBBox();
- return rectPath(bbox.x, bbox.y, bbox.width, bbox.height);
- }
- },
- /*\
- * Raphael.mapPath
- [ method ]
- **
- * Transform the path string with given matrix.
- > Parameters
- - path (string) path string
- - matrix (object) see @Matrix
- = (string) transformed path string
- \*/
- mapPath = R.mapPath = function (path, matrix) {
- if (!matrix) {
- return path;
- }
- var x, y, i, j, ii, jj, pathi;
- path = path2curve(path);
- for (i = 0, ii = path.length; i < ii; i++) {
- pathi = path[i];
- for (j = 1, jj = pathi.length; j < jj; j += 2) {
- x = matrix.x(pathi[j], pathi[j + 1]);
- y = matrix.y(pathi[j], pathi[j + 1]);
- pathi[j] = x;
- pathi[j + 1] = y;
- }
- }
- return path;
- };
-
- R._g = g;
- /*\
- * Raphael.type
- [ property (string) ]
- **
- * Can be “SVG”, “VML” or empty, depending on browser support.
- \*/
- R.type = (g.win.SVGAngle || g.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1") ? "SVG" : "VML");
- if (R.type == "VML") {
- var d = g.doc.createElement("div"),
- b;
- d.innerHTML = '<v:shape adj="1"/>';
- b = d.firstChild;
- b.style.behavior = "url(#default#VML)";
- if (!(b && typeof b.adj == "object")) {
- return (R.type = E);
- }
- d = null;
- }
- /*\
- * Raphael.svg
- [ property (boolean) ]
- **
- * `true` if browser supports SVG.
- \*/
- /*\
- * Raphael.vml
- [ property (boolean) ]
- **
- * `true` if browser supports VML.
- \*/
- R.svg = !(R.vml = R.type == "VML");
- R._Paper = Paper;
- /*\
- * Raphael.fn
- [ property (object) ]
- **
- * You can add your own method to the canvas. For example if you want to draw a pie chart,
- * you can create your own pie chart function and ship it as a Raphaël plugin. To do this
- * you need to extend the `Raphael.fn` object. You should modify the `fn` object before a
- * Raphaël instance is created, otherwise it will take no effect. Please note that the
- * ability for namespaced plugins was removed in Raphael 2.0. It is up to the plugin to
- * ensure any namespacing ensures proper context.
- > Usage
- | Raphael.fn.arrow = function (x1, y1, x2, y2, size) {
- | return this.path( ... );
- | };
- | // or create namespace
- | Raphael.fn.mystuff = {
- | arrow: function () {…},
- | star: function () {…},
- | // etc…
- | };
- | var paper = Raphael(10, 10, 630, 480);
- | // then use it
- | paper.arrow(10, 10, 30, 30, 5).attr({fill: "#f00"});
- | paper.mystuff.arrow();
- | paper.mystuff.star();
- \*/
- R.fn = paperproto = Paper.prototype = R.prototype;
- R._id = 0;
- R._oid = 0;
- /*\
- * Raphael.is
- [ method ]
- **
- * Handful of replacements for `typeof` operator.
- > Parameters
- - o (…) any object or primitive
- - type (string) name of the type, i.e. “string”, “function”, “number”, etc.
- = (boolean) is given value is of given type
- \*/
- R.is = function (o, type) {
- type = lowerCase.call(type);
- if (type == "finite") {
- return !isnan[has](+o);
- }
- if (type == "array") {
- return o instanceof Array;
- }
- return (type == "null" && o === null) ||
- (type == typeof o && o !== null) ||
- (type == "object" && o === Object(o)) ||
- (type == "array" && Array.isArray && Array.isArray(o)) ||
- objectToString.call(o).slice(8, -1).toLowerCase() == type;
- };
-
- function clone(obj) {
- if (typeof obj == "function" || Object(obj) !== obj) {
- return obj;
- }
- var res = new obj.constructor;
- for (var key in obj) if (obj[has](key)) {
- res[key] = clone(obj[key]);
- }
- return res;
- }
-
- /*\
- * Raphael.angle
- [ method ]
- **
- * Returns angle between two or three points
- > Parameters
- - x1 (number) x coord of first point
- - y1 (number) y coord of first point
- - x2 (number) x coord of second point
- - y2 (number) y coord of second point
- - x3 (number) #optional x coord of third point
- - y3 (number) #optional y coord of third point
- = (number) angle in degrees.
- \*/
- R.angle = function (x1, y1, x2, y2, x3, y3) {
- if (x3 == null) {
- var x = x1 - x2,
- y = y1 - y2;
- if (!x && !y) {
- return 0;
- }
- return (180 + math.atan2(-y, -x) * 180 / PI + 360) % 360;
- } else {
- return R.angle(x1, y1, x3, y3) - R.angle(x2, y2, x3, y3);
- }
- };
- /*\
- * Raphael.rad
- [ method ]
- **
- * Transform angle to radians
- > Parameters
- - deg (number) angle in degrees
- = (number) angle in radians.
- \*/
- R.rad = function (deg) {
- return deg % 360 * PI / 180;
- };
- /*\
- * Raphael.deg
- [ method ]
- **
- * Transform angle to degrees
- > Parameters
- - rad (number) angle in radians
- = (number) angle in degrees.
- \*/
- R.deg = function (rad) {
- return Math.round ((rad * 180 / PI% 360)* 1000) / 1000;
- };
- /*\
- * Raphael.snapTo
- [ method ]
- **
- * Snaps given value to given grid.
- > Parameters
- - values (array|number) given array of values or step of the grid
- - value (number) value to adjust
- - tolerance (number) #optional tolerance for snapping. Default is `10`.
- = (number) adjusted value.
- \*/
- R.snapTo = function (values, value, tolerance) {
- tolerance = R.is(tolerance, "finite") ? tolerance : 10;
- if (R.is(values, array)) {
- var i = values.length;
- while (i--) if (abs(values[i] - value) <= tolerance) {
- return values[i];
- }
- } else {
- values = +values;
- var rem = value % values;
- if (rem < tolerance) {
- return value - rem;
- }
- if (rem > values - tolerance) {
- return value - rem + values;
- }
- }
- return value;
- };
-
- /*\
- * Raphael.createUUID
- [ method ]
- **
- * Returns RFC4122, version 4 ID
- \*/
- var createUUID = R.createUUID = (function (uuidRegEx, uuidReplacer) {
- return function () {
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(uuidRegEx, uuidReplacer).toUpperCase();
- };
- })(/[xy]/g, function (c) {
- var r = math.random() * 16 | 0,
- v = c == "x" ? r : (r & 3 | 8);
- return v.toString(16);
- });
-
- /*\
- * Raphael.setWindow
- [ method ]
- **
- * Used when you need to draw in `&lt;iframe>`. Switched window to the iframe one.
- > Parameters
- - newwin (window) new window object
- \*/
- R.setWindow = function (newwin) {
- eve("raphael.setWindow", R, g.win, newwin);
- g.win = newwin;
- g.doc = g.win.document;
- if (R._engine.initWin) {
- R._engine.initWin(g.win);
- }
- };
- var toHex = function (color) {
- if (R.vml) {
- // http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/
- var trim = /^\s+|\s+$/g;
- var bod;
- try {
- var docum = new ActiveXObject("htmlfile");
- docum.write("<body>");
- docum.close();
- bod = docum.body;
- } catch(e) {
- bod = createPopup().document.body;
- }
- var range = bod.createTextRange();
- toHex = cacher(function (color) {
- try {
- bod.style.color = Str(color).replace(trim, E);
- var value = range.queryCommandValue("ForeColor");
- value = ((value & 255) << 16) | (value & 65280) | ((value & 16711680) >>> 16);
- return "#" + ("000000" + value.toString(16)).slice(-6);
- } catch(e) {
- return "none";
- }
- });
- } else {
- var i = g.doc.createElement("i");
- i.title = "Rapha\xebl Colour Picker";
- i.style.display = "none";
- g.doc.body.appendChild(i);
- toHex = cacher(function (color) {
- i.style.color = color;
- return g.doc.defaultView.getComputedStyle(i, E).getPropertyValue("color");
- });
- }
- return toHex(color);
- },
- hsbtoString = function () {
- return "hsb(" + [this.h, this.s, this.b] + ")";
- },
- hsltoString = function () {
- return "hsl(" + [this.h, this.s, this.l] + ")";
- },
- rgbtoString = function () {
- return this.hex;
- },
- prepareRGB = function (r, g, b) {
- if (g == null && R.is(r, "object") && "r" in r && "g" in r && "b" in r) {
- b = r.b;
- g = r.g;
- r = r.r;
- }
- if (g == null && R.is(r, string)) {
- var clr = R.getRGB(r);
- r = clr.r;
- g = clr.g;
- b = clr.b;
- }
- if (r > 1 || g > 1 || b > 1) {
- r /= 255;
- g /= 255;
- b /= 255;
- }
-
- return [r, g, b];
- },
- packageRGB = function (r, g, b, o) {
- r *= 255;
- g *= 255;
- b *= 255;
- var rgb = {
- r: r,
- g: g,
- b: b,
- hex: R.rgb(r, g, b),
- toString: rgbtoString
- };
- R.is(o, "finite") && (rgb.opacity = o);
- return rgb;
- };
-
- /*\
- * Raphael.color
- [ method ]
- **
- * Parses the color string and returns object with all values for the given color.
- > Parameters
- - clr (string) color string in one of the supported formats (see @Raphael.getRGB)
- = (object) Combined RGB & HSB object in format:
- o {
- o r (number) red,
- o g (number) green,
- o b (number) blue,
- o hex (string) color in HTML/CSS format: #••••••,
- o error (boolean) `true` if string can’t be parsed,
- o h (number) hue,
- o s (number) saturation,
- o v (number) value (brightness),
- o l (number) lightness
- o }
- \*/
- R.color = function (clr) {
- var rgb;
- if (R.is(clr, "object") && "h" in clr && "s" in clr && "b" in clr) {
- rgb = R.hsb2rgb(clr);
- clr.r = rgb.r;
- clr.g = rgb.g;
- clr.b = rgb.b;
- clr.hex = rgb.hex;
- } else if (R.is(clr, "object") && "h" in clr && "s" in clr && "l" in clr) {
- rgb = R.hsl2rgb(clr);
- clr.r = rgb.r;
- clr.g = rgb.g;
- clr.b = rgb.b;
- clr.hex = rgb.hex;
- } else {
- if (R.is(clr, "string")) {
- clr = R.getRGB(clr);
- }
- if (R.is(clr, "object") && "r" in clr && "g" in clr && "b" in clr) {
- rgb = R.rgb2hsl(clr);
- clr.h = rgb.h;
- clr.s = rgb.s;
- clr.l = rgb.l;
- rgb = R.rgb2hsb(clr);
- clr.v = rgb.b;
- } else {
- clr = {hex: "none"};
- clr.r = clr.g = clr.b = clr.h = clr.s = clr.v = clr.l = -1;
- }
- }
- clr.toString = rgbtoString;
- return clr;
- };
- /*\
- * Raphael.hsb2rgb
- [ method ]
- **
- * Converts HSB values to RGB object.
- > Parameters
- - h (number) hue
- - s (number) saturation
- - v (number) value or brightness
- = (object) RGB object in format:
- o {
- o r (number) red,
- o g (number) green,
- o b (number) blue,
- o hex (string) color in HTML/CSS format: #••••••
- o }
- \*/
- R.hsb2rgb = function (h, s, v, o) {
- if (this.is(h, "object") && "h" in h && "s" in h && "b" in h) {
- v = h.b;
- s = h.s;
- o = h.o;
- h = h.h;
- }
- h *= 360;
- var R, G, B, X, C;
- h = (h % 360) / 60;
- C = v * s;
- X = C * (1 - abs(h % 2 - 1));
- R = G = B = v - C;
-
- h = ~~h;
- R += [C, X, 0, 0, X, C][h];
- G += [X, C, C, X, 0, 0][h];
- B += [0, 0, X, C, C, X][h];
- return packageRGB(R, G, B, o);
- };
- /*\
- * Raphael.hsl2rgb
- [ method ]
- **
- * Converts HSL values to RGB object.
- > Parameters
- - h (number) hue
- - s (number) saturation
- - l (number) luminosity
- = (object) RGB object in format:
- o {
- o r (number) red,
- o g (number) green,
- o b (number) blue,
- o hex (string) color in HTML/CSS format: #••••••
- o }
- \*/
- R.hsl2rgb = function (h, s, l, o) {
- if (this.is(h, "object") && "h" in h && "s" in h && "l" in h) {
- l = h.l;
- s = h.s;
- h = h.h;
- }
- if (h > 1 || s > 1 || l > 1) {
- h /= 360;
- s /= 100;
- l /= 100;
- }
- h *= 360;
- var R, G, B, X, C;
- h = (h % 360) / 60;
- C = 2 * s * (l < .5 ? l : 1 - l);
- X = C * (1 - abs(h % 2 - 1));
- R = G = B = l - C / 2;
-
- h = ~~h;
- R += [C, X, 0, 0, X, C][h];
- G += [X, C, C, X, 0, 0][h];
- B += [0, 0, X, C, C, X][h];
- return packageRGB(R, G, B, o);
- };
- /*\
- * Raphael.rgb2hsb
- [ method ]
- **
- * Converts RGB values to HSB object.
- > Parameters
- - r (number) red
- - g (number) green
- - b (number) blue
- = (object) HSB object in format:
- o {
- o h (number) hue
- o s (number) saturation
- o b (number) brightness
- o }
- \*/
- R.rgb2hsb = function (r, g, b) {
- b = prepareRGB(r, g, b);
- r = b[0];
- g = b[1];
- b = b[2];
-
- var H, S, V, C;
- V = mmax(r, g, b);
- C = V - mmin(r, g, b);
- H = (C == 0 ? null :
- V == r ? (g - b) / C :
- V == g ? (b - r) / C + 2 :
- (r - g) / C + 4
- );
- H = ((H + 360) % 6) * 60 / 360;
- S = C == 0 ? 0 : C / V;
- return {h: H, s: S, b: V, toString: hsbtoString};
- };
- /*\
- * Raphael.rgb2hsl
- [ method ]
- **
- * Converts RGB values to HSL object.
- > Parameters
- - r (number) red
- - g (number) green
- - b (number) blue
- = (object) HSL object in format:
- o {
- o h (number) hue
- o s (number) saturation
- o l (number) luminosity
- o }
- \*/
- R.rgb2hsl = function (r, g, b) {
- b = prepareRGB(r, g, b);
- r = b[0];
- g = b[1];
- b = b[2];
-
- var H, S, L, M, m, C;
- M = mmax(r, g, b);
- m = mmin(r, g, b);
- C = M - m;
- H = (C == 0 ? null :
- M == r ? (g - b) / C :
- M == g ? (b - r) / C + 2 :
- (r - g) / C + 4);
- H = ((H + 360) % 6) * 60 / 360;
- L = (M + m) / 2;
- S = (C == 0 ? 0 :
- L < .5 ? C / (2 * L) :
- C / (2 - 2 * L));
- return {h: H, s: S, l: L, toString: hsltoString};
- };
- R._path2string = function () {
- return this.join(",").replace(p2s, "$1");
- };
- function repush(array, item) {
- for (var i = 0, ii = array.length; i < ii; i++) if (array[i] === item) {
- return array.push(array.splice(i, 1)[0]);
- }
- }
- function cacher(f, scope, postprocessor) {
- function newf() {
- var arg = Array.prototype.slice.call(arguments, 0),
- args = arg.join("\u2400"),
- cache = newf.cache = newf.cache || {},
- count = newf.count = newf.count || [];
- if (cache[has](args)) {
- repush(count, args);
- return postprocessor ? postprocessor(cache[args]) : cache[args];
- }
- count.length >= 1e3 && delete cache[count.shift()];
- count.push(args);
- cache[args] = f[apply](scope, arg);
- return postprocessor ? postprocessor(cache[args]) : cache[args];
- }
- return newf;
- }
-
- var preload = R._preload = function (src, f) {
- var img = g.doc.createElement("img");
- img.style.cssText = "position:absolute;left:-9999em;top:-9999em";
- img.onload = function () {
- f.call(this);
- this.onload = null;
- g.doc.body.removeChild(this);
- };
- img.onerror = function () {
- g.doc.body.removeChild(this);
- };
- g.doc.body.appendChild(img);
- img.src = src;
- };
-
- function clrToString() {
- return this.hex;
- }
-
- /*\
- * Raphael.getRGB
- [ method ]
- **
- * Parses colour string as RGB object
- > Parameters
- - colour (string) colour string in one of formats:
- # <ul>
- # <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
- # <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
- # <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
- # <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
- # <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
- # <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
- # <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
- # <li>hsl(•••, •••, •••) — same as hsb</li>
- # <li>hsl(•••%, •••%, •••%) — same as hsb</li>
- # </ul>
- = (object) RGB object in format:
- o {
- o r (number) red,
- o g (number) green,
- o b (number) blue
- o hex (string) color in HTML/CSS format: #••••••,
- o error (boolean) true if string can’t be parsed
- o }
- \*/
- R.getRGB = cacher(function (colour) {
- if (!colour || !!((colour = Str(colour)).indexOf("-") + 1)) {
- return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
- }
- if (colour == "none") {
- return {r: -1, g: -1, b: -1, hex: "none", toString: clrToString};
- }
- !(hsrg[has](colour.toLowerCase().substring(0, 2)) || colour.charAt() == "#") && (colour = toHex(colour));
- var res,
- red,
- green,
- blue,
- opacity,
- t,
- values,
- rgb = colour.match(colourRegExp);
- if (rgb) {
- if (rgb[2]) {
- blue = toInt(rgb[2].substring(5), 16);
- green = toInt(rgb[2].substring(3, 5), 16);
- red = toInt(rgb[2].substring(1, 3), 16);
- }
- if (rgb[3]) {
- blue = toInt((t = rgb[3].charAt(3)) + t, 16);
- green = toInt((t = rgb[3].charAt(2)) + t, 16);
- red = toInt((t = rgb[3].charAt(1)) + t, 16);
- }
- if (rgb[4]) {
- values = rgb[4][split](commaSpaces);
- red = toFloat(values[0]);
- values[0].slice(-1) == "%" && (red *= 2.55);
- green = toFloat(values[1]);
- values[1].slice(-1) == "%" && (green *= 2.55);
- blue = toFloat(values[2]);
- values[2].slice(-1) == "%" && (blue *= 2.55);
- rgb[1].toLowerCase().slice(0, 4) == "rgba" && (opacity = toFloat(values[3]));
- values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
- }
- if (rgb[5]) {
- values = rgb[5][split](commaSpaces);
- red = toFloat(values[0]);
- values[0].slice(-1) == "%" && (red *= 2.55);
- green = toFloat(values[1]);
- values[1].slice(-1) == "%" && (green *= 2.55);
- blue = toFloat(values[2]);
- values[2].slice(-1) == "%" && (blue *= 2.55);
- (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
- rgb[1].toLowerCase().slice(0, 4) == "hsba" && (opacity = toFloat(values[3]));
- values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
- return R.hsb2rgb(red, green, blue, opacity);
- }
- if (rgb[6]) {
- values = rgb[6][split](commaSpaces);
- red = toFloat(values[0]);
- values[0].slice(-1) == "%" && (red *= 2.55);
- green = toFloat(values[1]);
- values[1].slice(-1) == "%" && (green *= 2.55);
- blue = toFloat(values[2]);
- values[2].slice(-1) == "%" && (blue *= 2.55);
- (values[0].slice(-3) == "deg" || values[0].slice(-1) == "\xb0") && (red /= 360);
- rgb[1].toLowerCase().slice(0, 4) == "hsla" && (opacity = toFloat(values[3]));
- values[3] && values[3].slice(-1) == "%" && (opacity /= 100);
- return R.hsl2rgb(red, green, blue, opacity);
- }
- rgb = {r: red, g: green, b: blue, toString: clrToString};
- rgb.hex = "#" + (16777216 | blue | (green << 8) | (red << 16)).toString(16).slice(1);
- R.is(opacity, "finite") && (rgb.opacity = opacity);
- return rgb;
- }
- return {r: -1, g: -1, b: -1, hex: "none", error: 1, toString: clrToString};
- }, R);
- /*\
- * Raphael.hsb
- [ method ]
- **
- * Converts HSB values to hex representation of the colour.
- > Parameters
- - h (number) hue
- - s (number) saturation
- - b (number) value or brightness
- = (string) hex representation of the colour.
- \*/
- R.hsb = cacher(function (h, s, b) {
- return R.hsb2rgb(h, s, b).hex;
- });
- /*\
- * Raphael.hsl
- [ method ]
- **
- * Converts HSL values to hex representation of the colour.
- > Parameters
- - h (number) hue
- - s (number) saturation
- - l (number) luminosity
- = (string) hex representation of the colour.
- \*/
- R.hsl = cacher(function (h, s, l) {
- return R.hsl2rgb(h, s, l).hex;
- });
- /*\
- * Raphael.rgb
- [ method ]
- **
- * Converts RGB values to hex representation of the colour.
- > Parameters
- - r (number) red
- - g (number) green
- - b (number) blue
- = (string) hex representation of the colour.
- \*/
- R.rgb = cacher(function (r, g, b) {
- return "#" + (16777216 | b | (g << 8) | (r << 16)).toString(16).slice(1);
- });
- /*\
- * Raphael.getColor
- [ method ]
- **
- * On each call returns next colour in the spectrum. To reset it back to red call @Raphael.getColor.reset
- > Parameters
- - value (number) #optional brightness, default is `0.75`
- = (string) hex representation of the colour.
- \*/
- R.getColor = function (value) {
- var start = this.getColor.start = this.getColor.start || {h: 0, s: 1, b: value || .75},
- rgb = this.hsb2rgb(start.h, start.s, start.b);
- start.h += .075;
- if (start.h > 1) {
- start.h = 0;
- start.s -= .2;
- start.s <= 0 && (this.getColor.start = {h: 0, s: 1, b: start.b});
- }
- return rgb.hex;
- };
- /*\
- * Raphael.getColor.reset
- [ method ]
- **
- * Resets spectrum position for @Raphael.getColor back to red.
- \*/
- R.getColor.reset = function () {
- delete this.start;
- };
-
- // http://schepers.cc/getting-to-the-point
- function catmullRom2bezier(crp, z) {
- var d = [];
- for (var i = 0, iLen = crp.length; iLen - 2 * !z > i; i += 2) {
- var p = [
- {x: +crp[i - 2], y: +crp[i - 1]},
- {x: +crp[i], y: +crp[i + 1]},
- {x: +crp[i + 2], y: +crp[i + 3]},
- {x: +crp[i + 4], y: +crp[i + 5]}
- ];
- if (z) {
- if (!i) {
- p[0] = {x: +crp[iLen - 2], y: +crp[iLen - 1]};
- } else if (iLen - 4 == i) {
- p[3] = {x: +crp[0], y: +crp[1]};
- } else if (iLen - 2 == i) {
- p[2] = {x: +crp[0], y: +crp[1]};
- p[3] = {x: +crp[2], y: +crp[3]};
- }
- } else {
- if (iLen - 4 == i) {
- p[3] = p[2];
- } else if (!i) {
- p[0] = {x: +crp[i], y: +crp[i + 1]};
- }
- }
- d.push(["C",
- (-p[0].x + 6 * p[1].x + p[2].x) / 6,
- (-p[0].y + 6 * p[1].y + p[2].y) / 6,
- (p[1].x + 6 * p[2].x - p[3].x) / 6,
- (p[1].y + 6*p[2].y - p[3].y) / 6,
- p[2].x,
- p[2].y
- ]);
- }
-
- return d;
- }
- /*\
- * Raphael.parsePathString
- [ method ]
- **
- * Utility method
- **
- * Parses given path string into an array of arrays of path segments.
- > Parameters
- - pathString (string|array) path string or array of segments (in the last case it will be returned straight away)
- = (array) array of segments.
- \*/
- R.parsePathString = function (pathString) {
- if (!pathString) {
- return null;
- }
- var pth = paths(pathString);
- if (pth.arr) {
- return pathClone(pth.arr);
- }
-
- var paramCounts = {a: 7, c: 6, h: 1, l: 2, m: 2, r: 4, q: 4, s: 4, t: 2, v: 1, z: 0},
- data = [];
- if (R.is(pathString, array) && R.is(pathString[0], array)) { // rough assumption
- data = pathClone(pathString);
- }
- if (!data.length) {
- Str(pathString).replace(pathCommand, function (a, b, c) {
- var params = [],
- name = b.toLowerCase();
- c.replace(pathValues, function (a, b) {
- b && params.push(+b);
- });
- if (name == "m" && params.length > 2) {
- data.push([b][concat](params.splice(0, 2)));
- name = "l";
- b = b == "m" ? "l" : "L";
- }
- if (name == "r") {
- data.push([b][concat](params));
- } else while (params.length >= paramCounts[name]) {
- data.push([b][concat](params.splice(0, paramCounts[name])));
- if (!paramCounts[name]) {
- break;
- }
- }
- });
- }
- data.toString = R._path2string;
- pth.arr = pathClone(data);
- return data;
- };
- /*\
- * Raphael.parseTransformString
- [ method ]
- **
- * Utility method
- **
- * Parses given path string into an array of transformations.
- > Parameters
- - TString (string|array) transform string or array of transformations (in the last case it will be returned straight away)
- = (array) array of transformations.
- \*/
- R.parseTransformString = cacher(function (TString) {
- if (!TString) {
- return null;
- }
- var paramCounts = {r: 3, s: 4, t: 2, m: 6},
- data = [];
- if (R.is(TString, array) && R.is(TString[0], array)) { // rough assumption
- data = pathClone(TString);
- }
- if (!data.length) {
- Str(TString).replace(tCommand, function (a, b, c) {
- var params = [],
- name = lowerCase.call(b);
- c.replace(pathValues, function (a, b) {
- b && params.push(+b);
- });
- data.push([b][concat](params));
- });
- }
- data.toString = R._path2string;
- return data;
- });
- // PATHS
- var paths = function (ps) {
- var p = paths.ps = paths.ps || {};
- if (p[ps]) {
- p[ps].sleep = 100;
- } else {
- p[ps] = {
- sleep: 100
- };
- }
- setTimeout(function () {
- for (var key in p) if (p[has](key) && key != ps) {
- p[key].sleep--;
- !p[key].sleep && delete p[key];
- }
- });
- return p[ps];
- };
- /*\
- * Raphael.findDotsAtSegment
- [ method ]
- **
- * Utility method
- **
- * Find dot coordinates on the given cubic bezier curve at the given t.
- > Parameters
- - p1x (number) x of the first point of the curve
- - p1y (number) y of the first point of the curve
- - c1x (number) x of the first anchor of the curve
- - c1y (number) y of the first anchor of the curve
- - c2x (number) x of the second anchor of the curve
- - c2y (number) y of the second anchor of the curve
- - p2x (number) x of the second point of the curve
- - p2y (number) y of the second point of the curve
- - t (number) position on the curve (0..1)
- = (object) point information in format:
- o {
- o x: (number) x coordinate of the point
- o y: (number) y coordinate of the point
- o m: {
- o x: (number) x coordinate of the left anchor
- o y: (number) y coordinate of the left anchor
- o }
- o n: {
- o x: (number) x coordinate of the right anchor
- o y: (number) y coordinate of the right anchor
- o }
- o start: {
- o x: (number) x coordinate of the start of the curve
- o y: (number) y coordinate of the start of the curve
- o }
- o end: {
- o x: (number) x coordinate of the end of the curve
- o y: (number) y coordinate of the end of the curve
- o }
- o alpha: (number) angle of the curve derivative at the point
- o }
- \*/
- R.findDotsAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
- var t1 = 1 - t,
- t13 = pow(t1, 3),
- t12 = pow(t1, 2),
- t2 = t * t,
- t3 = t2 * t,
- x = t13 * p1x + t12 * 3 * t * c1x + t1 * 3 * t * t * c2x + t3 * p2x,
- y = t13 * p1y + t12 * 3 * t * c1y + t1 * 3 * t * t * c2y + t3 * p2y,
- mx = p1x + 2 * t * (c1x - p1x) + t2 * (c2x - 2 * c1x + p1x),
- my = p1y + 2 * t * (c1y - p1y) + t2 * (c2y - 2 * c1y + p1y),
- nx = c1x + 2 * t * (c2x - c1x) + t2 * (p2x - 2 * c2x + c1x),
- ny = c1y + 2 * t * (c2y - c1y) + t2 * (p2y - 2 * c2y + c1y),
- ax = t1 * p1x + t * c1x,
- ay = t1 * p1y + t * c1y,
- cx = t1 * c2x + t * p2x,
- cy = t1 * c2y + t * p2y,
- alpha = (90 - math.atan2(mx - nx, my - ny) * 180 / PI);
- (mx > nx || my < ny) && (alpha += 180);
- return {
- x: x,
- y: y,
- m: {x: mx, y: my},
- n: {x: nx, y: ny},
- start: {x: ax, y: ay},
- end: {x: cx, y: cy},
- alpha: alpha
- };
- };
- /*\
- * Raphael.bezierBBox
- [ method ]
- **
- * Utility method
- **
- * Return bounding box of a given cubic bezier curve
- > Parameters
- - p1x (number) x of the first point of the curve
- - p1y (number) y of the first point of the curve
- - c1x (number) x of the first anchor of the curve
- - c1y (number) y of the first anchor of the curve
- - c2x (number) x of the second anchor of the curve
- - c2y (number) y of the second anchor of the curve
- - p2x (number) x of the second point of the curve
- - p2y (number) y of the second point of the curve
- * or
- - bez (array) array of six points for bezier curve
- = (object) point information in format:
- o {
- o min: {
- o x: (number) x coordinate of the left point
- o y: (number) y coordinate of the top point
- o }
- o max: {
- o x: (number) x coordinate of the right point
- o y: (number) y coordinate of the bottom point
- o }
- o }
- \*/
- R.bezierBBox = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
- if (!R.is(p1x, "array")) {
- p1x = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
- }
- var bbox = curveDim.apply(null, p1x);
- return {
- x: bbox.min.x,
- y: bbox.min.y,
- x2: bbox.max.x,
- y2: bbox.max.y,
- width: bbox.max.x - bbox.min.x,
- height: bbox.max.y - bbox.min.y
- };
- };
- /*\
- * Raphael.isPointInsideBBox
- [ method ]
- **
- * Utility method
- **
- * Returns `true` if given point is inside bounding boxes.
- > Parameters
- - bbox (string) bounding box
- - x (string) x coordinate of the point
- - y (string) y coordinate of the point
- = (boolean) `true` if point inside
- \*/
- R.isPointInsideBBox = function (bbox, x, y) {
- return x >= bbox.x && x <= bbox.x2 && y >= bbox.y && y <= bbox.y2;
- };
- /*\
- * Raphael.isBBoxIntersect
- [ method ]
- **
- * Utility method
- **
- * Returns `true` if two bounding boxes intersect
- > Parameters
- - bbox1 (string) first bounding box
- - bbox2 (string) second bounding box
- = (boolean) `true` if they intersect
- \*/
- R.isBBoxIntersect = function (bbox1, bbox2) {
- var i = R.isPointInsideBBox;
- return i(bbox2, bbox1.x, bbox1.y)
- || i(bbox2, bbox1.x2, bbox1.y)
- || i(bbox2, bbox1.x, bbox1.y2)
- || i(bbox2, bbox1.x2, bbox1.y2)
- || i(bbox1, bbox2.x, bbox2.y)
- || i(bbox1, bbox2.x2, bbox2.y)
- || i(bbox1, bbox2.x, bbox2.y2)
- || i(bbox1, bbox2.x2, bbox2.y2)
- || (bbox1.x < bbox2.x2 && bbox1.x > bbox2.x || bbox2.x < bbox1.x2 && bbox2.x > bbox1.x)
- && (bbox1.y < bbox2.y2 && bbox1.y > bbox2.y || bbox2.y < bbox1.y2 && bbox2.y > bbox1.y);
- };
- function base3(t, p1, p2, p3, p4) {
- var t1 = -3 * p1 + 9 * p2 - 9 * p3 + 3 * p4,
- t2 = t * t1 + 6 * p1 - 12 * p2 + 6 * p3;
- return t * t2 - 3 * p1 + 3 * p2;
- }
- function bezlen(x1, y1, x2, y2, x3, y3, x4, y4, z) {
- if (z == null) {
- z = 1;
- }
- z = z > 1 ? 1 : z < 0 ? 0 : z;
- var z2 = z / 2,
- n = 12,
- Tvalues = [-0.1252,0.1252,-0.3678,0.3678,-0.5873,0.5873,-0.7699,0.7699,-0.9041,0.9041,-0.9816,0.9816],
- Cvalues = [0.2491,0.2491,0.2335,0.2335,0.2032,0.2032,0.1601,0.1601,0.1069,0.1069,0.0472,0.0472],
- sum = 0;
- for (var i = 0; i < n; i++) {
- var ct = z2 * Tvalues[i] + z2,
- xbase = base3(ct, x1, x2, x3, x4),
- ybase = base3(ct, y1, y2, y3, y4),
- comb = xbase * xbase + ybase * ybase;
- sum += Cvalues[i] * math.sqrt(comb);
- }
- return z2 * sum;
- }
- function getTatLen(x1, y1, x2, y2, x3, y3, x4, y4, ll) {
- if (ll < 0 || bezlen(x1, y1, x2, y2, x3, y3, x4, y4) < ll) {
- return;
- }
- var t = 1,
- step = t / 2,
- t2 = t - step,
- l,
- e = .01;
- l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
- while (abs(l - ll) > e) {
- step /= 2;
- t2 += (l < ll ? 1 : -1) * step;
- l = bezlen(x1, y1, x2, y2, x3, y3, x4, y4, t2);
- }
- return t2;
- }
- function intersect(x1, y1, x2, y2, x3, y3, x4, y4) {
- if (
- mmax(x1, x2) < mmin(x3, x4) ||
- mmin(x1, x2) > mmax(x3, x4) ||
- mmax(y1, y2) < mmin(y3, y4) ||
- mmin(y1, y2) > mmax(y3, y4)
- ) {
- return;
- }
- var nx = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4),
- ny = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4),
- denominator = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
-
- if (!denominator) {
- return;
- }
- var px = nx / denominator,
- py = ny / denominator,
- px2 = +px.toFixed(2),
- py2 = +py.toFixed(2);
- if (
- px2 < +mmin(x1, x2).toFixed(2) ||
- px2 > +mmax(x1, x2).toFixed(2) ||
- px2 < +mmin(x3, x4).toFixed(2) ||
- px2 > +mmax(x3, x4).toFixed(2) ||
- py2 < +mmin(y1, y2).toFixed(2) ||
- py2 > +mmax(y1, y2).toFixed(2) ||
- py2 < +mmin(y3, y4).toFixed(2) ||
- py2 > +mmax(y3, y4).toFixed(2)
- ) {
- return;
- }
- return {x: px, y: py};
- }
- function inter(bez1, bez2) {
- return interHelper(bez1, bez2);
- }
- function interCount(bez1, bez2) {
- return interHelper(bez1, bez2, 1);
- }
- function interHelper(bez1, bez2, justCount) {
- var bbox1 = R.bezierBBox(bez1),
- bbox2 = R.bezierBBox(bez2);
- if (!R.isBBoxIntersect(bbox1, bbox2)) {
- return justCount ? 0 : [];
- }
- var l1 = bezlen.apply(0, bez1),
- l2 = bezlen.apply(0, bez2),
- n1 = mmax(~~(l1 / 5), 1),
- n2 = mmax(~~(l2 / 5), 1),
- dots1 = [],
- dots2 = [],
- xy = {},
- res = justCount ? 0 : [];
- for (var i = 0; i < n1 + 1; i++) {
- var p = R.findDotsAtSegment.apply(R, bez1.concat(i / n1));
- dots1.push({x: p.x, y: p.y, t: i / n1});
- }
- for (i = 0; i < n2 + 1; i++) {
- p = R.findDotsAtSegment.apply(R, bez2.concat(i / n2));
- dots2.push({x: p.x, y: p.y, t: i / n2});
- }
- for (i = 0; i < n1; i++) {
- for (var j = 0; j < n2; j++) {
- var di = dots1[i],
- di1 = dots1[i + 1],
- dj = dots2[j],
- dj1 = dots2[j + 1],
- ci = abs(di1.x - di.x) < .001 ? "y" : "x",
- cj = abs(dj1.x - dj.x) < .001 ? "y" : "x",
- is = intersect(di.x, di.y, di1.x, di1.y, dj.x, dj.y, dj1.x, dj1.y);
- if (is) {
- if (xy[is.x.toFixed(4)] == is.y.toFixed(4)) {
- continue;
- }
- xy[is.x.toFixed(4)] = is.y.toFixed(4);
- var t1 = di.t + abs((is[ci] - di[ci]) / (di1[ci] - di[ci])) * (di1.t - di.t),
- t2 = dj.t + abs((is[cj] - dj[cj]) / (dj1[cj] - dj[cj])) * (dj1.t - dj.t);
- if (t1 >= 0 && t1 <= 1.001 && t2 >= 0 && t2 <= 1.001) {
- if (justCount) {
- res++;
- } else {
- res.push({
- x: is.x,
- y: is.y,
- t1: mmin(t1, 1),
- t2: mmin(t2, 1)
- });
- }
- }
- }
- }
- }
- return res;
- }
- /*\
- * Raphael.pathIntersection
- [ method ]
- **
- * Utility method
- **
- * Finds intersections of two paths
- > Parameters
- - path1 (string) path string
- - path2 (string) path string
- = (array) dots of intersection
- o [
- o {
- o x: (number) x coordinate of the point
- o y: (number) y coordinate of the point
- o t1: (number) t value for segment of path1
- o t2: (number) t value for segment of path2
- o segment1: (number) order number for segment of path1
- o segment2: (number) order number for segment of path2
- o bez1: (array) eight coordinates representing beziér curve for the segment of path1
- o bez2: (array) eight coordinates representing beziér curve for the segment of path2
- o }
- o ]
- \*/
- R.pathIntersection = function (path1, path2) {
- return interPathHelper(path1, path2);
- };
- R.pathIntersectionNumber = function (path1, path2) {
- return interPathHelper(path1, path2, 1);
- };
- function interPathHelper(path1, path2, justCount) {
- path1 = R._path2curve(path1);
- path2 = R._path2curve(path2);
- var x1, y1, x2, y2, x1m, y1m, x2m, y2m, bez1, bez2,
- res = justCount ? 0 : [];
- for (var i = 0, ii = path1.length; i < ii; i++) {
- var pi = path1[i];
- if (pi[0] == "M") {
- x1 = x1m = pi[1];
- y1 = y1m = pi[2];
- } else {
- if (pi[0] == "C") {
- bez1 = [x1, y1].concat(pi.slice(1));
- x1 = bez1[6];
- y1 = bez1[7];
- } else {
- bez1 = [x1, y1, x1, y1, x1m, y1m, x1m, y1m];
- x1 = x1m;
- y1 = y1m;
- }
- for (var j = 0, jj = path2.length; j < jj; j++) {
- var pj = path2[j];
- if (pj[0] == "M") {
- x2 = x2m = pj[1];
- y2 = y2m = pj[2];
- } else {
- if (pj[0] == "C") {
- bez2 = [x2, y2].concat(pj.slice(1));
- x2 = bez2[6];
- y2 = bez2[7];
- } else {
- bez2 = [x2, y2, x2, y2, x2m, y2m, x2m, y2m];
- x2 = x2m;
- y2 = y2m;
- }
- var intr = interHelper(bez1, bez2, justCount);
- if (justCount) {
- res += intr;
- } else {
- for (var k = 0, kk = intr.length; k < kk; k++) {
- intr[k].segment1 = i;
- intr[k].segment2 = j;
- intr[k].bez1 = bez1;
- intr[k].bez2 = bez2;
- }
- res = res.concat(intr);
- }
- }
- }
- }
- }
- return res;
- }
- /*\
- * Raphael.isPointInsidePath
- [ method ]
- **
- * Utility method
- **
- * Returns `true` if given point is inside a given closed path.
- > Parameters
- - path (string) path string
- - x (number) x of the point
- - y (number) y of the point
- = (boolean) true, if point is inside the path
- \*/
- R.isPointInsidePath = function (path, x, y) {
- var bbox = R.pathBBox(path);
- return R.isPointInsideBBox(bbox, x, y) &&
- interPathHelper(path, [["M", x, y], ["H", bbox.x2 + 10]], 1) % 2 == 1;
- };
- R._removedFactory = function (methodname) {
- return function () {
- eve("raphael.log", null, "Rapha\xebl: you are calling to method \u201c" + methodname + "\u201d of removed object", methodname);
- };
- };
- /*\
- * Raphael.pathBBox
- [ method ]
- **
- * Utility method
- **
- * Return bounding box of a given path
- > Parameters
- - path (string) path string
- = (object) bounding box
- o {
- o x: (number) x coordinate of the left top point of the box
- o y: (number) y coordinate of the left top point of the box
- o x2: (number) x coordinate of the right bottom point of the box
- o y2: (number) y coordinate of the right bottom point of the box
- o width: (number) width of the box
- o height: (number) height of the box
- o cx: (number) x coordinate of the center of the box
- o cy: (number) y coordinate of the center of the box
- o }
- \*/
- var pathDimensions = R.pathBBox = function (path) {
- var pth = paths(path);
- if (pth.bbox) {
- return clone(pth.bbox);
- }
- if (!path) {
- return {x: 0, y: 0, width: 0, height: 0, x2: 0, y2: 0};
- }
- path = path2curve(path);
- var x = 0,
- y = 0,
- X = [],
- Y = [],
- p;
- for (var i = 0, ii = path.length; i < ii; i++) {
- p = path[i];
- if (p[0] == "M") {
- x = p[1];
- y = p[2];
- X.push(x);
- Y.push(y);
- } else {
- var dim = curveDim(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
- X = X[concat](dim.min.x, dim.max.x);
- Y = Y[concat](dim.min.y, dim.max.y);
- x = p[5];
- y = p[6];
- }
- }
- var xmin = mmin[apply](0, X),
- ymin = mmin[apply](0, Y),
- xmax = mmax[apply](0, X),
- ymax = mmax[apply](0, Y),
- width = xmax - xmin,
- height = ymax - ymin,
- bb = {
- x: xmin,
- y: ymin,
- x2: xmax,
- y2: ymax,
- width: width,
- height: height,
- cx: xmin + width / 2,
- cy: ymin + height / 2
- };
- pth.bbox = clone(bb);
- return bb;
- },
- pathClone = function (pathArray) {
- var res = clone(pathArray);
- res.toString = R._path2string;
- return res;
- },
- pathToRelative = R._pathToRelative = function (pathArray) {
- var pth = paths(pathArray);
- if (pth.rel) {
- return pathClone(pth.rel);
- }
- if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
- pathArray = R.parsePathString(pathArray);
- }
- var res = [],
- x = 0,
- y = 0,
- mx = 0,
- my = 0,
- start = 0;
- if (pathArray[0][0] == "M") {
- x = pathArray[0][1];
- y = pathArray[0][2];
- mx = x;
- my = y;
- start++;
- res.push(["M", x, y]);
- }
- for (var i = start, ii = pathArray.length; i < ii; i++) {
- var r = res[i] = [],
- pa = pathArray[i];
- if (pa[0] != lowerCase.call(pa[0])) {
- r[0] = lowerCase.call(pa[0]);
- switch (r[0]) {
- case "a":
- r[1] = pa[1];
- r[2] = pa[2];
- r[3] = pa[3];
- r[4] = pa[4];
- r[5] = pa[5];
- r[6] = +(pa[6] - x).toFixed(3);
- r[7] = +(pa[7] - y).toFixed(3);
- break;
- case "v":
- r[1] = +(pa[1] - y).toFixed(3);
- break;
- case "m":
- mx = pa[1];
- my = pa[2];
- default:
- for (var j = 1, jj = pa.length; j < jj; j++) {
- r[j] = +(pa[j] - ((j % 2) ? x : y)).toFixed(3);
- }
- }
- } else {
- r = res[i] = [];
- if (pa[0] == "m") {
- mx = pa[1] + x;
- my = pa[2] + y;
- }
- for (var k = 0, kk = pa.length; k < kk; k++) {
- res[i][k] = pa[k];
- }
- }
- var len = res[i].length;
- switch (res[i][0]) {
- case "z":
- x = mx;
- y = my;
- break;
- case "h":
- x += +res[i][len - 1];
- break;
- case "v":
- y += +res[i][len - 1];
- break;
- default:
- x += +res[i][len - 2];
- y += +res[i][len - 1];
- }
- }
- res.toString = R._path2string;
- pth.rel = pathClone(res);
- return res;
- },
- pathToAbsolute = R._pathToAbsolute = function (pathArray) {
- var pth = paths(pathArray);
- if (pth.abs) {
- return pathClone(pth.abs);
- }
- if (!R.is(pathArray, array) || !R.is(pathArray && pathArray[0], array)) { // rough assumption
- pathArray = R.parsePathString(pathArray);
- }
- if (!pathArray || !pathArray.length) {
- return [["M", 0, 0]];
- }
- var res = [],
- x = 0,
- y = 0,
- mx = 0,
- my = 0,
- start = 0;
- if (pathArray[0][0] == "M") {
- x = +pathArray[0][1];
- y = +pathArray[0][2];
- mx = x;
- my = y;
- start++;
- res[0] = ["M", x, y];
- }
- var crz = pathArray.length == 3 && pathArray[0][0] == "M" && pathArray[1][0].toUpperCase() == "R" && pathArray[2][0].toUpperCase() == "Z";
- for (var r, pa, i = start, ii = pathArray.length; i < ii; i++) {
- res.push(r = []);
- pa = pathArray[i];
- if (pa[0] != upperCase.call(pa[0])) {
- r[0] = upperCase.call(pa[0]);
- switch (r[0]) {
- case "A":
- r[1] = pa[1];
- r[2] = pa[2];
- r[3] = pa[3];
- r[4] = pa[4];
- r[5] = pa[5];
- r[6] = +(pa[6] + x);
- r[7] = +(pa[7] + y);
- break;
- case "V":
- r[1] = +pa[1] + y;
- break;
- case "H":
- r[1] = +pa[1] + x;
- break;
- case "R":
- var dots = [x, y][concat](pa.slice(1));
- for (var j = 2, jj = dots.length; j < jj; j++) {
- dots[j] = +dots[j] + x;
- dots[++j] = +dots[j] + y;
- }
- res.pop();
- res = res[concat](catmullRom2bezier(dots, crz));
- break;
- case "M":
- mx = +pa[1] + x;
- my = +pa[2] + y;
- default:
- for (j = 1, jj = pa.length; j < jj; j++) {
- r[j] = +pa[j] + ((j % 2) ? x : y);
- }
- }
- } else if (pa[0] == "R") {
- dots = [x, y][concat](pa.slice(1));
- res.pop();
- res = res[concat](catmullRom2bezier(dots, crz));
- r = ["R"][concat](pa.slice(-2));
- } else {
- for (var k = 0, kk = pa.length; k < kk; k++) {
- r[k] = pa[k];
- }
- }
- switch (r[0]) {
- case "Z":
- x = mx;
- y = my;
- break;
- case "H":
- x = r[1];
- break;
- case "V":
- y = r[1];
- break;
- case "M":
- mx = r[r.length - 2];
- my = r[r.length - 1];
- default:
- x = r[r.length - 2];
- y = r[r.length - 1];
- }
- }
- res.toString = R._path2string;
- pth.abs = pathClone(res);
- return res;
- },
- l2c = function (x1, y1, x2, y2) {
- return [x1, y1, x2, y2, x2, y2];
- },
- q2c = function (x1, y1, ax, ay, x2, y2) {
- var _13 = 1 / 3,
- _23 = 2 / 3;
- return [
- _13 * x1 + _23 * ax,
- _13 * y1 + _23 * ay,
- _13 * x2 + _23 * ax,
- _13 * y2 + _23 * ay,
- x2,
- y2
- ];
- },
- a2c = function (x1, y1, rx, ry, angle, large_arc_flag, sweep_flag, x2, y2, recursive) {
- // for more information of where this math came from visit:
- // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
- var _120 = PI * 120 / 180,
- rad = PI / 180 * (+angle || 0),
- res = [],
- xy,
- rotate = cacher(function (x, y, rad) {
- var X = x * math.cos(rad) - y * math.sin(rad),
- Y = x * math.sin(rad) + y * math.cos(rad);
- return {x: X, y: Y};
- });
- if (!recursive) {
- xy = rotate(x1, y1, -rad);
- x1 = xy.x;
- y1 = xy.y;
- xy = rotate(x2, y2, -rad);
- x2 = xy.x;
- y2 = xy.y;
- var cos = math.cos(PI / 180 * angle),
- sin = math.sin(PI / 180 * angle),
- x = (x1 - x2) / 2,
- y = (y1 - y2) / 2;
- var h = (x * x) / (rx * rx) + (y * y) / (ry * ry);
- if (h > 1) {
- h = math.sqrt(h);
- rx = h * rx;
- ry = h * ry;
- }
- var rx2 = rx * rx,
- ry2 = ry * ry,
- k = (large_arc_flag == sweep_flag ? -1 : 1) *
- math.sqrt(abs((rx2 * ry2 - rx2 * y * y - ry2 * x * x) / (rx2 * y * y + ry2 * x * x))),
- cx = k * rx * y / ry + (x1 + x2) / 2,
- cy = k * -ry * x / rx + (y1 + y2) / 2,
- f1 = math.asin(((y1 - cy) / ry).toFixed(9)),
- f2 = math.asin(((y2 - cy) / ry).toFixed(9));
-
- f1 = x1 < cx ? PI - f1 : f1;
- f2 = x2 < cx ? PI - f2 : f2;
- f1 < 0 && (f1 = PI * 2 + f1);
- f2 < 0 && (f2 = PI * 2 + f2);
- if (sweep_flag && f1 > f2) {
- f1 = f1 - PI * 2;
- }
- if (!sweep_flag && f2 > f1) {
- f2 = f2 - PI * 2;
- }
- } else {
- f1 = recursive[0];
- f2 = recursive[1];
- cx = recursive[2];
- cy = recursive[3];
- }
- var df = f2 - f1;
- if (abs(df) > _120) {
- var f2old = f2,
- x2old = x2,
- y2old = y2;
- f2 = f1 + _120 * (sweep_flag && f2 > f1 ? 1 : -1);
- x2 = cx + rx * math.cos(f2);
- y2 = cy + ry * math.sin(f2);
- res = a2c(x2, y2, rx, ry, angle, 0, sweep_flag, x2old, y2old, [f2, f2old, cx, cy]);
- }
- df = f2 - f1;
- var c1 = math.cos(f1),
- s1 = math.sin(f1),
- c2 = math.cos(f2),
- s2 = math.sin(f2),
- t = math.tan(df / 4),
- hx = 4 / 3 * rx * t,
- hy = 4 / 3 * ry * t,
- m1 = [x1, y1],
- m2 = [x1 + hx * s1, y1 - hy * c1],
- m3 = [x2 + hx * s2, y2 - hy * c2],
- m4 = [x2, y2];
- m2[0] = 2 * m1[0] - m2[0];
- m2[1] = 2 * m1[1] - m2[1];
- if (recursive) {
- return [m2, m3, m4][concat](res);
- } else {
- res = [m2, m3, m4][concat](res).join()[split](",");
- var newres = [];
- for (var i = 0, ii = res.length; i < ii; i++) {
- newres[i] = i % 2 ? rotate(res[i - 1], res[i], rad).y : rotate(res[i], res[i + 1], rad).x;
- }
- return newres;
- }
- },
- findDotAtSegment = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
- var t1 = 1 - t;
- return {
- x: pow(t1, 3) * p1x + pow(t1, 2) * 3 * t * c1x + t1 * 3 * t * t * c2x + pow(t, 3) * p2x,
- y: pow(t1, 3) * p1y + pow(t1, 2) * 3 * t * c1y + t1 * 3 * t * t * c2y + pow(t, 3) * p2y
- };
- },
- curveDim = cacher(function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
- var a = (c2x - 2 * c1x + p1x) - (p2x - 2 * c2x + c1x),
- b = 2 * (c1x - p1x) - 2 * (c2x - c1x),
- c = p1x - c1x,
- t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a,
- t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a,
- y = [p1y, p2y],
- x = [p1x, p2x],
- dot;
- abs(t1) > "1e12" && (t1 = .5);
- abs(t2) > "1e12" && (t2 = .5);
- if (t1 > 0 && t1 < 1) {
- dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
- x.push(dot.x);
- y.push(dot.y);
- }
- if (t2 > 0 && t2 < 1) {
- dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
- x.push(dot.x);
- y.push(dot.y);
- }
- a = (c2y - 2 * c1y + p1y) - (p2y - 2 * c2y + c1y);
- b = 2 * (c1y - p1y) - 2 * (c2y - c1y);
- c = p1y - c1y;
- t1 = (-b + math.sqrt(b * b - 4 * a * c)) / 2 / a;
- t2 = (-b - math.sqrt(b * b - 4 * a * c)) / 2 / a;
- abs(t1) > "1e12" && (t1 = .5);
- abs(t2) > "1e12" && (t2 = .5);
- if (t1 > 0 && t1 < 1) {
- dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t1);
- x.push(dot.x);
- y.push(dot.y);
- }
- if (t2 > 0 && t2 < 1) {
- dot = findDotAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t2);
- x.push(dot.x);
- y.push(dot.y);
- }
- return {
- min: {x: mmin[apply](0, x), y: mmin[apply](0, y)},
- max: {x: mmax[apply](0, x), y: mmax[apply](0, y)}
- };
- }),
- path2curve = R._path2curve = cacher(function (path, path2) {
- var pth = !path2 && paths(path);
- if (!path2 && pth.curve) {
- return pathClone(pth.curve);
- }
- var p = pathToAbsolute(path),
- p2 = path2 && pathToAbsolute(path2),
- attrs = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
- attrs2 = {x: 0, y: 0, bx: 0, by: 0, X: 0, Y: 0, qx: null, qy: null},
- processPath = function (path, d, pcom) {
- var nx, ny, tq = {T:1, Q:1};
- if (!path) {
- return ["C", d.x, d.y, d.x, d.y, d.x, d.y];
- }
- !(path[0] in tq) && (d.qx = d.qy = null);
- switch (path[0]) {
- case "M":
- d.X = path[1];
- d.Y = path[2];
- break;
- case "A":
- path = ["C"][concat](a2c[apply](0, [d.x, d.y][concat](path.slice(1))));
- break;
- case "S":
- if (pcom == "C" || pcom == "S") { // In "S" case we have to take into account, if the previous command is C/S.
- nx = d.x * 2 - d.bx; // And reflect the previous
- ny = d.y * 2 - d.by; // command's control point relative to the current point.
- }
- else { // or some else or nothing
- nx = d.x;
- ny = d.y;
- }
- path = ["C", nx, ny][concat](path.slice(1));
- break;
- case "T":
- if (pcom == "Q" || pcom == "T") { // In "T" case we have to take into account, if the previous command is Q/T.
- d.qx = d.x * 2 - d.qx; // And make a reflection similar
- d.qy = d.y * 2 - d.qy; // to case "S".
- }
- else { // or something else or nothing
- d.qx = d.x;
- d.qy = d.y;
- }
- path = ["C"][concat](q2c(d.x, d.y, d.qx, d.qy, path[1], path[2]));
- break;
- case "Q":
- d.qx = path[1];
- d.qy = path[2];
- path = ["C"][concat](q2c(d.x, d.y, path[1], path[2], path[3], path[4]));
- break;
- case "L":
- path = ["C"][concat](l2c(d.x, d.y, path[1], path[2]));
- break;
- case "H":
- path = ["C"][concat](l2c(d.x, d.y, path[1], d.y));
- break;
- case "V":
- path = ["C"][concat](l2c(d.x, d.y, d.x, path[1]));
- break;
- case "Z":
- path = ["C"][concat](l2c(d.x, d.y, d.X, d.Y));
- break;
- }
- return path;
- },
- fixArc = function (pp, i) {
- if (pp[i].length > 7) {
- pp[i].shift();
- var pi = pp[i];
- while (pi.length) {
- pcoms1[i]="A"; // if created multiple C:s, their original seg is saved
- p2 && (pcoms2[i]="A"); // the same as above
- pp.splice(i++, 0, ["C"][concat](pi.splice(0, 6)));
- }
- pp.splice(i, 1);
- ii = mmax(p.length, p2 && p2.length || 0);
- }
- },
- fixM = function (path1, path2, a1, a2, i) {
- if (path1 && path2 && path1[i][0] == "M" && path2[i][0] != "M") {
- path2.splice(i, 0, ["M", a2.x, a2.y]);
- a1.bx = 0;
- a1.by = 0;
- a1.x = path1[i][1];
- a1.y = path1[i][2];
- ii = mmax(p.length, p2 && p2.length || 0);
- }
- },
- pcoms1 = [], // path commands of original path p
- pcoms2 = [], // path commands of original path p2
- pfirst = "", // temporary holder for original path command
- pcom = ""; // holder for previous path command of original path
- for (var i = 0, ii = mmax(p.length, p2 && p2.length || 0); i < ii; i++) {
- p[i] && (pfirst = p[i][0]); // save current path command
-
- if (pfirst != "C") // C is not saved yet, because it may be result of conversion
- {
- pcoms1[i] = pfirst; // Save current path command
- i && ( pcom = pcoms1[i-1]); // Get previous path command pcom
- }
- p[i] = processPath(p[i], attrs, pcom); // Previous path command is inputted to processPath
-
- if (pcoms1[i] != "A" && pfirst == "C") pcoms1[i] = "C"; // A is the only command
- // which may produce multiple C:s
- // so we have to make sure that C is also C in original path
-
- fixArc(p, i); // fixArc adds also the right amount of A:s to pcoms1
-
- if (p2) { // the same procedures is done to p2
- p2[i] && (pfirst = p2[i][0]);
- if (pfirst != "C")
- {
- pcoms2[i] = pfirst;
- i && (pcom = pcoms2[i-1]);
- }
- p2[i] = processPath(p2[i], attrs2, pcom);
-
- if (pcoms2[i]!="A" && pfirst=="C") pcoms2[i]="C";
-
- fixArc(p2, i);
- }
- fixM(p, p2, attrs, attrs2, i);
- fixM(p2, p, attrs2, attrs, i);
- var seg = p[i],
- seg2 = p2 && p2[i],
- seglen = seg.length,
- seg2len = p2 && seg2.length;
- attrs.x = seg[seglen - 2];
- attrs.y = seg[seglen - 1];
- attrs.bx = toFloat(seg[seglen - 4]) || attrs.x;
- attrs.by = toFloat(seg[seglen - 3]) || attrs.y;
- attrs2.bx = p2 && (toFloat(seg2[seg2len - 4]) || attrs2.x);
- attrs2.by = p2 && (toFloat(seg2[seg2len - 3]) || attrs2.y);
- attrs2.x = p2 && seg2[seg2len - 2];
- attrs2.y = p2 && seg2[seg2len - 1];
- }
- if (!p2) {
- pth.curve = pathClone(p);
- }
- return p2 ? [p, p2] : p;
- }, null, pathClone),
- parseDots = R._parseDots = cacher(function (gradient) {
- var dots = [];
- for (var i = 0, ii = gradient.length; i < ii; i++) {
- var dot = {},
- par = gradient[i].match(/^([^:]*):?([\d\.]*)/);
- dot.color = R.getRGB(par[1]);
- if (dot.color.error) {
- return null;
- }
- dot.color = dot.color.hex;
- par[2] && (dot.offset = par[2] + "%");
- dots.push(dot);
- }
- for (i = 1, ii = dots.length - 1; i < ii; i++) {
- if (!dots[i].offset) {
- var start = toFloat(dots[i - 1].offset || 0),
- end = 0;
- for (var j = i + 1; j < ii; j++) {
- if (dots[j].offset) {
- end = dots[j].offset;
- break;
- }
- }
- if (!end) {
- end = 100;
- j = ii;
- }
- end = toFloat(end);
- var d = (end - start) / (j - i + 1);
- for (; i < j; i++) {
- start += d;
- dots[i].offset = start + "%";
- }
- }
- }
- return dots;
- }),
- tear = R._tear = function (el, paper) {
- el == paper.top && (paper.top = el.prev);
- el == paper.bottom && (paper.bottom = el.next);
- el.next && (el.next.prev = el.prev);
- el.prev && (el.prev.next = el.next);
- },
- tofront = R._tofront = function (el, paper) {
- if (paper.top === el) {
- return;
- }
- tear(el, paper);
- el.next = null;
- el.prev = paper.top;
- paper.top.next = el;
- paper.top = el;
- },
- toback = R._toback = function (el, paper) {
- if (paper.bottom === el) {
- return;
- }
- tear(el, paper);
- el.next = paper.bottom;
- el.prev = null;
- paper.bottom.prev = el;
- paper.bottom = el;
- },
- insertafter = R._insertafter = function (el, el2, paper) {
- tear(el, paper);
- el2 == paper.top && (paper.top = el);
- el2.next && (el2.next.prev = el);
- el.next = el2.next;
- el.prev = el2;
- el2.next = el;
- },
- insertbefore = R._insertbefore = function (el, el2, paper) {
- tear(el, paper);
- el2 == paper.bottom && (paper.bottom = el);
- el2.prev && (el2.prev.next = el);
- el.prev = el2.prev;
- el2.prev = el;
- el.next = el2;
- },
- /*\
- * Raphael.toMatrix
- [ method ]
- **
- * Utility method
- **
- * Returns matrix of transformations applied to a given path
- > Parameters
- - path (string) path string
- - transform (string|array) transformation string
- = (object) @Matrix
- \*/
- toMatrix = R.toMatrix = function (path, transform) {
- var bb = pathDimensions(path),
- el = {
- _: {
- transform: E
- },
- getBBox: function () {
- return bb;
- }
- };
- extractTransform(el, transform);
- return el.matrix;
- },
- /*\
- * Raphael.transformPath
- [ method ]
- **
- * Utility method
- **
- * Returns path transformed by a given transformation
- > Parameters
- - path (string) path string
- - transform (string|array) transformation string
- = (string) path
- \*/
- transformPath = R.transformPath = function (path, transform) {
- return mapPath(path, toMatrix(path, transform));
- },
- extractTransform = R._extractTransform = function (el, tstr) {
- if (tstr == null) {
- return el._.transform;
- }
- tstr = Str(tstr).replace(/\.{3}|\u2026/g, el._.transform || E);
- var tdata = R.parseTransformString(tstr),
- deg = 0,
- dx = 0,
- dy = 0,
- sx = 1,
- sy = 1,
- _ = el._,
- m = new Matrix;
- _.transform = tdata || [];
- if (tdata) {
- for (var i = 0, ii = tdata.length; i < ii; i++) {
- var t = tdata[i],
- tlen = t.length,
- command = Str(t[0]).toLowerCase(),
- absolute = t[0] != command,
- inver = absolute ? m.invert() : 0,
- x1,
- y1,
- x2,
- y2,
- bb;
- if (command == "t" && tlen == 3) {
- if (absolute) {
- x1 = inver.x(0, 0);
- y1 = inver.y(0, 0);
- x2 = inver.x(t[1], t[2]);
- y2 = inver.y(t[1], t[2]);
- m.translate(x2 - x1, y2 - y1);
- } else {
- m.translate(t[1], t[2]);
- }
- } else if (command == "r") {
- if (tlen == 2) {
- bb = bb || el.getBBox(1);
- m.rotate(t[1], bb.x + bb.width / 2, bb.y + bb.height / 2);
- deg += t[1];
- } else if (tlen == 4) {
- if (absolute) {
- x2 = inver.x(t[2], t[3]);
- y2 = inver.y(t[2], t[3]);
- m.rotate(t[1], x2, y2);
- } else {
- m.rotate(t[1], t[2], t[3]);
- }
- deg += t[1];
- }
- } else if (command == "s") {
- if (tlen == 2 || tlen == 3) {
- bb = bb || el.getBBox(1);
- m.scale(t[1], t[tlen - 1], bb.x + bb.width / 2, bb.y + bb.height / 2);
- sx *= t[1];
- sy *= t[tlen - 1];
- } else if (tlen == 5) {
- if (absolute) {
- x2 = inver.x(t[3], t[4]);
- y2 = inver.y(t[3], t[4]);
- m.scale(t[1], t[2], x2, y2);
- } else {
- m.scale(t[1], t[2], t[3], t[4]);
- }
- sx *= t[1];
- sy *= t[2];
- }
- } else if (command == "m" && tlen == 7) {
- m.add(t[1], t[2], t[3], t[4], t[5], t[6]);
- }
- _.dirtyT = 1;
- el.matrix = m;
- }
- }
-
- /*\
- * Element.matrix
- [ property (object) ]
- **
- * Keeps @Matrix object, which represents element transformation
- \*/
- el.matrix = m;
-
- _.sx = sx;
- _.sy = sy;
- _.deg = deg;
- _.dx = dx = m.e;
- _.dy = dy = m.f;
-
- if (sx == 1 && sy == 1 && !deg && _.bbox) {
- _.bbox.x += +dx;
- _.bbox.y += +dy;
- } else {
- _.dirtyT = 1;
- }
- },
- getEmpty = function (item) {
- var l = item[0];
- switch (l.toLowerCase()) {
- case "t": return [l, 0, 0];
- case "m": return [l, 1, 0, 0, 1, 0, 0];
- case "r": if (item.length == 4) {
- return [l, 0, item[2], item[3]];
- } else {
- return [l, 0];
- }
- case "s": if (item.length == 5) {
- return [l, 1, 1, item[3], item[4]];
- } else if (item.length == 3) {
- return [l, 1, 1];
- } else {
- return [l, 1];
- }
- }
- },
- equaliseTransform = R._equaliseTransform = function (t1, t2) {
- t2 = Str(t2).replace(/\.{3}|\u2026/g, t1);
- t1 = R.parseTransformString(t1) || [];
- t2 = R.parseTransformString(t2) || [];
- var maxlength = mmax(t1.length, t2.length),
- from = [],
- to = [],
- i = 0, j, jj,
- tt1, tt2;
- for (; i < maxlength; i++) {
- tt1 = t1[i] || getEmpty(t2[i]);
- tt2 = t2[i] || getEmpty(tt1);
- if ((tt1[0] != tt2[0]) ||
- (tt1[0].toLowerCase() == "r" && (tt1[2] != tt2[2] || tt1[3] != tt2[3])) ||
- (tt1[0].toLowerCase() == "s" && (tt1[3] != tt2[3] || tt1[4] != tt2[4]))
- ) {
- return;
- }
- from[i] = [];
- to[i] = [];
- for (j = 0, jj = mmax(tt1.length, tt2.length); j < jj; j++) {
- j in tt1 && (from[i][j] = tt1[j]);
- j in tt2 && (to[i][j] = tt2[j]);
- }
- }
- return {
- from: from,
- to: to
- };
- };
- R._getContainer = function (x, y, w, h) {
- var container;
- container = h == null && !R.is(x, "object") ? g.doc.getElementById(x) : x;
- if (container == null) {
- return;
- }
- if (container.tagName) {
- if (y == null) {
- return {
- container: container,
- width: container.style.pixelWidth || container.offsetWidth,
- height: container.style.pixelHeight || container.offsetHeight
- };
- } else {
- return {
- container: container,
- width: y,
- height: w
- };
- }
- }
- return {
- container: 1,
- x: x,
- y: y,
- width: w,
- height: h
- };
- };
- /*\
- * Raphael.pathToRelative
- [ method ]
- **
- * Utility method
- **
- * Converts path to relative form
- > Parameters
- - pathString (string|array) path string or array of segments
- = (array) array of segments.
- \*/
- R.pathToRelative = pathToRelative;
- R._engine = {};
- /*\
- * Raphael.path2curve
- [ method ]
- **
- * Utility method
- **
- * Converts path to a new path where all segments are cubic bezier curves.
- > Parameters
- - pathString (string|array) path string or array of segments
- = (array) array of segments.
- \*/
- R.path2curve = path2curve;
- /*\
- * Raphael.matrix
- [ method ]
- **
- * Utility method
- **
- * Returns matrix based on given parameters.
- > Parameters
- - a (number)
- - b (number)
- - c (number)
- - d (number)
- - e (number)
- - f (number)
- = (object) @Matrix
- \*/
- R.matrix = function (a, b, c, d, e, f) {
- return new Matrix(a, b, c, d, e, f);
- };
- function Matrix(a, b, c, d, e, f) {
- if (a != null) {
- this.a = +a;
- this.b = +b;
- this.c = +c;
- this.d = +d;
- this.e = +e;
- this.f = +f;
- } else {
- this.a = 1;
- this.b = 0;
- this.c = 0;
- this.d = 1;
- this.e = 0;
- this.f = 0;
- }
- }
- (function (matrixproto) {
- /*\
- * Matrix.add
- [ method ]
- **
- * Adds given matrix to existing one.
- > Parameters
- - a (number)
- - b (number)
- - c (number)
- - d (number)
- - e (number)
- - f (number)
- or
- - matrix (object) @Matrix
- \*/
- matrixproto.add = function (a, b, c, d, e, f) {
- var out = [[], [], []],
- m = [[this.a, this.c, this.e], [this.b, this.d, this.f], [0, 0, 1]],
- matrix = [[a, c, e], [b, d, f], [0, 0, 1]],
- x, y, z, res;
-
- if (a && a instanceof Matrix) {
- matrix = [[a.a, a.c, a.e], [a.b, a.d, a.f], [0, 0, 1]];
- }
-
- for (x = 0; x < 3; x++) {
- for (y = 0; y < 3; y++) {
- res = 0;
- for (z = 0; z < 3; z++) {
- res += m[x][z] * matrix[z][y];
- }
- out[x][y] = res;
- }
- }
- this.a = out[0][0];
- this.b = out[1][0];
- this.c = out[0][1];
- this.d = out[1][1];
- this.e = out[0][2];
- this.f = out[1][2];
- };
- /*\
- * Matrix.invert
- [ method ]
- **
- * Returns inverted version of the matrix
- = (object) @Matrix
- \*/
- matrixproto.invert = function () {
- var me = this,
- x = me.a * me.d - me.b * me.c;
- return new Matrix(me.d / x, -me.b / x, -me.c / x, me.a / x, (me.c * me.f - me.d * me.e) / x, (me.b * me.e - me.a * me.f) / x);
- };
- /*\
- * Matrix.clone
- [ method ]
- **
- * Returns copy of the matrix
- = (object) @Matrix
- \*/
- matrixproto.clone = function () {
- return new Matrix(this.a, this.b, this.c, this.d, this.e, this.f);
- };
- /*\
- * Matrix.translate
- [ method ]
- **
- * Translate the matrix
- > Parameters
- - x (number)
- - y (number)
- \*/
- matrixproto.translate = function (x, y) {
- this.add(1, 0, 0, 1, x, y);
- };
- /*\
- * Matrix.scale
- [ method ]
- **
- * Scales the matrix
- > Parameters
- - x (number)
- - y (number) #optional
- - cx (number) #optional
- - cy (number) #optional
- \*/
- matrixproto.scale = function (x, y, cx, cy) {
- y == null && (y = x);
- (cx || cy) && this.add(1, 0, 0, 1, cx, cy);
- this.add(x, 0, 0, y, 0, 0);
- (cx || cy) && this.add(1, 0, 0, 1, -cx, -cy);
- };
- /*\
- * Matrix.rotate
- [ method ]
- **
- * Rotates the matrix
- > Parameters
- - a (number)
- - x (number)
- - y (number)
- \*/
- matrixproto.rotate = function (a, x, y) {
- a = R.rad(a);
- x = x || 0;
- y = y || 0;
- var cos = +math.cos(a).toFixed(9),
- sin = +math.sin(a).toFixed(9);
- this.add(cos, sin, -sin, cos, x, y);
- this.add(1, 0, 0, 1, -x, -y);
- };
- /*\
- * Matrix.x
- [ method ]
- **
- * Return x coordinate for given point after transformation described by the matrix. See also @Matrix.y
- > Parameters
- - x (number)
- - y (number)
- = (number) x
- \*/
- matrixproto.x = function (x, y) {
- return x * this.a + y * this.c + this.e;
- };
- /*\
- * Matrix.y
- [ method ]
- **
- * Return y coordinate for given point after transformation described by the matrix. See also @Matrix.x
- > Parameters
- - x (number)
- - y (number)
- = (number) y
- \*/
- matrixproto.y = function (x, y) {
- return x * this.b + y * this.d + this.f;
- };
- matrixproto.get = function (i) {
- return +this[Str.fromCharCode(97 + i)].toFixed(4);
- };
- matrixproto.toString = function () {
- return R.svg ?
- "matrix(" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)].join() + ")" :
- [this.get(0), this.get(2), this.get(1), this.get(3), 0, 0].join();
- };
- matrixproto.toFilter = function () {
- return "progid:DXImageTransform.Microsoft.Matrix(M11=" + this.get(0) +
- ", M12=" + this.get(2) + ", M21=" + this.get(1) + ", M22=" + this.get(3) +
- ", Dx=" + this.get(4) + ", Dy=" + this.get(5) + ", sizingmethod='auto expand')";
- };
- matrixproto.offset = function () {
- return [this.e.toFixed(4), this.f.toFixed(4)];
- };
- function norm(a) {
- return a[0] * a[0] + a[1] * a[1];
- }
- function normalize(a) {
- var mag = math.sqrt(norm(a));
- a[0] && (a[0] /= mag);
- a[1] && (a[1] /= mag);
- }
- /*\
- * Matrix.split
- [ method ]
- **
- * Splits matrix into primitive transformations
- = (object) in format:
- o dx (number) translation by x
- o dy (number) translation by y
- o scalex (number) scale by x
- o scaley (number) scale by y
- o shear (number) shear
- o rotate (number) rotation in deg
- o isSimple (boolean) could it be represented via simple transformations
- \*/
- matrixproto.split = function () {
- var out = {};
- // translation
- out.dx = this.e;
- out.dy = this.f;
-
- // scale and shear
- var row = [[this.a, this.c], [this.b, this.d]];
- out.scalex = math.sqrt(norm(row[0]));
- normalize(row[0]);
-
- out.shear = row[0][0] * row[1][0] + row[0][1] * row[1][1];
- row[1] = [row[1][0] - row[0][0] * out.shear, row[1][1] - row[0][1] * out.shear];
-
- out.scaley = math.sqrt(norm(row[1]));
- normalize(row[1]);
- out.shear /= out.scaley;
-
- // rotation
- var sin = -row[0][1],
- cos = row[1][1];
- if (cos < 0) {
- out.rotate = R.deg(math.acos(cos));
- if (sin < 0) {
- out.rotate = 360 - out.rotate;
- }
- } else {
- out.rotate = R.deg(math.asin(sin));
- }
-
- out.isSimple = !+out.shear.toFixed(9) && (out.scalex.toFixed(9) == out.scaley.toFixed(9) || !out.rotate);
- out.isSuperSimple = !+out.shear.toFixed(9) && out.scalex.toFixed(9) == out.scaley.toFixed(9) && !out.rotate;
- out.noRotation = !+out.shear.toFixed(9) && !out.rotate;
- return out;
- };
- /*\
- * Matrix.toTransformString
- [ method ]
- **
- * Return transform string that represents given matrix
- = (string) transform string
- \*/
- matrixproto.toTransformString = function (shorter) {
- var s = shorter || this[split]();
- if (s.isSimple) {
- s.scalex = +s.scalex.toFixed(4);
- s.scaley = +s.scaley.toFixed(4);
- s.rotate = +s.rotate.toFixed(4);
- return (s.dx || s.dy ? "t" + [s.dx, s.dy] : E) +
- (s.scalex != 1 || s.scaley != 1 ? "s" + [s.scalex, s.scaley, 0, 0] : E) +
- (s.rotate ? "r" + [s.rotate, 0, 0] : E);
- } else {
- return "m" + [this.get(0), this.get(1), this.get(2), this.get(3), this.get(4), this.get(5)];
- }
- };
- })(Matrix.prototype);
-
- // WebKit rendering bug workaround method
- var version = navigator.userAgent.match(/Version\/(.*?)\s/) || navigator.userAgent.match(/Chrome\/(\d+)/);
- if ((navigator.vendor == "Apple Computer, Inc.") && (version && version[1] < 4 || navigator.platform.slice(0, 2) == "iP") ||
- (navigator.vendor == "Google Inc." && version && version[1] < 8)) {
- /*\
- * Paper.safari
- [ method ]
- **
- * There is an inconvenient rendering bug in Safari (WebKit):
- * sometimes the rendering should be forced.
- * This method should help with dealing with this bug.
- \*/
- paperproto.safari = function () {
- var rect = this.rect(-99, -99, this.width + 99, this.height + 99).attr({stroke: "none"});
- setTimeout(function () {rect.remove();});
- };
- } else {
- paperproto.safari = fun;
- }
-
- var preventDefault = function () {
- this.returnValue = false;
- },
- preventTouch = function () {
- return this.originalEvent.preventDefault();
- },
- stopPropagation = function () {
- this.cancelBubble = true;
- },
- stopTouch = function () {
- return this.originalEvent.stopPropagation();
- },
- getEventPosition = function (e) {
- var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
- scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
-
- return {
- x: e.clientX + scrollX,
- y: e.clientY + scrollY
- };
- },
- addEvent = (function () {
- if (g.doc.addEventListener) {
- return function (obj, type, fn, element) {
- var f = function (e) {
- var pos = getEventPosition(e);
- return fn.call(element, e, pos.x, pos.y);
- };
- obj.addEventListener(type, f, false);
-
- if (supportsTouch && touchMap[type]) {
- var _f = function (e) {
- var pos = getEventPosition(e),
- olde = e;
-
- for (var i = 0, ii = e.targetTouches && e.targetTouches.length; i < ii; i++) {
- if (e.targetTouches[i].target == obj) {
- e = e.targetTouches[i];
- e.originalEvent = olde;
- e.preventDefault = preventTouch;
- e.stopPropagation = stopTouch;
- break;
- }
- }
-
- return fn.call(element, e, pos.x, pos.y);
- };
- obj.addEventListener(touchMap[type], _f, false);
- }
-
- return function () {
- obj.removeEventListener(type, f, false);
-
- if (supportsTouch && touchMap[type])
- obj.removeEventListener(touchMap[type], _f, false);
-
- return true;
- };
- };
- } else if (g.doc.attachEvent) {
- return function (obj, type, fn, element) {
- var f = function (e) {
- e = e || g.win.event;
- var scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
- scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
- x = e.clientX + scrollX,
- y = e.clientY + scrollY;
- e.preventDefault = e.preventDefault || preventDefault;
- e.stopPropagation = e.stopPropagation || stopPropagation;
- return fn.call(element, e, x, y);
- };
- obj.attachEvent("on" + type, f);
- var detacher = function () {
- obj.detachEvent("on" + type, f);
- return true;
- };
- return detacher;
- };
- }
- })(),
- drag = [],
- dragMove = function (e) {
- var x = e.clientX,
- y = e.clientY,
- scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
- scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft,
- dragi,
- j = drag.length;
- while (j--) {
- dragi = drag[j];
- if (supportsTouch && e.touches) {
- var i = e.touches.length,
- touch;
- while (i--) {
- touch = e.touches[i];
- if (touch.identifier == dragi.el._drag.id) {
- x = touch.clientX;
- y = touch.clientY;
- (e.originalEvent ? e.originalEvent : e).preventDefault();
- break;
- }
- }
- } else {
- e.preventDefault();
- }
- var node = dragi.el.node,
- o,
- next = node.nextSibling,
- parent = node.parentNode,
- display = node.style.display;
- g.win.opera && parent.removeChild(node);
- node.style.display = "none";
- o = dragi.el.paper.getElementByPoint(x, y);
- node.style.display = display;
- g.win.opera && (next ? parent.insertBefore(node, next) : parent.appendChild(node));
- o && eve("raphael.drag.over." + dragi.el.id, dragi.el, o);
- x += scrollX;
- y += scrollY;
- eve("raphael.drag.move." + dragi.el.id, dragi.move_scope || dragi.el, x - dragi.el._drag.x, y - dragi.el._drag.y, x, y, e);
- }
- },
- dragUp = function (e) {
- R.unmousemove(dragMove).unmouseup(dragUp);
- var i = drag.length,
- dragi;
- while (i--) {
- dragi = drag[i];
- dragi.el._drag = {};
- eve("raphael.drag.end." + dragi.el.id, dragi.end_scope || dragi.start_scope || dragi.move_scope || dragi.el, e);
- }
- drag = [];
- },
- /*\
- * Raphael.el
- [ property (object) ]
- **
- * You can add your own method to elements. This is usefull when you want to hack default functionality or
- * want to wrap some common transformation or attributes in one method. In difference to canvas methods,
- * you can redefine element method at any time. Expending element methods wouldn’t affect set.
- > Usage
- | Raphael.el.red = function () {
- | this.attr({fill: "#f00"});
- | };
- | // then use it
- | paper.circle(100, 100, 20).red();
- \*/
- elproto = R.el = {};
- /*\
- * Element.click
- [ method ]
- **
- * Adds event handler for click for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.unclick
- [ method ]
- **
- * Removes event handler for click for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.dblclick
- [ method ]
- **
- * Adds event handler for double click for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.undblclick
- [ method ]
- **
- * Removes event handler for double click for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.mousedown
- [ method ]
- **
- * Adds event handler for mousedown for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.unmousedown
- [ method ]
- **
- * Removes event handler for mousedown for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.mousemove
- [ method ]
- **
- * Adds event handler for mousemove for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.unmousemove
- [ method ]
- **
- * Removes event handler for mousemove for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.mouseout
- [ method ]
- **
- * Adds event handler for mouseout for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.unmouseout
- [ method ]
- **
- * Removes event handler for mouseout for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.mouseover
- [ method ]
- **
- * Adds event handler for mouseover for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.unmouseover
- [ method ]
- **
- * Removes event handler for mouseover for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.mouseup
- [ method ]
- **
- * Adds event handler for mouseup for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.unmouseup
- [ method ]
- **
- * Removes event handler for mouseup for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.touchstart
- [ method ]
- **
- * Adds event handler for touchstart for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.untouchstart
- [ method ]
- **
- * Removes event handler for touchstart for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.touchmove
- [ method ]
- **
- * Adds event handler for touchmove for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.untouchmove
- [ method ]
- **
- * Removes event handler for touchmove for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.touchend
- [ method ]
- **
- * Adds event handler for touchend for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.untouchend
- [ method ]
- **
- * Removes event handler for touchend for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
-
- /*\
- * Element.touchcancel
- [ method ]
- **
- * Adds event handler for touchcancel for the element.
- > Parameters
- - handler (function) handler for the event
- = (object) @Element
- \*/
- /*\
- * Element.untouchcancel
- [ method ]
- **
- * Removes event handler for touchcancel for the element.
- > Parameters
- - handler (function) #optional handler for the event
- = (object) @Element
- \*/
- for (var i = events.length; i--;) {
- (function (eventName) {
- R[eventName] = elproto[eventName] = function (fn, scope) {
- if (R.is(fn, "function")) {
- this.events = this.events || [];
- this.events.push({name: eventName, f: fn, unbind: addEvent(this.shape || this.node || g.doc, eventName, fn, scope || this)});
- }
- return this;
- };
- R["un" + eventName] = elproto["un" + eventName] = function (fn) {
- var events = this.events || [],
- l = events.length;
- while (l--){
- if (events[l].name == eventName && (R.is(fn, "undefined") || events[l].f == fn)) {
- events[l].unbind();
- events.splice(l, 1);
- !events.length && delete this.events;
- }
- }
- return this;
- };
- })(events[i]);
- }
-
- /*\
- * Element.data
- [ method ]
- **
- * Adds or retrieves given value asociated with given key.
- **
- * See also @Element.removeData
- > Parameters
- - key (string) key to store data
- - value (any) #optional value to store
- = (object) @Element
- * or, if value is not specified:
- = (any) value
- * or, if key and value are not specified:
- = (object) Key/value pairs for all the data associated with the element.
- > Usage
- | for (var i = 0, i < 5, i++) {
- | paper.circle(10 + 15 * i, 10, 10)
- | .attr({fill: "#000"})
- | .data("i", i)
- | .click(function () {
- | alert(this.data("i"));
- | });
- | }
- \*/
- elproto.data = function (key, value) {
- var data = eldata[this.id] = eldata[this.id] || {};
- if (arguments.length == 0) {
- return data;
- }
- if (arguments.length == 1) {
- if (R.is(key, "object")) {
- for (var i in key) if (key[has](i)) {
- this.data(i, key[i]);
- }
- return this;
- }
- eve("raphael.data.get." + this.id, this, data[key], key);
- return data[key];
- }
- data[key] = value;
- eve("raphael.data.set." + this.id, this, value, key);
- return this;
- };
- /*\
- * Element.removeData
- [ method ]
- **
- * Removes value associated with an element by given key.
- * If key is not provided, removes all the data of the element.
- > Parameters
- - key (string) #optional key
- = (object) @Element
- \*/
- elproto.removeData = function (key) {
- if (key == null) {
- eldata[this.id] = {};
- } else {
- eldata[this.id] && delete eldata[this.id][key];
- }
- return this;
- };
- /*\
- * Element.getData
- [ method ]
- **
- * Retrieves the element data
- = (object) data
- \*/
- elproto.getData = function () {
- return clone(eldata[this.id] || {});
- };
- /*\
- * Element.hover
- [ method ]
- **
- * Adds event handlers for hover for the element.
- > Parameters
- - f_in (function) handler for hover in
- - f_out (function) handler for hover out
- - icontext (object) #optional context for hover in handler
- - ocontext (object) #optional context for hover out handler
- = (object) @Element
- \*/
- elproto.hover = function (f_in, f_out, scope_in, scope_out) {
- return this.mouseover(f_in, scope_in).mouseout(f_out, scope_out || scope_in);
- };
- /*\
- * Element.unhover
- [ method ]
- **
- * Removes event handlers for hover for the element.
- > Parameters
- - f_in (function) handler for hover in
- - f_out (function) handler for hover out
- = (object) @Element
- \*/
- elproto.unhover = function (f_in, f_out) {
- return this.unmouseover(f_in).unmouseout(f_out);
- };
- var draggable = [];
- /*\
- * Element.drag
- [ method ]
- **
- * Adds event handlers for drag of the element.
- > Parameters
- - onmove (function) handler for moving
- - onstart (function) handler for drag start
- - onend (function) handler for drag end
- - mcontext (object) #optional context for moving handler
- - scontext (object) #optional context for drag start handler
- - econtext (object) #optional context for drag end handler
- * Additionaly following `drag` events will be triggered: `drag.start.<id>` on start,
- * `drag.end.<id>` on end and `drag.move.<id>` on every move. When element will be dragged over another element
- * `drag.over.<id>` will be fired as well.
- *
- * Start event and start handler will be called in specified context or in context of the element with following parameters:
- o x (number) x position of the mouse
- o y (number) y position of the mouse
- o event (object) DOM event object
- * Move event and move handler will be called in specified context or in context of the element with following parameters:
- o dx (number) shift by x from the start point
- o dy (number) shift by y from the start point
- o x (number) x position of the mouse
- o y (number) y position of the mouse
- o event (object) DOM event object
- * End event and end handler will be called in specified context or in context of the element with following parameters:
- o event (object) DOM event object
- = (object) @Element
- \*/
- elproto.drag = function (onmove, onstart, onend, move_scope, start_scope, end_scope) {
- function start(e) {
- (e.originalEvent || e).preventDefault();
- var x = e.clientX,
- y = e.clientY,
- scrollY = g.doc.documentElement.scrollTop || g.doc.body.scrollTop,
- scrollX = g.doc.documentElement.scrollLeft || g.doc.body.scrollLeft;
- this._drag.id = e.identifier;
- if (supportsTouch && e.touches) {
- var i = e.touches.length, touch;
- while (i--) {
- touch = e.touches[i];
- this._drag.id = touch.identifier;
- if (touch.identifier == this._drag.id) {
- x = touch.clientX;
- y = touch.clientY;
- break;
- }
- }
- }
- this._drag.x = x + scrollX;
- this._drag.y = y + scrollY;
- !drag.length && R.mousemove(dragMove).mouseup(dragUp);
- drag.push({el: this, move_scope: move_scope, start_scope: start_scope, end_scope: end_scope});
- onstart && eve.on("raphael.drag.start." + this.id, onstart);
- onmove && eve.on("raphael.drag.move." + this.id, onmove);
- onend && eve.on("raphael.drag.end." + this.id, onend);
- eve("raphael.drag.start." + this.id, start_scope || move_scope || this, e.clientX + scrollX, e.clientY + scrollY, e);
- }
- this._drag = {};
- draggable.push({el: this, start: start});
- this.mousedown(start);
- return this;
- };
- /*\
- * Element.onDragOver
- [ method ]
- **
- * Shortcut for assigning event handler for `drag.over.<id>` event, where id is id of the element (see @Element.id).
- > Parameters
- - f (function) handler for event, first argument would be the element you are dragging over
- \*/
- elproto.onDragOver = function (f) {
- f ? eve.on("raphael.drag.over." + this.id, f) : eve.unbind("raphael.drag.over." + this.id);
- };
- /*\
- * Element.undrag
- [ method ]
- **
- * Removes all drag event handlers from given element.
- \*/
- elproto.undrag = function () {
- var i = draggable.length;
- while (i--) if (draggable[i].el == this) {
- this.unmousedown(draggable[i].start);
- draggable.splice(i, 1);
- eve.unbind("raphael.drag.*." + this.id);
- }
- !draggable.length && R.unmousemove(dragMove).unmouseup(dragUp);
- drag = [];
- };
- /*\
- * Paper.circle
- [ method ]
- **
- * Draws a circle.
- **
- > Parameters
- **
- - x (number) x coordinate of the centre
- - y (number) y coordinate of the centre
- - r (number) radius
- = (object) Raphaël element object with type “circle”
- **
- > Usage
- | var c = paper.circle(50, 50, 40);
- \*/
- paperproto.circle = function (x, y, r) {
- var out = R._engine.circle(this, x || 0, y || 0, r || 0);
- this.__set__ && this.__set__.push(out);
- return out;
- };
- /*\
- * Paper.rect
- [ method ]
- *
- * Draws a rectangle.
- **
- > Parameters
- **
- - x (number) x coordinate of the top left corner
- - y (number) y coordinate of the top left corner
- - width (number) width
- - height (number) height
- - r (number) #optional radius for rounded corners, default is 0
- = (object) Raphaël element object with type “rect”
- **
- > Usage
- | // regular rectangle
- | var c = paper.rect(10, 10, 50, 50);
- | // rectangle with rounded corners
- | var c = paper.rect(40, 40, 50, 50, 10);
- \*/
- paperproto.rect = function (x, y, w, h, r) {
- var out = R._engine.rect(this, x || 0, y || 0, w || 0, h || 0, r || 0);
- this.__set__ && this.__set__.push(out);
- return out;
- };
- /*\
- * Paper.ellipse
- [ method ]
- **
- * Draws an ellipse.
- **
- > Parameters
- **
- - x (number) x coordinate of the centre
- - y (number) y coordinate of the centre
- - rx (number) horizontal radius
- - ry (number) vertical radius
- = (object) Raphaël element object with type “ellipse”
- **
- > Usage
- | var c = paper.ellipse(50, 50, 40, 20);
- \*/
- paperproto.ellipse = function (x, y, rx, ry) {
- var out = R._engine.ellipse(this, x || 0, y || 0, rx || 0, ry || 0);
- this.__set__ && this.__set__.push(out);
- return out;
- };
- /*\
- * Paper.path
- [ method ]
- **
- * Creates a path element by given path data string.
- > Parameters
- - pathString (string) #optional path string in SVG format.
- * Path string consists of one-letter commands, followed by comma seprarated arguments in numercal form. Example:
- | "M10,20L30,40"
- * Here we can see two commands: “M”, with arguments `(10, 20)` and “L” with arguments `(30, 40)`. Upper case letter mean command is absolute, lower case—relative.
- *
- # <p>Here is short list of commands available, for more details see <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path's data attribute's format are described in the SVG specification.">SVG path string format</a>.</p>
- # <table><thead><tr><th>Command</th><th>Name</th><th>Parameters</th></tr></thead><tbody>
- # <tr><td>M</td><td>moveto</td><td>(x y)+</td></tr>
- # <tr><td>Z</td><td>closepath</td><td>(none)</td></tr>
- # <tr><td>L</td><td>lineto</td><td>(x y)+</td></tr>
- # <tr><td>H</td><td>horizontal lineto</td><td>x+</td></tr>
- # <tr><td>V</td><td>vertical lineto</td><td>y+</td></tr>
- # <tr><td>C</td><td>curveto</td><td>(x1 y1 x2 y2 x y)+</td></tr>
- # <tr><td>S</td><td>smooth curveto</td><td>(x2 y2 x y)+</td></tr>
- # <tr><td>Q</td><td>quadratic Bézier curveto</td><td>(x1 y1 x y)+</td></tr>
- # <tr><td>T</td><td>smooth quadratic Bézier curveto</td><td>(x y)+</td></tr>
- # <tr><td>A</td><td>elliptical arc</td><td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td></tr>
- # <tr><td>R</td><td><a href="http://en.wikipedia.org/wiki/Catmull–Rom_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom curveto</a>*</td><td>x1 y1 (x y)+</td></tr></tbody></table>
- * * “Catmull-Rom curveto” is a not standard SVG command and added in 2.0 to make life easier.
- * Note: there is a special case when path consist of just three commands: “M10,10R…z”. In this case path will smoothly connects to its beginning.
- > Usage
- | var c = paper.path("M10 10L90 90");
- | // draw a diagonal line:
- | // move to 10,10, line to 90,90
- * For example of path strings, check out these icons: http://raphaeljs.com/icons/
- \*/
- paperproto.path = function (pathString) {
- pathString && !R.is(pathString, string) && !R.is(pathString[0], array) && (pathString += E);
- var out = R._engine.path(R.format[apply](R, arguments), this);
- this.__set__ && this.__set__.push(out);
- return out;
- };
- /*\
- * Paper.image
- [ method ]
- **
- * Embeds an image into the surface.
- **
- > Parameters
- **
- - src (string) URI of the source image
- - x (number) x coordinate position
- - y (number) y coordinate position
- - width (number) width of the image
- - height (number) height of the image
- = (object) Raphaël element object with type “image”
- **
- > Usage
- | var c = paper.image("apple.png", 10, 10, 80, 80);
- \*/
- paperproto.image = function (src, x, y, w, h) {
- var out = R._engine.image(this, src || "about:blank", x || 0, y || 0, w || 0, h || 0);
- this.__set__ && this.__set__.push(out);
- return out;
- };
- /*\
- * Paper.text
- [ method ]
- **
- * Draws a text string. If you need line breaks, put “\n” in the string.
- **
- > Parameters
- **
- - x (number) x coordinate position
- - y (number) y coordinate position
- - text (string) The text string to draw
- = (object) Raphaël element object with type “text”
- **
- > Usage
- | var t = paper.text(50, 50, "Raphaël\nkicks\nbutt!");
- \*/
- paperproto.text = function (x, y, text) {
- var out = R._engine.text(this, x || 0, y || 0, Str(text));
- this.__set__ && this.__set__.push(out);
- return out;
- };
- /*\
- * Paper.set
- [ method ]
- **
- * Creates array-like object to keep and operate several elements at once.
- * Warning: it doesn’t create any elements for itself in the page, it just groups existing elements.
- * Sets act as pseudo elements — all methods available to an element can be used on a set.
- = (object) array-like object that represents set of elements
- **
- > Usage
- | var st = paper.set();
- | st.push(
- | paper.circle(10, 10, 5),
- | paper.circle(30, 10, 5)
- | );
- | st.attr({fill: "red"}); // changes the fill of both circles
- \*/
- paperproto.set = function (itemsArray) {
- !R.is(itemsArray, "array") && (itemsArray = Array.prototype.splice.call(arguments, 0, arguments.length));
- var out = new Set(itemsArray);
- this.__set__ && this.__set__.push(out);
- out["paper"] = this;
- out["type"] = "set";
- return out;
- };
- /*\
- * Paper.setStart
- [ method ]
- **
- * Creates @Paper.set. All elements that will be created after calling this method and before calling
- * @Paper.setFinish will be added to the set.
- **
- > Usage
- | paper.setStart();
- | paper.circle(10, 10, 5),
- | paper.circle(30, 10, 5)
- | var st = paper.setFinish();
- | st.attr({fill: "red"}); // changes the fill of both circles
- \*/
- paperproto.setStart = function (set) {
- this.__set__ = set || this.set();
- };
- /*\
- * Paper.setFinish
- [ method ]
- **
- * See @Paper.setStart. This method finishes catching and returns resulting set.
- **
- = (object) set
- \*/
- paperproto.setFinish = function (set) {
- var out = this.__set__;
- delete this.__set__;
- return out;
- };
- /*\
- * Paper.getSize
- [ method ]
- **
- * Obtains current paper actual size.
- **
- = (object)
- \*/
- paperproto.getSize = function () {
- var container = this.canvas.parentNode;
- return {
- width: container.offsetWidth,
- height: container.offsetHeight
- };
- };
- /*\
- * Paper.setSize
- [ method ]
- **
- * If you need to change dimensions of the canvas call this method
- **
- > Parameters
- **
- - width (number) new width of the canvas
- - height (number) new height of the canvas
- \*/
- paperproto.setSize = function (width, height) {
- return R._engine.setSize.call(this, width, height);
- };
- /*\
- * Paper.setViewBox
- [ method ]
- **
- * Sets the view box of the paper. Practically it gives you ability to zoom and pan whole paper surface by
- * specifying new boundaries.
- **
- > Parameters
- **
- - x (number) new x position, default is `0`
- - y (number) new y position, default is `0`
- - w (number) new width of the canvas
- - h (number) new height of the canvas
- - fit (boolean) `true` if you want graphics to fit into new boundary box
- \*/
- paperproto.setViewBox = function (x, y, w, h, fit) {
- return R._engine.setViewBox.call(this, x, y, w, h, fit);
- };
- /*\
- * Paper.top
- [ property ]
- **
- * Points to the topmost element on the paper
- \*/
- /*\
- * Paper.bottom
- [ property ]
- **
- * Points to the bottom element on the paper
- \*/
- paperproto.top = paperproto.bottom = null;
- /*\
- * Paper.raphael
- [ property ]
- **
- * Points to the @Raphael object/function
- \*/
- paperproto.raphael = R;
- var getOffset = function (elem) {
- var box = elem.getBoundingClientRect(),
- doc = elem.ownerDocument,
- body = doc.body,
- docElem = doc.documentElement,
- clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
- top = box.top + (g.win.pageYOffset || docElem.scrollTop || body.scrollTop ) - clientTop,
- left = box.left + (g.win.pageXOffset || docElem.scrollLeft || body.scrollLeft) - clientLeft;
- return {
- y: top,
- x: left
- };
- };
- /*\
- * Paper.getElementByPoint
- [ method ]
- **
- * Returns you topmost element under given point.
- **
- = (object) Raphaël element object
- > Parameters
- **
- - x (number) x coordinate from the top left corner of the window
- - y (number) y coordinate from the top left corner of the window
- > Usage
- | paper.getElementByPoint(mouseX, mouseY).attr({stroke: "#f00"});
- \*/
- paperproto.getElementByPoint = function (x, y) {
- var paper = this,
- svg = paper.canvas,
- target = g.doc.elementFromPoint(x, y);
- if (g.win.opera && target.tagName == "svg") {
- var so = getOffset(svg),
- sr = svg.createSVGRect();
- sr.x = x - so.x;
- sr.y = y - so.y;
- sr.width = sr.height = 1;
- var hits = svg.getIntersectionList(sr, null);
- if (hits.length) {
- target = hits[hits.length - 1];
- }
- }
- if (!target) {
- return null;
- }
- while (target.parentNode && target != svg.parentNode && !target.raphael) {
- target = target.parentNode;
- }
- target == paper.canvas.parentNode && (target = svg);
- target = target && target.raphael ? paper.getById(target.raphaelid) : null;
- return target;
- };
-
- /*\
- * Paper.getElementsByBBox
- [ method ]
- **
- * Returns set of elements that have an intersecting bounding box
- **
- > Parameters
- **
- - bbox (object) bbox to check with
- = (object) @Set
- \*/
- paperproto.getElementsByBBox = function (bbox) {
- var set = this.set();
- this.forEach(function (el) {
- if (R.isBBoxIntersect(el.getBBox(), bbox)) {
- set.push(el);
- }
- });
- return set;
- };
-
- /*\
- * Paper.getById
- [ method ]
- **
- * Returns you element by its internal ID.
- **
- > Parameters
- **
- - id (number) id
- = (object) Raphaël element object
- \*/
- paperproto.getById = function (id) {
- var bot = this.bottom;
- while (bot) {
- if (bot.id == id) {
- return bot;
- }
- bot = bot.next;
- }
- return null;
- };
- /*\
- * Paper.forEach
- [ method ]
- **
- * Executes given function for each element on the paper
- *
- * If callback function returns `false` it will stop loop running.
- **
- > Parameters
- **
- - callback (function) function to run
- - thisArg (object) context object for the callback
- = (object) Paper object
- > Usage
- | paper.forEach(function (el) {
- | el.attr({ stroke: "blue" });
- | });
- \*/
- paperproto.forEach = function (callback, thisArg) {
- var bot = this.bottom;
- while (bot) {
- if (callback.call(thisArg, bot) === false) {
- return this;
- }
- bot = bot.next;
- }
- return this;
- };
- /*\
- * Paper.getElementsByPoint
- [ method ]
- **
- * Returns set of elements that have common point inside
- **
- > Parameters
- **
- - x (number) x coordinate of the point
- - y (number) y coordinate of the point
- = (object) @Set
- \*/
- paperproto.getElementsByPoint = function (x, y) {
- var set = this.set();
- this.forEach(function (el) {
- if (el.isPointInside(x, y)) {
- set.push(el);
- }
- });
- return set;
- };
- function x_y() {
- return this.x + S + this.y;
- }
- function x_y_w_h() {
- return this.x + S + this.y + S + this.width + " \xd7 " + this.height;
- }
- /*\
- * Element.isPointInside
- [ method ]
- **
- * Determine if given point is inside this element’s shape
- **
- > Parameters
- **
- - x (number) x coordinate of the point
- - y (number) y coordinate of the point
- = (boolean) `true` if point inside the shape
- \*/
- elproto.isPointInside = function (x, y) {
- var rp = this.realPath = getPath[this.type](this);
- if (this.attr('transform') && this.attr('transform').length) {
- rp = R.transformPath(rp, this.attr('transform'));
- }
- return R.isPointInsidePath(rp, x, y);
- };
- /*\
- * Element.getBBox
- [ method ]
- **
- * Return bounding box for a given element
- **
- > Parameters
- **
- - isWithoutTransform (boolean) flag, `true` if you want to have bounding box before transformations. Default is `false`.
- = (object) Bounding box object:
- o {
- o x: (number) top left corner x
- o y: (number) top left corner y
- o x2: (number) bottom right corner x
- o y2: (number) bottom right corner y
- o width: (number) width
- o height: (number) height
- o }
- \*/
- elproto.getBBox = function (isWithoutTransform) {
- if (this.removed) {
- return {};
- }
- var _ = this._;
- if (isWithoutTransform) {
- if (_.dirty || !_.bboxwt) {
- this.realPath = getPath[this.type](this);
- _.bboxwt = pathDimensions(this.realPath);
- _.bboxwt.toString = x_y_w_h;
- _.dirty = 0;
- }
- return _.bboxwt;
- }
- if (_.dirty || _.dirtyT || !_.bbox) {
- if (_.dirty || !this.realPath) {
- _.bboxwt = 0;
- this.realPath = getPath[this.type](this);
- }
- _.bbox = pathDimensions(mapPath(this.realPath, this.matrix));
- _.bbox.toString = x_y_w_h;
- _.dirty = _.dirtyT = 0;
- }
- return _.bbox;
- };
- /*\
- * Element.clone
- [ method ]
- **
- = (object) clone of a given element
- **
- \*/
- elproto.clone = function () {
- if (this.removed) {
- return null;
- }
- var out = this.paper[this.type]().attr(this.attr());
- this.__set__ && this.__set__.push(out);
- return out;
- };
- /*\
- * Element.glow
- [ method ]
- **
- * Return set of elements that create glow-like effect around given element. See @Paper.set.
- *
- * Note: Glow is not connected to the element. If you change element attributes it won’t adjust itself.
- **
- > Parameters
- **
- - glow (object) #optional parameters object with all properties optional:
- o {
- o width (number) size of the glow, default is `10`
- o fill (boolean) will it be filled, default is `false`
- o opacity (number) opacity, default is `0.5`
- o offsetx (number) horizontal offset, default is `0`
- o offsety (number) vertical offset, default is `0`
- o color (string) glow colour, default is `black`
- o }
- = (object) @Paper.set of elements that represents glow
- \*/
- elproto.glow = function (glow) {
- if (this.type == "text") {
- return null;
- }
- glow = glow || {};
- var s = {
- width: (glow.width || 10) + (+this.attr("stroke-width") || 1),
- fill: glow.fill || false,
- opacity: glow.opacity || .5,
- offsetx: glow.offsetx || 0,
- offsety: glow.offsety || 0,
- color: glow.color || "#000"
- },
- c = s.width / 2,
- r = this.paper,
- out = r.set(),
- path = this.realPath || getPath[this.type](this);
- path = this.matrix ? mapPath(path, this.matrix) : path;
- for (var i = 1; i < c + 1; i++) {
- out.push(r.path(path).attr({
- stroke: s.color,
- fill: s.fill ? s.color : "none",
- "stroke-linejoin": "round",
- "stroke-linecap": "round",
- "stroke-width": +(s.width / c * i).toFixed(3),
- opacity: +(s.opacity / c).toFixed(3)
- }));
- }
- return out.insertBefore(this).translate(s.offsetx, s.offsety);
- };
- var curveslengths = {},
- getPointAtSegmentLength = function (p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length) {
- if (length == null) {
- return bezlen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
- } else {
- return R.findDotsAtSegment(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, getTatLen(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, length));
- }
- },
- getLengthFactory = function (istotal, subpath) {
- return function (path, length, onlystart) {
- path = path2curve(path);
- var x, y, p, l, sp = "", subpaths = {}, point,
- len = 0;
- for (var i = 0, ii = path.length; i < ii; i++) {
- p = path[i];
- if (p[0] == "M") {
- x = +p[1];
- y = +p[2];
- } else {
- l = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6]);
- if (len + l > length) {
- if (subpath && !subpaths.start) {
- point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
- sp += ["C" + point.start.x, point.start.y, point.m.x, point.m.y, point.x, point.y];
- if (onlystart) {return sp;}
- subpaths.start = sp;
- sp = ["M" + point.x, point.y + "C" + point.n.x, point.n.y, point.end.x, point.end.y, p[5], p[6]].join();
- len += l;
- x = +p[5];
- y = +p[6];
- continue;
- }
- if (!istotal && !subpath) {
- point = getPointAtSegmentLength(x, y, p[1], p[2], p[3], p[4], p[5], p[6], length - len);
- return {x: point.x, y: point.y, alpha: point.alpha};
- }
- }
- len += l;
- x = +p[5];
- y = +p[6];
- }
- sp += p.shift() + p;
- }
- subpaths.end = sp;
- point = istotal ? len : subpath ? subpaths : R.findDotsAtSegment(x, y, p[0], p[1], p[2], p[3], p[4], p[5], 1);
- point.alpha && (point = {x: point.x, y: point.y, alpha: point.alpha});
- return point;
- };
- };
- var getTotalLength = getLengthFactory(1),
- getPointAtLength = getLengthFactory(),
- getSubpathsAtLength = getLengthFactory(0, 1);
- /*\
- * Raphael.getTotalLength
- [ method ]
- **
- * Returns length of the given path in pixels.
- **
- > Parameters
- **
- - path (string) SVG path string.
- **
- = (number) length.
- \*/
- R.getTotalLength = getTotalLength;
- /*\
- * Raphael.getPointAtLength
- [ method ]
- **
- * Return coordinates of the point located at the given length on the given path.
- **
- > Parameters
- **
- - path (string) SVG path string
- - length (number)
- **
- = (object) representation of the point:
- o {
- o x: (number) x coordinate
- o y: (number) y coordinate
- o alpha: (number) angle of derivative
- o }
- \*/
- R.getPointAtLength = getPointAtLength;
- /*\
- * Raphael.getSubpath
- [ method ]
- **
- * Return subpath of a given path from given length to given length.
- **
- > Parameters
- **
- - path (string) SVG path string
- - from (number) position of the start of the segment
- - to (number) position of the end of the segment
- **
- = (string) pathstring for the segment
- \*/
- R.getSubpath = function (path, from, to) {
- if (this.getTotalLength(path) - to < 1e-6) {
- return getSubpathsAtLength(path, from).end;
- }
- var a = getSubpathsAtLength(path, to, 1);
- return from ? getSubpathsAtLength(a, from).end : a;
- };
- /*\
- * Element.getTotalLength
- [ method ]
- **
- * Returns length of the path in pixels. Only works for element of “path” type.
- = (number) length.
- \*/
- elproto.getTotalLength = function () {
- var path = this.getPath();
- if (!path) {
- return;
- }
-
- if (this.node.getTotalLength) {
- return this.node.getTotalLength();
- }
-
- return getTotalLength(path);
- };
- /*\
- * Element.getPointAtLength
- [ method ]
- **
- * Return coordinates of the point located at the given length on the given path. Only works for element of “path” type.
- **
- > Parameters
- **
- - length (number)
- **
- = (object) representation of the point:
- o {
- o x: (number) x coordinate
- o y: (number) y coordinate
- o alpha: (number) angle of derivative
- o }
- \*/
- elproto.getPointAtLength = function (length) {
- var path = this.getPath();
- if (!path) {
- return;
- }
-
- return getPointAtLength(path, length);
- };
- /*\
- * Element.getPath
- [ method ]
- **
- * Returns path of the element. Only works for elements of “path” type and simple elements like circle.
- = (object) path
- **
- \*/
- elproto.getPath = function () {
- var path,
- getPath = R._getPath[this.type];
-
- if (this.type == "text" || this.type == "set") {
- return;
- }
-
- if (getPath) {
- path = getPath(this);
- }
-
- return path;
- };
- /*\
- * Element.getSubpath
- [ method ]
- **
- * Return subpath of a given element from given length to given length. Only works for element of “path” type.
- **
- > Parameters
- **
- - from (number) position of the start of the segment
- - to (number) position of the end of the segment
- **
- = (string) pathstring for the segment
- \*/
- elproto.getSubpath = function (from, to) {
- var path = this.getPath();
- if (!path) {
- return;
- }
-
- return R.getSubpath(path, from, to);
- };
- /*\
- * Raphael.easing_formulas
- [ property ]
- **
- * Object that contains easing formulas for animation. You could extend it with your own. By default it has following list of easing:
- # <ul>
- # <li>“linear”</li>
- # <li>“&lt;” or “easeIn” or “ease-in”</li>
- # <li>“>” or “easeOut” or “ease-out”</li>
- # <li>“&lt;>” or “easeInOut” or “ease-in-out”</li>
- # <li>“backIn” or “back-in”</li>
- # <li>“backOut” or “back-out”</li>
- # <li>“elastic”</li>
- # <li>“bounce”</li>
- # </ul>
- # <p>See also <a href="http://raphaeljs.com/easing.html">Easing demo</a>.</p>
- \*/
- var ef = R.easing_formulas = {
- linear: function (n) {
- return n;
- },
- "<": function (n) {
- return pow(n, 1.7);
- },
- ">": function (n) {
- return pow(n, .48);
- },
- "<>": function (n) {
- var q = .48 - n / 1.04,
- Q = math.sqrt(.1734 + q * q),
- x = Q - q,
- X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1),
- y = -Q - q,
- Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1),
- t = X + Y + .5;
- return (1 - t) * 3 * t * t + t * t * t;
- },
- backIn: function (n) {
- var s = 1.70158;
- return n * n * ((s + 1) * n - s);
- },
- backOut: function (n) {
- n = n - 1;
- var s = 1.70158;
- return n * n * ((s + 1) * n + s) + 1;
- },
- elastic: function (n) {
- if (n == !!n) {
- return n;
- }
- return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1;
- },
- bounce: function (n) {
- var s = 7.5625,
- p = 2.75,
- l;
- if (n < (1 / p)) {
- l = s * n * n;
- } else {
- if (n < (2 / p)) {
- n -= (1.5 / p);
- l = s * n * n + .75;
- } else {
- if (n < (2.5 / p)) {
- n -= (2.25 / p);
- l = s * n * n + .9375;
- } else {
- n -= (2.625 / p);
- l = s * n * n + .984375;
- }
- }
- }
- return l;
- }
- };
- ef.easeIn = ef["ease-in"] = ef["<"];
- ef.easeOut = ef["ease-out"] = ef[">"];
- ef.easeInOut = ef["ease-in-out"] = ef["<>"];
- ef["back-in"] = ef.backIn;
- ef["back-out"] = ef.backOut;
-
- var animationElements = [],
- requestAnimFrame = window.requestAnimationFrame ||
- window.webkitRequestAnimationFrame ||
- window.mozRequestAnimationFrame ||
- window.oRequestAnimationFrame ||
- window.msRequestAnimationFrame ||
- function (callback) {
- setTimeout(callback, 16);
- },
- animation = function () {
- var Now = +new Date,
- l = 0;
- for (; l < animationElements.length; l++) {
- var e = animationElements[l];
- if (e.el.removed || e.paused) {
- continue;
- }
- var time = Now - e.start,
- ms = e.ms,
- easing = e.easing,
- from = e.from,
- diff = e.diff,
- to = e.to,
- t = e.t,
- that = e.el,
- set = {},
- now,
- init = {},
- key;
- if (e.initstatus) {
- time = (e.initstatus * e.anim.top - e.prev) / (e.percent - e.prev) * ms;
- e.status = e.initstatus;
- delete e.initstatus;
- e.stop && animationElements.splice(l--, 1);
- } else {
- e.status = (e.prev + (e.percent - e.prev) * (time / ms)) / e.anim.top;
- }
- if (time < 0) {
- continue;
- }
- if (time < ms) {
- var pos = easing(time / ms);
- for (var attr in from) if (from[has](attr)) {
- switch (availableAnimAttrs[attr]) {
- case nu:
- now = +from[attr] + pos * ms * diff[attr];
- break;
- case "colour":
- now = "rgb(" + [
- upto255(round(from[attr].r + pos * ms * diff[attr].r)),
- upto255(round(from[attr].g + pos * ms * diff[attr].g)),
- upto255(round(from[attr].b + pos * ms * diff[attr].b))
- ].join(",") + ")";
- break;
- case "path":
- now = [];
- for (var i = 0, ii = from[attr].length; i < ii; i++) {
- now[i] = [from[attr][i][0]];
- for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
- now[i][j] = +from[attr][i][j] + pos * ms * diff[attr][i][j];
- }
- now[i] = now[i].join(S);
- }
- now = now.join(S);
- break;
- case "transform":
- if (diff[attr].real) {
- now = [];
- for (i = 0, ii = from[attr].length; i < ii; i++) {
- now[i] = [from[attr][i][0]];
- for (j = 1, jj = from[attr][i].length; j < jj; j++) {
- now[i][j] = from[attr][i][j] + pos * ms * diff[attr][i][j];
- }
- }
- } else {
- var get = function (i) {
- return +from[attr][i] + pos * ms * diff[attr][i];
- };
- // now = [["r", get(2), 0, 0], ["t", get(3), get(4)], ["s", get(0), get(1), 0, 0]];
- now = [["m", get(0), get(1), get(2), get(3), get(4), get(5)]];
- }
- break;
- case "csv":
- if (attr == "clip-rect") {
- now = [];
- i = 4;
- while (i--) {
- now[i] = +from[attr][i] + pos * ms * diff[attr][i];
- }
- }
- break;
- default:
- var from2 = [][concat](from[attr]);
- now = [];
- i = that.paper.customAttributes[attr].length;
- while (i--) {
- now[i] = +from2[i] + pos * ms * diff[attr][i];
- }
- break;
- }
- set[attr] = now;
- }
- that.attr(set);
- (function (id, that, anim) {
- setTimeout(function () {
- eve("raphael.anim.frame." + id, that, anim);
- });
- })(that.id, that, e.anim);
- } else {
- (function(f, el, a) {
- setTimeout(function() {
- eve("raphael.anim.frame." + el.id, el, a);
- eve("raphael.anim.finish." + el.id, el, a);
- R.is(f, "function") && f.call(el);
- });
- })(e.callback, that, e.anim);
- that.attr(to);
- animationElements.splice(l--, 1);
- if (e.repeat > 1 && !e.next) {
- for (key in to) if (to[has](key)) {
- init[key] = e.totalOrigin[key];
- }
- e.el.attr(init);
- runAnimation(e.anim, e.el, e.anim.percents[0], null, e.totalOrigin, e.repeat - 1);
- }
- if (e.next && !e.stop) {
- runAnimation(e.anim, e.el, e.next, null, e.totalOrigin, e.repeat);
- }
- }
- }
- R.svg && that && that.paper && that.paper.safari();
- animationElements.length && requestAnimFrame(animation);
- },
- upto255 = function (color) {
- return color > 255 ? 255 : color < 0 ? 0 : color;
- };
- /*\
- * Element.animateWith
- [ method ]
- **
- * Acts similar to @Element.animate, but ensure that given animation runs in sync with another given element.
- **
- > Parameters
- **
- - el (object) element to sync with
- - anim (object) animation to sync with
- - params (object) #optional final attributes for the element, see also @Element.attr
- - ms (number) #optional number of milliseconds for animation to run
- - easing (string) #optional easing type. Accept on of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
- - callback (function) #optional callback function. Will be called at the end of animation.
- * or
- - element (object) element to sync with
- - anim (object) animation to sync with
- - animation (object) #optional animation object, see @Raphael.animation
- **
- = (object) original element
- \*/
- elproto.animateWith = function (el, anim, params, ms, easing, callback) {
- var element = this;
- if (element.removed) {
- callback && callback.call(element);
- return element;
- }
- var a = params instanceof Animation ? params : R.animation(params, ms, easing, callback),
- x, y;
- runAnimation(a, element, a.percents[0], null, element.attr());
- for (var i = 0, ii = animationElements.length; i < ii; i++) {
- if (animationElements[i].anim == anim && animationElements[i].el == el) {
- animationElements[ii - 1].start = animationElements[i].start;
- break;
- }
- }
- return element;
- //
- //
- // var a = params ? R.animation(params, ms, easing, callback) : anim,
- // status = element.status(anim);
- // return this.animate(a).status(a, status * anim.ms / a.ms);
- };
- function CubicBezierAtTime(t, p1x, p1y, p2x, p2y, duration) {
- var cx = 3 * p1x,
- bx = 3 * (p2x - p1x) - cx,
- ax = 1 - cx - bx,
- cy = 3 * p1y,
- by = 3 * (p2y - p1y) - cy,
- ay = 1 - cy - by;
- function sampleCurveX(t) {
- return ((ax * t + bx) * t + cx) * t;
- }
- function solve(x, epsilon) {
- var t = solveCurveX(x, epsilon);
- return ((ay * t + by) * t + cy) * t;
- }
- function solveCurveX(x, epsilon) {
- var t0, t1, t2, x2, d2, i;
- for(t2 = x, i = 0; i < 8; i++) {
- x2 = sampleCurveX(t2) - x;
- if (abs(x2) < epsilon) {
- return t2;
- }
- d2 = (3 * ax * t2 + 2 * bx) * t2 + cx;
- if (abs(d2) < 1e-6) {
- break;
- }
- t2 = t2 - x2 / d2;
- }
- t0 = 0;
- t1 = 1;
- t2 = x;
- if (t2 < t0) {
- return t0;
- }
- if (t2 > t1) {
- return t1;
- }
- while (t0 < t1) {
- x2 = sampleCurveX(t2);
- if (abs(x2 - x) < epsilon) {
- return t2;
- }
- if (x > x2) {
- t0 = t2;
- } else {
- t1 = t2;
- }
- t2 = (t1 - t0) / 2 + t0;
- }
- return t2;
- }
- return solve(t, 1 / (200 * duration));
- }
- elproto.onAnimation = function (f) {
- f ? eve.on("raphael.anim.frame." + this.id, f) : eve.unbind("raphael.anim.frame." + this.id);
- return this;
- };
- function Animation(anim, ms) {
- var percents = [],
- newAnim = {};
- this.ms = ms;
- this.times = 1;
- if (anim) {
- for (var attr in anim) if (anim[has](attr)) {
- newAnim[toFloat(attr)] = anim[attr];
- percents.push(toFloat(attr));
- }
- percents.sort(sortByNumber);
- }
- this.anim = newAnim;
- this.top = percents[percents.length - 1];
- this.percents = percents;
- }
- /*\
- * Animation.delay
- [ method ]
- **
- * Creates a copy of existing animation object with given delay.
- **
- > Parameters
- **
- - delay (number) number of ms to pass between animation start and actual animation
- **
- = (object) new altered Animation object
- | var anim = Raphael.animation({cx: 10, cy: 20}, 2e3);
- | circle1.animate(anim); // run the given animation immediately
- | circle2.animate(anim.delay(500)); // run the given animation after 500 ms
- \*/
- Animation.prototype.delay = function (delay) {
- var a = new Animation(this.anim, this.ms);
- a.times = this.times;
- a.del = +delay || 0;
- return a;
- };
- /*\
- * Animation.repeat
- [ method ]
- **
- * Creates a copy of existing animation object with given repetition.
- **
- > Parameters
- **
- - repeat (number) number iterations of animation. For infinite animation pass `Infinity`
- **
- = (object) new altered Animation object
- \*/
- Animation.prototype.repeat = function (times) {
- var a = new Animation(this.anim, this.ms);
- a.del = this.del;
- a.times = math.floor(mmax(times, 0)) || 1;
- return a;
- };
- function runAnimation(anim, element, percent, status, totalOrigin, times) {
- percent = toFloat(percent);
- var params,
- isInAnim,
- isInAnimSet,
- percents = [],
- next,
- prev,
- timestamp,
- ms = anim.ms,
- from = {},
- to = {},
- diff = {};
- if (status) {
- for (i = 0, ii = animationElements.length; i < ii; i++) {
- var e = animationElements[i];
- if (e.el.id == element.id && e.anim == anim) {
- if (e.percent != percent) {
- animationElements.splice(i, 1);
- isInAnimSet = 1;
- } else {
- isInAnim = e;
- }
- element.attr(e.totalOrigin);
- break;
- }
- }
- } else {
- status = +to; // NaN
- }
- for (var i = 0, ii = anim.percents.length; i < ii; i++) {
- if (anim.percents[i] == percent || anim.percents[i] > status * anim.top) {
- percent = anim.percents[i];
- prev = anim.percents[i - 1] || 0;
- ms = ms / anim.top * (percent - prev);
- next = anim.percents[i + 1];
- params = anim.anim[percent];
- break;
- } else if (status) {
- element.attr(anim.anim[anim.percents[i]]);
- }
- }
- if (!params) {
- return;
- }
- if (!isInAnim) {
- for (var attr in params) if (params[has](attr)) {
- if (availableAnimAttrs[has](attr) || element.paper.customAttributes[has](attr)) {
- from[attr] = element.attr(attr);
- (from[attr] == null) && (from[attr] = availableAttrs[attr]);
- to[attr] = params[attr];
- switch (availableAnimAttrs[attr]) {
- case nu:
- diff[attr] = (to[attr] - from[attr]) / ms;
- break;
- case "colour":
- from[attr] = R.getRGB(from[attr]);
- var toColour = R.getRGB(to[attr]);
- diff[attr] = {
- r: (toColour.r - from[attr].r) / ms,
- g: (toColour.g - from[attr].g) / ms,
- b: (toColour.b - from[attr].b) / ms
- };
- break;
- case "path":
- var pathes = path2curve(from[attr], to[attr]),
- toPath = pathes[1];
- from[attr] = pathes[0];
- diff[attr] = [];
- for (i = 0, ii = from[attr].length; i < ii; i++) {
- diff[attr][i] = [0];
- for (var j = 1, jj = from[attr][i].length; j < jj; j++) {
- diff[attr][i][j] = (toPath[i][j] - from[attr][i][j]) / ms;
- }
- }
- break;
- case "transform":
- var _ = element._,
- eq = equaliseTransform(_[attr], to[attr]);
- if (eq) {
- from[attr] = eq.from;
- to[attr] = eq.to;
- diff[attr] = [];
- diff[attr].real = true;
- for (i = 0, ii = from[attr].length; i < ii; i++) {
- diff[attr][i] = [from[attr][i][0]];
- for (j = 1, jj = from[attr][i].length; j < jj; j++) {
- diff[attr][i][j] = (to[attr][i][j] - from[attr][i][j]) / ms;
- }
- }
- } else {
- var m = (element.matrix || new Matrix),
- to2 = {
- _: {transform: _.transform},
- getBBox: function () {
- return element.getBBox(1);
- }
- };
- from[attr] = [
- m.a,
- m.b,
- m.c,
- m.d,
- m.e,
- m.f
- ];
- extractTransform(to2, to[attr]);
- to[attr] = to2._.transform;
- diff[attr] = [
- (to2.matrix.a - m.a) / ms,
- (to2.matrix.b - m.b) / ms,
- (to2.matrix.c - m.c) / ms,
- (to2.matrix.d - m.d) / ms,
- (to2.matrix.e - m.e) / ms,
- (to2.matrix.f - m.f) / ms
- ];
- // from[attr] = [_.sx, _.sy, _.deg, _.dx, _.dy];
- // var to2 = {_:{}, getBBox: function () { return element.getBBox(); }};
- // extractTransform(to2, to[attr]);
- // diff[attr] = [
- // (to2._.sx - _.sx) / ms,
- // (to2._.sy - _.sy) / ms,
- // (to2._.deg - _.deg) / ms,
- // (to2._.dx - _.dx) / ms,
- // (to2._.dy - _.dy) / ms
- // ];
- }
- break;
- case "csv":
- var values = Str(params[attr])[split](separator),
- from2 = Str(from[attr])[split](separator);
- if (attr == "clip-rect") {
- from[attr] = from2;
- diff[attr] = [];
- i = from2.length;
- while (i--) {
- diff[attr][i] = (values[i] - from[attr][i]) / ms;
- }
- }
- to[attr] = values;
- break;
- default:
- values = [][concat](params[attr]);
- from2 = [][concat](from[attr]);
- diff[attr] = [];
- i = element.paper.customAttributes[attr].length;
- while (i--) {
- diff[attr][i] = ((values[i] || 0) - (from2[i] || 0)) / ms;
- }
- break;
- }
- }
- }
- var easing = params.easing,
- easyeasy = R.easing_formulas[easing];
- if (!easyeasy) {
- easyeasy = Str(easing).match(bezierrg);
- if (easyeasy && easyeasy.length == 5) {
- var curve = easyeasy;
- easyeasy = function (t) {
- return CubicBezierAtTime(t, +curve[1], +curve[2], +curve[3], +curve[4], ms);
- };
- } else {
- easyeasy = pipe;
- }
- }
- timestamp = params.start || anim.start || +new Date;
- e = {
- anim: anim,
- percent: percent,
- timestamp: timestamp,
- start: timestamp + (anim.del || 0),
- status: 0,
- initstatus: status || 0,
- stop: false,
- ms: ms,
- easing: easyeasy,
- from: from,
- diff: diff,
- to: to,
- el: element,
- callback: params.callback,
- prev: prev,
- next: next,
- repeat: times || anim.times,
- origin: element.attr(),
- totalOrigin: totalOrigin
- };
- animationElements.push(e);
- if (status && !isInAnim && !isInAnimSet) {
- e.stop = true;
- e.start = new Date - ms * status;
- if (animationElements.length == 1) {
- return animation();
- }
- }
- if (isInAnimSet) {
- e.start = new Date - e.ms * status;
- }
- animationElements.length == 1 && requestAnimFrame(animation);
- } else {
- isInAnim.initstatus = status;
- isInAnim.start = new Date - isInAnim.ms * status;
- }
- eve("raphael.anim.start." + element.id, element, anim);
- }
- /*\
- * Raphael.animation
- [ method ]
- **
- * Creates an animation object that can be passed to the @Element.animate or @Element.animateWith methods.
- * See also @Animation.delay and @Animation.repeat methods.
- **
- > Parameters
- **
- - params (object) final attributes for the element, see also @Element.attr
- - ms (number) number of milliseconds for animation to run
- - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
- - callback (function) #optional callback function. Will be called at the end of animation.
- **
- = (object) @Animation
- \*/
- R.animation = function (params, ms, easing, callback) {
- if (params instanceof Animation) {
- return params;
- }
- if (R.is(easing, "function") || !easing) {
- callback = callback || easing || null;
- easing = null;
- }
- params = Object(params);
- ms = +ms || 0;
- var p = {},
- json,
- attr;
- for (attr in params) if (params[has](attr) && toFloat(attr) != attr && toFloat(attr) + "%" != attr) {
- json = true;
- p[attr] = params[attr];
- }
- if (!json) {
- // if percent-like syntax is used and end-of-all animation callback used
- if(callback){
- // find the last one
- var lastKey = 0;
- for(var i in params){
- var percent = toInt(i);
- if(params[has](i) && percent > lastKey){
- lastKey = percent;
- }
- }
- lastKey += '%';
- // if already defined callback in the last keyframe, skip
- !params[lastKey].callback && (params[lastKey].callback = callback);
- }
- return new Animation(params, ms);
- } else {
- easing && (p.easing = easing);
- callback && (p.callback = callback);
- return new Animation({100: p}, ms);
- }
- };
- /*\
- * Element.animate
- [ method ]
- **
- * Creates and starts animation for given element.
- **
- > Parameters
- **
- - params (object) final attributes for the element, see also @Element.attr
- - ms (number) number of milliseconds for animation to run
- - easing (string) #optional easing type. Accept one of @Raphael.easing_formulas or CSS format: `cubic&#x2010;bezier(XX,&#160;XX,&#160;XX,&#160;XX)`
- - callback (function) #optional callback function. Will be called at the end of animation.
- * or
- - animation (object) animation object, see @Raphael.animation
- **
- = (object) original element
- \*/
- elproto.animate = function (params, ms, easing, callback) {
- var element = this;
- if (element.removed) {
- callback && callback.call(element);
- return element;
- }
- var anim = params instanceof Animation ? params : R.animation(params, ms, easing, callback);
- runAnimation(anim, element, anim.percents[0], null, element.attr());
- return element;
- };
- /*\
- * Element.setTime
- [ method ]
- **
- * Sets the status of animation of the element in milliseconds. Similar to @Element.status method.
- **
- > Parameters
- **
- - anim (object) animation object
- - value (number) number of milliseconds from the beginning of the animation
- **
- = (object) original element if `value` is specified
- * Note, that during animation following events are triggered:
- *
- * On each animation frame event `anim.frame.<id>`, on start `anim.start.<id>` and on end `anim.finish.<id>`.
- \*/
- elproto.setTime = function (anim, value) {
- if (anim && value != null) {
- this.status(anim, mmin(value, anim.ms) / anim.ms);
- }
- return this;
- };
- /*\
- * Element.status
- [ method ]
- **
- * Gets or sets the status of animation of the element.
- **
- > Parameters
- **
- - anim (object) #optional animation object
- - value (number) #optional 0 – 1. If specified, method works like a setter and sets the status of a given animation to the value. This will cause animation to jump to the given position.
- **
- = (number) status
- * or
- = (array) status if `anim` is not specified. Array of objects in format:
- o {
- o anim: (object) animation object
- o status: (number) status
- o }
- * or
- = (object) original element if `value` is specified
- \*/
- elproto.status = function (anim, value) {
- var out = [],
- i = 0,
- len,
- e;
- if (value != null) {
- runAnimation(anim, this, -1, mmin(value, 1));
- return this;
- } else {
- len = animationElements.length;
- for (; i < len; i++) {
- e = animationElements[i];
- if (e.el.id == this.id && (!anim || e.anim == anim)) {
- if (anim) {
- return e.status;
- }
- out.push({
- anim: e.anim,
- status: e.status
- });
- }
- }
- if (anim) {
- return 0;
- }
- return out;
- }
- };
- /*\
- * Element.pause
- [ method ]
- **
- * Stops animation of the element with ability to resume it later on.
- **
- > Parameters
- **
- - anim (object) #optional animation object
- **
- = (object) original element
- \*/
- elproto.pause = function (anim) {
- for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
- if (eve("raphael.anim.pause." + this.id, this, animationElements[i].anim) !== false) {
- animationElements[i].paused = true;
- }
- }
- return this;
- };
- /*\
- * Element.resume
- [ method ]
- **
- * Resumes animation if it was paused with @Element.pause method.
- **
- > Parameters
- **
- - anim (object) #optional animation object
- **
- = (object) original element
- \*/
- elproto.resume = function (anim) {
- for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
- var e = animationElements[i];
- if (eve("raphael.anim.resume." + this.id, this, e.anim) !== false) {
- delete e.paused;
- this.status(e.anim, e.status);
- }
- }
- return this;
- };
- /*\
- * Element.stop
- [ method ]
- **
- * Stops animation of the element.
- **
- > Parameters
- **
- - anim (object) #optional animation object
- **
- = (object) original element
- \*/
- elproto.stop = function (anim) {
- for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.id == this.id && (!anim || animationElements[i].anim == anim)) {
- if (eve("raphael.anim.stop." + this.id, this, animationElements[i].anim) !== false) {
- animationElements.splice(i--, 1);
- }
- }
- return this;
- };
- function stopAnimation(paper) {
- for (var i = 0; i < animationElements.length; i++) if (animationElements[i].el.paper == paper) {
- animationElements.splice(i--, 1);
- }
- }
- eve.on("raphael.remove", stopAnimation);
- eve.on("raphael.clear", stopAnimation);
- elproto.toString = function () {
- return "Rapha\xebl\u2019s object";
- };
-
- // Set
- var Set = function (items) {
- this.items = [];
- this.length = 0;
- this.type = "set";
- if (items) {
- for (var i = 0, ii = items.length; i < ii; i++) {
- if (items[i] && (items[i].constructor == elproto.constructor || items[i].constructor == Set)) {
- this[this.items.length] = this.items[this.items.length] = items[i];
- this.length++;
- }
- }
- }
- },
- setproto = Set.prototype;
- /*\
- * Set.push
- [ method ]
- **
- * Adds each argument to the current set.
- = (object) original element
- \*/
- setproto.push = function () {
- var item,
- len;
- for (var i = 0, ii = arguments.length; i < ii; i++) {
- item = arguments[i];
- if (item && (item.constructor == elproto.constructor || item.constructor == Set)) {
- len = this.items.length;
- this[len] = this.items[len] = item;
- this.length++;
- }
- }
- return this;
- };
- /*\
- * Set.pop
- [ method ]
- **
- * Removes last element and returns it.
- = (object) element
- \*/
- setproto.pop = function () {
- this.length && delete this[this.length--];
- return this.items.pop();
- };
- /*\
- * Set.forEach
- [ method ]
- **
- * Executes given function for each element in the set.
- *
- * If function returns `false` it will stop loop running.
- **
- > Parameters
- **
- - callback (function) function to run
- - thisArg (object) context object for the callback
- = (object) Set object
- \*/
- setproto.forEach = function (callback, thisArg) {
- for (var i = 0, ii = this.items.length; i < ii; i++) {
- if (callback.call(thisArg, this.items[i], i) === false) {
- return this;
- }
- }
- return this;
- };
- for (var method in elproto) if (elproto[has](method)) {
- setproto[method] = (function (methodname) {
- return function () {
- var arg = arguments;
- return this.forEach(function (el) {
- el[methodname][apply](el, arg);
- });
- };
- })(method);
- }
- setproto.attr = function (name, value) {
- if (name && R.is(name, array) && R.is(name[0], "object")) {
- for (var j = 0, jj = name.length; j < jj; j++) {
- this.items[j].attr(name[j]);
- }
- } else {
- for (var i = 0, ii = this.items.length; i < ii; i++) {
- this.items[i].attr(name, value);
- }
- }
- return this;
- };
- /*\
- * Set.clear
- [ method ]
- **
- * Removes all elements from the set
- \*/
- setproto.clear = function () {
- while (this.length) {
- this.pop();
- }
- };
- /*\
- * Set.splice
- [ method ]
- **
- * Removes given element from the set
- **
- > Parameters
- **
- - index (number) position of the deletion
- - count (number) number of element to remove
- - insertion… (object) #optional elements to insert
- = (object) set elements that were deleted
- \*/
- setproto.splice = function (index, count, insertion) {
- index = index < 0 ? mmax(this.length + index, 0) : index;
- count = mmax(0, mmin(this.length - index, count));
- var tail = [],
- todel = [],
- args = [],
- i;
- for (i = 2; i < arguments.length; i++) {
- args.push(arguments[i]);
- }
- for (i = 0; i < count; i++) {
- todel.push(this[index + i]);
- }
- for (; i < this.length - index; i++) {
- tail.push(this[index + i]);
- }
- var arglen = args.length;
- for (i = 0; i < arglen + tail.length; i++) {
- this.items[index + i] = this[index + i] = i < arglen ? args[i] : tail[i - arglen];
- }
- i = this.items.length = this.length -= count - arglen;
- while (this[i]) {
- delete this[i++];
- }
- return new Set(todel);
- };
- /*\
- * Set.exclude
- [ method ]
- **
- * Removes given element from the set
- **
- > Parameters
- **
- - element (object) element to remove
- = (boolean) `true` if object was found & removed from the set
- \*/
- setproto.exclude = function (el) {
- for (var i = 0, ii = this.length; i < ii; i++) if (this[i] == el) {
- this.splice(i, 1);
- return true;
- }
- };
- setproto.animate = function (params, ms, easing, callback) {
- (R.is(easing, "function") || !easing) && (callback = easing || null);
- var len = this.items.length,
- i = len,
- item,
- set = this,
- collector;
- if (!len) {
- return this;
- }
- callback && (collector = function () {
- !--len && callback.call(set);
- });
- easing = R.is(easing, string) ? easing : collector;
- var anim = R.animation(params, ms, easing, collector);
- item = this.items[--i].animate(anim);
- while (i--) {
- this.items[i] && !this.items[i].removed && this.items[i].animateWith(item, anim, anim);
- (this.items[i] && !this.items[i].removed) || len--;
- }
- return this;
- };
- setproto.insertAfter = function (el) {
- var i = this.items.length;
- while (i--) {
- this.items[i].insertAfter(el);
- }
- return this;
- };
- setproto.getBBox = function () {
- var x = [],
- y = [],
- x2 = [],
- y2 = [];
- for (var i = this.items.length; i--;) if (!this.items[i].removed) {
- var box = this.items[i].getBBox();
- x.push(box.x);
- y.push(box.y);
- x2.push(box.x + box.width);
- y2.push(box.y + box.height);
- }
- x = mmin[apply](0, x);
- y = mmin[apply](0, y);
- x2 = mmax[apply](0, x2);
- y2 = mmax[apply](0, y2);
- return {
- x: x,
- y: y,
- x2: x2,
- y2: y2,
- width: x2 - x,
- height: y2 - y
- };
- };
- setproto.clone = function (s) {
- s = this.paper.set();
- for (var i = 0, ii = this.items.length; i < ii; i++) {
- s.push(this.items[i].clone());
- }
- return s;
- };
- setproto.toString = function () {
- return "Rapha\xebl\u2018s set";
- };
-
- setproto.glow = function(glowConfig) {
- var ret = this.paper.set();
- this.forEach(function(shape, index){
- var g = shape.glow(glowConfig);
- if(g != null){
- g.forEach(function(shape2, index2){
- ret.push(shape2);
- });
- }
- });
- return ret;
- };
-
-
- /*\
- * Set.isPointInside
- [ method ]
- **
- * Determine if given point is inside this set’s elements
- **
- > Parameters
- **
- - x (number) x coordinate of the point
- - y (number) y coordinate of the point
- = (boolean) `true` if point is inside any of the set's elements
- \*/
- setproto.isPointInside = function (x, y) {
- var isPointInside = false;
- this.forEach(function (el) {
- if (el.isPointInside(x, y)) {
- isPointInside = true;
- return false; // stop loop
- }
- });
- return isPointInside;
- };
-
- /*\
- * Raphael.registerFont
- [ method ]
- **
- * Adds given font to the registered set of fonts for Raphaël. Should be used as an internal call from within Cufón’s font file.
- * Returns original parameter, so it could be used with chaining.
- # <a href="http://wiki.github.com/sorccu/cufon/about">More about Cufón and how to convert your font form TTF, OTF, etc to JavaScript file.</a>
- **
- > Parameters
- **
- - font (object) the font to register
- = (object) the font you passed in
- > Usage
- | Cufon.registerFont(Raphael.registerFont({…}));
- \*/
- R.registerFont = function (font) {
- if (!font.face) {
- return font;
- }
- this.fonts = this.fonts || {};
- var fontcopy = {
- w: font.w,
- face: {},
- glyphs: {}
- },
- family = font.face["font-family"];
- for (var prop in font.face) if (font.face[has](prop)) {
- fontcopy.face[prop] = font.face[prop];
- }
- if (this.fonts[family]) {
- this.fonts[family].push(fontcopy);
- } else {
- this.fonts[family] = [fontcopy];
- }
- if (!font.svg) {
- fontcopy.face["units-per-em"] = toInt(font.face["units-per-em"], 10);
- for (var glyph in font.glyphs) if (font.glyphs[has](glyph)) {
- var path = font.glyphs[glyph];
- fontcopy.glyphs[glyph] = {
- w: path.w,
- k: {},
- d: path.d && "M" + path.d.replace(/[mlcxtrv]/g, function (command) {
- return {l: "L", c: "C", x: "z", t: "m", r: "l", v: "c"}[command] || "M";
- }) + "z"
- };
- if (path.k) {
- for (var k in path.k) if (path[has](k)) {
- fontcopy.glyphs[glyph].k[k] = path.k[k];
- }
- }
- }
- }
- return font;
- };
- /*\
- * Paper.getFont
- [ method ]
- **
- * Finds font object in the registered fonts by given parameters. You could specify only one word from the font name, like “Myriad” for “Myriad Pro”.
- **
- > Parameters
- **
- - family (string) font family name or any word from it
- - weight (string) #optional font weight
- - style (string) #optional font style
- - stretch (string) #optional font stretch
- = (object) the font object
- > Usage
- | paper.print(100, 100, "Test string", paper.getFont("Times", 800), 30);
- \*/
- paperproto.getFont = function (family, weight, style, stretch) {
- stretch = stretch || "normal";
- style = style || "normal";
- weight = +weight || {normal: 400, bold: 700, lighter: 300, bolder: 800}[weight] || 400;
- if (!R.fonts) {
- return;
- }
- var font = R.fonts[family];
- if (!font) {
- var name = new RegExp("(^|\\s)" + family.replace(/[^\w\d\s+!~.:_-]/g, E) + "(\\s|$)", "i");
- for (var fontName in R.fonts) if (R.fonts[has](fontName)) {
- if (name.test(fontName)) {
- font = R.fonts[fontName];
- break;
- }
- }
- }
- var thefont;
- if (font) {
- for (var i = 0, ii = font.length; i < ii; i++) {
- thefont = font[i];
- if (thefont.face["font-weight"] == weight && (thefont.face["font-style"] == style || !thefont.face["font-style"]) && thefont.face["font-stretch"] == stretch) {
- break;
- }
- }
- }
- return thefont;
- };
- /*\
- * Paper.print
- [ method ]
- **
- * Creates path that represent given text written using given font at given position with given size.
- * Result of the method is path element that contains whole text as a separate path.
- **
- > Parameters
- **
- - x (number) x position of the text
- - y (number) y position of the text
- - string (string) text to print
- - font (object) font object, see @Paper.getFont
- - size (number) #optional size of the font, default is `16`
- - origin (string) #optional could be `"baseline"` or `"middle"`, default is `"middle"`
- - letter_spacing (number) #optional number in range `-1..1`, default is `0`
- - line_spacing (number) #optional number in range `1..3`, default is `1`
- = (object) resulting path element, which consist of all letters
- > Usage
- | var txt = r.print(10, 50, "print", r.getFont("Museo"), 30).attr({fill: "#fff"});
- \*/
- paperproto.print = function (x, y, string, font, size, origin, letter_spacing, line_spacing) {
- origin = origin || "middle"; // baseline|middle
- letter_spacing = mmax(mmin(letter_spacing || 0, 1), -1);
- line_spacing = mmax(mmin(line_spacing || 1, 3), 1);
- var letters = Str(string)[split](E),
- shift = 0,
- notfirst = 0,
- path = E,
- scale;
- R.is(font, "string") && (font = this.getFont(font));
- if (font) {
- scale = (size || 16) / font.face["units-per-em"];
- var bb = font.face.bbox[split](separator),
- top = +bb[0],
- lineHeight = bb[3] - bb[1],
- shifty = 0,
- height = +bb[1] + (origin == "baseline" ? lineHeight + (+font.face.descent) : lineHeight / 2);
- for (var i = 0, ii = letters.length; i < ii; i++) {
- if (letters[i] == "\n") {
- shift = 0;
- curr = 0;
- notfirst = 0;
- shifty += lineHeight * line_spacing;
- } else {
- var prev = notfirst && font.glyphs[letters[i - 1]] || {},
- curr = font.glyphs[letters[i]];
- shift += notfirst ? (prev.w || font.w) + (prev.k && prev.k[letters[i]] || 0) + (font.w * letter_spacing) : 0;
- notfirst = 1;
- }
- if (curr && curr.d) {
- path += R.transformPath(curr.d, ["t", shift * scale, shifty * scale, "s", scale, scale, top, height, "t", (x - top) / scale, (y - height) / scale]);
- }
- }
- }
- return this.path(path).attr({
- fill: "#000",
- stroke: "none"
- });
- };
-
- /*\
- * Paper.add
- [ method ]
- **
- * Imports elements in JSON array in format `{type: type, <attributes>}`
- **
- > Parameters
- **
- - json (array)
- = (object) resulting set of imported elements
- > Usage
- | paper.add([
- | {
- | type: "circle",
- | cx: 10,
- | cy: 10,
- | r: 5
- | },
- | {
- | type: "rect",
- | x: 10,
- | y: 10,
- | width: 10,
- | height: 10,
- | fill: "#fc0"
- | }
- | ]);
- \*/
- paperproto.add = function (json) {
- if (R.is(json, "array")) {
- var res = this.set(),
- i = 0,
- ii = json.length,
- j;
- for (; i < ii; i++) {
- j = json[i] || {};
- elements[has](j.type) && res.push(this[j.type]().attr(j));
- }
- }
- return res;
- };
-
- /*\
- * Raphael.format
- [ method ]
- **
- * Simple format function. Replaces construction of type “`{<number>}`” to the corresponding argument.
- **
- > Parameters
- **
- - token (string) string to format
- - … (string) rest of arguments will be treated as parameters for replacement
- = (string) formated string
- > Usage
- | var x = 10,
- | y = 20,
- | width = 40,
- | height = 50;
- | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
- | paper.path(Raphael.format("M{0},{1}h{2}v{3}h{4}z", x, y, width, height, -width));
- \*/
- R.format = function (token, params) {
- var args = R.is(params, array) ? [0][concat](params) : arguments;
- token && R.is(token, string) && args.length - 1 && (token = token.replace(formatrg, function (str, i) {
- return args[++i] == null ? E : args[i];
- }));
- return token || E;
- };
- /*\
- * Raphael.fullfill
- [ method ]
- **
- * A little bit more advanced format function than @Raphael.format. Replaces construction of type “`{<name>}`” to the corresponding argument.
- **
- > Parameters
- **
- - token (string) string to format
- - json (object) object which properties will be used as a replacement
- = (string) formated string
- > Usage
- | // this will draw a rectangular shape equivalent to "M10,20h40v50h-40z"
- | paper.path(Raphael.fullfill("M{x},{y}h{dim.width}v{dim.height}h{dim['negative width']}z", {
- | x: 10,
- | y: 20,
- | dim: {
- | width: 40,
- | height: 50,
- | "negative width": -40
- | }
- | }));
- \*/
- R.fullfill = (function () {
- var tokenRegex = /\{([^\}]+)\}/g,
- objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
- replacer = function (all, key, obj) {
- var res = obj;
- key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
- name = name || quotedName;
- if (res) {
- if (name in res) {
- res = res[name];
- }
- typeof res == "function" && isFunc && (res = res());
- }
- });
- res = (res == null || res == obj ? all : res) + "";
- return res;
- };
- return function (str, obj) {
- return String(str).replace(tokenRegex, function (all, key) {
- return replacer(all, key, obj);
- });
- };
- })();
- /*\
- * Raphael.ninja
- [ method ]
- **
- * If you want to leave no trace of Raphaël (Well, Raphaël creates only one global variable `Raphael`, but anyway.) You can use `ninja` method.
- * Beware, that in this case plugins could stop working, because they are depending on global variable existance.
- **
- = (object) Raphael object
- > Usage
- | (function (local_raphael) {
- | var paper = local_raphael(10, 10, 320, 200);
- | …
- | })(Raphael.ninja());
- \*/
- R.ninja = function () {
- oldRaphael.was ? (g.win.Raphael = oldRaphael.is) : delete Raphael;
- return R;
- };
- /*\
- * Raphael.st
- [ property (object) ]
- **
- * You can add your own method to elements and sets. It is wise to add a set method for each element method
- * you added, so you will be able to call the same method on sets too.
- **
- * See also @Raphael.el.
- > Usage
- | Raphael.el.red = function () {
- | this.attr({fill: "#f00"});
- | };
- | Raphael.st.red = function () {
- | this.forEach(function (el) {
- | el.red();
- | });
- | };
- | // then use it
- | paper.set(paper.circle(100, 100, 20), paper.circle(110, 100, 20)).red();
- \*/
- R.st = setproto;
-
- eve.on("raphael.DOMload", function () {
- loaded = true;
- });
-
- // Firefox <3.6 fix: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
- (function (doc, loaded, f) {
- if (doc.readyState == null && doc.addEventListener){
- doc.addEventListener(loaded, f = function () {
- doc.removeEventListener(loaded, f, false);
- doc.readyState = "complete";
- }, false);
- doc.readyState = "loading";
- }
- function isLoaded() {
- (/in/).test(doc.readyState) ? setTimeout(isLoaded, 9) : R.eve("raphael.DOMload");
- }
- isLoaded();
- })(document, "DOMContentLoaded");
-
-// ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël - JavaScript Vector Library │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ SVG Module │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
-// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com) │ \\
-// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
-// └─────────────────────────────────────────────────────────────────────┘ \\
-
-(function(){
- if (!R.svg) {
- return;
- }
- var has = "hasOwnProperty",
- Str = String,
- toFloat = parseFloat,
- toInt = parseInt,
- math = Math,
- mmax = math.max,
- abs = math.abs,
- pow = math.pow,
- separator = /[, ]+/,
- eve = R.eve,
- E = "",
- S = " ";
- var xlink = "http://www.w3.org/1999/xlink",
- markers = {
- block: "M5,0 0,2.5 5,5z",
- classic: "M5,0 0,2.5 5,5 3.5,3 3.5,2z",
- diamond: "M2.5,0 5,2.5 2.5,5 0,2.5z",
- open: "M6,1 1,3.5 6,6",
- oval: "M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"
- },
- markerCounter = {};
- R.toString = function () {
- return "Your browser supports SVG.\nYou are running Rapha\xebl " + this.version;
- };
- var $ = function (el, attr) {
- if (attr) {
- if (typeof el == "string") {
- el = $(el);
- }
- for (var key in attr) if (attr[has](key)) {
- if (key.substring(0, 6) == "xlink:") {
- el.setAttributeNS(xlink, key.substring(6), Str(attr[key]));
- } else {
- el.setAttribute(key, Str(attr[key]));
- }
- }
- } else {
- el = R._g.doc.createElementNS("http://www.w3.org/2000/svg", el);
- el.style && (el.style.webkitTapHighlightColor = "rgba(0,0,0,0)");
- }
- return el;
- },
- addGradientFill = function (element, gradient) {
- var type = "linear",
- id = element.id + gradient,
- fx = .5, fy = .5,
- o = element.node,
- SVG = element.paper,
- s = o.style,
- el = R._g.doc.getElementById(id);
- if (!el) {
- gradient = Str(gradient).replace(R._radial_gradient, function (all, _fx, _fy) {
- type = "radial";
- if (_fx && _fy) {
- fx = toFloat(_fx);
- fy = toFloat(_fy);
- var dir = ((fy > .5) * 2 - 1);
- pow(fx - .5, 2) + pow(fy - .5, 2) > .25 &&
- (fy = math.sqrt(.25 - pow(fx - .5, 2)) * dir + .5) &&
- fy != .5 &&
- (fy = fy.toFixed(5) - 1e-5 * dir);
- }
- return E;
- });
- gradient = gradient.split(/\s*\-\s*/);
- if (type == "linear") {
- var angle = gradient.shift();
- angle = -toFloat(angle);
- if (isNaN(angle)) {
- return null;
- }
- var vector = [0, 0, math.cos(R.rad(angle)), math.sin(R.rad(angle))],
- max = 1 / (mmax(abs(vector[2]), abs(vector[3])) || 1);
- vector[2] *= max;
- vector[3] *= max;
- if (vector[2] < 0) {
- vector[0] = -vector[2];
- vector[2] = 0;
- }
- if (vector[3] < 0) {
- vector[1] = -vector[3];
- vector[3] = 0;
- }
- }
- var dots = R._parseDots(gradient);
- if (!dots) {
- return null;
- }
- id = id.replace(/[\(\)\s,\xb0#]/g, "_");
-
- if (element.gradient && id != element.gradient.id) {
- SVG.defs.removeChild(element.gradient);
- delete element.gradient;
- }
-
- if (!element.gradient) {
- el = $(type + "Gradient", {id: id});
- element.gradient = el;
- $(el, type == "radial" ? {
- fx: fx,
- fy: fy
- } : {
- x1: vector[0],
- y1: vector[1],
- x2: vector[2],
- y2: vector[3],
- gradientTransform: element.matrix.invert()
- });
- SVG.defs.appendChild(el);
- for (var i = 0, ii = dots.length; i < ii; i++) {
- el.appendChild($("stop", {
- offset: dots[i].offset ? dots[i].offset : i ? "100%" : "0%",
- "stop-color": dots[i].color || "#fff"
- }));
- }
- }
- }
- $(o, {
- fill: "url('" + document.location + "#" + id + "')",
- opacity: 1,
- "fill-opacity": 1
- });
- s.fill = E;
- s.opacity = 1;
- s.fillOpacity = 1;
- return 1;
- },
- updatePosition = function (o) {
- var bbox = o.getBBox(1);
- $(o.pattern, {patternTransform: o.matrix.invert() + " translate(" + bbox.x + "," + bbox.y + ")"});
- },
- addArrow = function (o, value, isEnd) {
- if (o.type == "path") {
- var values = Str(value).toLowerCase().split("-"),
- p = o.paper,
- se = isEnd ? "end" : "start",
- node = o.node,
- attrs = o.attrs,
- stroke = attrs["stroke-width"],
- i = values.length,
- type = "classic",
- from,
- to,
- dx,
- refX,
- attr,
- w = 3,
- h = 3,
- t = 5;
- while (i--) {
- switch (values[i]) {
- case "block":
- case "classic":
- case "oval":
- case "diamond":
- case "open":
- case "none":
- type = values[i];
- break;
- case "wide": h = 5; break;
- case "narrow": h = 2; break;
- case "long": w = 5; break;
- case "short": w = 2; break;
- }
- }
- if (type == "open") {
- w += 2;
- h += 2;
- t += 2;
- dx = 1;
- refX = isEnd ? 4 : 1;
- attr = {
- fill: "none",
- stroke: attrs.stroke
- };
- } else {
- refX = dx = w / 2;
- attr = {
- fill: attrs.stroke,
- stroke: "none"
- };
- }
- if (o._.arrows) {
- if (isEnd) {
- o._.arrows.endPath && markerCounter[o._.arrows.endPath]--;
- o._.arrows.endMarker && markerCounter[o._.arrows.endMarker]--;
- } else {
- o._.arrows.startPath && markerCounter[o._.arrows.startPath]--;
- o._.arrows.startMarker && markerCounter[o._.arrows.startMarker]--;
- }
- } else {
- o._.arrows = {};
- }
- if (type != "none") {
- var pathId = "raphael-marker-" + type,
- markerId = "raphael-marker-" + se + type + w + h + "-obj" + o.id;
- if (!R._g.doc.getElementById(pathId)) {
- p.defs.appendChild($($("path"), {
- "stroke-linecap": "round",
- d: markers[type],
- id: pathId
- }));
- markerCounter[pathId] = 1;
- } else {
- markerCounter[pathId]++;
- }
- var marker = R._g.doc.getElementById(markerId),
- use;
- if (!marker) {
- marker = $($("marker"), {
- id: markerId,
- markerHeight: h,
- markerWidth: w,
- orient: "auto",
- refX: refX,
- refY: h / 2
- });
- use = $($("use"), {
- "xlink:href": "#" + pathId,
- transform: (isEnd ? "rotate(180 " + w / 2 + " " + h / 2 + ") " : E) + "scale(" + w / t + "," + h / t + ")",
- "stroke-width": (1 / ((w / t + h / t) / 2)).toFixed(4)
- });
- marker.appendChild(use);
- p.defs.appendChild(marker);
- markerCounter[markerId] = 1;
- } else {
- markerCounter[markerId]++;
- use = marker.getElementsByTagName("use")[0];
- }
- $(use, attr);
- var delta = dx * (type != "diamond" && type != "oval");
- if (isEnd) {
- from = o._.arrows.startdx * stroke || 0;
- to = R.getTotalLength(attrs.path) - delta * stroke;
- } else {
- from = delta * stroke;
- to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
- }
- attr = {};
- attr["marker-" + se] = "url(#" + markerId + ")";
- if (to || from) {
- attr.d = R.getSubpath(attrs.path, from, to);
- }
- $(node, attr);
- o._.arrows[se + "Path"] = pathId;
- o._.arrows[se + "Marker"] = markerId;
- o._.arrows[se + "dx"] = delta;
- o._.arrows[se + "Type"] = type;
- o._.arrows[se + "String"] = value;
- } else {
- if (isEnd) {
- from = o._.arrows.startdx * stroke || 0;
- to = R.getTotalLength(attrs.path) - from;
- } else {
- from = 0;
- to = R.getTotalLength(attrs.path) - (o._.arrows.enddx * stroke || 0);
- }
- o._.arrows[se + "Path"] && $(node, {d: R.getSubpath(attrs.path, from, to)});
- delete o._.arrows[se + "Path"];
- delete o._.arrows[se + "Marker"];
- delete o._.arrows[se + "dx"];
- delete o._.arrows[se + "Type"];
- delete o._.arrows[se + "String"];
- }
- for (attr in markerCounter) if (markerCounter[has](attr) && !markerCounter[attr]) {
- var item = R._g.doc.getElementById(attr);
- item && item.parentNode.removeChild(item);
- }
- }
- },
- dasharray = {
- "": [0],
- "none": [0],
- "-": [3, 1],
- ".": [1, 1],
- "-.": [3, 1, 1, 1],
- "-..": [3, 1, 1, 1, 1, 1],
- ". ": [1, 3],
- "- ": [4, 3],
- "--": [8, 3],
- "- .": [4, 3, 1, 3],
- "--.": [8, 3, 1, 3],
- "--..": [8, 3, 1, 3, 1, 3]
- },
- addDashes = function (o, value, params) {
- value = dasharray[Str(value).toLowerCase()];
- if (value) {
- var width = o.attrs["stroke-width"] || "1",
- butt = {round: width, square: width, butt: 0}[o.attrs["stroke-linecap"] || params["stroke-linecap"]] || 0,
- dashes = [],
- i = value.length;
- while (i--) {
- dashes[i] = value[i] * width + ((i % 2) ? 1 : -1) * butt;
- }
- $(o.node, {"stroke-dasharray": dashes.join(",")});
- }
- },
- setFillAndStroke = function (o, params) {
- var node = o.node,
- attrs = o.attrs,
- vis = node.style.visibility;
- node.style.visibility = "hidden";
- for (var att in params) {
- if (params[has](att)) {
- if (!R._availableAttrs[has](att)) {
- continue;
- }
- var value = params[att];
- attrs[att] = value;
- switch (att) {
- case "blur":
- o.blur(value);
- break;
- case "title":
- var title = node.getElementsByTagName("title");
-
- // Use the existing <title>.
- if (title.length && (title = title[0])) {
- title.firstChild.nodeValue = value;
- } else {
- title = $("title");
- var val = R._g.doc.createTextNode(value);
- title.appendChild(val);
- node.appendChild(title);
- }
- break;
- case "href":
- case "target":
- var pn = node.parentNode;
- if (pn.tagName.toLowerCase() != "a") {
- var hl = $("a");
- pn.insertBefore(hl, node);
- hl.appendChild(node);
- pn = hl;
- }
- if (att == "target") {
- pn.setAttributeNS(xlink, "show", value == "blank" ? "new" : value);
- } else {
- pn.setAttributeNS(xlink, att, value);
- }
- break;
- case "cursor":
- node.style.cursor = value;
- break;
- case "transform":
- o.transform(value);
- break;
- case "arrow-start":
- addArrow(o, value);
- break;
- case "arrow-end":
- addArrow(o, value, 1);
- break;
- case "clip-rect":
- var rect = Str(value).split(separator);
- if (rect.length == 4) {
- o.clip && o.clip.parentNode.parentNode.removeChild(o.clip.parentNode);
- var el = $("clipPath"),
- rc = $("rect");
- el.id = R.createUUID();
- $(rc, {
- x: rect[0],
- y: rect[1],
- width: rect[2],
- height: rect[3]
- });
- el.appendChild(rc);
- o.paper.defs.appendChild(el);
- $(node, {"clip-path": "url(#" + el.id + ")"});
- o.clip = rc;
- }
- if (!value) {
- var path = node.getAttribute("clip-path");
- if (path) {
- var clip = R._g.doc.getElementById(path.replace(/(^url\(#|\)$)/g, E));
- clip && clip.parentNode.removeChild(clip);
- $(node, {"clip-path": E});
- delete o.clip;
- }
- }
- break;
- case "path":
- if (o.type == "path") {
- $(node, {d: value ? attrs.path = R._pathToAbsolute(value) : "M0,0"});
- o._.dirty = 1;
- if (o._.arrows) {
- "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
- "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
- }
- }
- break;
- case "width":
- node.setAttribute(att, value);
- o._.dirty = 1;
- if (attrs.fx) {
- att = "x";
- value = attrs.x;
- } else {
- break;
- }
- case "x":
- if (attrs.fx) {
- value = -attrs.x - (attrs.width || 0);
- }
- case "rx":
- if (att == "rx" && o.type == "rect") {
- break;
- }
- case "cx":
- node.setAttribute(att, value);
- o.pattern && updatePosition(o);
- o._.dirty = 1;
- break;
- case "height":
- node.setAttribute(att, value);
- o._.dirty = 1;
- if (attrs.fy) {
- att = "y";
- value = attrs.y;
- } else {
- break;
- }
- case "y":
- if (attrs.fy) {
- value = -attrs.y - (attrs.height || 0);
- }
- case "ry":
- if (att == "ry" && o.type == "rect") {
- break;
- }
- case "cy":
- node.setAttribute(att, value);
- o.pattern && updatePosition(o);
- o._.dirty = 1;
- break;
- case "r":
- if (o.type == "rect") {
- $(node, {rx: value, ry: value});
- } else {
- node.setAttribute(att, value);
- }
- o._.dirty = 1;
- break;
- case "src":
- if (o.type == "image") {
- node.setAttributeNS(xlink, "href", value);
- }
- break;
- case "stroke-width":
- if (o._.sx != 1 || o._.sy != 1) {
- value /= mmax(abs(o._.sx), abs(o._.sy)) || 1;
- }
- node.setAttribute(att, value);
- if (attrs["stroke-dasharray"]) {
- addDashes(o, attrs["stroke-dasharray"], params);
- }
- if (o._.arrows) {
- "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
- "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
- }
- break;
- case "stroke-dasharray":
- addDashes(o, value, params);
- break;
- case "fill":
- var isURL = Str(value).match(R._ISURL);
- if (isURL) {
- el = $("pattern");
- var ig = $("image");
- el.id = R.createUUID();
- $(el, {x: 0, y: 0, patternUnits: "userSpaceOnUse", height: 1, width: 1});
- $(ig, {x: 0, y: 0, "xlink:href": isURL[1]});
- el.appendChild(ig);
-
- (function (el) {
- R._preload(isURL[1], function () {
- var w = this.offsetWidth,
- h = this.offsetHeight;
- $(el, {width: w, height: h});
- $(ig, {width: w, height: h});
- o.paper.safari();
- });
- })(el);
- o.paper.defs.appendChild(el);
- $(node, {fill: "url(#" + el.id + ")"});
- o.pattern = el;
- o.pattern && updatePosition(o);
- break;
- }
- var clr = R.getRGB(value);
- if (!clr.error) {
- delete params.gradient;
- delete attrs.gradient;
- !R.is(attrs.opacity, "undefined") &&
- R.is(params.opacity, "undefined") &&
- $(node, {opacity: attrs.opacity});
- !R.is(attrs["fill-opacity"], "undefined") &&
- R.is(params["fill-opacity"], "undefined") &&
- $(node, {"fill-opacity": attrs["fill-opacity"]});
- } else if ((o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value)) {
- if ("opacity" in attrs || "fill-opacity" in attrs) {
- var gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
- if (gradient) {
- var stops = gradient.getElementsByTagName("stop");
- $(stops[stops.length - 1], {"stop-opacity": ("opacity" in attrs ? attrs.opacity : 1) * ("fill-opacity" in attrs ? attrs["fill-opacity"] : 1)});
- }
- }
- attrs.gradient = value;
- attrs.fill = "none";
- break;
- }
- clr[has]("opacity") && $(node, {"fill-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
- case "stroke":
- clr = R.getRGB(value);
- node.setAttribute(att, clr.hex);
- att == "stroke" && clr[has]("opacity") && $(node, {"stroke-opacity": clr.opacity > 1 ? clr.opacity / 100 : clr.opacity});
- if (att == "stroke" && o._.arrows) {
- "startString" in o._.arrows && addArrow(o, o._.arrows.startString);
- "endString" in o._.arrows && addArrow(o, o._.arrows.endString, 1);
- }
- break;
- case "gradient":
- (o.type == "circle" || o.type == "ellipse" || Str(value).charAt() != "r") && addGradientFill(o, value);
- break;
- case "opacity":
- if (attrs.gradient && !attrs[has]("stroke-opacity")) {
- $(node, {"stroke-opacity": value > 1 ? value / 100 : value});
- }
- // fall
- case "fill-opacity":
- if (attrs.gradient) {
- gradient = R._g.doc.getElementById(node.getAttribute("fill").replace(/^url\(#|\)$/g, E));
- if (gradient) {
- stops = gradient.getElementsByTagName("stop");
- $(stops[stops.length - 1], {"stop-opacity": value});
- }
- break;
- }
- default:
- att == "font-size" && (value = toInt(value, 10) + "px");
- var cssrule = att.replace(/(\-.)/g, function (w) {
- return w.substring(1).toUpperCase();
- });
- node.style[cssrule] = value;
- o._.dirty = 1;
- node.setAttribute(att, value);
- break;
- }
- }
- }
-
- tuneText(o, params);
- node.style.visibility = vis;
- },
- leading = 1.2,
- tuneText = function (el, params) {
- if (el.type != "text" || !(params[has]("text") || params[has]("font") || params[has]("font-size") || params[has]("x") || params[has]("y"))) {
- return;
- }
- var a = el.attrs,
- node = el.node,
- fontSize = node.firstChild ? toInt(R._g.doc.defaultView.getComputedStyle(node.firstChild, E).getPropertyValue("font-size"), 10) : 10;
-
- if (params[has]("text")) {
- a.text = params.text;
- while (node.firstChild) {
- node.removeChild(node.firstChild);
- }
- var texts = Str(params.text).split("\n"),
- tspans = [],
- tspan;
- for (var i = 0, ii = texts.length; i < ii; i++) {
- tspan = $("tspan");
- i && $(tspan, {dy: fontSize * leading, x: a.x});
- tspan.appendChild(R._g.doc.createTextNode(texts[i]));
- node.appendChild(tspan);
- tspans[i] = tspan;
- }
- } else {
- tspans = node.getElementsByTagName("tspan");
- for (i = 0, ii = tspans.length; i < ii; i++) if (i) {
- $(tspans[i], {dy: fontSize * leading, x: a.x});
- } else {
- $(tspans[0], {dy: 0});
- }
- }
- $(node, {x: a.x, y: a.y});
- el._.dirty = 1;
- var bb = el._getBBox(),
- dif = a.y - (bb.y + bb.height / 2);
- dif && R.is(dif, "finite") && $(tspans[0], {dy: dif});
- },
- getRealNode = function (node) {
- if (node.parentNode && node.parentNode.tagName.toLowerCase() === "a") {
- return node.parentNode;
- } else {
- return node;
- }
- },
- Element = function (node, svg) {
- var X = 0,
- Y = 0;
- /*\
- * Element.node
- [ property (object) ]
- **
- * Gives you a reference to the DOM object, so you can assign event handlers or just mess around.
- **
- * Note: Don’t mess with it.
- > Usage
- | // draw a circle at coordinate 10,10 with radius of 10
- | var c = paper.circle(10, 10, 10);
- | c.node.onclick = function () {
- | c.attr("fill", "red");
- | };
- \*/
- this[0] = this.node = node;
- /*\
- * Element.raphael
- [ property (object) ]
- **
- * Internal reference to @Raphael object. In case it is not available.
- > Usage
- | Raphael.el.red = function () {
- | var hsb = this.paper.raphael.rgb2hsb(this.attr("fill"));
- | hsb.h = 1;
- | this.attr({fill: this.paper.raphael.hsb2rgb(hsb).hex});
- | }
- \*/
- node.raphael = true;
- /*\
- * Element.id
- [ property (number) ]
- **
- * Unique id of the element. Especially useful when you want to listen to events of the element,
- * because all events are fired in format `<module>.<action>.<id>`. Also useful for @Paper.getById method.
- \*/
- this.id = R._oid++;
- node.raphaelid = this.id;
- this.matrix = R.matrix();
- this.realPath = null;
- /*\
- * Element.paper
- [ property (object) ]
- **
- * Internal reference to “paper” where object drawn. Mainly for use in plugins and element extensions.
- > Usage
- | Raphael.el.cross = function () {
- | this.attr({fill: "red"});
- | this.paper.path("M10,10L50,50M50,10L10,50")
- | .attr({stroke: "red"});
- | }
- \*/
- this.paper = svg;
- this.attrs = this.attrs || {};
- this._ = {
- transform: [],
- sx: 1,
- sy: 1,
- deg: 0,
- dx: 0,
- dy: 0,
- dirty: 1
- };
- !svg.bottom && (svg.bottom = this);
- /*\
- * Element.prev
- [ property (object) ]
- **
- * Reference to the previous element in the hierarchy.
- \*/
- this.prev = svg.top;
- svg.top && (svg.top.next = this);
- svg.top = this;
- /*\
- * Element.next
- [ property (object) ]
- **
- * Reference to the next element in the hierarchy.
- \*/
- this.next = null;
- },
- elproto = R.el;
-
- Element.prototype = elproto;
- elproto.constructor = Element;
-
- R._engine.path = function (pathString, SVG) {
- var el = $("path");
- SVG.canvas && SVG.canvas.appendChild(el);
- var p = new Element(el, SVG);
- p.type = "path";
- setFillAndStroke(p, {
- fill: "none",
- stroke: "#000",
- path: pathString
- });
- return p;
- };
- /*\
- * Element.rotate
- [ method ]
- **
- * Deprecated! Use @Element.transform instead.
- * Adds rotation by given angle around given point to the list of
- * transformations of the element.
- > Parameters
- - deg (number) angle in degrees
- - cx (number) #optional x coordinate of the centre of rotation
- - cy (number) #optional y coordinate of the centre of rotation
- * If cx & cy aren’t specified centre of the shape is used as a point of rotation.
- = (object) @Element
- \*/
- elproto.rotate = function (deg, cx, cy) {
- if (this.removed) {
- return this;
- }
- deg = Str(deg).split(separator);
- if (deg.length - 1) {
- cx = toFloat(deg[1]);
- cy = toFloat(deg[2]);
- }
- deg = toFloat(deg[0]);
- (cy == null) && (cx = cy);
- if (cx == null || cy == null) {
- var bbox = this.getBBox(1);
- cx = bbox.x + bbox.width / 2;
- cy = bbox.y + bbox.height / 2;
- }
- this.transform(this._.transform.concat([["r", deg, cx, cy]]));
- return this;
- };
- /*\
- * Element.scale
- [ method ]
- **
- * Deprecated! Use @Element.transform instead.
- * Adds scale by given amount relative to given point to the list of
- * transformations of the element.
- > Parameters
- - sx (number) horisontal scale amount
- - sy (number) vertical scale amount
- - cx (number) #optional x coordinate of the centre of scale
- - cy (number) #optional y coordinate of the centre of scale
- * If cx & cy aren’t specified centre of the shape is used instead.
- = (object) @Element
- \*/
- elproto.scale = function (sx, sy, cx, cy) {
- if (this.removed) {
- return this;
- }
- sx = Str(sx).split(separator);
- if (sx.length - 1) {
- sy = toFloat(sx[1]);
- cx = toFloat(sx[2]);
- cy = toFloat(sx[3]);
- }
- sx = toFloat(sx[0]);
- (sy == null) && (sy = sx);
- (cy == null) && (cx = cy);
- if (cx == null || cy == null) {
- var bbox = this.getBBox(1);
- }
- cx = cx == null ? bbox.x + bbox.width / 2 : cx;
- cy = cy == null ? bbox.y + bbox.height / 2 : cy;
- this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
- return this;
- };
- /*\
- * Element.translate
- [ method ]
- **
- * Deprecated! Use @Element.transform instead.
- * Adds translation by given amount to the list of transformations of the element.
- > Parameters
- - dx (number) horisontal shift
- - dy (number) vertical shift
- = (object) @Element
- \*/
- elproto.translate = function (dx, dy) {
- if (this.removed) {
- return this;
- }
- dx = Str(dx).split(separator);
- if (dx.length - 1) {
- dy = toFloat(dx[1]);
- }
- dx = toFloat(dx[0]) || 0;
- dy = +dy || 0;
- this.transform(this._.transform.concat([["t", dx, dy]]));
- return this;
- };
- /*\
- * Element.transform
- [ method ]
- **
- * Adds transformation to the element which is separate to other attributes,
- * i.e. translation doesn’t change `x` or `y` of the rectange. The format
- * of transformation string is similar to the path string syntax:
- | "t100,100r30,100,100s2,2,100,100r45s1.5"
- * Each letter is a command. There are four commands: `t` is for translate, `r` is for rotate, `s` is for
- * scale and `m` is for matrix.
- *
- * There are also alternative “absolute” translation, rotation and scale: `T`, `R` and `S`. They will not take previous transformation into account. For example, `...T100,0` will always move element 100 px horisontally, while `...t100,0` could move it vertically if there is `r90` before. Just compare results of `r90t100,0` and `r90T100,0`.
- *
- * So, the example line above could be read like “translate by 100, 100; rotate 30° around 100, 100; scale twice around 100, 100;
- * rotate 45° around centre; scale 1.5 times relative to centre”. As you can see rotate and scale commands have origin
- * coordinates as optional parameters, the default is the centre point of the element.
- * Matrix accepts six parameters.
- > Usage
- | var el = paper.rect(10, 20, 300, 200);
- | // translate 100, 100, rotate 45°, translate -100, 0
- | el.transform("t100,100r45t-100,0");
- | // if you want you can append or prepend transformations
- | el.transform("...t50,50");
- | el.transform("s2...");
- | // or even wrap
- | el.transform("t50,50...t-50-50");
- | // to reset transformation call method with empty string
- | el.transform("");
- | // to get current value call it without parameters
- | console.log(el.transform());
- > Parameters
- - tstr (string) #optional transformation string
- * If tstr isn’t specified
- = (string) current transformation string
- * else
- = (object) @Element
- \*/
- elproto.transform = function (tstr) {
- var _ = this._;
- if (tstr == null) {
- return _.transform;
- }
- R._extractTransform(this, tstr);
-
- this.clip && $(this.clip, {transform: this.matrix.invert()});
- this.pattern && updatePosition(this);
- this.node && $(this.node, {transform: this.matrix});
-
- if (_.sx != 1 || _.sy != 1) {
- var sw = this.attrs[has]("stroke-width") ? this.attrs["stroke-width"] : 1;
- this.attr({"stroke-width": sw});
- }
-
- return this;
- };
- /*\
- * Element.hide
- [ method ]
- **
- * Makes element invisible. See @Element.show.
- = (object) @Element
- \*/
- elproto.hide = function () {
- !this.removed && this.paper.safari(this.node.style.display = "none");
- return this;
- };
- /*\
- * Element.show
- [ method ]
- **
- * Makes element visible. See @Element.hide.
- = (object) @Element
- \*/
- elproto.show = function () {
- !this.removed && this.paper.safari(this.node.style.display = "");
- return this;
- };
- /*\
- * Element.remove
- [ method ]
- **
- * Removes element from the paper.
- \*/
- elproto.remove = function () {
- var node = getRealNode(this.node);
- if (this.removed || !node.parentNode) {
- return;
- }
- var paper = this.paper;
- paper.__set__ && paper.__set__.exclude(this);
- eve.unbind("raphael.*.*." + this.id);
- if (this.gradient) {
- paper.defs.removeChild(this.gradient);
- }
- R._tear(this, paper);
-
- node.parentNode.removeChild(node);
-
- // Remove custom data for element
- this.removeData();
-
- for (var i in this) {
- this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
- }
- this.removed = true;
- };
- elproto._getBBox = function () {
- if (this.node.style.display == "none") {
- this.show();
- var hide = true;
- }
- var canvasHidden = false,
- containerStyle;
- if (this.paper.canvas.parentElement) {
- containerStyle = this.paper.canvas.parentElement.style;
- } //IE10+ can't find parentElement
- else if (this.paper.canvas.parentNode) {
- containerStyle = this.paper.canvas.parentNode.style;
- }
-
- if(containerStyle && containerStyle.display == "none") {
- canvasHidden = true;
- containerStyle.display = "";
- }
- var bbox = {};
- try {
- bbox = this.node.getBBox();
- } catch(e) {
- // Firefox 3.0.x, 25.0.1 (probably more versions affected) play badly here - possible fix
- bbox = {
- x: this.node.clientLeft,
- y: this.node.clientTop,
- width: this.node.clientWidth,
- height: this.node.clientHeight
- }
- } finally {
- bbox = bbox || {};
- if(canvasHidden){
- containerStyle.display = "none";
- }
- }
- hide && this.hide();
- return bbox;
- };
- /*\
- * Element.attr
- [ method ]
- **
- * Sets the attributes of the element.
- > Parameters
- - attrName (string) attribute’s name
- - value (string) value
- * or
- - params (object) object of name/value pairs
- * or
- - attrName (string) attribute’s name
- * or
- - attrNames (array) in this case method returns array of current values for given attribute names
- = (object) @Element if attrsName & value or params are passed in.
- = (...) value of the attribute if only attrsName is passed in.
- = (array) array of values of the attribute if attrsNames is passed in.
- = (object) object of attributes if nothing is passed in.
- > Possible parameters
- # <p>Please refer to the <a href="http://www.w3.org/TR/SVG/" title="The W3C Recommendation for the SVG language describes these properties in detail.">SVG specification</a> for an explanation of these parameters.</p>
- o arrow-end (string) arrowhead on the end of the path. The format for string is `<type>[-<width>[-<length>]]`. Possible types: `classic`, `block`, `open`, `oval`, `diamond`, `none`, width: `wide`, `narrow`, `medium`, length: `long`, `short`, `midium`.
- o clip-rect (string) comma or space separated values: x, y, width and height
- o cursor (string) CSS type of the cursor
- o cx (number) the x-axis coordinate of the center of the circle, or ellipse
- o cy (number) the y-axis coordinate of the center of the circle, or ellipse
- o fill (string) colour, gradient or image
- o fill-opacity (number)
- o font (string)
- o font-family (string)
- o font-size (number) font size in pixels
- o font-weight (string)
- o height (number)
- o href (string) URL, if specified element behaves as hyperlink
- o opacity (number)
- o path (string) SVG path string format
- o r (number) radius of the circle, ellipse or rounded corner on the rect
- o rx (number) horisontal radius of the ellipse
- o ry (number) vertical radius of the ellipse
- o src (string) image URL, only works for @Element.image element
- o stroke (string) stroke colour
- o stroke-dasharray (string) [“”, “`-`”, “`.`”, “`-.`”, “`-..`”, “`. `”, “`- `”, “`--`”, “`- .`”, “`--.`”, “`--..`”]
- o stroke-linecap (string) [“`butt`”, “`square`”, “`round`”]
- o stroke-linejoin (string) [“`bevel`”, “`round`”, “`miter`”]
- o stroke-miterlimit (number)
- o stroke-opacity (number)
- o stroke-width (number) stroke width in pixels, default is '1'
- o target (string) used with href
- o text (string) contents of the text element. Use `\n` for multiline text
- o text-anchor (string) [“`start`”, “`middle`”, “`end`”], default is “`middle`”
- o title (string) will create tooltip with a given text
- o transform (string) see @Element.transform
- o width (number)
- o x (number)
- o y (number)
- > Gradients
- * Linear gradient format: “`‹angle›-‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`90-#fff-#000`” – 90°
- * gradient from white to black or “`0-#fff-#f00:20-#000`” – 0° gradient from white via red (at 20%) to black.
- *
- * radial gradient: “`r[(‹fx›, ‹fy›)]‹colour›[-‹colour›[:‹offset›]]*-‹colour›`”, example: “`r#fff-#000`” –
- * gradient from white to black or “`r(0.25, 0.75)#fff-#000`” – gradient from white to black with focus point
- * at 0.25, 0.75. Focus point coordinates are in 0..1 range. Radial gradients can only be applied to circles and ellipses.
- > Path String
- # <p>Please refer to <a href="http://www.w3.org/TR/SVG/paths.html#PathData" title="Details of a path’s data attribute’s format are described in the SVG specification.">SVG documentation regarding path string</a>. Raphaël fully supports it.</p>
- > Colour Parsing
- # <ul>
- # <li>Colour name (“<code>red</code>”, “<code>green</code>”, “<code>cornflowerblue</code>”, etc)</li>
- # <li>#••• — shortened HTML colour: (“<code>#000</code>”, “<code>#fc0</code>”, etc)</li>
- # <li>#•••••• — full length HTML colour: (“<code>#000000</code>”, “<code>#bd2300</code>”)</li>
- # <li>rgb(•••, •••, •••) — red, green and blue channels’ values: (“<code>rgb(200,&nbsp;100,&nbsp;0)</code>”)</li>
- # <li>rgb(•••%, •••%, •••%) — same as above, but in %: (“<code>rgb(100%,&nbsp;175%,&nbsp;0%)</code>”)</li>
- # <li>rgba(•••, •••, •••, •••) — red, green and blue channels’ values: (“<code>rgba(200,&nbsp;100,&nbsp;0, .5)</code>”)</li>
- # <li>rgba(•••%, •••%, •••%, •••%) — same as above, but in %: (“<code>rgba(100%,&nbsp;175%,&nbsp;0%, 50%)</code>”)</li>
- # <li>hsb(•••, •••, •••) — hue, saturation and brightness values: (“<code>hsb(0.5,&nbsp;0.25,&nbsp;1)</code>”)</li>
- # <li>hsb(•••%, •••%, •••%) — same as above, but in %</li>
- # <li>hsba(•••, •••, •••, •••) — same as above, but with opacity</li>
- # <li>hsl(•••, •••, •••) — almost the same as hsb, see <a href="http://en.wikipedia.org/wiki/HSL_and_HSV" title="HSL and HSV - Wikipedia, the free encyclopedia">Wikipedia page</a></li>
- # <li>hsl(•••%, •••%, •••%) — same as above, but in %</li>
- # <li>hsla(•••, •••, •••, •••) — same as above, but with opacity</li>
- # <li>Optionally for hsb and hsl you could specify hue as a degree: “<code>hsl(240deg,&nbsp;1,&nbsp;.5)</code>” or, if you want to go fancy, “<code>hsl(240°,&nbsp;1,&nbsp;.5)</code>”</li>
- # </ul>
- \*/
- elproto.attr = function (name, value) {
- if (this.removed) {
- return this;
- }
- if (name == null) {
- var res = {};
- for (var a in this.attrs) if (this.attrs[has](a)) {
- res[a] = this.attrs[a];
- }
- res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
- res.transform = this._.transform;
- return res;
- }
- if (value == null && R.is(name, "string")) {
- if (name == "fill" && this.attrs.fill == "none" && this.attrs.gradient) {
- return this.attrs.gradient;
- }
- if (name == "transform") {
- return this._.transform;
- }
- var names = name.split(separator),
- out = {};
- for (var i = 0, ii = names.length; i < ii; i++) {
- name = names[i];
- if (name in this.attrs) {
- out[name] = this.attrs[name];
- } else if (R.is(this.paper.customAttributes[name], "function")) {
- out[name] = this.paper.customAttributes[name].def;
- } else {
- out[name] = R._availableAttrs[name];
- }
- }
- return ii - 1 ? out : out[names[0]];
- }
- if (value == null && R.is(name, "array")) {
- out = {};
- for (i = 0, ii = name.length; i < ii; i++) {
- out[name[i]] = this.attr(name[i]);
- }
- return out;
- }
- if (value != null) {
- var params = {};
- params[name] = value;
- } else if (name != null && R.is(name, "object")) {
- params = name;
- }
- for (var key in params) {
- eve("raphael.attr." + key + "." + this.id, this, params[key]);
- }
- for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
- var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
- this.attrs[key] = params[key];
- for (var subkey in par) if (par[has](subkey)) {
- params[subkey] = par[subkey];
- }
- }
- setFillAndStroke(this, params);
- return this;
- };
- /*\
- * Element.toFront
- [ method ]
- **
- * Moves the element so it is the closest to the viewer’s eyes, on top of other elements.
- = (object) @Element
- \*/
- elproto.toFront = function () {
- if (this.removed) {
- return this;
- }
- var node = getRealNode(this.node);
- node.parentNode.appendChild(node);
- var svg = this.paper;
- svg.top != this && R._tofront(this, svg);
- return this;
- };
- /*\
- * Element.toBack
- [ method ]
- **
- * Moves the element so it is the furthest from the viewer’s eyes, behind other elements.
- = (object) @Element
- \*/
- elproto.toBack = function () {
- if (this.removed) {
- return this;
- }
- var node = getRealNode(this.node);
- var parentNode = node.parentNode;
- parentNode.insertBefore(node, parentNode.firstChild);
- R._toback(this, this.paper);
- var svg = this.paper;
- return this;
- };
- /*\
- * Element.insertAfter
- [ method ]
- **
- * Inserts current object after the given one.
- = (object) @Element
- \*/
- elproto.insertAfter = function (element) {
- if (this.removed || !element) {
- return this;
- }
-
- var node = getRealNode(this.node);
- var afterNode = getRealNode(element.node || element[element.length - 1].node);
- if (afterNode.nextSibling) {
- afterNode.parentNode.insertBefore(node, afterNode.nextSibling);
- } else {
- afterNode.parentNode.appendChild(node);
- }
- R._insertafter(this, element, this.paper);
- return this;
- };
- /*\
- * Element.insertBefore
- [ method ]
- **
- * Inserts current object before the given one.
- = (object) @Element
- \*/
- elproto.insertBefore = function (element) {
- if (this.removed || !element) {
- return this;
- }
-
- var node = getRealNode(this.node);
- var beforeNode = getRealNode(element.node || element[0].node);
- beforeNode.parentNode.insertBefore(node, beforeNode);
- R._insertbefore(this, element, this.paper);
- return this;
- };
- elproto.blur = function (size) {
- // Experimental. No Safari support. Use it on your own risk.
- var t = this;
- if (+size !== 0) {
- var fltr = $("filter"),
- blur = $("feGaussianBlur");
- t.attrs.blur = size;
- fltr.id = R.createUUID();
- $(blur, {stdDeviation: +size || 1.5});
- fltr.appendChild(blur);
- t.paper.defs.appendChild(fltr);
- t._blur = fltr;
- $(t.node, {filter: "url(#" + fltr.id + ")"});
- } else {
- if (t._blur) {
- t._blur.parentNode.removeChild(t._blur);
- delete t._blur;
- delete t.attrs.blur;
- }
- t.node.removeAttribute("filter");
- }
- return t;
- };
- R._engine.circle = function (svg, x, y, r) {
- var el = $("circle");
- svg.canvas && svg.canvas.appendChild(el);
- var res = new Element(el, svg);
- res.attrs = {cx: x, cy: y, r: r, fill: "none", stroke: "#000"};
- res.type = "circle";
- $(el, res.attrs);
- return res;
- };
- R._engine.rect = function (svg, x, y, w, h, r) {
- var el = $("rect");
- svg.canvas && svg.canvas.appendChild(el);
- var res = new Element(el, svg);
- res.attrs = {x: x, y: y, width: w, height: h, rx: r || 0, ry: r || 0, fill: "none", stroke: "#000"};
- res.type = "rect";
- $(el, res.attrs);
- return res;
- };
- R._engine.ellipse = function (svg, x, y, rx, ry) {
- var el = $("ellipse");
- svg.canvas && svg.canvas.appendChild(el);
- var res = new Element(el, svg);
- res.attrs = {cx: x, cy: y, rx: rx, ry: ry, fill: "none", stroke: "#000"};
- res.type = "ellipse";
- $(el, res.attrs);
- return res;
- };
- R._engine.image = function (svg, src, x, y, w, h) {
- var el = $("image");
- $(el, {x: x, y: y, width: w, height: h, preserveAspectRatio: "none"});
- el.setAttributeNS(xlink, "href", src);
- svg.canvas && svg.canvas.appendChild(el);
- var res = new Element(el, svg);
- res.attrs = {x: x, y: y, width: w, height: h, src: src};
- res.type = "image";
- return res;
- };
- R._engine.text = function (svg, x, y, text) {
- var el = $("text");
- svg.canvas && svg.canvas.appendChild(el);
- var res = new Element(el, svg);
- res.attrs = {
- x: x,
- y: y,
- "text-anchor": "middle",
- text: text,
- "font-family": R._availableAttrs["font-family"],
- "font-size": R._availableAttrs["font-size"],
- stroke: "none",
- fill: "#000"
- };
- res.type = "text";
- setFillAndStroke(res, res.attrs);
- return res;
- };
- R._engine.setSize = function (width, height) {
- this.width = width || this.width;
- this.height = height || this.height;
- this.canvas.setAttribute("width", this.width);
- this.canvas.setAttribute("height", this.height);
- if (this._viewBox) {
- this.setViewBox.apply(this, this._viewBox);
- }
- return this;
- };
- R._engine.create = function () {
- var con = R._getContainer.apply(0, arguments),
- container = con && con.container,
- x = con.x,
- y = con.y,
- width = con.width,
- height = con.height;
- if (!container) {
- throw new Error("SVG container not found.");
- }
- var cnvs = $("svg"),
- css = "overflow:hidden;",
- isFloating;
- x = x || 0;
- y = y || 0;
- width = width || 512;
- height = height || 342;
- $(cnvs, {
- height: height,
- version: 1.1,
- width: width,
- xmlns: "http://www.w3.org/2000/svg",
- "xmlns:xlink": "http://www.w3.org/1999/xlink"
- });
- if (container == 1) {
- cnvs.style.cssText = css + "position:absolute;left:" + x + "px;top:" + y + "px";
- R._g.doc.body.appendChild(cnvs);
- isFloating = 1;
- } else {
- cnvs.style.cssText = css + "position:relative";
- if (container.firstChild) {
- container.insertBefore(cnvs, container.firstChild);
- } else {
- container.appendChild(cnvs);
- }
- }
- container = new R._Paper;
- container.width = width;
- container.height = height;
- container.canvas = cnvs;
- container.clear();
- container._left = container._top = 0;
- isFloating && (container.renderfix = function () {});
- container.renderfix();
- return container;
- };
- R._engine.setViewBox = function (x, y, w, h, fit) {
- eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
- var paperSize = this.getSize(),
- size = mmax(w / paperSize.width, h / paperSize.height),
- top = this.top,
- aspectRatio = fit ? "xMidYMid meet" : "xMinYMin",
- vb,
- sw;
- if (x == null) {
- if (this._vbSize) {
- size = 1;
- }
- delete this._vbSize;
- vb = "0 0 " + this.width + S + this.height;
- } else {
- this._vbSize = size;
- vb = x + S + y + S + w + S + h;
- }
- $(this.canvas, {
- viewBox: vb,
- preserveAspectRatio: aspectRatio
- });
- while (size && top) {
- sw = "stroke-width" in top.attrs ? top.attrs["stroke-width"] : 1;
- top.attr({"stroke-width": sw});
- top._.dirty = 1;
- top._.dirtyT = 1;
- top = top.prev;
- }
- this._viewBox = [x, y, w, h, !!fit];
- return this;
- };
- /*\
- * Paper.renderfix
- [ method ]
- **
- * Fixes the issue of Firefox and IE9 regarding subpixel rendering. If paper is dependant
- * on other elements after reflow it could shift half pixel which cause for lines to lost their crispness.
- * This method fixes the issue.
- **
- Special thanks to Mariusz Nowak (http://www.medikoo.com/) for this method.
- \*/
- R.prototype.renderfix = function () {
- var cnvs = this.canvas,
- s = cnvs.style,
- pos;
- try {
- pos = cnvs.getScreenCTM() || cnvs.createSVGMatrix();
- } catch (e) {
- pos = cnvs.createSVGMatrix();
- }
- var left = -pos.e % 1,
- top = -pos.f % 1;
- if (left || top) {
- if (left) {
- this._left = (this._left + left) % 1;
- s.left = this._left + "px";
- }
- if (top) {
- this._top = (this._top + top) % 1;
- s.top = this._top + "px";
- }
- }
- };
- /*\
- * Paper.clear
- [ method ]
- **
- * Clears the paper, i.e. removes all the elements.
- \*/
- R.prototype.clear = function () {
- R.eve("raphael.clear", this);
- var c = this.canvas;
- while (c.firstChild) {
- c.removeChild(c.firstChild);
- }
- this.bottom = this.top = null;
- (this.desc = $("desc")).appendChild(R._g.doc.createTextNode("Created with Rapha\xebl " + R.version));
- c.appendChild(this.desc);
- c.appendChild(this.defs = $("defs"));
- };
- /*\
- * Paper.remove
- [ method ]
- **
- * Removes the paper from the DOM.
- \*/
- R.prototype.remove = function () {
- eve("raphael.remove", this);
- this.canvas.parentNode && this.canvas.parentNode.removeChild(this.canvas);
- for (var i in this) {
- this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
- }
- };
- var setproto = R.st;
- for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
- setproto[method] = (function (methodname) {
- return function () {
- var arg = arguments;
- return this.forEach(function (el) {
- el[methodname].apply(el, arg);
- });
- };
- })(method);
- }
-})();
-
-// ┌─────────────────────────────────────────────────────────────────────┐ \\
-// │ Raphaël - JavaScript Vector Library │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ VML Module │ \\
-// ├─────────────────────────────────────────────────────────────────────┤ \\
-// │ Copyright (c) 2008-2011 Dmitry Baranovskiy (http://raphaeljs.com) │ \\
-// │ Copyright (c) 2008-2011 Sencha Labs (http://sencha.com) │ \\
-// │ Licensed under the MIT (http://raphaeljs.com/license.html) license. │ \\
-// └─────────────────────────────────────────────────────────────────────┘ \\
-
-(function(){
- if (!R.vml) {
- return;
- }
- var has = "hasOwnProperty",
- Str = String,
- toFloat = parseFloat,
- math = Math,
- round = math.round,
- mmax = math.max,
- mmin = math.min,
- abs = math.abs,
- fillString = "fill",
- separator = /[, ]+/,
- eve = R.eve,
- ms = " progid:DXImageTransform.Microsoft",
- S = " ",
- E = "",
- map = {M: "m", L: "l", C: "c", Z: "x", m: "t", l: "r", c: "v", z: "x"},
- bites = /([clmz]),?([^clmz]*)/gi,
- blurregexp = / progid:\S+Blur\([^\)]+\)/g,
- val = /-?[^,\s-]+/g,
- cssDot = "position:absolute;left:0;top:0;width:1px;height:1px;behavior:url(#default#VML)",
- zoom = 21600,
- pathTypes = {path: 1, rect: 1, image: 1},
- ovalTypes = {circle: 1, ellipse: 1},
- path2vml = function (path) {
- var total = /[ahqstv]/ig,
- command = R._pathToAbsolute;
- Str(path).match(total) && (command = R._path2curve);
- total = /[clmz]/g;
- if (command == R._pathToAbsolute && !Str(path).match(total)) {
- var res = Str(path).replace(bites, function (all, command, args) {
- var vals = [],
- isMove = command.toLowerCase() == "m",
- res = map[command];
- args.replace(val, function (value) {
- if (isMove && vals.length == 2) {
- res += vals + map[command == "m" ? "l" : "L"];
- vals = [];
- }
- vals.push(round(value * zoom));
- });
- return res + vals;
- });
- return res;
- }
- var pa = command(path), p, r;
- res = [];
- for (var i = 0, ii = pa.length; i < ii; i++) {
- p = pa[i];
- r = pa[i][0].toLowerCase();
- r == "z" && (r = "x");
- for (var j = 1, jj = p.length; j < jj; j++) {
- r += round(p[j] * zoom) + (j != jj - 1 ? "," : E);
- }
- res.push(r);
- }
- return res.join(S);
- },
- compensation = function (deg, dx, dy) {
- var m = R.matrix();
- m.rotate(-deg, .5, .5);
- return {
- dx: m.x(dx, dy),
- dy: m.y(dx, dy)
- };
- },
- setCoords = function (p, sx, sy, dx, dy, deg) {
- var _ = p._,
- m = p.matrix,
- fillpos = _.fillpos,
- o = p.node,
- s = o.style,
- y = 1,
- flip = "",
- dxdy,
- kx = zoom / sx,
- ky = zoom / sy;
- s.visibility = "hidden";
- if (!sx || !sy) {
- return;
- }
- o.coordsize = abs(kx) + S + abs(ky);
- s.rotation = deg * (sx * sy < 0 ? -1 : 1);
- if (deg) {
- var c = compensation(deg, dx, dy);
- dx = c.dx;
- dy = c.dy;
- }
- sx < 0 && (flip += "x");
- sy < 0 && (flip += " y") && (y = -1);
- s.flip = flip;
- o.coordorigin = (dx * -kx) + S + (dy * -ky);
- if (fillpos || _.fillsize) {
- var fill = o.getElementsByTagName(fillString);
- fill = fill && fill[0];
- o.removeChild(fill);
- if (fillpos) {
- c = compensation(deg, m.x(fillpos[0], fillpos[1]), m.y(fillpos[0], fillpos[1]));
- fill.position = c.dx * y + S + c.dy * y;
- }
- if (_.fillsize) {
- fill.size = _.fillsize[0] * abs(sx) + S + _.fillsize[1] * abs(sy);
- }
- o.appendChild(fill);
- }
- s.visibility = "visible";
- };
- R.toString = function () {
- return "Your browser doesn\u2019t support SVG. Falling down to VML.\nYou are running Rapha\xebl " + this.version;
- };
- var addArrow = function (o, value, isEnd) {
- var values = Str(value).toLowerCase().split("-"),
- se = isEnd ? "end" : "start",
- i = values.length,
- type = "classic",
- w = "medium",
- h = "medium";
- while (i--) {
- switch (values[i]) {
- case "block":
- case "classic":
- case "oval":
- case "diamond":
- case "open":
- case "none":
- type = values[i];
- break;
- case "wide":
- case "narrow": h = values[i]; break;
- case "long":
- case "short": w = values[i]; break;
- }
- }
- var stroke = o.node.getElementsByTagName("stroke")[0];
- stroke[se + "arrow"] = type;
- stroke[se + "arrowlength"] = w;
- stroke[se + "arrowwidth"] = h;
- },
- setFillAndStroke = function (o, params) {
- // o.paper.canvas.style.display = "none";
- o.attrs = o.attrs || {};
- var node = o.node,
- a = o.attrs,
- s = node.style,
- xy,
- newpath = pathTypes[o.type] && (params.x != a.x || params.y != a.y || params.width != a.width || params.height != a.height || params.cx != a.cx || params.cy != a.cy || params.rx != a.rx || params.ry != a.ry || params.r != a.r),
- isOval = ovalTypes[o.type] && (a.cx != params.cx || a.cy != params.cy || a.r != params.r || a.rx != params.rx || a.ry != params.ry),
- res = o;
-
-
- for (var par in params) if (params[has](par)) {
- a[par] = params[par];
- }
- if (newpath) {
- a.path = R._getPath[o.type](o);
- o._.dirty = 1;
- }
- params.href && (node.href = params.href);
- params.title && (node.title = params.title);
- params.target && (node.target = params.target);
- params.cursor && (s.cursor = params.cursor);
- "blur" in params && o.blur(params.blur);
- if (params.path && o.type == "path" || newpath) {
- node.path = path2vml(~Str(a.path).toLowerCase().indexOf("r") ? R._pathToAbsolute(a.path) : a.path);
- o._.dirty = 1;
- if (o.type == "image") {
- o._.fillpos = [a.x, a.y];
- o._.fillsize = [a.width, a.height];
- setCoords(o, 1, 1, 0, 0, 0);
- }
- }
- "transform" in params && o.transform(params.transform);
- if (isOval) {
- var cx = +a.cx,
- cy = +a.cy,
- rx = +a.rx || +a.r || 0,
- ry = +a.ry || +a.r || 0;
- node.path = R.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x", round((cx - rx) * zoom), round((cy - ry) * zoom), round((cx + rx) * zoom), round((cy + ry) * zoom), round(cx * zoom));
- o._.dirty = 1;
- }
- if ("clip-rect" in params) {
- var rect = Str(params["clip-rect"]).split(separator);
- if (rect.length == 4) {
- rect[2] = +rect[2] + (+rect[0]);
- rect[3] = +rect[3] + (+rect[1]);
- var div = node.clipRect || R._g.doc.createElement("div"),
- dstyle = div.style;
- dstyle.clip = R.format("rect({1}px {2}px {3}px {0}px)", rect);
- if (!node.clipRect) {
- dstyle.position = "absolute";
- dstyle.top = 0;
- dstyle.left = 0;
- dstyle.width = o.paper.width + "px";
- dstyle.height = o.paper.height + "px";
- node.parentNode.insertBefore(div, node);
- div.appendChild(node);
- node.clipRect = div;
- }
- }
- if (!params["clip-rect"]) {
- node.clipRect && (node.clipRect.style.clip = "auto");
- }
- }
- if (o.textpath) {
- var textpathStyle = o.textpath.style;
- params.font && (textpathStyle.font = params.font);
- params["font-family"] && (textpathStyle.fontFamily = '"' + params["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g, E) + '"');
- params["font-size"] && (textpathStyle.fontSize = params["font-size"]);
- params["font-weight"] && (textpathStyle.fontWeight = params["font-weight"]);
- params["font-style"] && (textpathStyle.fontStyle = params["font-style"]);
- }
- if ("arrow-start" in params) {
- addArrow(res, params["arrow-start"]);
- }
- if ("arrow-end" in params) {
- addArrow(res, params["arrow-end"], 1);
- }
- if (params.opacity != null ||
- params["stroke-width"] != null ||
- params.fill != null ||
- params.src != null ||
- params.stroke != null ||
- params["stroke-width"] != null ||
- params["stroke-opacity"] != null ||
- params["fill-opacity"] != null ||
- params["stroke-dasharray"] != null ||
- params["stroke-miterlimit"] != null ||
- params["stroke-linejoin"] != null ||
- params["stroke-linecap"] != null) {
- var fill = node.getElementsByTagName(fillString),
- newfill = false;
- fill = fill && fill[0];
- !fill && (newfill = fill = createNode(fillString));
- if (o.type == "image" && params.src) {
- fill.src = params.src;
- }
- params.fill && (fill.on = true);
- if (fill.on == null || params.fill == "none" || params.fill === null) {
- fill.on = false;
- }
- if (fill.on && params.fill) {
- var isURL = Str(params.fill).match(R._ISURL);
- if (isURL) {
- fill.parentNode == node && node.removeChild(fill);
- fill.rotate = true;
- fill.src = isURL[1];
- fill.type = "tile";
- var bbox = o.getBBox(1);
- fill.position = bbox.x + S + bbox.y;
- o._.fillpos = [bbox.x, bbox.y];
-
- R._preload(isURL[1], function () {
- o._.fillsize = [this.offsetWidth, this.offsetHeight];
- });
- } else {
- fill.color = R.getRGB(params.fill).hex;
- fill.src = E;
- fill.type = "solid";
- if (R.getRGB(params.fill).error && (res.type in {circle: 1, ellipse: 1} || Str(params.fill).charAt() != "r") && addGradientFill(res, params.fill, fill)) {
- a.fill = "none";
- a.gradient = params.fill;
- fill.rotate = false;
- }
- }
- }
- if ("fill-opacity" in params || "opacity" in params) {
- var opacity = ((+a["fill-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+R.getRGB(params.fill).o + 1 || 2) - 1);
- opacity = mmin(mmax(opacity, 0), 1);
- fill.opacity = opacity;
- if (fill.src) {
- fill.color = "none";
- }
- }
- node.appendChild(fill);
- var stroke = (node.getElementsByTagName("stroke") && node.getElementsByTagName("stroke")[0]),
- newstroke = false;
- !stroke && (newstroke = stroke = createNode("stroke"));
- if ((params.stroke && params.stroke != "none") ||
- params["stroke-width"] ||
- params["stroke-opacity"] != null ||
- params["stroke-dasharray"] ||
- params["stroke-miterlimit"] ||
- params["stroke-linejoin"] ||
- params["stroke-linecap"]) {
- stroke.on = true;
- }
- (params.stroke == "none" || params.stroke === null || stroke.on == null || params.stroke == 0 || params["stroke-width"] == 0) && (stroke.on = false);
- var strokeColor = R.getRGB(params.stroke);
- stroke.on && params.stroke && (stroke.color = strokeColor.hex);
- opacity = ((+a["stroke-opacity"] + 1 || 2) - 1) * ((+a.opacity + 1 || 2) - 1) * ((+strokeColor.o + 1 || 2) - 1);
- var width = (toFloat(params["stroke-width"]) || 1) * .75;
- opacity = mmin(mmax(opacity, 0), 1);
- params["stroke-width"] == null && (width = a["stroke-width"]);
- params["stroke-width"] && (stroke.weight = width);
- width && width < 1 && (opacity *= width) && (stroke.weight = 1);
- stroke.opacity = opacity;
-
- params["stroke-linejoin"] && (stroke.joinstyle = params["stroke-linejoin"] || "miter");
- stroke.miterlimit = params["stroke-miterlimit"] || 8;
- params["stroke-linecap"] && (stroke.endcap = params["stroke-linecap"] == "butt" ? "flat" : params["stroke-linecap"] == "square" ? "square" : "round");
- if ("stroke-dasharray" in params) {
- var dasharray = {
- "-": "shortdash",
- ".": "shortdot",
- "-.": "shortdashdot",
- "-..": "shortdashdotdot",
- ". ": "dot",
- "- ": "dash",
- "--": "longdash",
- "- .": "dashdot",
- "--.": "longdashdot",
- "--..": "longdashdotdot"
- };
- stroke.dashstyle = dasharray[has](params["stroke-dasharray"]) ? dasharray[params["stroke-dasharray"]] : E;
- }
- newstroke && node.appendChild(stroke);
- }
- if (res.type == "text") {
- res.paper.canvas.style.display = E;
- var span = res.paper.span,
- m = 100,
- fontSize = a.font && a.font.match(/\d+(?:\.\d*)?(?=px)/);
- s = span.style;
- a.font && (s.font = a.font);
- a["font-family"] && (s.fontFamily = a["font-family"]);
- a["font-weight"] && (s.fontWeight = a["font-weight"]);
- a["font-style"] && (s.fontStyle = a["font-style"]);
- fontSize = toFloat(a["font-size"] || fontSize && fontSize[0]) || 10;
- s.fontSize = fontSize * m + "px";
- res.textpath.string && (span.innerHTML = Str(res.textpath.string).replace(/</g, "&#60;").replace(/&/g, "&#38;").replace(/\n/g, "<br>"));
- var brect = span.getBoundingClientRect();
- res.W = a.w = (brect.right - brect.left) / m;
- res.H = a.h = (brect.bottom - brect.top) / m;
- // res.paper.canvas.style.display = "none";
- res.X = a.x;
- res.Y = a.y + res.H / 2;
-
- ("x" in params || "y" in params) && (res.path.v = R.format("m{0},{1}l{2},{1}", round(a.x * zoom), round(a.y * zoom), round(a.x * zoom) + 1));
- var dirtyattrs = ["x", "y", "text", "font", "font-family", "font-weight", "font-style", "font-size"];
- for (var d = 0, dd = dirtyattrs.length; d < dd; d++) if (dirtyattrs[d] in params) {
- res._.dirty = 1;
- break;
- }
-
- // text-anchor emulation
- switch (a["text-anchor"]) {
- case "start":
- res.textpath.style["v-text-align"] = "left";
- res.bbx = res.W / 2;
- break;
- case "end":
- res.textpath.style["v-text-align"] = "right";
- res.bbx = -res.W / 2;
- break;
- default:
- res.textpath.style["v-text-align"] = "center";
- res.bbx = 0;
- break;
- }
- res.textpath.style["v-text-kern"] = true;
- }
- // res.paper.canvas.style.display = E;
- },
- addGradientFill = function (o, gradient, fill) {
- o.attrs = o.attrs || {};
- var attrs = o.attrs,
- pow = Math.pow,
- opacity,
- oindex,
- type = "linear",
- fxfy = ".5 .5";
- o.attrs.gradient = gradient;
- gradient = Str(gradient).replace(R._radial_gradient, function (all, fx, fy) {
- type = "radial";
- if (fx && fy) {
- fx = toFloat(fx);
- fy = toFloat(fy);
- pow(fx - .5, 2) + pow(fy - .5, 2) > .25 && (fy = math.sqrt(.25 - pow(fx - .5, 2)) * ((fy > .5) * 2 - 1) + .5);
- fxfy = fx + S + fy;
- }
- return E;
- });
- gradient = gradient.split(/\s*\-\s*/);
- if (type == "linear") {
- var angle = gradient.shift();
- angle = -toFloat(angle);
- if (isNaN(angle)) {
- return null;
- }
- }
- var dots = R._parseDots(gradient);
- if (!dots) {
- return null;
- }
- o = o.shape || o.node;
- if (dots.length) {
- o.removeChild(fill);
- fill.on = true;
- fill.method = "none";
- fill.color = dots[0].color;
- fill.color2 = dots[dots.length - 1].color;
- var clrs = [];
- for (var i = 0, ii = dots.length; i < ii; i++) {
- dots[i].offset && clrs.push(dots[i].offset + S + dots[i].color);
- }
- fill.colors = clrs.length ? clrs.join() : "0% " + fill.color;
- if (type == "radial") {
- fill.type = "gradientTitle";
- fill.focus = "100%";
- fill.focussize = "0 0";
- fill.focusposition = fxfy;
- fill.angle = 0;
- } else {
- // fill.rotate= true;
- fill.type = "gradient";
- fill.angle = (270 - angle) % 360;
- }
- o.appendChild(fill);
- }
- return 1;
- },
- Element = function (node, vml) {
- this[0] = this.node = node;
- node.raphael = true;
- this.id = R._oid++;
- node.raphaelid = this.id;
- this.X = 0;
- this.Y = 0;
- this.attrs = {};
- this.paper = vml;
- this.matrix = R.matrix();
- this._ = {
- transform: [],
- sx: 1,
- sy: 1,
- dx: 0,
- dy: 0,
- deg: 0,
- dirty: 1,
- dirtyT: 1
- };
- !vml.bottom && (vml.bottom = this);
- this.prev = vml.top;
- vml.top && (vml.top.next = this);
- vml.top = this;
- this.next = null;
- };
- var elproto = R.el;
-
- Element.prototype = elproto;
- elproto.constructor = Element;
- elproto.transform = function (tstr) {
- if (tstr == null) {
- return this._.transform;
- }
- var vbs = this.paper._viewBoxShift,
- vbt = vbs ? "s" + [vbs.scale, vbs.scale] + "-1-1t" + [vbs.dx, vbs.dy] : E,
- oldt;
- if (vbs) {
- oldt = tstr = Str(tstr).replace(/\.{3}|\u2026/g, this._.transform || E);
- }
- R._extractTransform(this, vbt + tstr);
- var matrix = this.matrix.clone(),
- skew = this.skew,
- o = this.node,
- split,
- isGrad = ~Str(this.attrs.fill).indexOf("-"),
- isPatt = !Str(this.attrs.fill).indexOf("url(");
- matrix.translate(1, 1);
- if (isPatt || isGrad || this.type == "image") {
- skew.matrix = "1 0 0 1";
- skew.offset = "0 0";
- split = matrix.split();
- if ((isGrad && split.noRotation) || !split.isSimple) {
- o.style.filter = matrix.toFilter();
- var bb = this.getBBox(),
- bbt = this.getBBox(1),
- dx = bb.x - bbt.x,
- dy = bb.y - bbt.y;
- o.coordorigin = (dx * -zoom) + S + (dy * -zoom);
- setCoords(this, 1, 1, dx, dy, 0);
- } else {
- o.style.filter = E;
- setCoords(this, split.scalex, split.scaley, split.dx, split.dy, split.rotate);
- }
- } else {
- o.style.filter = E;
- skew.matrix = Str(matrix);
- skew.offset = matrix.offset();
- }
- if (oldt !== null) { // empty string value is true as well
- this._.transform = oldt;
- R._extractTransform(this, oldt);
- }
- return this;
- };
- elproto.rotate = function (deg, cx, cy) {
- if (this.removed) {
- return this;
- }
- if (deg == null) {
- return;
- }
- deg = Str(deg).split(separator);
- if (deg.length - 1) {
- cx = toFloat(deg[1]);
- cy = toFloat(deg[2]);
- }
- deg = toFloat(deg[0]);
- (cy == null) && (cx = cy);
- if (cx == null || cy == null) {
- var bbox = this.getBBox(1);
- cx = bbox.x + bbox.width / 2;
- cy = bbox.y + bbox.height / 2;
- }
- this._.dirtyT = 1;
- this.transform(this._.transform.concat([["r", deg, cx, cy]]));
- return this;
- };
- elproto.translate = function (dx, dy) {
- if (this.removed) {
- return this;
- }
- dx = Str(dx).split(separator);
- if (dx.length - 1) {
- dy = toFloat(dx[1]);
- }
- dx = toFloat(dx[0]) || 0;
- dy = +dy || 0;
- if (this._.bbox) {
- this._.bbox.x += dx;
- this._.bbox.y += dy;
- }
- this.transform(this._.transform.concat([["t", dx, dy]]));
- return this;
- };
- elproto.scale = function (sx, sy, cx, cy) {
- if (this.removed) {
- return this;
- }
- sx = Str(sx).split(separator);
- if (sx.length - 1) {
- sy = toFloat(sx[1]);
- cx = toFloat(sx[2]);
- cy = toFloat(sx[3]);
- isNaN(cx) && (cx = null);
- isNaN(cy) && (cy = null);
- }
- sx = toFloat(sx[0]);
- (sy == null) && (sy = sx);
- (cy == null) && (cx = cy);
- if (cx == null || cy == null) {
- var bbox = this.getBBox(1);
- }
- cx = cx == null ? bbox.x + bbox.width / 2 : cx;
- cy = cy == null ? bbox.y + bbox.height / 2 : cy;
-
- this.transform(this._.transform.concat([["s", sx, sy, cx, cy]]));
- this._.dirtyT = 1;
- return this;
- };
- elproto.hide = function () {
- !this.removed && (this.node.style.display = "none");
- return this;
- };
- elproto.show = function () {
- !this.removed && (this.node.style.display = E);
- return this;
- };
- // Needed to fix the vml setViewBox issues
- elproto.auxGetBBox = R.el.getBBox;
- elproto.getBBox = function(){
- var b = this.auxGetBBox();
- if (this.paper && this.paper._viewBoxShift)
- {
- var c = {};
- var z = 1/this.paper._viewBoxShift.scale;
- c.x = b.x - this.paper._viewBoxShift.dx;
- c.x *= z;
- c.y = b.y - this.paper._viewBoxShift.dy;
- c.y *= z;
- c.width = b.width * z;
- c.height = b.height * z;
- c.x2 = c.x + c.width;
- c.y2 = c.y + c.height;
- return c;
- }
- return b;
- };
- elproto._getBBox = function () {
- if (this.removed) {
- return {};
- }
- return {
- x: this.X + (this.bbx || 0) - this.W / 2,
- y: this.Y - this.H,
- width: this.W,
- height: this.H
- };
- };
- elproto.remove = function () {
- if (this.removed || !this.node.parentNode) {
- return;
- }
- this.paper.__set__ && this.paper.__set__.exclude(this);
- R.eve.unbind("raphael.*.*." + this.id);
- R._tear(this, this.paper);
- this.node.parentNode.removeChild(this.node);
- this.shape && this.shape.parentNode.removeChild(this.shape);
- for (var i in this) {
- this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
- }
- this.removed = true;
- };
- elproto.attr = function (name, value) {
- if (this.removed) {
- return this;
- }
- if (name == null) {
- var res = {};
- for (var a in this.attrs) if (this.attrs[has](a)) {
- res[a] = this.attrs[a];
- }
- res.gradient && res.fill == "none" && (res.fill = res.gradient) && delete res.gradient;
- res.transform = this._.transform;
- return res;
- }
- if (value == null && R.is(name, "string")) {
- if (name == fillString && this.attrs.fill == "none" && this.attrs.gradient) {
- return this.attrs.gradient;
- }
- var names = name.split(separator),
- out = {};
- for (var i = 0, ii = names.length; i < ii; i++) {
- name = names[i];
- if (name in this.attrs) {
- out[name] = this.attrs[name];
- } else if (R.is(this.paper.customAttributes[name], "function")) {
- out[name] = this.paper.customAttributes[name].def;
- } else {
- out[name] = R._availableAttrs[name];
- }
- }
- return ii - 1 ? out : out[names[0]];
- }
- if (this.attrs && value == null && R.is(name, "array")) {
- out = {};
- for (i = 0, ii = name.length; i < ii; i++) {
- out[name[i]] = this.attr(name[i]);
- }
- return out;
- }
- var params;
- if (value != null) {
- params = {};
- params[name] = value;
- }
- value == null && R.is(name, "object") && (params = name);
- for (var key in params) {
- eve("raphael.attr." + key + "." + this.id, this, params[key]);
- }
- if (params) {
- for (key in this.paper.customAttributes) if (this.paper.customAttributes[has](key) && params[has](key) && R.is(this.paper.customAttributes[key], "function")) {
- var par = this.paper.customAttributes[key].apply(this, [].concat(params[key]));
- this.attrs[key] = params[key];
- for (var subkey in par) if (par[has](subkey)) {
- params[subkey] = par[subkey];
- }
- }
- // this.paper.canvas.style.display = "none";
- if (params.text && this.type == "text") {
- this.textpath.string = params.text;
- }
- setFillAndStroke(this, params);
- // this.paper.canvas.style.display = E;
- }
- return this;
- };
- elproto.toFront = function () {
- !this.removed && this.node.parentNode.appendChild(this.node);
- this.paper && this.paper.top != this && R._tofront(this, this.paper);
- return this;
- };
- elproto.toBack = function () {
- if (this.removed) {
- return this;
- }
- if (this.node.parentNode.firstChild != this.node) {
- this.node.parentNode.insertBefore(this.node, this.node.parentNode.firstChild);
- R._toback(this, this.paper);
- }
- return this;
- };
- elproto.insertAfter = function (element) {
- if (this.removed) {
- return this;
- }
- if (element.constructor == R.st.constructor) {
- element = element[element.length - 1];
- }
- if (element.node.nextSibling) {
- element.node.parentNode.insertBefore(this.node, element.node.nextSibling);
- } else {
- element.node.parentNode.appendChild(this.node);
- }
- R._insertafter(this, element, this.paper);
- return this;
- };
- elproto.insertBefore = function (element) {
- if (this.removed) {
- return this;
- }
- if (element.constructor == R.st.constructor) {
- element = element[0];
- }
- element.node.parentNode.insertBefore(this.node, element.node);
- R._insertbefore(this, element, this.paper);
- return this;
- };
- elproto.blur = function (size) {
- var s = this.node.runtimeStyle,
- f = s.filter;
- f = f.replace(blurregexp, E);
- if (+size !== 0) {
- this.attrs.blur = size;
- s.filter = f + S + ms + ".Blur(pixelradius=" + (+size || 1.5) + ")";
- s.margin = R.format("-{0}px 0 0 -{0}px", round(+size || 1.5));
- } else {
- s.filter = f;
- s.margin = 0;
- delete this.attrs.blur;
- }
- return this;
- };
-
- R._engine.path = function (pathString, vml) {
- var el = createNode("shape");
- el.style.cssText = cssDot;
- el.coordsize = zoom + S + zoom;
- el.coordorigin = vml.coordorigin;
- var p = new Element(el, vml),
- attr = {fill: "none", stroke: "#000"};
- pathString && (attr.path = pathString);
- p.type = "path";
- p.path = [];
- p.Path = E;
- setFillAndStroke(p, attr);
- vml.canvas.appendChild(el);
- var skew = createNode("skew");
- skew.on = true;
- el.appendChild(skew);
- p.skew = skew;
- p.transform(E);
- return p;
- };
- R._engine.rect = function (vml, x, y, w, h, r) {
- var path = R._rectPath(x, y, w, h, r),
- res = vml.path(path),
- a = res.attrs;
- res.X = a.x = x;
- res.Y = a.y = y;
- res.W = a.width = w;
- res.H = a.height = h;
- a.r = r;
- a.path = path;
- res.type = "rect";
- return res;
- };
- R._engine.ellipse = function (vml, x, y, rx, ry) {
- var res = vml.path(),
- a = res.attrs;
- res.X = x - rx;
- res.Y = y - ry;
- res.W = rx * 2;
- res.H = ry * 2;
- res.type = "ellipse";
- setFillAndStroke(res, {
- cx: x,
- cy: y,
- rx: rx,
- ry: ry
- });
- return res;
- };
- R._engine.circle = function (vml, x, y, r) {
- var res = vml.path(),
- a = res.attrs;
- res.X = x - r;
- res.Y = y - r;
- res.W = res.H = r * 2;
- res.type = "circle";
- setFillAndStroke(res, {
- cx: x,
- cy: y,
- r: r
- });
- return res;
- };
- R._engine.image = function (vml, src, x, y, w, h) {
- var path = R._rectPath(x, y, w, h),
- res = vml.path(path).attr({stroke: "none"}),
- a = res.attrs,
- node = res.node,
- fill = node.getElementsByTagName(fillString)[0];
- a.src = src;
- res.X = a.x = x;
- res.Y = a.y = y;
- res.W = a.width = w;
- res.H = a.height = h;
- a.path = path;
- res.type = "image";
- fill.parentNode == node && node.removeChild(fill);
- fill.rotate = true;
- fill.src = src;
- fill.type = "tile";
- res._.fillpos = [x, y];
- res._.fillsize = [w, h];
- node.appendChild(fill);
- setCoords(res, 1, 1, 0, 0, 0);
- return res;
- };
- R._engine.text = function (vml, x, y, text) {
- var el = createNode("shape"),
- path = createNode("path"),
- o = createNode("textpath");
- x = x || 0;
- y = y || 0;
- text = text || "";
- path.v = R.format("m{0},{1}l{2},{1}", round(x * zoom), round(y * zoom), round(x * zoom) + 1);
- path.textpathok = true;
- o.string = Str(text);
- o.on = true;
- el.style.cssText = cssDot;
- el.coordsize = zoom + S + zoom;
- el.coordorigin = "0 0";
- var p = new Element(el, vml),
- attr = {
- fill: "#000",
- stroke: "none",
- font: R._availableAttrs.font,
- text: text
- };
- p.shape = el;
- p.path = path;
- p.textpath = o;
- p.type = "text";
- p.attrs.text = Str(text);
- p.attrs.x = x;
- p.attrs.y = y;
- p.attrs.w = 1;
- p.attrs.h = 1;
- setFillAndStroke(p, attr);
- el.appendChild(o);
- el.appendChild(path);
- vml.canvas.appendChild(el);
- var skew = createNode("skew");
- skew.on = true;
- el.appendChild(skew);
- p.skew = skew;
- p.transform(E);
- return p;
- };
- R._engine.setSize = function (width, height) {
- var cs = this.canvas.style;
- this.width = width;
- this.height = height;
- width == +width && (width += "px");
- height == +height && (height += "px");
- cs.width = width;
- cs.height = height;
- cs.clip = "rect(0 " + width + " " + height + " 0)";
- if (this._viewBox) {
- R._engine.setViewBox.apply(this, this._viewBox);
- }
- return this;
- };
- R._engine.setViewBox = function (x, y, w, h, fit) {
- R.eve("raphael.setViewBox", this, this._viewBox, [x, y, w, h, fit]);
- var paperSize = this.getSize(),
- width = paperSize.width,
- height = paperSize.height,
- H, W;
- if (fit) {
- H = height / h;
- W = width / w;
- if (w * H < width) {
- x -= (width - w * H) / 2 / H;
- }
- if (h * W < height) {
- y -= (height - h * W) / 2 / W;
- }
- }
- this._viewBox = [x, y, w, h, !!fit];
- this._viewBoxShift = {
- dx: -x,
- dy: -y,
- scale: paperSize
- };
- this.forEach(function (el) {
- el.transform("...");
- });
- return this;
- };
- var createNode;
- R._engine.initWin = function (win) {
- var doc = win.document;
- if (doc.styleSheets.length < 31) {
- doc.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)");
- } else {
- // no more room, add to the existing one
- // http://msdn.microsoft.com/en-us/library/ms531194%28VS.85%29.aspx
- doc.styleSheets[0].addRule(".rvml", "behavior:url(#default#VML)");
- }
- try {
- !doc.namespaces.rvml && doc.namespaces.add("rvml", "urn:schemas-microsoft-com:vml");
- createNode = function (tagName) {
- return doc.createElement('<rvml:' + tagName + ' class="rvml">');
- };
- } catch (e) {
- createNode = function (tagName) {
- return doc.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">');
- };
- }
- };
- R._engine.initWin(R._g.win);
- R._engine.create = function () {
- var con = R._getContainer.apply(0, arguments),
- container = con.container,
- height = con.height,
- s,
- width = con.width,
- x = con.x,
- y = con.y;
- if (!container) {
- throw new Error("VML container not found.");
- }
- var res = new R._Paper,
- c = res.canvas = R._g.doc.createElement("div"),
- cs = c.style;
- x = x || 0;
- y = y || 0;
- width = width || 512;
- height = height || 342;
- res.width = width;
- res.height = height;
- width == +width && (width += "px");
- height == +height && (height += "px");
- res.coordsize = zoom * 1e3 + S + zoom * 1e3;
- res.coordorigin = "0 0";
- res.span = R._g.doc.createElement("span");
- res.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;";
- c.appendChild(res.span);
- cs.cssText = R.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden", width, height);
- if (container == 1) {
- R._g.doc.body.appendChild(c);
- cs.left = x + "px";
- cs.top = y + "px";
- cs.position = "absolute";
- } else {
- if (container.firstChild) {
- container.insertBefore(c, container.firstChild);
- } else {
- container.appendChild(c);
- }
- }
- res.renderfix = function () {};
- return res;
- };
- R.prototype.clear = function () {
- R.eve("raphael.clear", this);
- this.canvas.innerHTML = E;
- this.span = R._g.doc.createElement("span");
- this.span.style.cssText = "position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";
- this.canvas.appendChild(this.span);
- this.bottom = this.top = null;
- };
- R.prototype.remove = function () {
- R.eve("raphael.remove", this);
- this.canvas.parentNode.removeChild(this.canvas);
- for (var i in this) {
- this[i] = typeof this[i] == "function" ? R._removedFactory(i) : null;
- }
- return true;
- };
-
- var setproto = R.st;
- for (var method in elproto) if (elproto[has](method) && !setproto[has](method)) {
- setproto[method] = (function (methodname) {
- return function () {
- var arg = arguments;
- return this.forEach(function (el) {
- el[methodname].apply(el, arg);
- });
- };
- })(method);
- }
-})();
-
- // EXPOSE
- // SVG and VML are appended just before the EXPOSE line
- // Even with AMD, Raphael should be defined globally
- oldRaphael.was ? (g.win.Raphael = R) : (Raphael = R);
-
- if(typeof exports == "object"){
- module.exports = R;
- }
- return R;
-}));
diff --git a/yarn.lock b/yarn.lock
index fd8d67a6b0d..9d38799fc35 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1746,6 +1746,10 @@ etag@~1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/etag/-/etag-1.7.0.tgz#03d30b5f67dd6e632d2945d30d6652731a34d5d8"
+eve-raphael@0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/eve-raphael/-/eve-raphael-0.5.0.tgz#17c754b792beef3fa6684d79cf5a47c63c4cda30"
+
event-emitter@~0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.4.tgz#8d63ddfb4cfe1fae3b32ca265c4c720222080bb5"
@@ -3554,6 +3558,12 @@ range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+raphael@^2.2.7:
+ version "2.2.7"
+ resolved "https://registry.yarnpkg.com/raphael/-/raphael-2.2.7.tgz#231b19141f8d086986d8faceb66f8b562ee2c810"
+ dependencies:
+ eve-raphael "0.5.0"
+
raw-body@~2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.2.0.tgz#994976cf6a5096a41162840492f0bdc5d6e7fb96"