summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-11 12:09:55 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-11 12:09:55 +0000
commitbd27a42f5497d66db227aaa5978e11c0fe072105 (patch)
tree2dae237465c4f240371b866e0918575a3d7a7c1c /app
parente184bc1abfe4fe4fef8c25c0d2ccb4c0063e7d5e (diff)
downloadgitlab-ce-bd27a42f5497d66db227aaa5978e11c0fe072105.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/flash.js58
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue249
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_header.vue209
-rw-r--r--app/assets/javascripts/packages/details/components/composer_installation.vue59
-rw-r--r--app/assets/javascripts/packages/details/components/installation_commands.vue2
-rw-r--r--app/assets/javascripts/packages/details/constants.js4
-rw-r--r--app/assets/javascripts/packages/details/store/getters.js9
-rw-r--r--app/controllers/import/gitea_controller.rb10
-rw-r--r--app/controllers/import/github_controller.rb76
-rw-r--r--app/helpers/packages_helper.rb4
-rw-r--r--app/policies/personal_access_token_policy.rb2
-rw-r--r--app/services/import/github_service.rb2
-rw-r--r--app/views/projects/packages/packages/show.html.haml2
13 files changed, 493 insertions, 193 deletions
diff --git a/app/assets/javascripts/flash.js b/app/assets/javascripts/flash.js
index 74c00d21535..262e7c4e412 100644
--- a/app/assets/javascripts/flash.js
+++ b/app/assets/javascripts/flash.js
@@ -1,3 +1,4 @@
+import * as Sentry from '@sentry/browser';
import { escape } from 'lodash';
import { spriteIcon } from './lib/utils/common_utils';
@@ -109,8 +110,65 @@ const createFlash = function createFlash(
return flashContainer;
};
+/*
+ * Flash banner supports different types of Flash configurations
+ * along with ability to provide actionConfig which can be used to show
+ * additional action or link on banner next to message
+ *
+ * @param {Object} options Options to control the flash message
+ * @param {String} options.message Flash message text
+ * @param {String} options.type Type of Flash, it can be `notice`, `success`, `warning` or `alert` (default)
+ * @param {Object} options.parent Reference to parent element under which Flash needs to appear
+ * @param {Object} options.actonConfig Map of config to show action on banner
+ * @param {String} href URL to which action config should point to (default: '#')
+ * @param {String} title Title of action
+ * @param {Function} clickHandler Method to call when action is clicked on
+ * @param {Boolean} options.fadeTransition Boolean to determine whether to fade the alert out
+ * @param {Boolean} options.captureError Boolean to determine whether to send error to sentry
+ * @param {Object} options.error Error to be captured in sentry
+ */
+const newCreateFlash = function newCreateFlash({
+ message,
+ type = FLASH_TYPES.ALERT,
+ parent = document,
+ actionConfig = null,
+ fadeTransition = true,
+ addBodyClass = false,
+ captureError = false,
+ error = null,
+}) {
+ const flashContainer = parent.querySelector('.flash-container');
+
+ if (!flashContainer) return null;
+
+ flashContainer.innerHTML = createFlashEl(message, type);
+
+ const flashEl = flashContainer.querySelector(`.flash-${type}`);
+
+ if (actionConfig) {
+ flashEl.insertAdjacentHTML('beforeend', createAction(actionConfig));
+
+ if (actionConfig.clickHandler) {
+ flashEl
+ .querySelector('.flash-action')
+ .addEventListener('click', e => actionConfig.clickHandler(e));
+ }
+ }
+
+ removeFlashClickListener(flashEl, fadeTransition);
+
+ flashContainer.classList.add('gl-display-block');
+
+ if (addBodyClass) document.body.classList.add('flash-shown');
+
+ if (captureError && error) Sentry.captureException(error);
+
+ return flashContainer;
+};
+
export {
createFlash as default,
+ newCreateFlash,
createFlashEl,
createAction,
hideFlash,
diff --git a/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue
new file mode 100644
index 00000000000..54586c67fef
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/dashboard_actions_menu.vue
@@ -0,0 +1,249 @@
+<script>
+import { mapState, mapGetters, mapActions } from 'vuex';
+import {
+ GlDeprecatedButton,
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownItem,
+ GlModal,
+ GlIcon,
+ GlModalDirective,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
+import DuplicateDashboardModal from './duplicate_dashboard_modal.vue';
+import CreateDashboardModal from './create_dashboard_modal.vue';
+import { s__ } from '~/locale';
+import invalidUrl from '~/lib/utils/invalid_url';
+import { redirectTo } from '~/lib/utils/url_utility';
+import TrackEventDirective from '~/vue_shared/directives/track_event';
+import { getAddMetricTrackingOptions } from '../utils';
+
+export default {
+ components: {
+ GlDeprecatedButton,
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownItem,
+ GlModal,
+ GlIcon,
+ DuplicateDashboardModal,
+ CreateDashboardModal,
+ CustomMetricsFormFields,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ GlTooltip: GlTooltipDirective,
+ TrackEvent: TrackEventDirective,
+ },
+ props: {
+ addingMetricsAvailable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ customMetricsPath: {
+ type: String,
+ required: false,
+ default: invalidUrl,
+ },
+ validateQueryPath: {
+ type: String,
+ required: false,
+ default: invalidUrl,
+ },
+ defaultBranch: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return { customMetricsFormIsValid: null };
+ },
+ computed: {
+ ...mapState('monitoringDashboard', [
+ 'projectPath',
+ 'isUpdatingStarredValue',
+ 'addDashboardDocumentationPath',
+ ]),
+ ...mapGetters('monitoringDashboard', ['selectedDashboard']),
+ isOutOfTheBoxDashboard() {
+ return this.selectedDashboard?.out_of_the_box_dashboard;
+ },
+ isMenuItemEnabled() {
+ return {
+ createDashboard: Boolean(this.projectPath),
+ editDashboard: this.selectedDashboard?.can_edit,
+ };
+ },
+ isMenuItemShown() {
+ return {
+ duplicateDashboard: this.isOutOfTheBoxDashboard,
+ };
+ },
+ },
+ methods: {
+ ...mapActions('monitoringDashboard', ['toggleStarredValue']),
+ setFormValidity(isValid) {
+ this.customMetricsFormIsValid = isValid;
+ },
+ hideAddMetricModal() {
+ this.$refs.addMetricModal.hide();
+ },
+ getAddMetricTrackingOptions,
+ submitCustomMetricsForm() {
+ this.$refs.customMetricsForm.submit();
+ },
+ selectDashboard(dashboard) {
+ // Once the sidebar See metrics link is updated to the new URL,
+ // this sort of hardcoding will not be necessary.
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/229277
+ const baseURL = `${this.projectPath}/-/metrics`;
+ const dashboardPath = encodeURIComponent(
+ dashboard.out_of_the_box_dashboard ? dashboard.path : dashboard.display_name,
+ );
+ redirectTo(`${baseURL}/${dashboardPath}`);
+ },
+ },
+
+ modalIds: {
+ addMetric: 'addMetric',
+ createDashboard: 'createDashboard',
+ duplicateDashboard: 'duplicateDashboard',
+ },
+ i18n: {
+ actionsMenu: s__('Metrics|More actions'),
+ duplicateDashboard: s__('Metrics|Duplicate current dashboard'),
+ starDashboard: s__('Metrics|Star dashboard'),
+ unstarDashboard: s__('Metrics|Unstar dashboard'),
+ addMetric: s__('Metrics|Add metric'),
+ editDashboardInfo: s__('Metrics|Duplicate this dashboard to edit dashboard YAML'),
+ editDashboard: s__('Metrics|Edit dashboard YAML'),
+ createDashboard: s__('Metrics|Create new dashboard'),
+ },
+};
+</script>
+
+<template>
+ <gl-new-dropdown
+ v-gl-tooltip
+ data-testid="actions-menu"
+ data-qa-selector="actions_menu_dropdown"
+ right
+ no-caret
+ toggle-class="gl-px-3!"
+ :title="$options.i18n.actionsMenu"
+ >
+ <template #button-content>
+ <gl-icon class="gl-mr-0!" name="ellipsis_v" />
+ </template>
+
+ <template v-if="addingMetricsAvailable">
+ <gl-new-dropdown-item
+ v-gl-modal="$options.modalIds.addMetric"
+ data-qa-selector="add_metric_button"
+ data-testid="add-metric-item"
+ >
+ {{ $options.i18n.addMetric }}
+ </gl-new-dropdown-item>
+ <gl-modal
+ ref="addMetricModal"
+ :modal-id="$options.modalIds.addMetric"
+ :title="$options.i18n.addMetric"
+ data-testid="add-metric-modal"
+ >
+ <form ref="customMetricsForm" :action="customMetricsPath" method="post">
+ <custom-metrics-form-fields
+ :validate-query-path="validateQueryPath"
+ form-operation="post"
+ @formValidation="setFormValidity"
+ />
+ </form>
+ <div slot="modal-footer">
+ <gl-deprecated-button @click="hideAddMetricModal">
+ {{ __('Cancel') }}
+ </gl-deprecated-button>
+ <gl-deprecated-button
+ v-track-event="getAddMetricTrackingOptions()"
+ data-testid="add-metric-modal-submit-button"
+ :disabled="!customMetricsFormIsValid"
+ variant="success"
+ @click="submitCustomMetricsForm"
+ >
+ {{ __('Save changes') }}
+ </gl-deprecated-button>
+ </div>
+ </gl-modal>
+ </template>
+
+ <gl-new-dropdown-item
+ v-if="isMenuItemEnabled.editDashboard"
+ :href="selectedDashboard ? selectedDashboard.project_blob_path : null"
+ data-qa-selector="edit_dashboard_button_enabled"
+ data-testid="edit-dashboard-item-enabled"
+ >
+ {{ $options.i18n.editDashboard }}
+ </gl-new-dropdown-item>
+
+ <!--
+ wrapper for tooltip as button can be `disabled`
+ https://bootstrap-vue.org/docs/components/tooltip#disabled-elements
+ -->
+ <div v-else v-gl-tooltip :title="$options.i18n.editDashboardInfo">
+ <gl-new-dropdown-item
+ :alt="$options.i18n.editDashboardInfo"
+ :href="selectedDashboard ? selectedDashboard.project_blob_path : null"
+ data-testid="edit-dashboard-item-disabled"
+ disabled
+ class="gl-cursor-not-allowed"
+ >
+ <span class="gl-text-gray-400">{{ $options.i18n.editDashboard }}</span>
+ </gl-new-dropdown-item>
+ </div>
+
+ <template v-if="isMenuItemShown.duplicateDashboard">
+ <gl-new-dropdown-item
+ v-gl-modal="$options.modalIds.duplicateDashboard"
+ data-testid="duplicate-dashboard-item"
+ >
+ {{ $options.i18n.duplicateDashboard }}
+ </gl-new-dropdown-item>
+
+ <duplicate-dashboard-modal
+ :default-branch="defaultBranch"
+ :modal-id="$options.modalIds.duplicateDashboard"
+ data-testid="duplicate-dashboard-modal"
+ @dashboardDuplicated="selectDashboard"
+ />
+ </template>
+
+ <gl-new-dropdown-item
+ v-if="selectedDashboard"
+ data-testid="star-dashboard-item"
+ :disabled="isUpdatingStarredValue"
+ @click="toggleStarredValue()"
+ >
+ {{ selectedDashboard.starred ? $options.i18n.unstarDashboard : $options.i18n.starDashboard }}
+ </gl-new-dropdown-item>
+
+ <gl-new-dropdown-divider />
+
+ <gl-new-dropdown-item
+ v-gl-modal="$options.modalIds.createDashboard"
+ data-testid="create-dashboard-item"
+ :disabled="!isMenuItemEnabled.createDashboard"
+ :class="{ 'monitoring-actions-item-disabled': !isMenuItemEnabled.createDashboard }"
+ >
+ {{ $options.i18n.createDashboard }}
+ </gl-new-dropdown-item>
+
+ <template v-if="isMenuItemEnabled.createDashboard">
+ <create-dashboard-modal
+ data-testid="create-dashboard-modal"
+ :add-dashboard-documentation-path="addDashboardDocumentationPath"
+ :modal-id="$options.modalIds.createDashboard"
+ :project-path="projectPath"
+ />
+ </template>
+ </gl-new-dropdown>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue
index a7e23be98b3..1c921548ce7 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_header.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue
@@ -7,17 +7,11 @@ import {
GlDeprecatedDropdownItem,
GlDeprecatedDropdownHeader,
GlDeprecatedDropdownDivider,
- GlNewDropdown,
- GlNewDropdownDivider,
- GlNewDropdownItem,
- GlModal,
GlLoadingIcon,
GlSearchBoxByType,
GlModalDirective,
GlTooltipDirective,
} from '@gitlab/ui';
-import { s__ } from '~/locale';
-import CustomMetricsFormFields from '~/custom_metrics/components/custom_metrics_form_fields.vue';
import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import Icon from '~/vue_shared/components/icon.vue';
@@ -25,11 +19,9 @@ import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_p
import DashboardsDropdown from './dashboards_dropdown.vue';
import RefreshButton from './refresh_button.vue';
-import CreateDashboardModal from './create_dashboard_modal.vue';
-import DuplicateDashboardModal from './duplicate_dashboard_modal.vue';
+import ActionsMenu from './dashboard_actions_menu.vue';
-import TrackEventDirective from '~/vue_shared/directives/track_event';
-import { getAddMetricTrackingOptions, timeRangeToUrl } from '../utils';
+import { timeRangeToUrl } from '../utils';
import { timeRanges } from '~/vue_shared/constants';
import { timezones } from '../format_date';
@@ -42,23 +34,17 @@ export default {
GlDeprecatedDropdownItem,
GlDeprecatedDropdownHeader,
GlDeprecatedDropdownDivider,
- GlNewDropdown,
- GlNewDropdownDivider,
- GlNewDropdownItem,
GlSearchBoxByType,
- GlModal,
- CustomMetricsFormFields,
DateTimePicker,
DashboardsDropdown,
RefreshButton,
- DuplicateDashboardModal,
- CreateDashboardModal,
+
+ ActionsMenu,
},
directives: {
GlModal: GlModalDirective,
GlTooltip: GlTooltipDirective,
- TrackEvent: TrackEventDirective,
},
props: {
defaultBranch: {
@@ -94,29 +80,19 @@ export default {
required: true,
},
},
- data() {
- return {
- formIsValid: null,
- };
- },
computed: {
...mapState('monitoringDashboard', [
'emptyState',
'environmentsLoading',
'currentEnvironmentName',
- 'isUpdatingStarredValue',
'dashboardTimezone',
'projectPath',
'canAccessOperationsSettings',
'operationsSettingsPath',
'currentDashboard',
- 'addDashboardDocumentationPath',
'externalDashboardUrl',
]),
...mapGetters('monitoringDashboard', ['selectedDashboard', 'filteredEnvironments']),
- isOutOfTheBoxDashboard() {
- return this.selectedDashboard?.out_of_the_box_dashboard;
- },
shouldShowEmptyState() {
return Boolean(this.emptyState);
},
@@ -130,7 +106,7 @@ export default {
// Custom metrics only avaialble on system dashboards because
// they are stored in the database. This can be improved. See:
// https://gitlab.com/gitlab-org/gitlab/-/issues/28241
- this.selectedDashboard?.system_dashboard
+ this.selectedDashboard?.out_of_the_box_dashboard
);
},
showRearrangePanelsBtn() {
@@ -139,15 +115,12 @@ export default {
displayUtc() {
return this.dashboardTimezone === timezones.UTC;
},
- shouldShowActionsMenu() {
- return Boolean(this.projectPath);
- },
shouldShowSettingsButton() {
return this.canAccessOperationsSettings && this.operationsSettingsPath;
},
},
methods: {
- ...mapActions('monitoringDashboard', ['filterEnvironments', 'toggleStarredValue']),
+ ...mapActions('monitoringDashboard', ['filterEnvironments']),
selectDashboard(dashboard) {
// Once the sidebar See metrics link is updated to the new URL,
// this sort of hardcoding will not be necessary.
@@ -171,16 +144,6 @@ export default {
toggleRearrangingPanels() {
this.$emit('setRearrangingPanels', !this.isRearrangingPanels);
},
- setFormValidity(isValid) {
- this.formIsValid = isValid;
- },
- hideAddMetricModal() {
- this.$refs.addMetricModal.hide();
- },
- getAddMetricTrackingOptions,
- submitCustomMetricsForm() {
- this.$refs.customMetricsForm.submit();
- },
getEnvironmentPath(environment) {
// Once the sidebar See metrics link is updated to the new URL,
// this sort of hardcoding will not be necessary.
@@ -193,16 +156,6 @@ export default {
return mergeUrlParams({ environment }, url);
},
},
- modalIds: {
- addMetric: 'addMetric',
- createDashboard: 'createDashboard',
- duplicateDashboard: 'duplicateDashboard',
- },
- i18n: {
- starDashboard: s__('Metrics|Star dashboard'),
- unstarDashboard: s__('Metrics|Unstar dashboard'),
- addMetric: s__('Metrics|Add metric'),
- },
timeRanges,
};
</script>
@@ -280,29 +233,6 @@ export default {
<div class="flex-grow-1"></div>
<div class="d-sm-flex">
- <div v-if="selectedDashboard" class="mb-2 mr-2 d-flex">
- <!--
- wrapper for tooltip as button can be `disabled`
- https://bootstrap-vue.org/docs/components/tooltip#disabled-elements
- -->
- <div
- v-gl-tooltip
- class="flex-grow-1"
- :title="
- selectedDashboard.starred ? $options.i18n.unstarDashboard : $options.i18n.starDashboard
- "
- >
- <gl-button
- ref="toggleStarBtn"
- class="w-100"
- :disabled="isUpdatingStarredValue"
- variant="default"
- :icon="selectedDashboard.starred ? 'star' : 'star-o'"
- @click="toggleStarredValue()"
- />
- </div>
- </div>
-
<div v-if="showRearrangePanelsBtn" class="mb-2 mr-2 d-flex">
<gl-button
:pressed="isRearrangingPanels"
@@ -313,58 +243,6 @@ export default {
{{ __('Arrange charts') }}
</gl-button>
</div>
- <div v-if="addingMetricsAvailable" class="mb-2 mr-2 d-flex d-sm-block">
- <gl-button
- ref="addMetricBtn"
- v-gl-modal="$options.modalIds.addMetric"
- variant="default"
- data-qa-selector="add_metric_button"
- class="flex-grow-1"
- >
- {{ $options.i18n.addMetric }}
- </gl-button>
- <gl-modal
- ref="addMetricModal"
- :modal-id="$options.modalIds.addMetric"
- :title="$options.i18n.addMetric"
- >
- <form ref="customMetricsForm" :action="customMetricsPath" method="post">
- <custom-metrics-form-fields
- :validate-query-path="validateQueryPath"
- form-operation="post"
- @formValidation="setFormValidity"
- />
- </form>
- <div slot="modal-footer">
- <gl-button @click="hideAddMetricModal">
- {{ __('Cancel') }}
- </gl-button>
- <gl-button
- ref="submitCustomMetricsFormBtn"
- v-track-event="getAddMetricTrackingOptions()"
- :disabled="!formIsValid"
- variant="success"
- category="primary"
- @click="submitCustomMetricsForm"
- >
- {{ __('Save changes') }}
- </gl-button>
- </div>
- </gl-modal>
- </div>
-
- <div
- v-if="selectedDashboard && selectedDashboard.can_edit"
- class="mb-2 mr-2 d-flex d-sm-block"
- >
- <gl-button
- class="flex-grow-1 js-edit-link"
- :href="selectedDashboard.project_blob_path"
- data-qa-selector="edit_dashboard_button"
- >
- {{ __('Edit dashboard') }}
- </gl-button>
- </div>
<div
v-if="externalDashboardUrl && externalDashboardUrl.length"
@@ -382,65 +260,28 @@ export default {
</gl-button>
</div>
- <!-- This separator should be displayed only if at least one of the action menu or settings button are displayed -->
- <span
- v-if="shouldShowActionsMenu || shouldShowSettingsButton"
- aria-hidden="true"
- class="gl-pl-3 border-left gl-mb-3 d-none d-sm-block"
- ></span>
+ <div class="gl-mb-3 gl-mr-3 d-flex d-sm-block">
+ <actions-menu
+ :adding-metrics-available="addingMetricsAvailable"
+ :custom-metrics-path="customMetricsPath"
+ :validate-query-path="validateQueryPath"
+ :default-branch="defaultBranch"
+ />
+ </div>
- <div v-if="shouldShowActionsMenu" class="gl-mb-3 gl-mr-3 d-flex d-sm-block">
- <gl-new-dropdown
- v-gl-tooltip
- right
- class="gl-flex-grow-1"
- data-testid="actions-menu"
- data-qa-selector="actions_menu_dropdown"
- :title="s__('Metrics|Create dashboard')"
- :icon="'plus-square'"
- >
- <gl-new-dropdown-item
- v-gl-modal="$options.modalIds.createDashboard"
- data-testid="action-create-dashboard"
- >{{ s__('Metrics|Create new dashboard') }}</gl-new-dropdown-item
- >
+ <template v-if="shouldShowSettingsButton">
+ <span aria-hidden="true" class="gl-pl-3 border-left gl-mb-3 d-none d-sm-block"></span>
- <create-dashboard-modal
- data-testid="create-dashboard-modal"
- :add-dashboard-documentation-path="addDashboardDocumentationPath"
- :modal-id="$options.modalIds.createDashboard"
- :project-path="projectPath"
+ <div class="mb-2 mr-2 d-flex d-sm-block">
+ <gl-button
+ v-gl-tooltip
+ data-testid="metrics-settings-button"
+ icon="settings"
+ :href="operationsSettingsPath"
+ :title="s__('Metrics|Metrics Settings')"
/>
-
- <template v-if="isOutOfTheBoxDashboard">
- <gl-new-dropdown-divider />
-
- <gl-new-dropdown-item
- ref="duplicateDashboardItem"
- v-gl-modal="$options.modalIds.duplicateDashboard"
- data-testid="action-duplicate-dashboard"
- >
- {{ s__('Metrics|Duplicate current dashboard') }}
- </gl-new-dropdown-item>
-
- <duplicate-dashboard-modal
- :default-branch="defaultBranch"
- :modal-id="$options.modalIds.duplicateDashboard"
- @dashboardDuplicated="selectDashboard"
- />
- </template>
- </gl-new-dropdown>
- </div>
-
- <div v-if="shouldShowSettingsButton" class="mb-2 mr-2 d-flex d-sm-block">
- <gl-button
- v-gl-tooltip
- data-testid="metrics-settings-button"
- icon="settings"
- :href="operationsSettingsPath"
- :title="s__('Metrics|Metrics Settings')"
- />
- </div>
+ </div>
+ </template>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/packages/details/components/composer_installation.vue b/app/assets/javascripts/packages/details/components/composer_installation.vue
new file mode 100644
index 00000000000..c295995935f
--- /dev/null
+++ b/app/assets/javascripts/packages/details/components/composer_installation.vue
@@ -0,0 +1,59 @@
+<script>
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import CodeInstruction from './code_instruction.vue';
+import { TrackingActions } from '../constants';
+import { mapGetters, mapState } from 'vuex';
+
+export default {
+ name: 'ComposerInstallation',
+ components: {
+ CodeInstruction,
+ GlLink,
+ GlSprintf,
+ },
+ computed: {
+ ...mapState(['composerHelpPath']),
+ ...mapGetters(['composerRegistryInclude', 'composerPackageInclude']),
+ },
+ i18n: {
+ registryInclude: s__('PackageRegistry|composer.json registry include'),
+ copyRegistryInclude: s__('PackageRegistry|Copy registry include'),
+ packageInclude: s__('PackageRegistry|composer.json require package include'),
+ copyPackageInclude: s__('PackageRegistry|Copy require package include'),
+ infoLine: s__(
+ 'PackageRegistry|For more information on Composer packages in GitLab, %{linkStart}see the documentation.%{linkEnd}',
+ ),
+ },
+ trackingActions: { ...TrackingActions },
+};
+</script>
+
+<template>
+ <div>
+ <p class="gl-mt-3 gl-font-weight-bold" data-testid="registry-include-title">
+ {{ $options.i18n.registryInclude }}
+ </p>
+ <code-instruction
+ :instruction="composerRegistryInclude"
+ :copy-text="$options.i18n.copyRegistryInclude"
+ :tracking-action="$options.trackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND"
+ />
+
+ <p class="gl-mt-3 gl-font-weight-bold" data-testid="package-include-title">
+ {{ $options.i18n.packageInclude }}
+ </p>
+ <code-instruction
+ :instruction="composerPackageInclude"
+ :copy-text="$options.i18n.copyPackageInclude"
+ :tracking-action="$options.trackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND"
+ />
+ <span data-testid="help-text">
+ <gl-sprintf :message="$options.i18n.infoLine">
+ <template #link="{ content }">
+ <gl-link :href="composerHelpPath" target="_blank">{{ content }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/packages/details/components/installation_commands.vue b/app/assets/javascripts/packages/details/components/installation_commands.vue
index 8ed1c0f267f..219e72df9dc 100644
--- a/app/assets/javascripts/packages/details/components/installation_commands.vue
+++ b/app/assets/javascripts/packages/details/components/installation_commands.vue
@@ -4,6 +4,7 @@ import MavenInstallation from './maven_installation.vue';
import NpmInstallation from './npm_installation.vue';
import NugetInstallation from './nuget_installation.vue';
import PypiInstallation from './pypi_installation.vue';
+import ComposerInstallation from './composer_installation.vue';
import { PackageType } from '../../shared/constants';
export default {
@@ -14,6 +15,7 @@ export default {
[PackageType.NPM]: NpmInstallation,
[PackageType.NUGET]: NugetInstallation,
[PackageType.PYPI]: PypiInstallation,
+ [PackageType.COMPOSER]: ComposerInstallation,
},
props: {
packageEntity: {
diff --git a/app/assets/javascripts/packages/details/constants.js b/app/assets/javascripts/packages/details/constants.js
index 88469656eb2..c6e1b388132 100644
--- a/app/assets/javascripts/packages/details/constants.js
+++ b/app/assets/javascripts/packages/details/constants.js
@@ -7,6 +7,7 @@ export const TrackingLabels = {
NPM_INSTALLATION: 'npm_installation',
NUGET_INSTALLATION: 'nuget_installation',
PYPI_INSTALLATION: 'pypi_installation',
+ COMPOSER_INSTALLATION: 'composer_installation',
};
export const TrackingActions = {
@@ -31,6 +32,9 @@ export const TrackingActions = {
COPY_PIP_INSTALL_COMMAND: 'copy_pip_install_command',
COPY_PYPI_SETUP_COMMAND: 'copy_pypi_setup_command',
+
+ COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND: 'copy_composer_registry_include_command',
+ COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND: 'copy_composer_package_include_command',
};
export const NpmManager = {
diff --git a/app/assets/javascripts/packages/details/store/getters.js b/app/assets/javascripts/packages/details/store/getters.js
index bcf74713f03..77dc24ff169 100644
--- a/app/assets/javascripts/packages/details/store/getters.js
+++ b/app/assets/javascripts/packages/details/store/getters.js
@@ -104,3 +104,12 @@ export const pypiSetupCommand = ({ pypiSetupPath }) => `[gitlab]
repository = ${pypiSetupPath}
username = __token__
password = <your personal access token>`;
+
+export const composerRegistryInclude = ({ composerPath }) => {
+ const base = { type: 'composer', url: composerPath };
+ return JSON.stringify(base);
+};
+export const composerPackageInclude = ({ packageEntity }) => {
+ const base = { package_name: packageEntity.name };
+ return JSON.stringify(base);
+};
diff --git a/app/controllers/import/gitea_controller.rb b/app/controllers/import/gitea_controller.rb
index efeff8439e4..4785a71b8a1 100644
--- a/app/controllers/import/gitea_controller.rb
+++ b/app/controllers/import/gitea_controller.rb
@@ -54,6 +54,16 @@ class Import::GiteaController < Import::GithubController
end
end
+ override :client_repos
+ def client_repos
+ @client_repos ||= filtered(client.repos)
+ end
+
+ override :client
+ def client
+ @client ||= Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
+ end
+
override :client_options
def client_options
{ host: provider_url, api_version: 'v1' }
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index ac6b8c06d66..29fe34f0734 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -10,6 +10,9 @@ class Import::GithubController < Import::BaseController
before_action :provider_auth, only: [:status, :realtime_changes, :create]
before_action :expire_etag_cache, only: [:status, :create]
+ OAuthConfigMissingError = Class.new(StandardError)
+
+ rescue_from OAuthConfigMissingError, with: :missing_oauth_config
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
rescue_from Octokit::TooManyRequests, with: :provider_rate_limit
@@ -22,7 +25,7 @@ class Import::GithubController < Import::BaseController
end
def callback
- session[access_token_key] = client.get_token(params[:code])
+ session[access_token_key] = get_token(params[:code])
redirect_to status_import_url
end
@@ -77,9 +80,7 @@ class Import::GithubController < Import::BaseController
override :provider_url
def provider_url
strong_memoize(:provider_url) do
- provider = Gitlab::Auth::OAuth::Provider.config_for('github')
-
- provider&.dig('url').presence || 'https://github.com'
+ oauth_config&.dig('url').presence || 'https://github.com'
end
end
@@ -104,11 +105,66 @@ class Import::GithubController < Import::BaseController
end
def client
- @client ||= Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
+ @client ||= if Feature.enabled?(:remove_legacy_github_client)
+ Gitlab::GithubImport::Client.new(session[access_token_key])
+ else
+ Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
+ end
end
def client_repos
- @client_repos ||= filtered(client.repos)
+ @client_repos ||= if Feature.enabled?(:remove_legacy_github_client)
+ filtered(concatenated_repos)
+ else
+ filtered(client.repos)
+ end
+ end
+
+ def concatenated_repos
+ return [] unless client.respond_to?(:each_page)
+
+ client.each_page(:repos).flat_map(&:objects)
+ end
+
+ def oauth_client
+ raise OAuthConfigMissingError unless oauth_config
+
+ @oauth_client ||= ::OAuth2::Client.new(
+ oauth_config.app_id,
+ oauth_config.app_secret,
+ oauth_options.merge(ssl: { verify: oauth_config['verify_ssl'] })
+ )
+ end
+
+ def oauth_config
+ @oauth_config ||= Gitlab::Auth::OAuth::Provider.config_for('github')
+ end
+
+ def oauth_options
+ if oauth_config
+ oauth_config.dig('args', 'client_options').deep_symbolize_keys
+ else
+ OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys
+ end
+ end
+
+ def authorize_url
+ if Feature.enabled?(:remove_legacy_github_client)
+ oauth_client.auth_code.authorize_url(
+ redirect_uri: callback_import_url,
+ scope: 'repo, user, user:email'
+ )
+ else
+ client.authorize_url(callback_import_url)
+ end
+ end
+
+ def get_token(code)
+ if Feature.enabled?(:remove_legacy_github_client)
+ oauth_client.auth_code.get_token(code).token
+ else
+ client.get_token(code)
+ end
end
def verify_import_enabled
@@ -116,7 +172,7 @@ class Import::GithubController < Import::BaseController
end
def go_to_provider_for_permissions
- redirect_to client.authorize_url(callback_import_url)
+ redirect_to authorize_url
end
def import_enabled?
@@ -152,6 +208,12 @@ class Import::GithubController < Import::BaseController
alert: _("GitHub API rate limit exceeded. Try again after %{reset_time}") % { reset_time: reset_time }
end
+ def missing_oauth_config
+ session[access_token_key] = nil
+ redirect_to new_import_url,
+ alert: _('Missing OAuth configuration for GitHub.')
+ end
+
def access_token_key
:"#{provider_name}_access_token"
end
diff --git a/app/helpers/packages_helper.rb b/app/helpers/packages_helper.rb
index a0434284ce6..e6ecc403a88 100644
--- a/app/helpers/packages_helper.rb
+++ b/app/helpers/packages_helper.rb
@@ -30,6 +30,10 @@ module PackagesHelper
full_url.sub!('://', '://__token__:<your_personal_token>@')
end
+ def composer_registry_url(group_id)
+ expose_url(api_v4_group___packages_composer_packages_path(id: group_id, format: '.json'))
+ end
+
def packages_coming_soon_enabled?(resource)
::Feature.enabled?(:packages_coming_soon, resource) && ::Gitlab.dev_env_or_com?
end
diff --git a/app/policies/personal_access_token_policy.rb b/app/policies/personal_access_token_policy.rb
index aa87550fd6b..1e5404b7822 100644
--- a/app/policies/personal_access_token_policy.rb
+++ b/app/policies/personal_access_token_policy.rb
@@ -3,7 +3,7 @@
class PersonalAccessTokenPolicy < BasePolicy
condition(:is_owner) { user && subject.user_id == user.id }
- rule { is_owner | admin & ~blocked }.policy do
+ rule { (is_owner | admin) & ~blocked }.policy do
enable :read_token
enable :revoke_token
end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
index 0cf17568c78..a2923b1e4f9 100644
--- a/app/services/import/github_service.rb
+++ b/app/services/import/github_service.rb
@@ -33,7 +33,7 @@ module Import
end
def repo
- @repo ||= client.repo(params[:repo_id].to_i)
+ @repo ||= client.repository(params[:repo_id].to_i)
end
def project_name
diff --git a/app/views/projects/packages/packages/show.html.haml b/app/views/projects/packages/packages/show.html.haml
index 2f547a4811f..a66ae466d9d 100644
--- a/app/views/projects/packages/packages/show.html.haml
+++ b/app/views/projects/packages/packages/show.html.haml
@@ -20,4 +20,6 @@
pypi_path: pypi_registry_url(@project.id),
pypi_setup_path: package_registry_project_url(@project.id, :pypi),
pypi_help_path: help_page_path('user/packages/pypi_repository/index'),
+ composer_path: composer_registry_url(@project&.group&.id),
+ composer_help_path: help_page_path('user/packages/composer_repository/index'),
project_name: @project.name} }