From 347bf09d6ecf4871da234c06ca8ee541e27b5105 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 23 Feb 2021 09:14:52 +0000 Subject: Add latest changes from gitlab-org/gitlab@13-10-stable-ee --- app/assets/javascripts/admin/users/tabs.js | 11 +- .../instance_statistics/components/app.vue | 48 ----- .../components/charts_config.js | 87 -------- .../components/instance_counts.vue | 64 ------ .../components/instance_statistics_count_chart.vue | 206 ------------------- .../components/projects_and_groups_chart.vue | 224 --------------------- .../instance_statistics/components/users_chart.vue | 143 ------------- .../analytics/instance_statistics/constants.js | 5 - .../graphql/fragments/count.fragment.graphql | 4 - .../graphql/queries/groups.query.graphql | 13 -- .../graphql/queries/instance_count.query.graphql | 13 -- .../instance_statistics_count.query.graphql | 34 ---- .../graphql/queries/projects.query.graphql | 13 -- .../graphql/queries/users.query.graphql | 13 -- .../analytics/instance_statistics/index.js | 24 --- .../analytics/instance_statistics/utils.js | 68 ------- .../analytics/usage_trends/components/app.vue | 48 +++++ .../usage_trends/components/charts_config.js | 73 +++++++ .../components/projects_and_groups_chart.vue | 224 +++++++++++++++++++++ .../usage_trends/components/usage_counts.vue | 64 ++++++ .../components/usage_trends_count_chart.vue | 206 +++++++++++++++++++ .../usage_trends/components/users_chart.vue | 143 +++++++++++++ .../analytics/usage_trends/constants.js | 5 + .../graphql/fragments/count.fragment.graphql | 4 + .../graphql/queries/groups.query.graphql | 13 ++ .../graphql/queries/projects.query.graphql | 13 ++ .../graphql/queries/usage_count.query.graphql | 13 ++ .../queries/usage_trends_count.query.graphql | 34 ++++ .../graphql/queries/users.query.graphql | 13 ++ .../javascripts/analytics/usage_trends/index.js | 24 +++ .../javascripts/analytics/usage_trends/utils.js | 68 +++++++ app/assets/javascripts/api.js | 22 ++ .../keep_latest_artifact_checkbox.vue | 18 +- app/assets/javascripts/blob/blob_file_dropzone.js | 4 + app/assets/javascripts/blob_edit/blob_bundle.js | 2 + app/assets/javascripts/boards/boards_util.js | 10 +- .../javascripts/boards/components/board_card.vue | 81 +++++--- .../boards/components/board_card_deprecated.vue | 61 ++++++ .../boards/components/board_card_layout.vue | 98 --------- .../javascripts/boards/components/board_column.vue | 12 +- .../boards/components/board_content.vue | 9 +- .../javascripts/boards/components/board_list.vue | 36 ++-- .../boards/components/board_list_deprecated.vue | 2 +- .../boards/components/boards_selector.vue | 29 +-- .../boards/components/config_toggle.vue | 64 ++++++ app/assets/javascripts/boards/config_toggle.js | 25 ++- app/assets/javascripts/boards/index.js | 2 +- .../boards/mount_multiple_boards_switcher.js | 4 +- app/assets/javascripts/boards/stores/actions.js | 51 ++++- app/assets/javascripts/boards/stores/getters.js | 12 +- .../javascripts/boards/stores/mutation_types.js | 7 +- app/assets/javascripts/boards/stores/mutations.js | 48 ++--- app/assets/javascripts/boards/stores/state.js | 5 +- app/assets/javascripts/clone_panel.js | 1 - .../javascripts/clusters_list/store/actions.js | 2 +- .../commit/pipelines/pipelines_bundle.js | 2 - .../commit/pipelines/pipelines_table.vue | 9 - .../create_cluster/eks_cluster/constants.js | 5 +- .../deprecated_jquery_dropdown/gl_dropdown.js | 43 +++- .../components/design_notes/design_discussion.vue | 4 +- .../diffs/components/diff_discussion_reply.vue | 5 +- app/assets/javascripts/diffs/store/utils.js | 2 +- app/assets/javascripts/editor/constants.js | 3 + .../editor/extensions/editor_lite_webide_ext.js | 164 +++++++++++++++ app/assets/javascripts/experiment_tracking.js | 25 +++ .../feature_flags/components/edit_feature_flag.vue | 2 + app/assets/javascripts/flash.js | 2 +- .../groups/components/invite_members_banner.vue | 4 +- .../ide/components/branches/search_list.vue | 5 +- .../ide/components/file_templates/bar.vue | 4 +- .../ide/components/merge_requests/list.vue | 5 +- app/assets/javascripts/ide/components/nav_form.vue | 25 +-- .../javascripts/ide/components/pipelines/list.vue | 3 +- .../javascripts/ide/components/repo_editor.vue | 81 ++++++-- app/assets/javascripts/ide/components/repo_tab.vue | 46 +++-- .../javascripts/ide/components/repo_tabs.vue | 6 +- app/assets/javascripts/ide/index.js | 1 - .../import_groups/components/import_table.vue | 15 +- .../import_groups/components/import_table_row.vue | 26 ++- .../import_entities/import_groups/index.js | 3 + .../javascripts/incidents_settings/constants.js | 2 +- .../edit/components/jira_trigger_fields.vue | 123 ++++++++++- app/assets/javascripts/integrations/edit/index.js | 2 + .../invite_members/components/group_select.vue | 103 ++++++++++ .../components/invite_group_trigger.vue | 34 ++++ .../components/invite_members_modal.vue | 131 +++++++++--- .../components/invite_members_trigger.vue | 4 +- .../components/members_token_select.vue | 4 +- app/assets/javascripts/invite_members/constants.js | 2 +- .../invite_members/init_invite_group_trigger.js | 20 ++ .../javascripts/issue_show/components/app.vue | 2 +- .../components/fields/description_template.vue | 4 +- .../javascripts/issue_show/components/form.vue | 6 +- app/assets/javascripts/issue_show/stores/index.js | 2 +- .../javascripts/issue_show/utils/parse_data.js | 2 +- .../javascripts/jira_connect/components/app.vue | 6 +- app/assets/javascripts/jira_connect/index.js | 1 + app/assets/javascripts/merge_request_tabs.js | 2 - .../javascripts/monitoring/stores/actions.js | 2 +- .../notes/components/discussion_actions.vue | 4 +- .../components/discussion_reply_placeholder.vue | 29 ++- .../javascripts/notes/components/note_form.vue | 2 +- app/assets/javascripts/notes/stores/utils.js | 4 +- app/assets/javascripts/notifications/constants.js | 4 +- app/assets/javascripts/packages/list/constants.js | 2 +- app/assets/javascripts/packages/shared/utils.js | 2 +- .../pages/admin/instance_statistics/index.js | 3 - .../javascripts/pages/admin/usage_trends/index.js | 3 + app/assets/javascripts/pages/groups/edit/index.js | 3 + .../pages/groups/group_members/index.js | 2 + .../pages/groups/settings/ci_cd/show/index.js | 3 + .../settings/packages_and_registries/index.js | 3 + .../pages/groups/settings/repository/show/index.js | 3 + .../feature_flags_user_lists/edit/index.js | 22 +- .../projects/feature_flags_user_lists/new/index.js | 28 +-- .../pages/projects/project_members/index.js | 2 + .../pages/projects/settings/ci_cd/show/index.js | 3 + .../projects/settings/operations/show/index.js | 5 + .../projects/settings/repository/show/index.js | 3 + .../javascripts/pages/projects/show/index.js | 7 +- .../javascripts/pages/projects/tags/new/index.js | 8 +- app/assets/javascripts/performance/constants.js | 21 ++ .../components/performance_bar_app.vue | 7 + app/assets/javascripts/performance_bar/index.js | 2 + .../components/header/pipeline_editor_header.vue | 42 +++- .../components/header/pipeline_status.vue | 120 +++++++++++ .../graphql/queries/client/pipeline.graphql | 17 ++ .../pipeline_editor/graphql/resolvers.js | 23 +++ .../pipeline_new/components/pipeline_new_form.vue | 2 +- .../pipelines/components/graph/graph_component.vue | 18 +- .../components/graph/graph_component_wrapper.vue | 16 +- .../pipelines/components/graph/utils.js | 2 +- .../pipelines/components/graph_shared/api.js | 8 + .../components/graph_shared/links_inner.vue | 72 ++++++- .../components/graph_shared/links_layer.vue | 5 + .../components/pipelines_list/empty_state.vue | 12 +- .../components/pipelines_list/pipeline_url.vue | 80 ++++---- .../components/pipelines_list/pipelines.vue | 10 - .../components/pipelines_list/pipelines_table.vue | 5 - .../pipelines_list/pipelines_table_row.vue | 10 +- .../pipelines/pipeline_details_bundle.js | 9 +- .../pipelines/pipeline_details_graph.js | 3 +- .../javascripts/pipelines/pipelines_index.js | 4 - app/assets/javascripts/profile/profile.js | 1 + .../javascripts/projects/commits/store/actions.js | 2 +- .../projects/compare/components/app.vue | 8 +- .../charts/components/pipeline_charts.vue | 4 +- .../components/service_desk_root.vue | 2 +- .../javascripts/projects/upload_file_experiment.js | 21 ++ .../components/grouped_test_reports_app.vue | 2 +- .../components/configuration_table.vue | 5 +- .../components/features_constants.js | 11 + app/assets/javascripts/sentry/sentry_config.js | 2 +- app/assets/javascripts/sentry/wrapper.js | 26 --- .../components/subscriptions/subscriptions.vue | 2 + .../components/time_tracking/help_state.vue | 6 +- .../javascripts/static_site_editor/constants.js | 2 +- app/assets/javascripts/tracking.js | 20 +- app/assets/javascripts/user_popovers.js | 26 ++- .../components/states/mr_widget_conflicts.vue | 2 +- .../alert_details/components/alert_details.vue | 4 +- .../alert_details/components/alert_metrics.vue | 2 +- .../components/settings/settings_block.vue | 7 +- .../javascripts/vue_shared/components/tabs/tab.vue | 47 ----- .../javascripts/vue_shared/components/tabs/tabs.js | 76 ------- .../vue_shared/components/tooltip_on_truncate.vue | 7 +- .../components/upload_dropzone/upload_dropzone.vue | 21 +- .../javascripts/vue_shared/directives/tooltip.js | 35 ---- .../vue_shared/security_reports/constants.js | 1 + app/assets/stylesheets/framework.scss | 1 - app/assets/stylesheets/framework/buttons.scss | 1 + app/assets/stylesheets/framework/diffs.scss | 1 - app/assets/stylesheets/framework/editor-lite.scss | 5 + app/assets/stylesheets/framework/filters.scss | 2 +- app/assets/stylesheets/framework/layout.scss | 3 +- app/assets/stylesheets/framework/lists.scss | 6 - app/assets/stylesheets/framework/mixins.scss | 1 - app/assets/stylesheets/framework/tooltips.scss | 6 - app/assets/stylesheets/framework/typography.scss | 8 +- app/assets/stylesheets/framework/variables.scss | 2 +- .../stylesheets/highlight/conflict_colors.scss | 2 - app/assets/stylesheets/mailer_client_specific.scss | 1 - app/assets/stylesheets/page_bundles/ide.scss | 3 +- .../stylesheets/page_bundles/jira_connect.scss | 56 ++---- app/assets/stylesheets/page_bundles/signup.scss | 5 - app/assets/stylesheets/pages/clusters.scss | 1 + app/assets/stylesheets/pages/login.scss | 11 - app/assets/stylesheets/pages/merge_requests.scss | 5 +- app/assets/stylesheets/pages/note_form.scss | 15 +- app/assets/stylesheets/startup/startup-signin.scss | 5 - app/assets/stylesheets/themes/theme_helper.scss | 1 + app/assets/stylesheets/vendors/atwho.scss | 5 +- 192 files changed, 2786 insertions(+), 1797 deletions(-) delete mode 100644 app/assets/javascripts/analytics/instance_statistics/components/app.vue delete mode 100644 app/assets/javascripts/analytics/instance_statistics/components/charts_config.js delete mode 100644 app/assets/javascripts/analytics/instance_statistics/components/instance_counts.vue delete mode 100644 app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue delete mode 100644 app/assets/javascripts/analytics/instance_statistics/components/projects_and_groups_chart.vue delete mode 100644 app/assets/javascripts/analytics/instance_statistics/components/users_chart.vue delete mode 100644 app/assets/javascripts/analytics/instance_statistics/constants.js delete mode 100644 app/assets/javascripts/analytics/instance_statistics/graphql/fragments/count.fragment.graphql delete mode 100644 app/assets/javascripts/analytics/instance_statistics/graphql/queries/groups.query.graphql delete mode 100644 app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_count.query.graphql delete mode 100644 app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql delete mode 100644 app/assets/javascripts/analytics/instance_statistics/graphql/queries/projects.query.graphql delete mode 100644 app/assets/javascripts/analytics/instance_statistics/graphql/queries/users.query.graphql delete mode 100644 app/assets/javascripts/analytics/instance_statistics/index.js delete mode 100644 app/assets/javascripts/analytics/instance_statistics/utils.js create mode 100644 app/assets/javascripts/analytics/usage_trends/components/app.vue create mode 100644 app/assets/javascripts/analytics/usage_trends/components/charts_config.js create mode 100644 app/assets/javascripts/analytics/usage_trends/components/projects_and_groups_chart.vue create mode 100644 app/assets/javascripts/analytics/usage_trends/components/usage_counts.vue create mode 100644 app/assets/javascripts/analytics/usage_trends/components/usage_trends_count_chart.vue create mode 100644 app/assets/javascripts/analytics/usage_trends/components/users_chart.vue create mode 100644 app/assets/javascripts/analytics/usage_trends/constants.js create mode 100644 app/assets/javascripts/analytics/usage_trends/graphql/fragments/count.fragment.graphql create mode 100644 app/assets/javascripts/analytics/usage_trends/graphql/queries/groups.query.graphql create mode 100644 app/assets/javascripts/analytics/usage_trends/graphql/queries/projects.query.graphql create mode 100644 app/assets/javascripts/analytics/usage_trends/graphql/queries/usage_count.query.graphql create mode 100644 app/assets/javascripts/analytics/usage_trends/graphql/queries/usage_trends_count.query.graphql create mode 100644 app/assets/javascripts/analytics/usage_trends/graphql/queries/users.query.graphql create mode 100644 app/assets/javascripts/analytics/usage_trends/index.js create mode 100644 app/assets/javascripts/analytics/usage_trends/utils.js create mode 100644 app/assets/javascripts/boards/components/board_card_deprecated.vue delete mode 100644 app/assets/javascripts/boards/components/board_card_layout.vue create mode 100644 app/assets/javascripts/boards/components/config_toggle.vue create mode 100644 app/assets/javascripts/editor/extensions/editor_lite_webide_ext.js create mode 100644 app/assets/javascripts/experiment_tracking.js create mode 100644 app/assets/javascripts/invite_members/components/group_select.vue create mode 100644 app/assets/javascripts/invite_members/components/invite_group_trigger.vue create mode 100644 app/assets/javascripts/invite_members/init_invite_group_trigger.js delete mode 100644 app/assets/javascripts/pages/admin/instance_statistics/index.js create mode 100644 app/assets/javascripts/pages/admin/usage_trends/index.js create mode 100644 app/assets/javascripts/pipeline_editor/components/header/pipeline_status.vue create mode 100644 app/assets/javascripts/pipeline_editor/graphql/queries/client/pipeline.graphql create mode 100644 app/assets/javascripts/pipelines/components/graph_shared/api.js create mode 100644 app/assets/javascripts/projects/upload_file_experiment.js delete mode 100644 app/assets/javascripts/sentry/wrapper.js delete mode 100644 app/assets/javascripts/vue_shared/components/tabs/tab.vue delete mode 100644 app/assets/javascripts/vue_shared/components/tabs/tabs.js delete mode 100644 app/assets/javascripts/vue_shared/directives/tooltip.js delete mode 100644 app/assets/stylesheets/framework/tooltips.scss (limited to 'app/assets') diff --git a/app/assets/javascripts/admin/users/tabs.js b/app/assets/javascripts/admin/users/tabs.js index 9ada77396c7..cbaab7df4e9 100644 --- a/app/assets/javascripts/admin/users/tabs.js +++ b/app/assets/javascripts/admin/users/tabs.js @@ -1,11 +1,20 @@ +import Api from '~/api'; import { historyPushState } from '~/lib/utils/common_utils'; import { mergeUrlParams } from '~/lib/utils/url_utility'; const COHORTS_PANE = 'cohorts'; +const COHORTS_PANE_TAB_CLICK_EVENT = 'i_analytics_cohorts'; const tabClickHandler = (e) => { const { hash } = e.currentTarget; - const tab = hash === `#${COHORTS_PANE}` ? COHORTS_PANE : null; + + let tab = null; + + if (hash === `#${COHORTS_PANE}`) { + tab = COHORTS_PANE; + Api.trackRedisHllUserEvent(COHORTS_PANE_TAB_CLICK_EVENT); + } + const newUrl = mergeUrlParams({ tab }, window.location.href); historyPushState(newUrl); }; diff --git a/app/assets/javascripts/analytics/instance_statistics/components/app.vue b/app/assets/javascripts/analytics/instance_statistics/components/app.vue deleted file mode 100644 index 3bf41eaa008..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/components/app.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - diff --git a/app/assets/javascripts/analytics/instance_statistics/components/charts_config.js b/app/assets/javascripts/analytics/instance_statistics/components/charts_config.js deleted file mode 100644 index 6fba3c56cfe..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/components/charts_config.js +++ /dev/null @@ -1,87 +0,0 @@ -import { s__, __, sprintf } from '~/locale'; -import query from '../graphql/queries/instance_count.query.graphql'; - -const noDataMessage = s__('InstanceStatistics|No data available.'); - -export default [ - { - loadChartError: sprintf( - s__( - 'InstanceStatistics|Could not load the pipelines chart. Please refresh the page to try again.', - ), - ), - noDataMessage, - chartTitle: s__('InstanceStatistics|Pipelines'), - yAxisTitle: s__('InstanceStatistics|Items'), - xAxisTitle: s__('InstanceStatistics|Month'), - queries: [ - { - query, - title: s__('InstanceStatistics|Pipelines total'), - identifier: 'PIPELINES', - loadError: sprintf( - s__('InstanceStatistics|There was an error fetching the total pipelines'), - ), - }, - { - query, - title: s__('InstanceStatistics|Pipelines succeeded'), - identifier: 'PIPELINES_SUCCEEDED', - loadError: sprintf( - s__('InstanceStatistics|There was an error fetching the successful pipelines'), - ), - }, - { - query, - title: s__('InstanceStatistics|Pipelines failed'), - identifier: 'PIPELINES_FAILED', - loadError: sprintf( - s__('InstanceStatistics|There was an error fetching the failed pipelines'), - ), - }, - { - query, - title: s__('InstanceStatistics|Pipelines canceled'), - identifier: 'PIPELINES_CANCELED', - loadError: sprintf( - s__('InstanceStatistics|There was an error fetching the cancelled pipelines'), - ), - }, - { - query, - title: s__('InstanceStatistics|Pipelines skipped'), - identifier: 'PIPELINES_SKIPPED', - loadError: sprintf( - s__('InstanceStatistics|There was an error fetching the skipped pipelines'), - ), - }, - ], - }, - { - loadChartError: sprintf( - s__( - 'InstanceStatistics|Could not load the issues and merge requests chart. Please refresh the page to try again.', - ), - ), - noDataMessage, - chartTitle: s__('InstanceStatistics|Issues & Merge Requests'), - yAxisTitle: s__('InstanceStatistics|Items'), - xAxisTitle: s__('InstanceStatistics|Month'), - queries: [ - { - query, - title: __('Issues'), - identifier: 'ISSUES', - loadError: sprintf(s__('InstanceStatistics|There was an error fetching the issues')), - }, - { - query, - title: __('Merge requests'), - identifier: 'MERGE_REQUESTS', - loadError: sprintf( - s__('InstanceStatistics|There was an error fetching the merge requests'), - ), - }, - ], - }, -]; diff --git a/app/assets/javascripts/analytics/instance_statistics/components/instance_counts.vue b/app/assets/javascripts/analytics/instance_statistics/components/instance_counts.vue deleted file mode 100644 index f3779ed62e9..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/components/instance_counts.vue +++ /dev/null @@ -1,64 +0,0 @@ - - - diff --git a/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue b/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue deleted file mode 100644 index e2defe0572d..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/components/instance_statistics_count_chart.vue +++ /dev/null @@ -1,206 +0,0 @@ - - diff --git a/app/assets/javascripts/analytics/instance_statistics/components/projects_and_groups_chart.vue b/app/assets/javascripts/analytics/instance_statistics/components/projects_and_groups_chart.vue deleted file mode 100644 index 3ffec90fb68..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/components/projects_and_groups_chart.vue +++ /dev/null @@ -1,224 +0,0 @@ - - diff --git a/app/assets/javascripts/analytics/instance_statistics/components/users_chart.vue b/app/assets/javascripts/analytics/instance_statistics/components/users_chart.vue deleted file mode 100644 index 73940f028a1..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/components/users_chart.vue +++ /dev/null @@ -1,143 +0,0 @@ - - diff --git a/app/assets/javascripts/analytics/instance_statistics/constants.js b/app/assets/javascripts/analytics/instance_statistics/constants.js deleted file mode 100644 index 846c0ef408b..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/constants.js +++ /dev/null @@ -1,5 +0,0 @@ -import { getDateInPast } from '~/lib/utils/datetime_utility'; - -export const TOTAL_DAYS_TO_SHOW = 365; -export const TODAY = new Date(); -export const START_DATE = getDateInPast(TODAY, TOTAL_DAYS_TO_SHOW); diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/fragments/count.fragment.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/fragments/count.fragment.graphql deleted file mode 100644 index 40cef95c2e7..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/graphql/fragments/count.fragment.graphql +++ /dev/null @@ -1,4 +0,0 @@ -fragment Count on InstanceStatisticsMeasurement { - count - recordedAt -} diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/groups.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/groups.query.graphql deleted file mode 100644 index ec56d91ffaa..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/groups.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" -#import "../fragments/count.fragment.graphql" - -query getGroupsCount($first: Int, $after: String) { - groups: instanceStatisticsMeasurements(identifier: GROUPS, first: $first, after: $after) { - nodes { - ...Count - } - pageInfo { - ...PageInfo - } - } -} diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_count.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_count.query.graphql deleted file mode 100644 index dd22a16cd51..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_count.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" -#import "../fragments/count.fragment.graphql" - -query getCount($identifier: MeasurementIdentifier!, $first: Int, $after: String) { - instanceStatisticsMeasurements(identifier: $identifier, first: $first, after: $after) { - nodes { - ...Count - } - pageInfo { - ...PageInfo - } - } -} diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql deleted file mode 100644 index f14c2658674..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/instance_statistics_count.query.graphql +++ /dev/null @@ -1,34 +0,0 @@ -#import "../fragments/count.fragment.graphql" - -query getInstanceCounts { - projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: 1) { - nodes { - ...Count - } - } - groups: instanceStatisticsMeasurements(identifier: GROUPS, first: 1) { - nodes { - ...Count - } - } - users: instanceStatisticsMeasurements(identifier: USERS, first: 1) { - nodes { - ...Count - } - } - issues: instanceStatisticsMeasurements(identifier: ISSUES, first: 1) { - nodes { - ...Count - } - } - mergeRequests: instanceStatisticsMeasurements(identifier: MERGE_REQUESTS, first: 1) { - nodes { - ...Count - } - } - pipelines: instanceStatisticsMeasurements(identifier: PIPELINES, first: 1) { - nodes { - ...Count - } - } -} diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/projects.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/projects.query.graphql deleted file mode 100644 index 0845b703435..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/projects.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" -#import "../fragments/count.fragment.graphql" - -query getProjectsCount($first: Int, $after: String) { - projects: instanceStatisticsMeasurements(identifier: PROJECTS, first: $first, after: $after) { - nodes { - ...Count - } - pageInfo { - ...PageInfo - } - } -} diff --git a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/users.query.graphql b/app/assets/javascripts/analytics/instance_statistics/graphql/queries/users.query.graphql deleted file mode 100644 index 6235e36eb89..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/graphql/queries/users.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" -#import "../fragments/count.fragment.graphql" - -query getUsersCount($first: Int, $after: String) { - users: instanceStatisticsMeasurements(identifier: USERS, first: $first, after: $after) { - nodes { - ...Count - } - pageInfo { - ...PageInfo - } - } -} diff --git a/app/assets/javascripts/analytics/instance_statistics/index.js b/app/assets/javascripts/analytics/instance_statistics/index.js deleted file mode 100644 index 0d7dcf6ace8..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/index.js +++ /dev/null @@ -1,24 +0,0 @@ -import Vue from 'vue'; -import VueApollo from 'vue-apollo'; -import createDefaultClient from '~/lib/graphql'; -import InstanceStatisticsApp from './components/app.vue'; - -Vue.use(VueApollo); - -const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), -}); - -export default () => { - const el = document.getElementById('js-instance-statistics-app'); - - if (!el) return false; - - return new Vue({ - el, - apolloProvider, - render(h) { - return h(InstanceStatisticsApp); - }, - }); -}; diff --git a/app/assets/javascripts/analytics/instance_statistics/utils.js b/app/assets/javascripts/analytics/instance_statistics/utils.js deleted file mode 100644 index 396962ffad6..00000000000 --- a/app/assets/javascripts/analytics/instance_statistics/utils.js +++ /dev/null @@ -1,68 +0,0 @@ -import { masks } from 'dateformat'; -import { get } from 'lodash'; -import { formatDate } from '~/lib/utils/datetime_utility'; - -const { isoDate } = masks; - -/** - * Takes an array of items and returns one item per month with the average of the `count`s from that month - * @param {Array} items - * @param {Number} items[index].count value to be averaged - * @param {String} items[index].recordedAt item dateTime time stamp to be collected into a month - * @param {Object} options - * @param {Object} options.shouldRound an option to specify whether the retuned averages should be rounded - * @return {Array} items collected into [month, average], - * where month is a dateTime string representing the first of the given month - * and average is the average of the count - */ -export function getAverageByMonth(items = [], options = {}) { - const { shouldRound = false } = options; - const itemsMap = items.reduce((memo, item) => { - const { count, recordedAt } = item; - const date = new Date(recordedAt); - const month = formatDate(new Date(date.getFullYear(), date.getMonth(), 1), isoDate); - if (memo[month]) { - const { sum, recordCount } = memo[month]; - return { ...memo, [month]: { sum: sum + count, recordCount: recordCount + 1 } }; - } - - return { ...memo, [month]: { sum: count, recordCount: 1 } }; - }, {}); - - return Object.keys(itemsMap).map((month) => { - const { sum, recordCount } = itemsMap[month]; - const avg = sum / recordCount; - if (shouldRound) { - return [month, Math.round(avg)]; - } - - return [month, avg]; - }); -} - -/** - * Takes an array of instance counts and returns the last item in the list - * @param {Array} arr array of instance counts in the form { count: Number, recordedAt: date String } - * @return {String} the 'recordedAt' value of the earliest item - */ -export const getEarliestDate = (arr = []) => { - const len = arr.length; - return get(arr, `[${len - 1}].recordedAt`, null); -}; - -/** - * Takes an array of queries and produces an object with the query identifier as key - * and a supplied defaultValue as its value - * @param {Array} queries array of chart query configs, - * see ./analytics/instance_statistics/components/charts_config.js - * @param {any} defaultValue value to set each identifier to - * @return {Object} key value pair of the form { queryIdentifier: defaultValue } - */ -export const generateDataKeys = (queries, defaultValue) => - queries.reduce( - (acc, { identifier }) => ({ - ...acc, - [identifier]: defaultValue, - }), - {}, - ); diff --git a/app/assets/javascripts/analytics/usage_trends/components/app.vue b/app/assets/javascripts/analytics/usage_trends/components/app.vue new file mode 100644 index 00000000000..c6436160ea2 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/components/app.vue @@ -0,0 +1,48 @@ + + + diff --git a/app/assets/javascripts/analytics/usage_trends/components/charts_config.js b/app/assets/javascripts/analytics/usage_trends/components/charts_config.js new file mode 100644 index 00000000000..b6b440b710f --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/components/charts_config.js @@ -0,0 +1,73 @@ +import { s__, __, sprintf } from '~/locale'; +import query from '../graphql/queries/usage_count.query.graphql'; + +const noDataMessage = s__('UsageTrends|No data available.'); + +export default [ + { + loadChartError: sprintf( + s__('UsageTrends|Could not load the pipelines chart. Please refresh the page to try again.'), + ), + noDataMessage, + chartTitle: s__('UsageTrends|Pipelines'), + yAxisTitle: s__('UsageTrends|Items'), + xAxisTitle: s__('UsageTrends|Month'), + queries: [ + { + query, + title: s__('UsageTrends|Pipelines total'), + identifier: 'PIPELINES', + loadError: sprintf(s__('UsageTrends|There was an error fetching the total pipelines')), + }, + { + query, + title: s__('UsageTrends|Pipelines succeeded'), + identifier: 'PIPELINES_SUCCEEDED', + loadError: sprintf(s__('UsageTrends|There was an error fetching the successful pipelines')), + }, + { + query, + title: s__('UsageTrends|Pipelines failed'), + identifier: 'PIPELINES_FAILED', + loadError: sprintf(s__('UsageTrends|There was an error fetching the failed pipelines')), + }, + { + query, + title: s__('UsageTrends|Pipelines canceled'), + identifier: 'PIPELINES_CANCELED', + loadError: sprintf(s__('UsageTrends|There was an error fetching the cancelled pipelines')), + }, + { + query, + title: s__('UsageTrends|Pipelines skipped'), + identifier: 'PIPELINES_SKIPPED', + loadError: sprintf(s__('UsageTrends|There was an error fetching the skipped pipelines')), + }, + ], + }, + { + loadChartError: sprintf( + s__( + 'UsageTrends|Could not load the issues and merge requests chart. Please refresh the page to try again.', + ), + ), + noDataMessage, + chartTitle: s__('UsageTrends|Issues & Merge Requests'), + yAxisTitle: s__('UsageTrends|Items'), + xAxisTitle: s__('UsageTrends|Month'), + queries: [ + { + query, + title: __('Issues'), + identifier: 'ISSUES', + loadError: sprintf(s__('UsageTrends|There was an error fetching the issues')), + }, + { + query, + title: __('Merge requests'), + identifier: 'MERGE_REQUESTS', + loadError: sprintf(s__('UsageTrends|There was an error fetching the merge requests')), + }, + ], + }, +]; diff --git a/app/assets/javascripts/analytics/usage_trends/components/projects_and_groups_chart.vue b/app/assets/javascripts/analytics/usage_trends/components/projects_and_groups_chart.vue new file mode 100644 index 00000000000..66aa939938e --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/components/projects_and_groups_chart.vue @@ -0,0 +1,224 @@ + + diff --git a/app/assets/javascripts/analytics/usage_trends/components/usage_counts.vue b/app/assets/javascripts/analytics/usage_trends/components/usage_counts.vue new file mode 100644 index 00000000000..9a0a4f61a74 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/components/usage_counts.vue @@ -0,0 +1,64 @@ + + + diff --git a/app/assets/javascripts/analytics/usage_trends/components/usage_trends_count_chart.vue b/app/assets/javascripts/analytics/usage_trends/components/usage_trends_count_chart.vue new file mode 100644 index 00000000000..8d7761694d1 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/components/usage_trends_count_chart.vue @@ -0,0 +1,206 @@ + + diff --git a/app/assets/javascripts/analytics/usage_trends/components/users_chart.vue b/app/assets/javascripts/analytics/usage_trends/components/users_chart.vue new file mode 100644 index 00000000000..09dfcddcb73 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/components/users_chart.vue @@ -0,0 +1,143 @@ + + diff --git a/app/assets/javascripts/analytics/usage_trends/constants.js b/app/assets/javascripts/analytics/usage_trends/constants.js new file mode 100644 index 00000000000..846c0ef408b --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/constants.js @@ -0,0 +1,5 @@ +import { getDateInPast } from '~/lib/utils/datetime_utility'; + +export const TOTAL_DAYS_TO_SHOW = 365; +export const TODAY = new Date(); +export const START_DATE = getDateInPast(TODAY, TOTAL_DAYS_TO_SHOW); diff --git a/app/assets/javascripts/analytics/usage_trends/graphql/fragments/count.fragment.graphql b/app/assets/javascripts/analytics/usage_trends/graphql/fragments/count.fragment.graphql new file mode 100644 index 00000000000..2bde5973600 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/graphql/fragments/count.fragment.graphql @@ -0,0 +1,4 @@ +fragment Count on UsageTrendsMeasurement { + count + recordedAt +} diff --git a/app/assets/javascripts/analytics/usage_trends/graphql/queries/groups.query.graphql b/app/assets/javascripts/analytics/usage_trends/graphql/queries/groups.query.graphql new file mode 100644 index 00000000000..b1249cc9480 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/graphql/queries/groups.query.graphql @@ -0,0 +1,13 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" +#import "../fragments/count.fragment.graphql" + +query getGroupsCount($first: Int, $after: String) { + groups: usageTrendsMeasurements(identifier: GROUPS, first: $first, after: $after) { + nodes { + ...Count + } + pageInfo { + ...PageInfo + } + } +} diff --git a/app/assets/javascripts/analytics/usage_trends/graphql/queries/projects.query.graphql b/app/assets/javascripts/analytics/usage_trends/graphql/queries/projects.query.graphql new file mode 100644 index 00000000000..2e10b6cce3e --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/graphql/queries/projects.query.graphql @@ -0,0 +1,13 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" +#import "../fragments/count.fragment.graphql" + +query getProjectsCount($first: Int, $after: String) { + projects: usageTrendsMeasurements(identifier: PROJECTS, first: $first, after: $after) { + nodes { + ...Count + } + pageInfo { + ...PageInfo + } + } +} diff --git a/app/assets/javascripts/analytics/usage_trends/graphql/queries/usage_count.query.graphql b/app/assets/javascripts/analytics/usage_trends/graphql/queries/usage_count.query.graphql new file mode 100644 index 00000000000..2a5546efb68 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/graphql/queries/usage_count.query.graphql @@ -0,0 +1,13 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" +#import "../fragments/count.fragment.graphql" + +query getCount($identifier: MeasurementIdentifier!, $first: Int, $after: String) { + usageTrendsMeasurements(identifier: $identifier, first: $first, after: $after) { + nodes { + ...Count + } + pageInfo { + ...PageInfo + } + } +} diff --git a/app/assets/javascripts/analytics/usage_trends/graphql/queries/usage_trends_count.query.graphql b/app/assets/javascripts/analytics/usage_trends/graphql/queries/usage_trends_count.query.graphql new file mode 100644 index 00000000000..8cadcfae380 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/graphql/queries/usage_trends_count.query.graphql @@ -0,0 +1,34 @@ +#import "../fragments/count.fragment.graphql" + +query getInstanceCounts { + projects: usageTrendsMeasurements(identifier: PROJECTS, first: 1) { + nodes { + ...Count + } + } + groups: usageTrendsMeasurements(identifier: GROUPS, first: 1) { + nodes { + ...Count + } + } + users: usageTrendsMeasurements(identifier: USERS, first: 1) { + nodes { + ...Count + } + } + issues: usageTrendsMeasurements(identifier: ISSUES, first: 1) { + nodes { + ...Count + } + } + mergeRequests: usageTrendsMeasurements(identifier: MERGE_REQUESTS, first: 1) { + nodes { + ...Count + } + } + pipelines: usageTrendsMeasurements(identifier: PIPELINES, first: 1) { + nodes { + ...Count + } + } +} diff --git a/app/assets/javascripts/analytics/usage_trends/graphql/queries/users.query.graphql b/app/assets/javascripts/analytics/usage_trends/graphql/queries/users.query.graphql new file mode 100644 index 00000000000..7c02ac49a42 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/graphql/queries/users.query.graphql @@ -0,0 +1,13 @@ +#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" +#import "../fragments/count.fragment.graphql" + +query getUsersCount($first: Int, $after: String) { + users: usageTrendsMeasurements(identifier: USERS, first: $first, after: $after) { + nodes { + ...Count + } + pageInfo { + ...PageInfo + } + } +} diff --git a/app/assets/javascripts/analytics/usage_trends/index.js b/app/assets/javascripts/analytics/usage_trends/index.js new file mode 100644 index 00000000000..d1880b09f15 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/index.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import UsageTrendsApp from './components/app.vue'; + +Vue.use(VueApollo); + +const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), +}); + +export default () => { + const el = document.getElementById('js-usage-trends-app'); + + if (!el) return false; + + return new Vue({ + el, + apolloProvider, + render(h) { + return h(UsageTrendsApp); + }, + }); +}; diff --git a/app/assets/javascripts/analytics/usage_trends/utils.js b/app/assets/javascripts/analytics/usage_trends/utils.js new file mode 100644 index 00000000000..91907877ed6 --- /dev/null +++ b/app/assets/javascripts/analytics/usage_trends/utils.js @@ -0,0 +1,68 @@ +import { masks } from 'dateformat'; +import { get } from 'lodash'; +import { formatDate } from '~/lib/utils/datetime_utility'; + +const { isoDate } = masks; + +/** + * Takes an array of items and returns one item per month with the average of the `count`s from that month + * @param {Array} items + * @param {Number} items[index].count value to be averaged + * @param {String} items[index].recordedAt item dateTime time stamp to be collected into a month + * @param {Object} options + * @param {Object} options.shouldRound an option to specify whether the retuned averages should be rounded + * @return {Array} items collected into [month, average], + * where month is a dateTime string representing the first of the given month + * and average is the average of the count + */ +export function getAverageByMonth(items = [], options = {}) { + const { shouldRound = false } = options; + const itemsMap = items.reduce((memo, item) => { + const { count, recordedAt } = item; + const date = new Date(recordedAt); + const month = formatDate(new Date(date.getFullYear(), date.getMonth(), 1), isoDate); + if (memo[month]) { + const { sum, recordCount } = memo[month]; + return { ...memo, [month]: { sum: sum + count, recordCount: recordCount + 1 } }; + } + + return { ...memo, [month]: { sum: count, recordCount: 1 } }; + }, {}); + + return Object.keys(itemsMap).map((month) => { + const { sum, recordCount } = itemsMap[month]; + const avg = sum / recordCount; + if (shouldRound) { + return [month, Math.round(avg)]; + } + + return [month, avg]; + }); +} + +/** + * Takes an array of usage counts and returns the last item in the list + * @param {Array} arr array of usage counts in the form { count: Number, recordedAt: date String } + * @return {String} the 'recordedAt' value of the earliest item + */ +export const getEarliestDate = (arr = []) => { + const len = arr.length; + return get(arr, `[${len - 1}].recordedAt`, null); +}; + +/** + * Takes an array of queries and produces an object with the query identifier as key + * and a supplied defaultValue as its value + * @param {Array} queries array of chart query configs, + * see ./analytics/usage_trends/components/charts_config.js + * @param {any} defaultValue value to set each identifier to + * @return {Object} key value pair of the form { queryIdentifier: defaultValue } + */ +export const generateDataKeys = (queries, defaultValue) => + queries.reduce( + (acc, { identifier }) => ({ + ...acc, + [identifier]: defaultValue, + }), + {}, + ); diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index c7e6b98a934..a9bb2909c39 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -24,6 +24,7 @@ const Api = { projectPackagesPath: '/api/:version/projects/:id/packages', projectPackagePath: '/api/:version/projects/:id/packages/:package_id', groupProjectsPath: '/api/:version/groups/:id/projects.json', + groupSharePath: '/api/:version/groups/:id/share', projectsPath: '/api/:version/projects.json', projectPath: '/api/:version/projects/:id', forkedProjectsPath: '/api/:version/projects/:id/forks', @@ -39,6 +40,7 @@ const Api = { projectRunnersPath: '/api/:version/projects/:id/runners', projectProtectedBranchesPath: '/api/:version/projects/:id/protected_branches', projectSearchPath: '/api/:version/projects/:id/search', + projectSharePath: '/api/:version/projects/:id/share', projectMilestonesPath: '/api/:version/projects/:id/milestones', projectIssuePath: '/api/:version/projects/:id/issues/:issue_iid', mergeRequestsPath: '/api/:version/merge_requests', @@ -365,6 +367,16 @@ const Api = { }); }, + projectShareWithGroup(id, options = {}) { + const url = Api.buildUrl(Api.projectSharePath).replace(':id', encodeURIComponent(id)); + + return axios.post(url, { + expires_at: options.expires_at, + group_access: options.group_access, + group_id: options.group_id, + }); + }, + projectMilestones(id, params = {}) { const url = Api.buildUrl(Api.projectMilestonesPath).replace(':id', encodeURIComponent(id)); @@ -426,6 +438,16 @@ const Api = { }); }, + groupShareWithGroup(id, options = {}) { + const url = Api.buildUrl(Api.groupSharePath).replace(':id', encodeURIComponent(id)); + + return axios.post(url, { + expires_at: options.expires_at, + group_access: options.group_access, + group_id: options.group_id, + }); + }, + commit(id, sha, params = {}) { const url = Api.buildUrl(this.commitPath) .replace(':id', encodeURIComponent(id)) diff --git a/app/assets/javascripts/artifacts_settings/keep_latest_artifact_checkbox.vue b/app/assets/javascripts/artifacts_settings/keep_latest_artifact_checkbox.vue index 92f1cc8117a..d797469dd53 100644 --- a/app/assets/javascripts/artifacts_settings/keep_latest_artifact_checkbox.vue +++ b/app/assets/javascripts/artifacts_settings/keep_latest_artifact_checkbox.vue @@ -2,7 +2,6 @@ import { GlAlert, GlFormCheckbox, GlLink } from '@gitlab/ui'; import { __ } from '~/locale'; import UpdateKeepLatestArtifactProjectSetting from './graphql/mutations/update_keep_latest_artifact_project_setting.mutation.graphql'; -import GetKeepLatestArtifactApplicationSetting from './graphql/queries/get_keep_latest_artifact_application_setting.query.graphql'; import GetKeepLatestArtifactProjectSetting from './graphql/queries/get_keep_latest_artifact_project_setting.query.graphql'; export default { @@ -14,7 +13,6 @@ export default { enabledHelpText: __( 'The latest artifacts created by jobs in the most recent successful pipeline will be stored.', ), - disabledHelpText: __('This feature is disabled at the instance level.'), helpLinkText: __('More information'), checkboxText: __('Keep artifacts from most recent successful jobs'), }, @@ -46,19 +44,12 @@ export default { this.reportError(this.$options.errors.fetchError); }, }, - projectSettingDisabled: { - query: GetKeepLatestArtifactApplicationSetting, - update(data) { - return !data.ciApplicationSettings?.keepLatestArtifact; - }, - }, }, data() { return { keepLatestArtifact: null, errorMessage: '', isAlertDismissed: false, - projectSettingDisabled: true, }; }, computed: { @@ -66,9 +57,7 @@ export default { return this.errorMessage && !this.isAlertDismissed; }, helpText() { - return this.projectSettingDisabled - ? this.$options.i18n.disabledHelpText - : this.$options.i18n.enabledHelpText; + return this.$options.i18n.enabledHelpText; }, }, methods: { @@ -106,10 +95,7 @@ export default { @dismiss="isAlertDismissed = true" >{{ errorMessage }} - {{ $options.i18n.checkboxText }} {{ $options.i18n.helpLinkText }} diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js b/app/assets/javascripts/blob/blob_file_dropzone.js index e72c5c90986..445602a8765 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js +++ b/app/assets/javascripts/blob/blob_file_dropzone.js @@ -3,6 +3,7 @@ import Dropzone from 'dropzone'; import $ from 'jquery'; import { sprintf, __ } from '~/locale'; +import { trackUploadFileFormSubmitted } from '~/projects/upload_file_experiment'; import { HIDDEN_CLASS } from '../lib/utils/constants'; import csrf from '../lib/utils/csrf'; import { visitUrl } from '../lib/utils/url_utility'; @@ -83,6 +84,9 @@ export default class BlobFileDropzone { submitButton.on('click', (e) => { e.preventDefault(); e.stopPropagation(); + + trackUploadFileFormSubmitted(); + if (dropzone[0].dropzone.getQueuedFiles().length === 0) { // eslint-disable-next-line no-alert alert(__('Please select a file')); diff --git a/app/assets/javascripts/blob_edit/blob_bundle.js b/app/assets/javascripts/blob_edit/blob_bundle.js index 173c82ef9b0..6d9b56b4bb8 100644 --- a/app/assets/javascripts/blob_edit/blob_bundle.js +++ b/app/assets/javascripts/blob_edit/blob_bundle.js @@ -4,6 +4,7 @@ import $ from 'jquery'; import initPopover from '~/blob/suggest_gitlab_ci_yml'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { disableButtonIfEmptyField, setCookie } from '~/lib/utils/common_utils'; +import { initUploadFileTrigger } from '~/projects/upload_file_experiment'; import Tracking from '~/tracking'; import BlobFileDropzone from '../blob/blob_file_dropzone'; import NewCommitForm from '../new_commit_form'; @@ -47,6 +48,7 @@ export const initUploadForm = () => { new NewCommitForm(uploadBlobForm); disableButtonIfEmptyField(uploadBlobForm.find('.js-commit-message'), '.btn-upload-file'); + initUploadFileTrigger(); } }; diff --git a/app/assets/javascripts/boards/boards_util.js b/app/assets/javascripts/boards/boards_util.js index 13ad820477f..cf7e8cb94d1 100644 --- a/app/assets/javascripts/boards/boards_util.js +++ b/app/assets/javascripts/boards/boards_util.js @@ -36,11 +36,11 @@ export function formatIssue(issue) { } export function formatListIssues(listIssues) { - const issues = {}; - let listIssuesCount; + const boardItems = {}; + let listItemsCount; const listData = listIssues.nodes.reduce((map, list) => { - listIssuesCount = list.issues.count; + listItemsCount = list.issues.count; let sortedIssues = list.issues.edges.map((issueNode) => ({ ...issueNode.node, })); @@ -58,14 +58,14 @@ export function formatListIssues(listIssues) { assignees: i.assignees?.nodes || [], }; - issues[id] = listIssue; + boardItems[id] = listIssue; return id; }), }; }, {}); - return { listData, issues, listIssuesCount }; + return { listData, boardItems, listItemsCount }; } export function formatListsPageInfo(lists) { diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index e6009343626..a0fee1b064b 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -1,14 +1,11 @@ diff --git a/app/assets/javascripts/boards/components/board_card_deprecated.vue b/app/assets/javascripts/boards/components/board_card_deprecated.vue new file mode 100644 index 00000000000..e12a2836f67 --- /dev/null +++ b/app/assets/javascripts/boards/components/board_card_deprecated.vue @@ -0,0 +1,61 @@ + + + diff --git a/app/assets/javascripts/boards/components/board_card_layout.vue b/app/assets/javascripts/boards/components/board_card_layout.vue deleted file mode 100644 index 5e3c3702519..00000000000 --- a/app/assets/javascripts/boards/components/board_card_layout.vue +++ /dev/null @@ -1,98 +0,0 @@ - - - diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue index 41b9ee795eb..95a90d7ab11 100644 --- a/app/assets/javascripts/boards/components/board_column.vue +++ b/app/assets/javascripts/boards/components/board_column.vue @@ -32,12 +32,12 @@ export default { }, computed: { ...mapState(['filterParams', 'highlightedLists']), - ...mapGetters(['getIssuesByList']), + ...mapGetters(['getBoardItemsByList']), highlighted() { return this.highlightedLists.includes(this.list.id); }, - listIssues() { - return this.getIssuesByList(this.list.id); + listItems() { + return this.getBoardItemsByList(this.list.id); }, isListDraggable() { return isListDraggable(this.list); @@ -46,7 +46,7 @@ export default { watch: { filterParams: { handler() { - this.fetchIssuesForList({ listId: this.list.id }); + this.fetchItemsForList({ listId: this.list.id }); }, deep: true, immediate: true, @@ -63,7 +63,7 @@ export default { }, }, methods: { - ...mapActions(['fetchIssuesForList']), + ...mapActions(['fetchItemsForList']), }, }; @@ -87,7 +87,7 @@ export default { diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 9b10e7d7db5..6b7e04df7a4 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -11,7 +11,10 @@ import BoardColumnDeprecated from './board_column_deprecated.vue'; export default { components: { - BoardColumn: gon.features?.graphqlBoardLists ? BoardColumn : BoardColumnDeprecated, + BoardColumn: + gon.features?.graphqlBoardLists || gon.features?.epicBoards + ? BoardColumn + : BoardColumnDeprecated, BoardContentSidebar: () => import('ee_component/boards/components/board_content_sidebar.vue'), EpicsSwimlanes: () => import('ee_component/boards/components/epics_swimlanes.vue'), GlAlert, @@ -33,10 +36,10 @@ export default { }, }, computed: { - ...mapState(['boardLists', 'error']), + ...mapState(['boardLists', 'error', 'isEpicBoard']), ...mapGetters(['isSwimlanesOn']), boardListsToUse() { - return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn + return this.glFeatures.graphqlBoardLists || this.isSwimlanesOn || this.isEpicBoard ? sortBy([...Object.values(this.boardLists)], 'position') : this.lists; }, diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue index 7495b1163be..4bd5a530b8c 100644 --- a/app/assets/javascripts/boards/components/board_list.vue +++ b/app/assets/javascripts/boards/components/board_list.vue @@ -12,8 +12,8 @@ import BoardNewIssue from './board_new_issue.vue'; export default { name: 'BoardList', i18n: { - loadingIssues: __('Loading issues'), - loadingMoreissues: __('Loading more issues'), + loading: __('Loading'), + loadingMoreboardItems: __('Loading more'), showingAllIssues: __('Showing all issues'), }, components: { @@ -30,7 +30,7 @@ export default { type: Object, required: true, }, - issues: { + boardItems: { type: Array, required: true, }, @@ -51,11 +51,11 @@ export default { ...mapState(['pageInfoByListId', 'listsFlags']), paginatedIssueText() { return sprintf(__('Showing %{pageSize} of %{total} issues'), { - pageSize: this.issues.length, + pageSize: this.boardItems.length, total: this.list.issuesCount, }); }, - issuesSizeExceedsMax() { + boardItemsSizeExceedsMax() { return this.list.maxIssueCount > 0 && this.list.issuesCount > this.list.maxIssueCount; }, hasNextPage() { @@ -72,7 +72,7 @@ export default { return this.canAdminList ? this.$refs.list.$el : this.$refs.list; }, showingAllIssues() { - return this.issues.length === this.list.issuesCount; + return this.boardItems.length === this.list.issuesCount; }, treeRootWrapper() { return this.canAdminList ? Draggable : 'ul'; @@ -85,14 +85,14 @@ export default { tag: 'ul', 'ghost-class': 'board-card-drag-active', 'data-list-id': this.list.id, - value: this.issues, + value: this.boardItems, }; return this.canAdminList ? options : {}; }, }, watch: { - issues() { + boardItems() { this.$nextTick(() => { this.showCount = this.scrollHeight() > Math.ceil(this.listHeight()); }); @@ -112,7 +112,7 @@ export default { this.listRef.removeEventListener('scroll', this.onScroll); }, methods: { - ...mapActions(['fetchIssuesForList', 'moveIssue']), + ...mapActions(['fetchItemsForList', 'moveIssue']), listHeight() { return this.listRef.getBoundingClientRect().height; }, @@ -126,7 +126,7 @@ export default { this.listRef.scrollTop = 0; }, loadNextPage() { - this.fetchIssuesForList({ listId: this.list.id, fetchNext: true }); + this.fetchItemsForList({ listId: this.list.id, fetchNext: true }); }, toggleForm() { this.showIssueForm = !this.showIssueForm; @@ -201,7 +201,7 @@ export default {
@@ -214,23 +214,27 @@ export default { v-bind="treeRootOptions" :data-board="list.id" :data-board-type="list.listType" - :class="{ 'bg-danger-100': issuesSizeExceedsMax }" + :class="{ 'bg-danger-100': boardItemsSizeExceedsMax }" class="board-list gl-w-full gl-h-full gl-list-style-none gl-mb-0 gl-p-2 js-board-list" data-testid="tree-root-wrapper" @start="handleDragOnStart" @end="handleDragOnEnd" >
  • - + {{ $options.i18n.showingAllIssues }} {{ paginatedIssueText }}
  • diff --git a/app/assets/javascripts/boards/components/board_list_deprecated.vue b/app/assets/javascripts/boards/components/board_list_deprecated.vue index 9b4961d362d..d59fbcc1b31 100644 --- a/app/assets/javascripts/boards/components/board_list_deprecated.vue +++ b/app/assets/javascripts/boards/components/board_list_deprecated.vue @@ -11,7 +11,7 @@ import { sortableEnd, } from '../mixins/sortable_default_options'; import boardsStore from '../stores/boards_store'; -import boardCard from './board_card.vue'; +import boardCard from './board_card_deprecated.vue'; import boardNewIssue from './board_new_issue_deprecated.vue'; // This component is being replaced in favor of './board_list.vue' for GraphQL boards diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue index 2a064aaa885..3bd94631396 100644 --- a/app/assets/javascripts/boards/components/boards_selector.vue +++ b/app/assets/javascripts/boards/components/boards_selector.vue @@ -158,6 +158,18 @@ export default { cancel() { this.showPage(''); }, + boardUpdate(data) { + if (!data?.[this.parentType]) { + return []; + } + return data[this.parentType].boards.edges.map(({ node }) => ({ + id: getIdFromGraphQLId(node.id), + name: node.name, + })); + }, + boardQuery() { + return this.groupId ? groupQuery : projectQuery; + }, loadBoards(toggleDropdown = true) { if (toggleDropdown && this.boards.length > 0) { return; @@ -167,21 +179,14 @@ export default { variables() { return { fullPath: this.fullPath }; }, - query() { - return this.groupId ? groupQuery : projectQuery; - }, + query: this.boardQuery, loadingKey: 'loadingBoards', - update(data) { - if (!data?.[this.parentType]) { - return []; - } - return data[this.parentType].boards.edges.map(({ node }) => ({ - id: getIdFromGraphQLId(node.id), - name: node.name, - })); - }, + update: this.boardUpdate, }); + this.loadRecentBoards(); + }, + loadRecentBoards() { this.loadingRecentBoards = true; // Follow up to fetch recent boards using GraphQL // https://gitlab.com/gitlab-org/gitlab/-/issues/300985 diff --git a/app/assets/javascripts/boards/components/config_toggle.vue b/app/assets/javascripts/boards/components/config_toggle.vue new file mode 100644 index 00000000000..7ec99e51f5b --- /dev/null +++ b/app/assets/javascripts/boards/components/config_toggle.vue @@ -0,0 +1,64 @@ + + + diff --git a/app/assets/javascripts/boards/config_toggle.js b/app/assets/javascripts/boards/config_toggle.js index 2d1ec238274..7f327c5764d 100644 --- a/app/assets/javascripts/boards/config_toggle.js +++ b/app/assets/javascripts/boards/config_toggle.js @@ -1 +1,24 @@ -export default () => {}; +import Vue from 'vue'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import ConfigToggle from './components/config_toggle.vue'; + +export default (boardsStore) => { + const el = document.querySelector('.js-board-config'); + + if (!el) { + return; + } + + gl.boardConfigToggle = new Vue({ + el, + render(h) { + return h(ConfigToggle, { + props: { + boardsStore, + canAdminList: parseBoolean(el.dataset.canAdminList), + hasScope: parseBoolean(el.dataset.hasScope), + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 859295318ed..f9dfa60a59b 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -6,7 +6,6 @@ import 'ee_else_ce/boards/models/issue'; import 'ee_else_ce/boards/models/list'; import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar'; import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown'; -import boardConfigToggle from 'ee_else_ce/boards/config_toggle'; import { setWeightFetchingState, setEpicFetchingState, @@ -40,6 +39,7 @@ import { } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; import sidebarEventHub from '~/sidebar/event_hub'; +import boardConfigToggle from './config_toggle'; import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher'; Vue.use(VueApollo); diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js index fa58af24ba2..7bec0901ed2 100644 --- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js +++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; import { mapGetters } from 'vuex'; -import BoardsSelector from '~/boards/components/boards_selector.vue'; +import BoardsSelector from 'ee_else_ce/boards/components/boards_selector.vue'; import BoardsSelectorDeprecated from '~/boards/components/boards_selector_deprecated.vue'; import store from '~/boards/stores'; import createDefaultClient from '~/lib/graphql'; @@ -51,7 +51,7 @@ export default (params = {}) => { ...mapGetters(['shouldUseGraphQL']), }, render(createElement) { - if (this.shouldUseGraphQL) { + if (this.shouldUseGraphQL || params.isEpicBoard) { return createElement(BoardsSelector, { props: this.boardsSelectorProps, }); diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index a7cf1e9e647..b8d84899782 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -1,6 +1,12 @@ import { pick } from 'lodash'; import boardListsQuery from 'ee_else_ce/boards/graphql/board_lists.query.graphql'; -import { BoardType, ListType, inactiveId, flashAnimationDuration } from '~/boards/constants'; +import { + BoardType, + ListType, + inactiveId, + flashAnimationDuration, + ISSUABLE, +} from '~/boards/constants'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import createGqClient, { fetchPolicies } from '~/lib/graphql'; import { convertObjectPropsToCamelCase, urlParamsToObject } from '~/lib/utils/common_utils'; @@ -79,7 +85,11 @@ export default { } }, - fetchLists: ({ commit, state, dispatch }) => { + fetchLists: ({ dispatch }) => { + dispatch('fetchIssueLists'); + }, + + fetchIssueLists: ({ commit, state, dispatch }) => { const { boardType, filterParams, fullPath, boardId } = state; const variables = { @@ -253,8 +263,8 @@ export default { }); }, - fetchIssuesForList: ({ state, commit }, { listId, fetchNext = false }) => { - commit(types.REQUEST_ISSUES_FOR_LIST, { listId, fetchNext }); + fetchItemsForList: ({ state, commit }, { listId, fetchNext = false }) => { + commit(types.REQUEST_ITEMS_FOR_LIST, { listId, fetchNext }); const { fullPath, boardId, boardType, filterParams } = state; @@ -279,11 +289,11 @@ export default { }) .then(({ data }) => { const { lists } = data[boardType]?.board; - const listIssues = formatListIssues(lists); + const listItems = formatListIssues(lists); const listPageInfo = formatListsPageInfo(lists); - commit(types.RECEIVE_ISSUES_FOR_LIST_SUCCESS, { listIssues, listPageInfo, listId }); + commit(types.RECEIVE_ITEMS_FOR_LIST_SUCCESS, { listItems, listPageInfo, listId }); }) - .catch(() => commit(types.RECEIVE_ISSUES_FOR_LIST_FAILURE, listId)); + .catch(() => commit(types.RECEIVE_ITEMS_FOR_LIST_FAILURE, listId)); }, resetIssues: ({ commit }) => { @@ -294,8 +304,8 @@ export default { { state, commit }, { issueId, issueIid, issuePath, fromListId, toListId, moveBeforeId, moveAfterId }, ) => { - const originalIssue = state.issues[issueId]; - const fromList = state.issuesByListId[fromListId]; + const originalIssue = state.boardItems[issueId]; + const fromList = state.boardItemsByListId[fromListId]; const originalIndex = fromList.indexOf(Number(issueId)); commit(types.MOVE_ISSUE, { originalIssue, fromListId, toListId, moveBeforeId, moveAfterId }); @@ -532,10 +542,17 @@ export default { commit(types.SET_SELECTED_PROJECT, project); }, - toggleBoardItemMultiSelection: ({ commit, state }, boardItem) => { + toggleBoardItemMultiSelection: ({ commit, state, dispatch, getters }, boardItem) => { const { selectedBoardItems } = state; const index = selectedBoardItems.indexOf(boardItem); + // If user already selected an item (activeIssue) without using mult-select, + // include that item in the selection and unset state.ActiveId to hide the sidebar. + if (getters.activeIssue) { + commit(types.ADD_BOARD_ITEM_TO_SELECTION, getters.activeIssue); + dispatch('unsetActiveId'); + } + if (index === -1) { commit(types.ADD_BOARD_ITEM_TO_SELECTION, boardItem); } else { @@ -547,6 +564,20 @@ export default { commit(types.SET_ADD_COLUMN_FORM_VISIBLE, visible); }, + resetBoardItemMultiSelection: ({ commit }) => { + commit(types.RESET_BOARD_ITEM_SELECTION); + }, + + toggleBoardItem: ({ state, dispatch }, { boardItem, sidebarType = ISSUABLE }) => { + dispatch('resetBoardItemMultiSelection'); + + if (boardItem.id === state.activeId) { + dispatch('unsetActiveId'); + } else { + dispatch('setActiveId', { id: boardItem.id, sidebarType }); + } + }, + fetchBacklog: () => { notImplemented(); }, diff --git a/app/assets/javascripts/boards/stores/getters.js b/app/assets/javascripts/boards/stores/getters.js index cab97088bc6..308dbd0f1b0 100644 --- a/app/assets/javascripts/boards/stores/getters.js +++ b/app/assets/javascripts/boards/stores/getters.js @@ -4,17 +4,17 @@ import { inactiveId } from '../constants'; export default { isSidebarOpen: (state) => state.activeId !== inactiveId, isSwimlanesOn: () => false, - getIssueById: (state) => (id) => { - return state.issues[id] || {}; + getBoardItemById: (state) => (id) => { + return state.boardItems[id] || {}; }, - getIssuesByList: (state, getters) => (listId) => { - const listIssueIds = state.issuesByListId[listId] || []; - return listIssueIds.map((id) => getters.getIssueById(id)); + getBoardItemsByList: (state, getters) => (listId) => { + const listItemsIds = state.boardItemsByListId[listId] || []; + return listItemsIds.map((id) => getters.getBoardItemById(id)); }, activeIssue: (state) => { - return state.issues[state.activeId] || {}; + return state.boardItems[state.activeId] || {}; }, groupPathForActiveIssue: (_, getters) => { diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js index a89e961ae2d..4b43cca9675 100644 --- a/app/assets/javascripts/boards/stores/mutation_types.js +++ b/app/assets/javascripts/boards/stores/mutation_types.js @@ -14,9 +14,9 @@ export const MOVE_LIST = 'MOVE_LIST'; export const UPDATE_LIST_FAILURE = 'UPDATE_LIST_FAILURE'; export const REMOVE_LIST = 'REMOVE_LIST'; export const REMOVE_LIST_FAILURE = 'REMOVE_LIST_FAILURE'; -export const REQUEST_ISSUES_FOR_LIST = 'REQUEST_ISSUES_FOR_LIST'; -export const RECEIVE_ISSUES_FOR_LIST_FAILURE = 'RECEIVE_ISSUES_FOR_LIST_FAILURE'; -export const RECEIVE_ISSUES_FOR_LIST_SUCCESS = 'RECEIVE_ISSUES_FOR_LIST_SUCCESS'; +export const REQUEST_ITEMS_FOR_LIST = 'REQUEST_ITEMS_FOR_LIST'; +export const RECEIVE_ITEMS_FOR_LIST_FAILURE = 'RECEIVE_ITEMS_FOR_LIST_FAILURE'; +export const RECEIVE_ITEMS_FOR_LIST_SUCCESS = 'RECEIVE_ITEMS_FOR_LIST_SUCCESS'; export const CREATE_ISSUE_FAILURE = 'CREATE_ISSUE_FAILURE'; export const REQUEST_ADD_ISSUE = 'REQUEST_ADD_ISSUE'; export const RECEIVE_ADD_ISSUE_SUCCESS = 'RECEIVE_ADD_ISSUE_SUCCESS'; @@ -45,3 +45,4 @@ export const REMOVE_BOARD_ITEM_FROM_SELECTION = 'REMOVE_BOARD_ITEM_FROM_SELECTIO export const SET_ADD_COLUMN_FORM_VISIBLE = 'SET_ADD_COLUMN_FORM_VISIBLE'; export const ADD_LIST_TO_HIGHLIGHTED_LISTS = 'ADD_LIST_TO_HIGHLIGHTED_LISTS'; export const REMOVE_LIST_FROM_HIGHLIGHTED_LISTS = 'REMOVE_LIST_FROM_HIGHLIGHTED_LISTS'; +export const RESET_BOARD_ITEM_SELECTION = 'RESET_BOARD_ITEM_SELECTION'; diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index 79c98c3d90c..8246ed8eb09 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -11,13 +11,13 @@ const notImplemented = () => { }; export const removeIssueFromList = ({ state, listId, issueId }) => { - Vue.set(state.issuesByListId, listId, pull(state.issuesByListId[listId], issueId)); + Vue.set(state.boardItemsByListId, listId, pull(state.boardItemsByListId[listId], issueId)); const list = state.boardLists[listId]; Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount - 1 }); }; export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfterId, atIndex }) => { - const listIssues = state.issuesByListId[listId]; + const listIssues = state.boardItemsByListId[listId]; let newIndex = atIndex || 0; if (moveBeforeId) { newIndex = listIssues.indexOf(moveBeforeId) + 1; @@ -25,19 +25,20 @@ export const addIssueToList = ({ state, listId, issueId, moveBeforeId, moveAfter newIndex = listIssues.indexOf(moveAfterId); } listIssues.splice(newIndex, 0, issueId); - Vue.set(state.issuesByListId, listId, listIssues); + Vue.set(state.boardItemsByListId, listId, listIssues); const list = state.boardLists[listId]; Vue.set(state.boardLists, listId, { ...list, issuesCount: list.issuesCount + 1 }); }; export default { [mutationTypes.SET_INITIAL_BOARD_DATA](state, data) { - const { boardType, disabled, boardId, fullPath, boardConfig } = data; + const { boardType, disabled, boardId, fullPath, boardConfig, isEpicBoard } = data; state.boardId = boardId; state.fullPath = fullPath; state.boardType = boardType; state.disabled = disabled; state.boardConfig = boardConfig; + state.isEpicBoard = isEpicBoard; }, [mutationTypes.RECEIVE_BOARD_LISTS_SUCCESS]: (state, lists) => { @@ -103,26 +104,23 @@ export default { state.boardLists = listsBackup; }, - [mutationTypes.REQUEST_ISSUES_FOR_LIST]: (state, { listId, fetchNext }) => { + [mutationTypes.REQUEST_ITEMS_FOR_LIST]: (state, { listId, fetchNext }) => { Vue.set(state.listsFlags, listId, { [fetchNext ? 'isLoadingMore' : 'isLoading']: true }); }, - [mutationTypes.RECEIVE_ISSUES_FOR_LIST_SUCCESS]: ( - state, - { listIssues, listPageInfo, listId }, - ) => { - const { listData, issues } = listIssues; - Vue.set(state, 'issues', { ...state.issues, ...issues }); + [mutationTypes.RECEIVE_ITEMS_FOR_LIST_SUCCESS]: (state, { listItems, listPageInfo, listId }) => { + const { listData, boardItems } = listItems; + Vue.set(state, 'boardItems', { ...state.boardItems, ...boardItems }); Vue.set( - state.issuesByListId, + state.boardItemsByListId, listId, - union(state.issuesByListId[listId] || [], listData[listId]), + union(state.boardItemsByListId[listId] || [], listData[listId]), ); Vue.set(state.pageInfoByListId, listId, listPageInfo[listId]); Vue.set(state.listsFlags, listId, { isLoading: false, isLoadingMore: false }); }, - [mutationTypes.RECEIVE_ISSUES_FOR_LIST_FAILURE]: (state, listId) => { + [mutationTypes.RECEIVE_ITEMS_FOR_LIST_FAILURE]: (state, listId) => { state.error = s__( 'Boards|An error occurred while fetching the board issues. Please reload the page.', ); @@ -130,18 +128,18 @@ export default { }, [mutationTypes.RESET_ISSUES]: (state) => { - Object.keys(state.issuesByListId).forEach((listId) => { - Vue.set(state.issuesByListId, listId, []); + Object.keys(state.boardItemsByListId).forEach((listId) => { + Vue.set(state.boardItemsByListId, listId, []); }); }, [mutationTypes.UPDATE_ISSUE_BY_ID]: (state, { issueId, prop, value }) => { - if (!state.issues[issueId]) { + if (!state.boardItems[issueId]) { /* eslint-disable-next-line @gitlab/require-i18n-strings */ throw new Error('No issue found.'); } - Vue.set(state.issues[issueId], prop, value); + Vue.set(state.boardItems[issueId], prop, value); }, [mutationTypes.SET_ASSIGNEE_LOADING](state, isLoading) { @@ -168,7 +166,7 @@ export default { const toList = state.boardLists[toListId]; const issue = moveIssueListHelper(originalIssue, fromList, toList); - Vue.set(state.issues, issue.id, issue); + Vue.set(state.boardItems, issue.id, issue); removeIssueFromList({ state, listId: fromListId, issueId: issue.id }); addIssueToList({ state, listId: toListId, issueId: issue.id, moveBeforeId, moveAfterId }); @@ -176,7 +174,7 @@ export default { [mutationTypes.MOVE_ISSUE_SUCCESS]: (state, { issue }) => { const issueId = getIdFromGraphQLId(issue.id); - Vue.set(state.issues, issueId, formatIssue({ ...issue, id: issueId })); + Vue.set(state.boardItems, issueId, formatIssue({ ...issue, id: issueId })); }, [mutationTypes.MOVE_ISSUE_FAILURE]: ( @@ -184,7 +182,7 @@ export default { { originalIssue, fromListId, toListId, originalIndex }, ) => { state.error = s__('Boards|An error occurred while moving the issue. Please try again.'); - Vue.set(state.issues, originalIssue.id, originalIssue); + Vue.set(state.boardItems, originalIssue.id, originalIssue); removeIssueFromList({ state, listId: toListId, issueId: originalIssue.id }); addIssueToList({ state, @@ -217,7 +215,7 @@ export default { issueId: issue.id, atIndex: position, }); - Vue.set(state.issues, issue.id, issue); + Vue.set(state.boardItems, issue.id, issue); }, [mutationTypes.ADD_ISSUE_TO_LIST_FAILURE]: (state, { list, issueId }) => { @@ -227,7 +225,7 @@ export default { [mutationTypes.REMOVE_ISSUE_FROM_LIST]: (state, { list, issue }) => { removeIssueFromList({ state, listId: list.id, issueId: issue.id }); - Vue.delete(state.issues, issue.id); + Vue.delete(state.boardItems, issue.id); }, [mutationTypes.SET_CURRENT_PAGE]: () => { @@ -282,4 +280,8 @@ export default { [mutationTypes.REMOVE_LIST_FROM_HIGHLIGHTED_LISTS]: (state, listId) => { state.highlightedLists = state.highlightedLists.filter((id) => id !== listId); }, + + [mutationTypes.RESET_BOARD_ITEM_SELECTION]: (state) => { + state.selectedBoardItems = []; + }, }; diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js index 91544d6c9c5..85d92589d30 100644 --- a/app/assets/javascripts/boards/stores/state.js +++ b/app/assets/javascripts/boards/stores/state.js @@ -2,16 +2,17 @@ import { inactiveId } from '~/boards/constants'; export default () => ({ boardType: null, + fullPath: null, disabled: false, isShowingLabels: true, activeId: inactiveId, sidebarType: '', boardLists: {}, listsFlags: {}, - issuesByListId: {}, + boardItemsByListId: {}, isSettingAssignees: false, pageInfoByListId: {}, - issues: {}, + boardItems: {}, filterParams: {}, boardConfig: {}, labels: [], diff --git a/app/assets/javascripts/clone_panel.js b/app/assets/javascripts/clone_panel.js index c9fae8f17a4..ec831a77bde 100644 --- a/app/assets/javascripts/clone_panel.js +++ b/app/assets/javascripts/clone_panel.js @@ -15,7 +15,6 @@ export default function initClonePanel() { } $('a', $cloneOptions).on('click', (e) => { - e.preventDefault(); const $this = $(e.currentTarget); const url = $this.attr('href'); if (url && (url.startsWith('vscode://') || url.startsWith('xcode://'))) { diff --git a/app/assets/javascripts/clusters_list/store/actions.js b/app/assets/javascripts/clusters_list/store/actions.js index 45d3e0cbc23..40a86a1e58c 100644 --- a/app/assets/javascripts/clusters_list/store/actions.js +++ b/app/assets/javascripts/clusters_list/store/actions.js @@ -1,9 +1,9 @@ +import * as Sentry from '@sentry/browser'; import { deprecatedCreateFlash as flash } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import Poll from '~/lib/utils/poll'; import { __ } from '~/locale'; -import * as Sentry from '~/sentry/wrapper'; import { MAX_REQUESTS } from '../constants'; import * as types from './mutation_types'; diff --git a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js index 920ffde3e32..2e050c066f1 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_bundle.js +++ b/app/assets/javascripts/commit/pipelines/pipelines_bundle.js @@ -31,10 +31,8 @@ export default () => { return createElement(CommitPipelinesTable, { props: { endpoint: pipelineTableViewEl.dataset.endpoint, - helpPagePath: pipelineTableViewEl.dataset.helpPagePath, emptyStateSvgPath: pipelineTableViewEl.dataset.emptyStateSvgPath, errorStateSvgPath: pipelineTableViewEl.dataset.errorStateSvgPath, - autoDevopsHelpPath: pipelineTableViewEl.dataset.helpAutoDevopsPath, }, }); }, diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index 787152d00ef..81d96333f7a 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -25,14 +25,6 @@ export default { type: String, required: true, }, - helpPagePath: { - type: String, - required: true, - }, - autoDevopsHelpPath: { - type: String, - required: true, - }, errorStateSvgPath: { type: String, required: true, @@ -212,7 +204,6 @@ export default { diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index 87b4f33c216..b37a75eb2a3 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -283,7 +283,7 @@ export function addContextLines(options) { * Trims the first char of the `richText` property when it's either a space or a diff symbol. * @param {Object} line * @returns {Object} - * @deprecated + * @deprecated Use `line.rich_text = line.rich_text ? line.rich_text.replace(/^[+ -]/, '') : undefined;` instead!. For more information, see https://gitlab.com/gitlab-org/gitlab/-/issues/299329 */ export function trimFirstCharOfLineContent(line = {}) { // eslint-disable-next-line no-param-reassign diff --git a/app/assets/javascripts/editor/constants.js b/app/assets/javascripts/editor/constants.js index d9e6a6c13e2..c991316dda2 100644 --- a/app/assets/javascripts/editor/constants.js +++ b/app/assets/javascripts/editor/constants.js @@ -16,6 +16,9 @@ export const EDITOR_READY_EVENT = 'editor-ready'; export const EDITOR_TYPE_CODE = 'vs.editor.ICodeEditor'; export const EDITOR_TYPE_DIFF = 'vs.editor.IDiffEditor'; +export const EDITOR_CODE_INSTANCE_FN = 'createInstance'; +export const EDITOR_DIFF_INSTANCE_FN = 'createDiffInstance'; + // // EXTENSIONS' CONSTANTS // diff --git a/app/assets/javascripts/editor/extensions/editor_lite_webide_ext.js b/app/assets/javascripts/editor/extensions/editor_lite_webide_ext.js new file mode 100644 index 00000000000..83b0386d470 --- /dev/null +++ b/app/assets/javascripts/editor/extensions/editor_lite_webide_ext.js @@ -0,0 +1,164 @@ +import { debounce } from 'lodash'; +import { KeyCode, KeyMod, Range } from 'monaco-editor'; +import { EDITOR_TYPE_DIFF } from '~/editor/constants'; +import { EditorLiteExtension } from '~/editor/extensions/editor_lite_extension_base'; +import Disposable from '~/ide/lib/common/disposable'; +import { editorOptions } from '~/ide/lib/editor_options'; +import keymap from '~/ide/lib/keymap.json'; + +const isDiffEditorType = (instance) => { + return instance.getEditorType() === EDITOR_TYPE_DIFF; +}; + +export const UPDATE_DIMENSIONS_DELAY = 200; + +export class EditorWebIdeExtension extends EditorLiteExtension { + constructor({ instance, modelManager, ...options } = {}) { + super({ + instance, + ...options, + modelManager, + disposable: new Disposable(), + debouncedUpdate: debounce(() => { + instance.updateDimensions(); + }, UPDATE_DIMENSIONS_DELAY), + }); + + window.addEventListener('resize', instance.debouncedUpdate, false); + + instance.onDidDispose(() => { + window.removeEventListener('resize', instance.debouncedUpdate); + + // catch any potential errors with disposing the error + // this is mainly for tests caused by elements not existing + try { + instance.disposable.dispose(); + } catch (e) { + if (process.env.NODE_ENV !== 'test') { + // eslint-disable-next-line no-console + console.error(e); + } + } + }); + + EditorWebIdeExtension.addActions(instance); + } + + static addActions(instance) { + const { store } = instance; + const getKeyCode = (key) => { + const monacoKeyMod = key.indexOf('KEY_') === 0; + + return monacoKeyMod ? KeyCode[key] : KeyMod[key]; + }; + + keymap.forEach((command) => { + const { bindings, id, label, action } = command; + + const keybindings = bindings.map((binding) => { + const keys = binding.split('+'); + + // eslint-disable-next-line no-bitwise + return keys.length > 1 ? getKeyCode(keys[0]) | getKeyCode(keys[1]) : getKeyCode(keys[0]); + }); + + instance.addAction({ + id, + label, + keybindings, + run() { + store.dispatch(action.name, action.params); + return null; + }, + }); + }); + } + + createModel(file, head = null) { + return this.modelManager.addModel(file, head); + } + + attachModel(model) { + if (isDiffEditorType(this)) { + this.setModel({ + original: model.getOriginalModel(), + modified: model.getModel(), + }); + + return; + } + + this.setModel(model.getModel()); + + this.updateOptions( + editorOptions.reduce((acc, obj) => { + Object.keys(obj).forEach((key) => { + Object.assign(acc, { + [key]: obj[key](model), + }); + }); + return acc; + }, {}), + ); + } + + attachMergeRequestModel(model) { + this.setModel({ + original: model.getBaseModel(), + modified: model.getModel(), + }); + } + + updateDimensions() { + this.layout(); + this.updateDiffView(); + } + + setPos({ lineNumber, column }) { + this.revealPositionInCenter({ + lineNumber, + column, + }); + this.setPosition({ + lineNumber, + column, + }); + } + + onPositionChange(cb) { + if (!this.onDidChangeCursorPosition) { + return; + } + + this.disposable.add(this.onDidChangeCursorPosition((e) => cb(this, e))); + } + + updateDiffView() { + if (!isDiffEditorType(this)) { + return; + } + + this.updateOptions({ + renderSideBySide: EditorWebIdeExtension.renderSideBySide(this.getDomNode()), + }); + } + + replaceSelectedText(text) { + let selection = this.getSelection(); + const range = new Range( + selection.startLineNumber, + selection.startColumn, + selection.endLineNumber, + selection.endColumn, + ); + + this.executeEdits('', [{ range, text }]); + + selection = this.getSelection(); + this.setPosition({ lineNumber: selection.endLineNumber, column: selection.endColumn }); + } + + static renderSideBySide(domElement) { + return domElement.offsetWidth >= 700; + } +} diff --git a/app/assets/javascripts/experiment_tracking.js b/app/assets/javascripts/experiment_tracking.js new file mode 100644 index 00000000000..4611af3f857 --- /dev/null +++ b/app/assets/javascripts/experiment_tracking.js @@ -0,0 +1,25 @@ +import { get } from 'lodash'; +import Tracking from '~/tracking'; + +const TRACKING_CONTEXT_SCHEMA = 'iglu:com.gitlab/gitlab_experiment/jsonschema/1-0-0'; + +export default class ExperimentTracking { + constructor(experimentName, { label } = {}) { + this.label = label; + this.experimentData = get(window, ['gon', 'global', 'experiment', experimentName]); + } + + event(action) { + if (!this.experimentData) { + return false; + } + + return Tracking.event(document.body.dataset.page, action, { + label: this.label, + context: { + schema: TRACKING_CONTEXT_SCHEMA, + data: this.experimentData, + }, + }); + } +} diff --git a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue index b1e60066e11..e7f4b51c964 100644 --- a/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue +++ b/app/assets/javascripts/feature_flags/components/edit_feature_flag.vue @@ -86,6 +86,8 @@ export default { data-track-event="click_button" data-track-label="feature_flag_toggle" class="gl-mr-4" + :label="__('Feature flag status')" + label-position="hidden" @change="toggleActive" />

    {{ title }}

    diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js index d14af53746e..d26a6bc5f6b 100644 --- a/app/assets/javascripts/flash.js +++ b/app/assets/javascripts/flash.js @@ -1,5 +1,5 @@ +import * as Sentry from '@sentry/browser'; import { escape } from 'lodash'; -import * as Sentry from '~/sentry/wrapper'; import { spriteIcon } from './lib/utils/common_utils'; const FLASH_TYPES = { diff --git a/app/assets/javascripts/groups/components/invite_members_banner.vue b/app/assets/javascripts/groups/components/invite_members_banner.vue index 81c5e3ce85d..747cea6a46e 100644 --- a/app/assets/javascripts/groups/components/invite_members_banner.vue +++ b/app/assets/javascripts/groups/components/invite_members_banner.vue @@ -35,7 +35,9 @@ export default { this.track(this.$options.dismissEvent); }, trackOnShow() { - if (!this.isDismissed) this.track(this.$options.displayEvent); + this.$nextTick(() => { + if (!this.isDismissed) this.track(this.$options.displayEvent); + }); }, addTrackingAttributesToButton() { if (this.$refs.banner === undefined) return; diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue index 1ae7cf9339d..62e93335a20 100644 --- a/app/assets/javascripts/ide/components/branches/search_list.vue +++ b/app/assets/javascripts/ide/components/branches/search_list.vue @@ -57,7 +57,10 @@ export default {