diff options
Diffstat (limited to 'app')
11 files changed, 115 insertions, 20 deletions
diff --git a/app/assets/javascripts/environments/components/kubernetes_summary.vue b/app/assets/javascripts/environments/components/kubernetes_summary.vue index e2fbc6fd2e7..1f4e91afe35 100644 --- a/app/assets/javascripts/environments/components/kubernetes_summary.vue +++ b/app/assets/javascripts/environments/components/kubernetes_summary.vue @@ -159,6 +159,7 @@ export default { completed: 'success', failed: 'danger', suspended: 'neutral', + pending: 'info', }, icons: { Active: { icon: 'status_success', class: 'gl-text-green-500' }, diff --git a/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js b/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js index 8375b8793d9..1bc8706cfce 100644 --- a/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js +++ b/app/assets/javascripts/environments/graphql/resolvers/kubernetes.js @@ -6,8 +6,10 @@ import { WatchApi, EVENT_DATA, } from '@gitlab/cluster-client'; +import produce from 'immer'; import { humanizeClusterErrors } from '../../helpers/k8s_integration_helper'; import k8sPodsQuery from '../queries/k8s_pods.query.graphql'; +import k8sWorkloadsQuery from '../queries/k8s_workloads.query.graphql'; const mapWorkloadItems = (items, kind) => { return items.map((item) => { @@ -88,6 +90,44 @@ const watchPods = ({ configuration, namespace, client }) => { }); }; +const watchWorkloadItems = ({ kind, apiVersion, configuration, namespace, client }) => { + const itemKind = kind.toLowerCase().replace('list', 's'); + + const path = namespace + ? `/apis/${apiVersion}/namespaces/${namespace}/${itemKind}` + : `/apis/${apiVersion}/${itemKind}`; + const config = new Configuration(configuration); + const watcherApi = new WatchApi(config); + + watcherApi + .subscribeToStream(path, { watch: true }) + .then((watcher) => { + let result = []; + + watcher.on(EVENT_DATA, (data) => { + result = mapWorkloadItems(data, kind); + + const sourceData = client.readQuery({ + query: k8sWorkloadsQuery, + variables: { configuration, namespace }, + }); + + const updatedData = produce(sourceData, (draft) => { + draft.k8sWorkloads[kind] = result; + }); + + client.writeQuery({ + query: k8sWorkloadsQuery, + variables: { configuration, namespace }, + data: updatedData, + }); + }); + }) + .catch((err) => { + handleClusterError(err); + }); +}; + export default { k8sPods(_, { configuration, namespace }, { client }) { const config = new Configuration(configuration); @@ -143,7 +183,7 @@ export default { } }); }, - k8sWorkloads(_, { configuration, namespace }) { + k8sWorkloads(_, { configuration, namespace }, { client }) { const appsV1api = new AppsV1Api(new Configuration(configuration)); const batchV1api = new BatchV1Api(new Configuration(configuration)); @@ -189,10 +229,12 @@ export default { } for (const promiseResult of results) { if (promiseResult.status === 'fulfilled' && promiseResult?.value) { - const { kind, items } = promiseResult.value; + const { kind, items, apiVersion } = promiseResult.value; if (items?.length > 0) { summaryList[kind] = mapWorkloadItems(items, kind); + + watchWorkloadItems({ kind, apiVersion, configuration, namespace, client }); } } } diff --git a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js index 164a2d98e90..93a78e21dfb 100644 --- a/app/assets/javascripts/environments/helpers/k8s_integration_helper.js +++ b/app/assets/javascripts/environments/helpers/k8s_integration_helper.js @@ -1,5 +1,5 @@ import { differenceInSeconds } from '~/lib/utils/datetime_utility'; -import { CLUSTER_AGENT_ERROR_MESSAGES } from '../constants'; +import { CLUSTER_AGENT_ERROR_MESSAGES, STATUS_TRUE, STATUS_FALSE } from '../constants'; export function generateServicePortsString(ports) { if (!ports?.length) return ''; @@ -39,19 +39,21 @@ export function getServiceAge(creationTimestamp) { export function getDeploymentsStatuses(items) { const failed = []; const ready = []; + const pending = []; items.forEach((item) => { const [available, progressing] = item.status?.conditions ?? []; - // eslint-disable-next-line @gitlab/require-i18n-strings - if (available.status === 'True') { + if (available?.status === STATUS_TRUE) { ready.push(item); - // eslint-disable-next-line @gitlab/require-i18n-strings - } else if (available.status !== 'True' && progressing.status !== 'True') { + } else if (available?.status === STATUS_FALSE && progressing?.status !== STATUS_TRUE) { failed.push(item); + } else { + pending.push(item); } }); return { + ...(pending.length && { pending }), ...(failed.length && { failed }), ...(ready.length && { ready }), }; diff --git a/app/assets/javascripts/graphql_shared/constants.js b/app/assets/javascripts/graphql_shared/constants.js index 7f2c41d9aaf..ef0dcea26ad 100644 --- a/app/assets/javascripts/graphql_shared/constants.js +++ b/app/assets/javascripts/graphql_shared/constants.js @@ -15,6 +15,7 @@ export const TYPENAME_GROUP = 'Group'; export const TYPENAME_ISSUE = 'Issue'; export const TYPENAME_ITERATION = 'Iteration'; export const TYPENAME_ITERATIONS_CADENCE = 'Iterations::Cadence'; +export const TYPENAME_MEMBER_ROLE = 'MemberRole'; export const TYPENAME_MERGE_REQUEST = 'MergeRequest'; export const TYPENAME_MILESTONE = 'Milestone'; export const TYPENAME_NOTE = 'Note'; diff --git a/app/assets/javascripts/import/constants.js b/app/assets/javascripts/import/constants.js index b02eb3c4307..cbb01d0bbf1 100644 --- a/app/assets/javascripts/import/constants.js +++ b/app/assets/javascripts/import/constants.js @@ -7,9 +7,12 @@ export const BULK_IMPORT_STATIC_ITEMS = { epics: __('Epic'), issues: __('Issue'), labels: __('Label'), + iterations: __('Iteration'), + iterations_cadences: s__('Iterations|Iteration cadence'), members: __('Member'), merge_requests: __('Merge request'), milestones: __('Milestone'), + namespace_settings: s__('GroupSettings|Namespace setting'), project: __('Project'), }; diff --git a/app/assets/javascripts/import/details/components/import_details_table.vue b/app/assets/javascripts/import/details/components/import_details_table.vue index 535ccb525ac..fa22991bec0 100644 --- a/app/assets/javascripts/import/details/components/import_details_table.vue +++ b/app/assets/javascripts/import/details/components/import_details_table.vue @@ -21,7 +21,6 @@ export default { GlTable, PaginationBar, }, - STATISTIC_ITEMS, i18n: { fetchErrorMessage: s__('Import|An error occurred while fetching import details.'), diff --git a/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js b/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js index 69e669b9abd..4046d25acc3 100644 --- a/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js +++ b/app/assets/javascripts/import_entities/import_groups/graphql/client_factory.js @@ -111,7 +111,7 @@ export function createResolvers({ endpoints }) { }, }, Mutation: { - async updateImportStatus( + updateImportStatus( _, { id, status: newStatus, hasFailures = false }, { client, getCacheKey }, diff --git a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue index ceb9200dfad..9893572ae16 100644 --- a/app/assets/javascripts/invite_members/components/invite_groups_modal.vue +++ b/app/assets/javascripts/invite_members/components/invite_groups_modal.vue @@ -106,6 +106,9 @@ export default { inviteDisabled() { return Object.keys(this.groupToBeSharedWith).length === 0; }, + staticRoles() { + return { validRoles: this.accessLevels }; + }, }, mounted() { if (this.reloadPageOnSubmit) { @@ -182,7 +185,7 @@ export default { :modal-id="modalId" :modal-title="$options.labels.title" :name="name" - :access-levels="accessLevels" + :access-levels="staticRoles" :default-access-level="defaultAccessLevel" :help-link="helpLink" v-bind="$attrs" @@ -194,6 +197,7 @@ export default { :invalid-feedback-message="invalidFeedbackMessage" :is-loading="isLoading" :full-path="fullPath" + is-group-invite @reset="resetFields" @submit="sendInvite" > diff --git a/app/assets/javascripts/invite_members/components/invite_members_modal.vue b/app/assets/javascripts/invite_members/components/invite_members_modal.vue index 27124808550..91158ef15c8 100644 --- a/app/assets/javascripts/invite_members/components/invite_members_modal.vue +++ b/app/assets/javascripts/invite_members/components/invite_members_modal.vue @@ -85,6 +85,11 @@ export default { type: Number, required: true, }, + defaultMemberRoleId: { + type: Number, + required: false, + default: null, + }, helpLink: { type: String, required: true, @@ -181,6 +186,9 @@ export default { showUserLimitNotification() { return !isEmpty(this.usersLimitDataset.alertVariant); }, + staticRoles() { + return { validRoles: this.accessLevels }; + }, limitVariant() { return this.usersLimitDataset.alertVariant; }, @@ -267,7 +275,7 @@ export default { this.shouldShowEmptyInvitesAlert = true; this.$refs.alerts.focus(); }, - getInvitePayload({ accessLevel, expiresAt }) { + getInvitePayload({ accessLevel, expiresAt, memberRoleId }) { const [usersToInviteByEmail, usersToAddById] = this.partitionNewUsersToInvite(); const email = usersToInviteByEmail !== '' ? { email: usersToInviteByEmail } : {}; @@ -277,12 +285,13 @@ export default { format: 'json', expires_at: expiresAt, access_level: accessLevel, + member_role_id: memberRoleId, invite_source: this.source, ...email, ...userId, }; }, - async sendInvite({ accessLevel, expiresAt }) { + async sendInvite({ accessLevel, expiresAt, memberRoleId }) { this.isLoading = true; this.clearValidation(); @@ -296,7 +305,7 @@ export default { : Api.inviteGroupMembers.bind(Api); try { - const payload = this.getInvitePayload({ accessLevel, expiresAt }); + const payload = this.getInvitePayload({ accessLevel, expiresAt, memberRoleId }); const response = await apiAddByInvite(this.id, payload); const { error, message } = responseFromSuccess(response); @@ -377,14 +386,16 @@ export default { :modal-id="modalId" :modal-title="modalTitle" :name="name" - :access-levels="accessLevels" + :access-levels="staticRoles" :default-access-level="defaultAccessLevel" + :default-member-role-id="defaultMemberRoleId" :help-link="helpLink" :label-intro-text="labelIntroText" :label-search-field="labelSearchField" :form-group-description="formGroupDescription" :invalid-feedback-message="invalidFeedbackMessage" :is-loading="isLoading" + :is-project="isProject" :new-users-to-invite="newUsersToInvite" :root-group-id="rootId" :users-limit-dataset="usersLimitDataset" diff --git a/app/assets/javascripts/invite_members/components/invite_modal_base.vue b/app/assets/javascripts/invite_members/components/invite_modal_base.vue index 86e3e56a525..00b7c3f4bdd 100644 --- a/app/assets/javascripts/invite_members/components/invite_modal_base.vue +++ b/app/assets/javascripts/invite_members/components/invite_modal_base.vue @@ -12,6 +12,7 @@ import { import Tracking from '~/tracking'; import { sprintf } from '~/locale'; import ContentTransition from '~/vue_shared/components/content_transition.vue'; +import { initialSelectedRole, roleDropdownItems } from 'ee_else_ce/members/utils'; import { ACCESS_LEVEL, ACCESS_EXPIRE_DATE, @@ -68,6 +69,11 @@ export default { type: Number, required: true, }, + defaultMemberRoleId: { + type: Number, + required: false, + default: null, + }, helpLink: { type: String, required: true, @@ -95,6 +101,11 @@ export default { required: false, default: false, }, + isLoadingRoles: { + type: Boolean, + required: false, + default: false, + }, invalidFeedbackMessage: { type: String, required: false, @@ -134,14 +145,14 @@ export default { data() { // Be sure to check out reset! return { - selectedAccessLevel: this.defaultAccessLevel, + selectedAccessLevel: null, selectedDate: undefined, minDate: new Date(), }; }, computed: { - accessLevelsOptions() { - return Object.entries(this.accessLevels).map(([text, value]) => ({ text, value })); + accessLevelOptions() { + return roleDropdownItems(this.accessLevels); }, introText() { return sprintf(this.labelIntroText, { name: this.name }); @@ -188,11 +199,17 @@ export default { }; }, }, + watch: { + accessLevelOptions: { + immediate: true, + handler: 'resetSelectedAccessLevel', + }, + }, methods: { onReset() { // This component isn't necessarily disposed, // so we might need to reset it's state. - this.selectedAccessLevel = this.defaultAccessLevel; + this.resetSelectedAccessLevel(); this.selectedDate = undefined; this.$emit('reset'); @@ -216,14 +233,27 @@ export default { // We never want to hide when submitting e.preventDefault(); + const { accessLevel, memberRoleId } = this.accessLevelOptions.flatten.find( + (item) => item.value === this.selectedAccessLevel, + ); this.$emit('submit', { - accessLevel: this.selectedAccessLevel, + accessLevel, + memberRoleId, expiresAt: this.selectedDate, }); }, onClose() { this.$emit('close'); }, + resetSelectedAccessLevel() { + const accessLevel = { + integerValue: this.defaultAccessLevel, + memberRoleId: this.defaultMemberRoleId, + }; + this.selectedAccessLevel = initialSelectedRole(this.accessLevelOptions.flatten, { + accessLevel, + }); + }, }, HEADER_CLOSE_LABEL, ACCESS_EXPIRE_DATE, @@ -298,7 +328,8 @@ export default { :id="dropdownId" v-model="selectedAccessLevel" data-testid="access-level-dropdown" - :items="accessLevelsOptions" + :items="accessLevelOptions.formatted" + :loading="isLoadingRoles" block /> </gl-form-group> diff --git a/app/assets/javascripts/invite_members/init_invite_members_modal.js b/app/assets/javascripts/invite_members/init_invite_members_modal.js index 8dfe697e2cb..bd291ecc90f 100644 --- a/app/assets/javascripts/invite_members/init_invite_members_modal.js +++ b/app/assets/javascripts/invite_members/init_invite_members_modal.js @@ -36,6 +36,7 @@ export default (function initInviteMembersModal() { isProject: parseBoolean(el.dataset.isProject), accessLevels: JSON.parse(el.dataset.accessLevels), defaultAccessLevel: parseInt(el.dataset.defaultAccessLevel, 10), + defaultMemberRoleId: parseInt(el.dataset.defaultMemberRoleId, 10) || null, usersFilter: el.dataset.usersFilter, filterId: parseInt(el.dataset.filterId, 10), usersLimitDataset: convertObjectPropsToCamelCase( |