Commit d1d63b17 authored by Nicolò Mezzopera's avatar Nicolò Mezzopera
Browse files

Merge branch '238607-remove-feature-flag-licenses_app-and-code-that-is-enabled-by-it' into 'master'

Remove feature flag licenses_app and code that is enabled by it

Closes #238607

See merge request gitlab-org/gitlab!42520
parents 9c4ea521 5434772f
......@@ -923,8 +923,3 @@ $compare-branches-sticky-header-height: 68px;
- Issue: https://gitlab.com/gitlab-org/design.gitlab.com/issues/242
*/
$enable-validation-icons: false;
/*
Licenses
*/
$license-header-cell-width: 150px;
import LicenseCard from './license_card.vue';
import SkeletonLicenseCard from './skeleton_license_card.vue';
export { LicenseCard, SkeletonLicenseCard };
<script>
import { mapState, mapActions } from 'vuex';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { __ } from '~/locale';
import LicenseCardBody from './license_card_body.vue';
export default {
name: 'LicenseCard',
components: {
LicenseCardBody,
GlDropdown,
GlDropdownItem,
},
props: {
license: {
type: Object,
required: false,
default() {
return { licensee: {} };
},
},
isCurrentLicense: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
...mapState(['activeUserCount', 'guestUserCount', 'deleteQueue', 'downloadLicensePath']),
isRemoving() {
return this.deleteQueue.includes(this.license.id);
},
},
methods: {
...mapActions(['fetchDeleteLicense']),
capitalizeFirstCharacter,
confirmDeleteLicense(...args) {
window.confirm(__('Are you sure you want to permanently delete this license?')); // eslint-disable-line no-alert
this.fetchDeleteLicense(...args);
},
},
};
</script>
<template>
<div class="card license-card mb-5">
<div class="card-header">
<div class="d-flex justify-content-between align-items-center">
<h4>
{{
sprintf(__('GitLab Enterprise Edition %{plan}'), {
plan: capitalizeFirstCharacter(license.plan),
})
}}
</h4>
<gl-dropdown right class="js-manage-license" :text="__('Manage')" :disabled="isRemoving">
<gl-dropdown-item
v-if="isCurrentLicense"
class="js-download-license"
:href="downloadLicensePath"
>
{{ __('Download license') }}
</gl-dropdown-item>
<gl-dropdown-item
class="js-delete-license text-danger"
@click="confirmDeleteLicense(license)"
>
{{ __('Delete license') }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</div>
<license-card-body
:license="license"
:is-removing="isRemoving"
:active-user-count="activeUserCount"
:guest-user-count="guestUserCount"
/>
</div>
</template>
<script>
import { GlLink, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import { Cell, HeaderCell, InfoCell, DateCell } from '../cells';
export default {
name: 'LicenseCardBody',
components: {
GlIcon,
Cell,
HeaderCell,
InfoCell,
DateCell,
GlLink,
},
props: {
license: {
type: Object,
required: false,
default() {
return {
licensee: {},
};
},
},
isRemoving: {
type: Boolean,
required: false,
default: false,
},
activeUserCount: {
type: Number,
required: true,
},
guestUserCount: {
type: Number,
required: true,
},
},
data() {
return {
info: {
currentActiveUserCount: __(
"Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use.",
),
historicalMax: __(`This is the maximum number of users that have existed at the same time since the license started.
This is the minimum number of seats you will need to buy when you renew your license.`),
overage: __(`GitLab allows you to continue using your license even if you exceed the number of seats you purchased.
You will be required to pay for these seats when you renew your license.`),
},
};
},
computed: {
seatsInUseComponent() {
return this.license.plan === 'ultimate' ? 'info-cell' : 'cell';
},
seatsInUseForThisLicense() {
return this.license.plan === 'ultimate'
? this.activeUserCount - this.guestUserCount
: this.activeUserCount;
},
},
methods: {
licenseeValue(key) {
return this.license.licensee[key] || __('Unknown');
},
},
};
</script>
<template>
<div class="card-body license-card-body p-0">
<div
v-if="isRemoving"
class="p-5 d-flex justify-content-center align-items-center license-card-loading"
>
<gl-icon name="spinner" /><span class="ml-2">{{ __('Removing license…') }}</span>
</div>
<div v-else class="license-table js-license-table">
<div class="license-row d-flex">
<header-cell :title="__('Usage')" icon="monitor" />
<cell :title="__('Seats in license')" :value="license.userLimit || __('Unlimited')" />
<component
:is="seatsInUseComponent"
:title="__('Seats currently in use')"
:value="seatsInUseForThisLicense"
:popover-content="info.currentActiveUserCount"
/>
<info-cell
:title="__('Max seats used')"
:value="license.historicalMax"
:popover-content="info.historicalMax"
/>
<info-cell
:title="__('Users outside of license')"
:value="license.overage"
:popover-content="info.overage"
/>
</div>
<div class="license-row d-flex">
<header-cell :title="__('Validity')" icon="calendar" />
<date-cell :title="__('Start date')" :value="license.startsAt" />
<date-cell :title="__('End date')" :value="license.expiresAt" :is-expirable="true" />
<date-cell :title="__('Uploaded on')" :value="license.createdAt" />
</div>
<div class="license-row d-flex">
<header-cell :title="__('Registration')" icon="user" />
<cell :title="__('Licensed to')" :value="licenseeValue('Name')" />
<cell :title="__('Email address')" :value="licenseeValue('Email')" />
<cell :title="__('Company')" :value="licenseeValue('Company')" />
</div>
</div>
</div>
</template>
<script>
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import { SkeletonCell, SkeletonHeaderCell } from '../cells';
export default {
name: 'SkeletonLicenseCard',
components: {
GlSkeletonLoading,
SkeletonCell,
SkeletonHeaderCell,
},
};
</script>
<template>
<div class="card license-card skeleton-license-card">
<div class="card-header d-flex justify-content-between align-items-center py-3">
<gl-skeleton-loading class="w-75 skeleton-bar" :lines="1" />
</div>
<div class="card-body p-0">
<div class="license-table">
<div class="license-row d-flex">
<skeleton-header-cell />
<skeleton-cell />
<skeleton-cell />
<skeleton-cell />
<skeleton-cell />
</div>
<div class="license-row d-flex">
<skeleton-header-cell />
<skeleton-cell />
<skeleton-cell />
<skeleton-cell />
</div>
<div class="license-row d-flex">
<skeleton-header-cell />
<skeleton-cell />
<skeleton-cell />
<skeleton-cell />
</div>
</div>
</div>
</div>
</template>
<script>
import { isNumber } from 'lodash';
export default {
// name: 'Cell' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
// eslint-disable-next-line @gitlab/require-i18n-strings
name: 'Cell',
props: {
title: {
type: String,
required: false,
default: null,
},
value: {
type: [String, Number],
required: false,
default: null,
},
isFlexible: {
type: Boolean,
required: false,
default: true,
},
},
computed: {
valueClass() {
return { number: isNumber(this.value) };
},
flexClass() {
return { 'flex-grow-1': this.isFlexible };
},
},
};
</script>
<template>
<div class="license-cell p-3 text-nowrap flex-shrink-0" :class="flexClass">
<span class="title d-flex align-items-center justify-content-start">
<slot name="title">
<span>{{ title }}</span>
</slot>
</span>
<div class="value mt-2" :class="valueClass">
<slot name="value">
<span>{{ value }}</span>
</slot>
</div>
</div>
</template>
<script>
import { dateInWords } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import Cell from './cell.vue';
export default {
name: 'DateCell',
components: {
Cell,
},
props: {
title: {
type: String,
required: false,
default: null,
},
value: {
type: [String, Date],
required: false,
default: null,
},
dateNow: {
type: Date,
required: false,
default() {
return new Date();
},
},
isExpirable: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
dateInWordsValue() {
return dateInWords(this.dateValue);
},
dateValue() {
return new Date(this.value);
},
isExpired() {
return this.isExpirable && this.dateValue < this.dateNow;
},
valueClass() {
return { 'text-danger': this.isExpired };
},
fallbackValue() {
return this.isExpirable ? this.dateInWords || __('Never') : this.dateInWords;
},
},
};
</script>
<template>
<cell :title="title" :value="fallbackValue">
<div v-if="value" slot="value" :class="valueClass">
{{ dateInWordsValue }}
<span v-if="isExpired"> - {{ __('Expired') }} </span>
</div>
</cell>
</template>
<script>
import { GlIcon } from '@gitlab/ui';
import Cell from './cell.vue';
export default {
name: 'HeaderCell',
components: {
GlIcon,
Cell,
},
props: {
title: {
type: String,
required: true,
},
icon: {
type: String,
required: true,
},
},
};
</script>
<template>
<cell class="license-header-cell" :is-flexible="false">
<template #title>
<gl-icon class="icon" :name="icon" />
<span class="ml-2 font-weight-bold">{{ title }}</span>
</template>
</cell>
</template>
import Cell from './cell.vue';
import HeaderCell from './header_cell.vue';
import InfoCell from './info_cell.vue';
import DateCell from './date_cell.vue';
import SkeletonCell from './skeleton_cell.vue';
import SkeletonHeaderCell from './skeleton_header_cell.vue';
export { Cell, HeaderCell, InfoCell, DateCell, SkeletonCell, SkeletonHeaderCell };
<script>
import { GlPopover, GlIcon } from '@gitlab/ui';
import Cell from './cell.vue';
export default {
name: 'InfoCell',
components: {
GlIcon,
GlPopover,
Cell,
},
props: {
title: {
type: String,
required: true,
default: null,
},
value: {
type: [Number, String],
required: false,
default: null,
},
popoverContent: {
type: String,
required: false,
default: null,
},
},
data() {
return {
popoverTarget: null,
};
},
mounted() {
this.popoverTarget = this.$refs.popoverTarget;
},
};
</script>
<template>
<cell class="license-info-cell" :value="value">
<template slot="title">
<span class="mr-2 text">{{ title }}</span>
<button ref="popoverTarget" type="button" class="btn-link information-target">
<gl-icon name="information" class="icon d-block" />
</button>
<gl-popover
placement="bottom"
:target="popoverTarget"
:content="popoverContent"
triggers="hover"
/>
</template>
</cell>
</template>
<script>
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import Cell from './cell.vue';
export default {
name: 'SkeletonCell',
components: {
Cell,
GlSkeletonLoading,
},
};
</script>
<template>
<cell>
<gl-skeleton-loading slot="title" class="w-75 skeleton-bar" :lines="1" />
<gl-skeleton-loading slot="value" class="w-50 skeleton-bar" :lines="1" />
</cell>
</template>
<script>
import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui';
import Cell from './cell.vue';
export default {
name: 'SkeletonHeaderCell',
components: {
Cell,
GlSkeletonLoading,
},
};
</script>
<template>
<cell class="license-header-cell" :is-flexible="false">
<gl-skeleton-loading slot="title" class="w-75 skeleton-bar" :lines="1" />
</cell>
</template>
<script>
import { mapState, mapGetters } from 'vuex';
import { GlButton } from '@gitlab/ui';
import { LicenseCard, SkeletonLicenseCard } from './cards';
export default {
name: 'LicenseCardsList',
components: {
LicenseCard,
SkeletonLicenseCard,
GlButton,
},
computed: {
...mapState(['licenses', 'isLoadingLicenses', 'newLicensePath']),
...mapGetters(['hasLicenses']),
},
};
</script>
<template>
<div>
<div class="d-flex justify-content-between align-items-center">
<h4>{{ __('Instance license') }}</h4>
<gl-button class="my-3 js-add-license" variant="success" :href="newLicensePath">
{{ __('Add license') }}
</gl-button>
</div>
<ul class="license-list list-unstyled">
<li v-if="isLoadingLicenses">
<skeleton-license-card />
</li>
<li v-for="(license, index) in licenses" v-else-if="hasLicenses" :key="license.id">
<license-card :license="license" :is-current-license="index === 0" />
</li>
<li v-else>
<strong>
{{ __('No licenses found.') }}
</strong>
</li>
</ul>
</div>
</template>
import Vue from 'vue';
import { mapActions } from 'vuex';
import store from './store';
import LicenseCardsList from './components/license_cards_list.vue';
export default function mountInstanceLicenseApp(mountElement) {
if (!mountElement) return undefined;
const {
activeUserCount,
guestUserCount,
licensesPath,
deleteLicensePath,
newLicensePath,
downloadLicensePath,
} = mountElement.dataset;
return new Vue({
el: mountElement,
store,
created() {
this.setInitialData({
licensesPath,
deleteLicensePath,
newLicensePath,
downloadLicensePath,
activeUserCount: parseInt(activeUserCount, 10),
guestUserCount: parseInt(guestUserCount, 10),
});
this.fetchLicenses();
},
methods: {
...mapActions(['setInitialData', 'fetchLicenses']),
},
render(createElement) {