diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-18 18:10:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-01-18 18:10:42 +0300 |
commit | bfc7eec58ee891178d2a81c424f6c1de22feae5f (patch) | |
tree | 7cf9e2f76befc383e99d28d397ecec4bb9e8d2c9 /app | |
parent | f23a9a17ed6237c346d2e9210c6841e319e8d030 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
27 files changed, 304 insertions, 265 deletions
diff --git a/app/assets/javascripts/boards/components/board_card_layout.vue b/app/assets/javascripts/boards/components/board_card_layout.vue index 8b0265237ba..0a2301394c1 100644 --- a/app/assets/javascripts/boards/components/board_card_layout.vue +++ b/app/assets/javascripts/boards/components/board_card_layout.vue @@ -1,13 +1,17 @@ <script> +import { mapActions, mapGetters } from 'vuex'; import IssueCardInner from './issue_card_inner.vue'; import IssueCardInnerDeprecated from './issue_card_inner_deprecated.vue'; import boardsStore from '../stores/boards_store'; +import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import { ISSUABLE } from '~/boards/constants'; export default { name: 'BoardCardLayout', components: { IssueCardInner: gon.features?.graphqlBoardLists ? IssueCardInner : IssueCardInnerDeprecated, }, + mixins: [glFeatureFlagMixin()], props: { list: { type: Object, @@ -42,11 +46,13 @@ export default { }; }, computed: { + ...mapGetters(['isSwimlanesOn']), multiSelectVisible() { return this.multiSelect.list.findIndex((issue) => issue.id === this.issue.id) > -1; }, }, methods: { + ...mapActions(['setActiveId']), mouseDown() { this.showDetail = true; }, @@ -57,6 +63,11 @@ export default { // Don't do anything if this happened on a no trigger element if (e.target.classList.contains('js-no-trigger')) return; + if (this.glFeatures.graphqlBoardLists || this.isSwimlanesOn) { + this.setActiveId({ id: this.issue.id, sidebarType: ISSUABLE }); + return; + } + const isMultiSelect = e.ctrlKey || e.metaKey; if (this.showDetail || isMultiSelect) { diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 2d62dc0fa4b..19254343208 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -109,14 +109,14 @@ export default { /> </component> - <template v-else> - <epics-swimlanes - ref="swimlanes" - :lists="boardListsToUse" - :can-admin-list="canAdminList" - :disabled="disabled" - /> - <board-content-sidebar /> - </template> + <epics-swimlanes + v-else + ref="swimlanes" + :lists="boardListsToUse" + :can-admin-list="canAdminList" + :disabled="disabled" + /> + + <board-content-sidebar v-if="isSwimlanesOn || glFeatures.graphqlBoardLists" /> </div> </template> diff --git a/app/assets/javascripts/environments/components/canary_deployment_callout.vue b/app/assets/javascripts/environments/components/canary_deployment_callout.vue deleted file mode 100644 index a5c0d78524b..00000000000 --- a/app/assets/javascripts/environments/components/canary_deployment_callout.vue +++ /dev/null @@ -1,68 +0,0 @@ -<script> -import { GlButton, GlLink, GlIcon } from '@gitlab/ui'; -import PersistentUserCallout from '~/persistent_user_callout'; - -export default { - components: { - GlButton, - GlLink, - GlIcon, - }, - props: { - canaryDeploymentFeatureId: { - type: String, - required: true, - }, - userCalloutsPath: { - type: String, - required: true, - }, - lockPromotionSvgPath: { - type: String, - required: true, - }, - helpCanaryDeploymentsPath: { - type: String, - required: true, - }, - }, - mounted() { - const callout = this.$refs['canary-deployment-callout']; - PersistentUserCallout.factory(callout); - }, -}; -</script> - -<template> - <div - ref="canary-deployment-callout" - class="p-3 canary-deployment-callout" - :data-dismiss-endpoint="userCalloutsPath" - :data-feature-id="canaryDeploymentFeatureId" - > - <img class="canary-deployment-callout-lock pr-3" :src="lockPromotionSvgPath" /> - - <div class="pl-3"> - <p class="font-weight-bold mb-1"> - {{ __('Upgrade plan to unlock Canary Deployments feature') }} - </p> - - <p class="canary-deployment-callout-message"> - {{ - __( - 'Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application.', - ) - }} - <gl-link :href="helpCanaryDeploymentsPath">{{ __('Read more') }}</gl-link> - </p> - - <gl-button href="https://about.gitlab.com/sales/" category="secondary" variant="info">{{ - __('Contact sales to upgrade') - }}</gl-button> - </div> - - <div class="ml-auto pr-2 canary-deployment-callout-close js-close"> - <gl-icon name="close" /> - </div> - </div> -</template> diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue index e7697f14802..c6b34fecbb7 100644 --- a/app/assets/javascripts/environments/components/container.vue +++ b/app/assets/javascripts/environments/components/container.vue @@ -10,11 +10,6 @@ export default { GlLoadingIcon, }, props: { - canaryDeploymentFeatureId: { - type: String, - required: false, - default: null, - }, isLoading: { type: Boolean, required: true, @@ -46,11 +41,6 @@ export default { required: false, default: '', }, - showCanaryDeploymentCallout: { - type: Boolean, - required: false, - default: false, - }, userCalloutsPath: { type: String, required: false, @@ -75,8 +65,6 @@ export default { <environment-table :environments="environments" :can-read-environment="canReadEnvironment" - :canary-deployment-feature-id="canaryDeploymentFeatureId" - :show-canary-deployment-callout="showCanaryDeploymentCallout" :user-callouts-path="userCalloutsPath" :lock-promotion-svg-path="lockPromotionSvgPath" :help-canary-deployments-path="helpCanaryDeploymentsPath" diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index 13228873f44..6f68c6e864a 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -39,11 +39,6 @@ export default { type: String, required: true, }, - canaryDeploymentFeatureId: { - type: String, - required: false, - default: '', - }, canCreateEnvironment: { type: Boolean, required: true, @@ -75,11 +70,6 @@ export default { required: false, default: '', }, - showCanaryDeploymentCallout: { - type: Boolean, - required: false, - default: false, - }, userCalloutsPath: { type: String, required: false, @@ -205,8 +195,6 @@ export default { :environments="state.environments" :pagination="state.paginationInformation" :can-read-environment="canReadEnvironment" - :canary-deployment-feature-id="canaryDeploymentFeatureId" - :show-canary-deployment-callout="showCanaryDeploymentCallout" :user-callouts-path="userCalloutsPath" :lock-promotion-svg-path="lockPromotionSvgPath" :help-canary-deployments-path="helpCanaryDeploymentsPath" diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index ff183e51395..bbb56ca6f26 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -8,14 +8,12 @@ import { s__ } from '~/locale'; import EnvironmentItem from './environment_item.vue'; import DeployBoard from './deploy_board.vue'; import CanaryUpdateModal from './canary_update_modal.vue'; -import CanaryDeploymentCallout from './canary_deployment_callout.vue'; export default { components: { EnvironmentItem, GlLoadingIcon, DeployBoard, - CanaryDeploymentCallout, EnvironmentAlert: () => import('ee_component/environments/components/environment_alert.vue'), CanaryUpdateModal, }, @@ -35,11 +33,6 @@ export default { required: false, default: false, }, - canaryDeploymentFeatureId: { - type: String, - required: false, - default: '', - }, helpCanaryDeploymentsPath: { type: String, required: false, @@ -50,11 +43,6 @@ export default { required: false, default: '', }, - showCanaryDeploymentCallout: { - type: Boolean, - required: false, - default: false, - }, userCalloutsPath: { type: String, required: false, @@ -123,9 +111,6 @@ export default { shouldRenderFolderContent(env) { return env.isFolder && env.isOpen && env.children && env.children.length > 0; }, - shouldShowCanaryCallout(env) { - return env.showCanaryCallout && this.showCanaryDeploymentCallout; - }, shouldRenderAlert(env) { return env?.has_opened_alert; }, @@ -243,17 +228,6 @@ export default { </div> </template> </template> - - <template v-if="shouldShowCanaryCallout(model)"> - <canary-deployment-callout - :key="`canary-promo-${i}`" - :canary-deployment-feature-id="canaryDeploymentFeatureId" - :user-callouts-path="userCalloutsPath" - :lock-promotion-svg-path="lockPromotionSvgPath" - :help-canary-deployments-path="helpCanaryDeploymentsPath" - :data-js-canary-promo-key="i" - /> - </template> </template> </div> </template> diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js index 6c547c3713a..e4726412f99 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js +++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js @@ -1,6 +1,5 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import canaryCalloutMixin from '../mixins/canary_callout_mixin'; import environmentsFolderApp from './environments_folder_view.vue'; import { parseBoolean } from '../../lib/utils/common_utils'; import Translate from '../../vue_shared/translate'; @@ -21,7 +20,6 @@ export default () => { components: { environmentsFolderApp, }, - mixins: [canaryCalloutMixin], apolloProvider, provide: { projectPath: el.dataset.projectPath, @@ -43,7 +41,6 @@ export default () => { folderName: this.folderName, cssContainerClass: this.cssContainerClass, canReadEnvironment: this.canReadEnvironment, - ...this.canaryCalloutProps, }, }); }, diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue index 25f5483c58b..dbb60fa4622 100644 --- a/app/assets/javascripts/environments/folder/environments_folder_view.vue +++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue @@ -34,16 +34,6 @@ export default { type: Boolean, required: true, }, - canaryDeploymentFeatureId: { - type: String, - required: false, - default: '', - }, - showCanaryDeploymentCallout: { - type: Boolean, - required: false, - default: false, - }, userCalloutsPath: { type: String, required: false, @@ -98,8 +88,6 @@ export default { :environments="state.environments" :pagination="state.paginationInformation" :can-read-environment="canReadEnvironment" - :canary-deployment-feature-id="canaryDeploymentFeatureId" - :show-canary-deployment-callout="showCanaryDeploymentCallout" :user-callouts-path="userCalloutsPath" :lock-promotion-svg-path="lockPromotionSvgPath" :help-canary-deployments-path="helpCanaryDeploymentsPath" diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js index 8e8af3f32f7..4d734a457ab 100644 --- a/app/assets/javascripts/environments/index.js +++ b/app/assets/javascripts/environments/index.js @@ -1,6 +1,5 @@ import Vue from 'vue'; import VueApollo from 'vue-apollo'; -import canaryCalloutMixin from './mixins/canary_callout_mixin'; import environmentsComponent from './components/environments_app.vue'; import { parseBoolean } from '../lib/utils/common_utils'; import Translate from '../vue_shared/translate'; @@ -20,7 +19,6 @@ export default () => { components: { environmentsComponent, }, - mixins: [canaryCalloutMixin], apolloProvider, provide: { projectPath: el.dataset.projectPath, @@ -46,7 +44,6 @@ export default () => { deployBoardsHelpPath: this.deployBoardsHelpPath, canCreateEnvironment: this.canCreateEnvironment, canReadEnvironment: this.canReadEnvironment, - ...this.canaryCalloutProps, }, }); }, diff --git a/app/assets/javascripts/environments/mixins/canary_callout_mixin.js b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js deleted file mode 100644 index e9f1a144cb3..00000000000 --- a/app/assets/javascripts/environments/mixins/canary_callout_mixin.js +++ /dev/null @@ -1,26 +0,0 @@ -import { parseBoolean } from '~/lib/utils/common_utils'; - -export default { - data() { - const data = this.$options.el.dataset; - - return { - canaryDeploymentFeatureId: data.environmentsDataCanaryDeploymentFeatureId, - showCanaryDeploymentCallout: parseBoolean(data.environmentsDataShowCanaryDeploymentCallout), - userCalloutsPath: data.environmentsDataUserCalloutsPath, - lockPromotionSvgPath: data.environmentsDataLockPromotionSvgPath, - helpCanaryDeploymentsPath: data.environmentsDataHelpCanaryDeploymentsPath, - }; - }, - computed: { - canaryCalloutProps() { - return { - canaryDeploymentFeatureId: this.canaryDeploymentFeatureId, - showCanaryDeploymentCallout: this.showCanaryDeploymentCallout, - userCalloutsPath: this.userCalloutsPath, - lockPromotionSvgPath: this.lockPromotionSvgPath, - helpCanaryDeploymentsPath: this.helpCanaryDeploymentsPath, - }; - }, - }, -}; diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js index 2f4f53953f6..8911885e920 100644 --- a/app/assets/javascripts/environments/stores/environments_store.js +++ b/app/assets/javascripts/environments/stores/environments_store.js @@ -81,17 +81,6 @@ export default class EnvironmentsStore { this.state.environments = filteredEnvironments; - /** - * Add the canary callout banner underneath the second environment listed. - * - * If there is only one environment, then add to it underneath the first. - */ - if (this.state.environments.length >= 2) { - this.state.environments[1].showCanaryCallout = true; - } else if (this.state.environments.length === 1) { - this.state.environments[0].showCanaryCallout = true; - } - return filteredEnvironments; } diff --git a/app/assets/javascripts/jira_connect/api.js b/app/assets/javascripts/jira_connect/api.js index 55f2ef4f820..d689a2d1962 100644 --- a/app/assets/javascripts/jira_connect/api.js +++ b/app/assets/javascripts/jira_connect/api.js @@ -22,3 +22,12 @@ export const removeSubscription = async (removePath) => { }, }); }; + +export const fetchGroups = async (groupsPath, { page, perPage }) => { + return axios.get(groupsPath, { + params: { + page, + per_page: perPage, + }, + }); +}; diff --git a/app/assets/javascripts/jira_connect/components/app.vue b/app/assets/javascripts/jira_connect/components/app.vue index e08918a6720..f5bf30f4488 100644 --- a/app/assets/javascripts/jira_connect/components/app.vue +++ b/app/assets/javascripts/jira_connect/components/app.vue @@ -1,20 +1,33 @@ <script> import { mapState } from 'vuex'; -import { GlAlert } from '@gitlab/ui'; +import { GlAlert, GlButton, GlModal, GlModalDirective } from '@gitlab/ui'; +import { __ } from '~/locale'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import GroupsList from './groups_list.vue'; export default { name: 'JiraConnectApp', components: { GlAlert, + GlButton, + GlModal, + GroupsList, + }, + directives: { + GlModalDirective, }, mixins: [glFeatureFlagsMixin()], computed: { ...mapState(['errorMessage']), - showNewUi() { + showNewUI() { return this.glFeatures.newJiraConnectUi; }, }, + modal: { + cancelProps: { + text: __('Cancel'), + }, + }, }; </script> @@ -26,8 +39,25 @@ export default { <h1>GitLab for Jira Configuration</h1> - <div v-if="showNewUi"> + <div + v-if="showNewUI" + class="gl-display-flex gl-justify-content-space-between gl-my-5 gl-pb-4 gl-border-b-solid gl-border-b-1 gl-border-b-gray-200" + > <h3 data-testid="new-jira-connect-ui-heading">{{ s__('Integrations|Linked namespaces') }}</h3> + <gl-button + v-gl-modal-directive="'add-namespace-modal'" + category="primary" + variant="info" + class="gl-align-self-center" + >{{ s__('Integrations|Add namespace') }}</gl-button + > + <gl-modal + modal-id="add-namespace-modal" + :title="s__('Integrations|Link namespaces')" + :action-cancel="$options.modal.cancelProps" + > + <groups-list /> + </gl-modal> </div> </div> </template> diff --git a/app/assets/javascripts/jira_connect/components/groups_list.vue b/app/assets/javascripts/jira_connect/components/groups_list.vue new file mode 100644 index 00000000000..eeddd32addc --- /dev/null +++ b/app/assets/javascripts/jira_connect/components/groups_list.vue @@ -0,0 +1,88 @@ +<script> +import { GlTabs, GlTab, GlLoadingIcon, GlPagination } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; +import { fetchGroups } from '~/jira_connect/api'; +import { defaultPerPage } from '~/jira_connect/constants'; +import GroupsListItem from './groups_list_item.vue'; + +export default { + components: { + GlTabs, + GlTab, + GlLoadingIcon, + GlPagination, + GroupsListItem, + }, + inject: { + groupsPath: { + default: '', + }, + }, + data() { + return { + groups: [], + isLoading: false, + page: 1, + perPage: defaultPerPage, + totalItems: 0, + }; + }, + mounted() { + this.loadGroups(); + }, + methods: { + loadGroups() { + this.isLoading = true; + + fetchGroups(this.groupsPath, { + page: this.page, + perPage: this.perPage, + }) + .then((response) => { + const { page, total } = parseIntPagination(normalizeHeaders(response.headers)); + this.page = page; + this.totalItems = total; + this.groups = response.data; + }) + .catch(() => { + // eslint-disable-next-line no-alert + alert(s__('Integrations|Failed to load namespaces. Please try again.')); + }) + .finally(() => { + this.isLoading = false; + }); + }, + }, +}; +</script> + +<template> + <gl-tabs> + <gl-tab :title="__('Groups and subgroups')" class="gl-pt-3"> + <gl-loading-icon v-if="isLoading" size="md" /> + <div v-else-if="groups.length === 0" class="gl-text-center"> + <h5>{{ s__('Integrations|No available namespaces.') }}</h5> + <p class="gl-mt-5"> + {{ + s__('Integrations|You must have owner or maintainer permissions to link namespaces.') + }} + </p> + </div> + <ul v-else class="gl-list-style-none gl-pl-0"> + <groups-list-item v-for="group in groups" :key="group.id" :group="group" /> + </ul> + + <div class="gl-display-flex gl-justify-content-center gl-mt-5"> + <gl-pagination + v-if="totalItems > perPage && groups.length > 0" + v-model="page" + class="gl-mb-0" + :per-page="perPage" + :total-items="totalItems" + @input="loadGroups" + /> + </div> + </gl-tab> + </gl-tabs> +</template> diff --git a/app/assets/javascripts/jira_connect/components/groups_list_item.vue b/app/assets/javascripts/jira_connect/components/groups_list_item.vue new file mode 100644 index 00000000000..15e37ab3cb0 --- /dev/null +++ b/app/assets/javascripts/jira_connect/components/groups_list_item.vue @@ -0,0 +1,42 @@ +<script> +import { GlIcon, GlAvatar } from '@gitlab/ui'; + +export default { + components: { + GlIcon, + GlAvatar, + }, + props: { + group: { + type: Object, + required: true, + }, + }, +}; +</script> + +<template> + <li class="gl-border-b-1 gl-border-b-solid gl-border-b-gray-200"> + <div class="gl-display-flex gl-align-items-center gl-py-3"> + <gl-icon name="folder-o" class="gl-mr-3" /> + <div class="gl-display-none gl-flex-shrink-0 gl-display-sm-flex gl-mr-3"> + <gl-avatar :size="32" shape="rect" :entity-name="group.name" :src="group.avatar_url" /> + </div> + <div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center"> + <div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1"> + <div class="gl-display-flex gl-align-items-center gl-flex-wrap"> + <span + class="gl-mr-3 gl-text-gray-900! gl-font-weight-bold" + data-testid="group-list-item-name" + > + {{ group.full_name }} + </span> + </div> + <div v-if="group.description" data-testid="group-list-item-description"> + <p class="gl-mt-2! gl-mb-0 gl-text-gray-600" v-text="group.description"></p> + </div> + </div> + </div> + </div> + </li> +</template> diff --git a/app/assets/javascripts/jira_connect/constants.js b/app/assets/javascripts/jira_connect/constants.js new file mode 100644 index 00000000000..2b3be5cd5cd --- /dev/null +++ b/app/assets/javascripts/jira_connect/constants.js @@ -0,0 +1 @@ +export const defaultPerPage = 10; diff --git a/app/assets/javascripts/jira_connect/index.js b/app/assets/javascripts/jira_connect/index.js index 0dbcb778a6c..dc2a77f4e0c 100644 --- a/app/assets/javascripts/jira_connect/index.js +++ b/app/assets/javascripts/jira_connect/index.js @@ -73,11 +73,16 @@ function initJiraConnect() { Vue.use(Translate); Vue.use(GlFeatureFlagsPlugin); + const { groupsPath } = el.dataset; + return new Vue({ el, store, + provide: { + groupsPath, + }, render(createElement) { - return createElement(JiraConnectApp, {}); + return createElement(JiraConnectApp); }, }); } diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index 2d40f60a758..15f7c0c874e 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -4,6 +4,8 @@ import * as timeago from 'timeago.js'; import dateFormat from 'dateformat'; import { languageCode, s__, __, n__ } from '../../locale'; +const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; + window.timeago = timeago; /** @@ -851,3 +853,45 @@ export const format24HourTimeStringFromInt = (time) => { const formatted24HourString = time > 9 ? `${time}:00` : `0${time}:00`; return formatted24HourString; }; + +/** + * A utility function which checks if two date ranges overlap. + * + * @param {Object} givenPeriodLeft - the first period to compare. + * @param {Object} givenPeriodRight - the second period to compare. + * @returns {Object} { overlap: number of days the overlap is present, overlapStartDate: the start date of the overlap in time format, overlapEndDate: the end date of the overlap in time format } + * @throws {Error} Uncaught Error: Invalid period + * + * @example + * getOverlappingDaysInPeriods( + * { start: new Date(2021, 0, 11), end: new Date(2021, 0, 13) }, + * { start: new Date(2021, 0, 11), end: new Date(2021, 0, 14) } + * ) => { daysOverlap: 2, overlapStartDate: 1610323200000, overlapEndDate: 1610496000000 } + * + */ +export const getOverlappingDaysInPeriods = (givenPeriodLeft = {}, givenPeriodRight = {}) => { + const leftStartTime = new Date(givenPeriodLeft.start).getTime(); + const leftEndTime = new Date(givenPeriodLeft.end).getTime(); + const rightStartTime = new Date(givenPeriodRight.start).getTime(); + const rightEndTime = new Date(givenPeriodRight.end).getTime(); + + if (!(leftStartTime <= leftEndTime && rightStartTime <= rightEndTime)) { + throw new Error(__('Invalid period')); + } + + const isOverlapping = leftStartTime < rightEndTime && rightStartTime < leftEndTime; + + if (!isOverlapping) { + return { daysOverlap: 0 }; + } + + const overlapStartDate = Math.max(leftStartTime, rightStartTime); + const overlapEndDate = rightEndTime > leftEndTime ? leftEndTime : rightEndTime; + const differenceInMs = overlapEndDate - overlapStartDate; + + return { + daysOverlap: Math.ceil(differenceInMs / MILLISECONDS_IN_DAY), + overlapStartDate, + overlapEndDate, + }; +}; diff --git a/app/assets/stylesheets/page_bundles/environments.scss b/app/assets/stylesheets/page_bundles/environments.scss index 470660adba9..7d5f501d633 100644 --- a/app/assets/stylesheets/page_bundles/environments.scss +++ b/app/assets/stylesheets/page_bundles/environments.scss @@ -218,57 +218,3 @@ padding-right: 10px; } } - -.canary-deployment-callout { - border-bottom: 1px solid var(--gray-500, $gray-500); - display: flex; - - @include media-breakpoint-down(sm) { - display: none; - } - - &-lock { - height: 82px; - width: 92px; - } - - &-message { - max-width: 600px; - color: var(--gray-500, $gray-500); - } - - &-close { - color: var(--gray-500, $gray-500); - cursor: pointer; - } - - &-button { - border-color: var(--blue-500, $blue-500); - color: var(--blue-500, $blue-500); - - &:not(:disabled):not(.disabled):active { - background-color: var(--blue-200, $blue-200); - border: 2px solid var(--blue-600, $blue-600); - color: var(--blue-700, $blue-700); - height: 34px; - padding: 5px 9px; - } - - &:focus { - background-color: var(--blue-500, $blue-500); - border: 2px solid var(--blue-500, $blue-500); - box-shadow: 0 0 4px 1px var(--blue-200, $blue-200); - color: var(--blue-600, $blue-600); - height: 34px; - padding: 5px 9px; - } - - &:hover { - background-color: var(--blue-500, $blue-500); - border: 2px solid var(--blue-500, $blue-500); - color: var(--blue-600, $blue-600); - height: 34px; - padding: 5px 9px; - } - } -} diff --git a/app/assets/stylesheets/page_bundles/jira_connect.scss b/app/assets/stylesheets/page_bundles/jira_connect.scss index 81e9b04b18e..231723ca4e3 100644 --- a/app/assets/stylesheets/page_bundles/jira_connect.scss +++ b/app/assets/stylesheets/page_bundles/jira_connect.scss @@ -1,15 +1,24 @@ @import 'mixins_and_variables_and_functions'; -/** -NOTE: We should only import styles that we actually use. -Ex: - @import '@gitlab/ui/src/scss/gitlab_ui'; -*/ + @import '@gitlab/ui/src/scss/bootstrap'; @import 'bootstrap-vue/src/index'; @import '@gitlab/ui/src/scss/utilities'; @import '@gitlab/ui/src/components/base/alert/alert'; +// We should only import styles that we actually use. +@import '@gitlab/ui/src/components/base/alert/alert'; +@import '@gitlab/ui/src/components/base/avatar/avatar'; +@import '@gitlab/ui/src/components/base/badge/badge'; +@import '@gitlab/ui/src/components/base/button/button'; +@import '@gitlab/ui/src/components/base/icon/icon'; +@import '@gitlab/ui/src/components/base/link/link'; +@import '@gitlab/ui/src/components/base/loading_icon/loading_icon'; +@import '@gitlab/ui/src/components/base/modal/modal'; +@import '@gitlab/ui/src/components/base/pagination/pagination'; +@import '@gitlab/ui/src/components/base/tabs/tabs/tabs'; +@import '@gitlab/ui/src/components/base/tooltip/tooltip'; + $atlaskit-border-color: #dfe1e6; .ac-content { diff --git a/app/assets/stylesheets/page_bundles/oncall_schedules.scss b/app/assets/stylesheets/page_bundles/oncall_schedules.scss index 2ab3bdcc474..1b190024457 100644 --- a/app/assets/stylesheets/page_bundles/oncall_schedules.scss +++ b/app/assets/stylesheets/page_bundles/oncall_schedules.scss @@ -113,8 +113,6 @@ $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradi } .item-label { - @include gl-py-4; - @include gl-pl-4; border-right: $border-style; border-bottom: $border-style; } diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb index f4e05e19eca..84b61340e93 100644 --- a/app/graphql/types/notes/note_type.rb +++ b/app/graphql/types/notes/note_type.rb @@ -46,6 +46,13 @@ module Types field :confidential, GraphQL::BOOLEAN_TYPE, null: true, description: 'Indicates if this note is confidential', method: :confidential? + field :url, GraphQL::STRING_TYPE, + null: true, + description: 'URL to view this Note in the Web UI' + + def url + ::Gitlab::UrlBuilder.build(object) + end def system_note_icon_name SystemNoteHelper.system_note_icon_name(object) if object.system? diff --git a/app/helpers/jira_connect_helper.rb b/app/helpers/jira_connect_helper.rb index c30eb1b007a..f1527b9b85a 100644 --- a/app/helpers/jira_connect_helper.rb +++ b/app/helpers/jira_connect_helper.rb @@ -4,4 +4,10 @@ module JiraConnectHelper def new_jira_connect_ui? Feature.enabled?(:new_jira_connect_ui, type: :development, default_enabled: :yaml) end + + def jira_connect_app_data + { + groups_path: api_v4_groups_path(params: { min_access_level: Gitlab::Access::MAINTAINER }) + } + end end diff --git a/app/services/jira_connect/sync_service.rb b/app/services/jira_connect/sync_service.rb index b2af284f1f0..bddc7cbe5a0 100644 --- a/app/services/jira_connect/sync_service.rb +++ b/app/services/jira_connect/sync_service.rb @@ -31,7 +31,7 @@ module JiraConnect jira_response: response&.to_json } - if response && (response['errorMessages'] || response['rejectedBuilds'].present?) + if response && response['errorMessages'].present? logger.error(message) else logger.info(message) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 53872c67f49..25d46ada885 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -37,16 +37,11 @@ module Projects raise InvalidStateError, 'missing pages artifacts' unless build.artifacts? raise InvalidStateError, 'build SHA is outdated for this ref' unless latest? - # Create temporary directory in which we will extract the artifacts - make_secure_tmp_dir(tmp_path) do |archive_path| - extract_archive!(archive_path) + build.artifacts_file.use_file do |artifacts_path| + deploy_to_legacy_storage(artifacts_path) - # Check if we did extract public directory - archive_public_path = File.join(archive_path, PUBLIC_DIR) - raise InvalidStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path) - raise InvalidStateError, 'build SHA is outdated for this ref' unless latest? + create_pages_deployment(artifacts_path, build) - deploy_page!(archive_public_path) success end rescue InvalidStateError => e @@ -84,15 +79,29 @@ module Projects ) end - def extract_archive!(temp_path) + def deploy_to_legacy_storage(artifacts_path) + # Create temporary directory in which we will extract the artifacts + make_secure_tmp_dir(tmp_path) do |tmp_path| + extract_archive!(artifacts_path, tmp_path) + + # Check if we did extract public directory + archive_public_path = File.join(tmp_path, PUBLIC_DIR) + raise InvalidStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path) + raise InvalidStateError, 'build SHA is outdated for this ref' unless latest? + + deploy_page!(archive_public_path) + end + end + + def extract_archive!(artifacts_path, temp_path) if artifacts.ends_with?('.zip') - extract_zip_archive!(temp_path) + extract_zip_archive!(artifacts_path, temp_path) else raise InvalidStateError, 'unsupported artifacts format' end end - def extract_zip_archive!(temp_path) + def extract_zip_archive!(artifacts_path, temp_path) raise InvalidStateError, 'missing artifacts metadata' unless build.artifacts_metadata? # Calculate page size after extract @@ -102,11 +111,8 @@ module Projects raise InvalidStateError, "artifacts for pages are too large: #{public_entry.total_size}" end - build.artifacts_file.use_file do |artifacts_path| - SafeZip::Extract.new(artifacts_path) - .extract(directories: [PUBLIC_DIR], to: temp_path) - create_pages_deployment(artifacts_path, build) - end + SafeZip::Extract.new(artifacts_path) + .extract(directories: [PUBLIC_DIR], to: temp_path) rescue SafeZip::Extract::Error => e raise FailedToExtractError, e.message end @@ -150,6 +156,9 @@ module Projects deployment = project.pages_deployments.create!(file: file, file_count: entries_count, file_sha256: sha256) + + raise InvalidStateError, 'build SHA is outdated for this ref' unless latest? + project.update_pages_deployment!(deployment) end diff --git a/app/views/jira_connect/subscriptions/index.html.haml b/app/views/jira_connect/subscriptions/index.html.haml index 30dad19be37..ed765f80b74 100644 --- a/app/views/jira_connect/subscriptions/index.html.haml +++ b/app/views/jira_connect/subscriptions/index.html.haml @@ -20,7 +20,7 @@ .gl-mt-5 %p Note: this integration only works with accounts on GitLab.com (SaaS). - else - .js-jira-connect-app + .js-jira-connect-app{ data: jira_connect_app_data } - unless new_jira_connect_ui? %form#add-subscription-form.subscription-form{ action: jira_connect_subscriptions_path } diff --git a/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb b/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb index 64e65958113..7c86b194574 100644 --- a/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb +++ b/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb @@ -109,6 +109,13 @@ module ContainerExpirationPolicies log_extra_metadata_on_done(field, value) end + + before_truncate_size = result.payload[:cleanup_tags_service_before_truncate_size] + after_truncate_size = result.payload[:cleanup_tags_service_after_truncate_size] + truncated = before_truncate_size && + after_truncate_size && + before_truncate_size != after_truncate_size + log_extra_metadata_on_done(:cleanup_tags_service_truncated, !!truncated) end end end |