Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3c0faf1c6b
commit
89a0c1fa66
2
Gemfile
2
Gemfile
|
|
@ -563,7 +563,7 @@ gem 'flipper', '~> 0.26.2' # rubocop:todo Gemfile/MissingFeatureCategory
|
|||
gem 'flipper-active_record', '~> 0.26.2' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'flipper-active_support_cache_store', '~> 0.26.2' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'unleash', '~> 3.2.2' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'gitlab-experiment', '~> 0.8.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'gitlab-experiment', '~> 0.9.1', feature_category: :shared
|
||||
|
||||
# Structured logging
|
||||
gem 'lograge', '~> 0.5' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
|
|
|||
|
|
@ -210,7 +210,7 @@
|
|||
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
|
||||
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
|
||||
{"name":"gitlab-dangerfiles","version":"4.6.0","platform":"ruby","checksum":"441b37b17d1dad36268517490a30aaf57e43dffb2e9ebc1da38d3bc9fa20741e"},
|
||||
{"name":"gitlab-experiment","version":"0.8.0","platform":"ruby","checksum":"b4e2f73e0af19cdd899a745f5a846c1318d44054e068a8f4ac887f6b1017d3f9"},
|
||||
{"name":"gitlab-experiment","version":"0.9.1","platform":"ruby","checksum":"f230ee742154805a755d5f2539dc44d93cdff08c5bbbb7656018d61f93d01f48"},
|
||||
{"name":"gitlab-fog-azure-rm","version":"1.8.0","platform":"ruby","checksum":"e4f24b174b273b88849d12fbcfecb79ae1c09f56cbd614998714c7f0a81e6c28"},
|
||||
{"name":"gitlab-labkit","version":"0.34.0","platform":"ruby","checksum":"ca5c504201390cd07ba1029e6ca3059f4e2e6005eb121ba8a103af1e166a3ecd"},
|
||||
{"name":"gitlab-license","version":"2.3.0","platform":"ruby","checksum":"60cae3871c46607dde58994faf761c6755adc61133a92e5ab59ab26a8b9b4157"},
|
||||
|
|
|
|||
|
|
@ -677,7 +677,7 @@ GEM
|
|||
danger (>= 9.3.0)
|
||||
danger-gitlab (>= 8.0.0)
|
||||
rake (~> 13.0)
|
||||
gitlab-experiment (0.8.0)
|
||||
gitlab-experiment (0.9.1)
|
||||
activesupport (>= 3.0)
|
||||
request_store (>= 1.0)
|
||||
gitlab-fog-azure-rm (1.8.0)
|
||||
|
|
@ -1875,7 +1875,7 @@ DEPENDENCIES
|
|||
gitlab-backup-cli!
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 4.6.0)
|
||||
gitlab-experiment (~> 0.8.0)
|
||||
gitlab-experiment (~> 0.9.1)
|
||||
gitlab-fog-azure-rm (~> 1.8.0)
|
||||
gitlab-http!
|
||||
gitlab-labkit (~> 0.34.0)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
calculateDeploymentStatus,
|
||||
calculateStatefulSetStatus,
|
||||
calculateDaemonSetStatus,
|
||||
} from '~/kubernetes_dashboard/helpers/k8s_integration_helper';
|
||||
import { STATUS_READY, STATUS_FAILED } from '~/kubernetes_dashboard/constants';
|
||||
import { CLUSTER_AGENT_ERROR_MESSAGES } from '../constants';
|
||||
|
|
@ -46,16 +47,10 @@ export function getDeploymentsStatuses(items) {
|
|||
|
||||
export function getDaemonSetStatuses(items) {
|
||||
const failed = items.filter((item) => {
|
||||
return (
|
||||
item.status?.numberMisscheduled > 0 ||
|
||||
item.status?.numberReady !== item.status?.desiredNumberScheduled
|
||||
);
|
||||
return calculateDaemonSetStatus(item) === STATUS_FAILED;
|
||||
});
|
||||
const ready = items.filter((item) => {
|
||||
return (
|
||||
item.status?.numberReady === item.status?.desiredNumberScheduled &&
|
||||
!item.status?.numberMisscheduled
|
||||
);
|
||||
return calculateDaemonSetStatus(item) === STATUS_READY;
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ class GfmAutoComplete {
|
|||
displayTpl({ name }) {
|
||||
const reviewState = REVIEW_STATES[name];
|
||||
|
||||
return `<li><span class="gl-font-weight-bold gl-display-block">${reviewState.header}</span><small class="description gl-display-block gl-w-full gl-float-left! gl-px-0!">${reviewState.description}</small></li>`;
|
||||
return `<li><span class="name gl-font-weight-bold">${reviewState.header}</span><small class="description"><em>${reviewState.description}</em></small></li>`;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import k8sPodsQuery from './queries/k8s_dashboard_pods.query.graphql';
|
|||
import k8sDeploymentsQuery from './queries/k8s_dashboard_deployments.query.graphql';
|
||||
import k8sStatefulSetsQuery from './queries/k8s_dashboard_stateful_sets.query.graphql';
|
||||
import k8sReplicaSetsQuery from './queries/k8s_dashboard_replica_sets.query.graphql';
|
||||
import k8sDaemonSetsQuery from './queries/k8s_dashboard_daemon_sets.query.graphql';
|
||||
import { resolvers } from './resolvers';
|
||||
|
||||
export const apolloProvider = () => {
|
||||
|
|
@ -83,6 +84,24 @@ export const apolloProvider = () => {
|
|||
},
|
||||
});
|
||||
|
||||
cache.writeQuery({
|
||||
query: k8sDaemonSetsQuery,
|
||||
data: {
|
||||
metadata: {
|
||||
name: null,
|
||||
namespace: null,
|
||||
creationTimestamp: null,
|
||||
labels: null,
|
||||
annotations: null,
|
||||
},
|
||||
status: {
|
||||
numberMisscheduled: null,
|
||||
numberReady: null,
|
||||
desiredNumberScheduled: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return new VueApollo({
|
||||
defaultClient,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
query getK8sDashboardDaemonSets($configuration: LocalConfiguration) {
|
||||
k8sDaemonSets(configuration: $configuration) @client {
|
||||
metadata {
|
||||
name
|
||||
namespace
|
||||
creationTimestamp
|
||||
labels
|
||||
annotations
|
||||
}
|
||||
status {
|
||||
numberMisscheduled
|
||||
numberReady
|
||||
desiredNumberScheduled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import k8sDashboardPodsQuery from '../queries/k8s_dashboard_pods.query.graphql';
|
|||
import k8sDashboardDeploymentsQuery from '../queries/k8s_dashboard_deployments.query.graphql';
|
||||
import k8sDashboardStatefulSetsQuery from '../queries/k8s_dashboard_stateful_sets.query.graphql';
|
||||
import k8sDashboardReplicaSetsQuery from '../queries/k8s_dashboard_replica_sets.query.graphql';
|
||||
import k8sDaemonSetsQuery from '../queries/k8s_dashboard_daemon_sets.query.graphql';
|
||||
|
||||
export default {
|
||||
k8sPods(_, { configuration }, { client }) {
|
||||
|
|
@ -129,4 +130,40 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
k8sDaemonSets(_, { configuration, namespace = '' }, { client }) {
|
||||
const config = new Configuration(configuration);
|
||||
|
||||
const appsV1api = new AppsV1Api(config);
|
||||
const deploymentsApi = namespace
|
||||
? appsV1api.listAppsV1NamespacedDaemonSet({ namespace })
|
||||
: appsV1api.listAppsV1DaemonSetForAllNamespaces();
|
||||
return deploymentsApi
|
||||
.then((res) => {
|
||||
const watchPath = buildWatchPath({
|
||||
resource: 'daemonsets',
|
||||
api: 'apis/apps/v1',
|
||||
namespace,
|
||||
});
|
||||
watchWorkloadItems({
|
||||
client,
|
||||
query: k8sDaemonSetsQuery,
|
||||
configuration,
|
||||
namespace,
|
||||
watchPath,
|
||||
queryField: 'k8sDaemonSets',
|
||||
});
|
||||
|
||||
const data = res?.items || [];
|
||||
|
||||
return data.map(mapWorkloadItem);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
try {
|
||||
await handleClusterError(err);
|
||||
} catch (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -48,3 +48,13 @@ export function calculateStatefulSetStatus(item) {
|
|||
}
|
||||
return STATUS_FAILED;
|
||||
}
|
||||
|
||||
export function calculateDaemonSetStatus(item) {
|
||||
if (
|
||||
item.status?.numberReady === item.status?.desiredNumberScheduled &&
|
||||
!item.status?.numberMisscheduled
|
||||
) {
|
||||
return STATUS_READY;
|
||||
}
|
||||
return STATUS_FAILED;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import { getAge, calculateDaemonSetStatus } from '../helpers/k8s_integration_helper';
|
||||
import WorkloadLayout from '../components/workload_layout.vue';
|
||||
import k8sDaemonSetsQuery from '../graphql/queries/k8s_dashboard_daemon_sets.query.graphql';
|
||||
import { STATUS_FAILED, STATUS_READY, STATUS_LABELS } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WorkloadLayout,
|
||||
},
|
||||
inject: ['configuration'],
|
||||
apollo: {
|
||||
k8sDaemonSets: {
|
||||
query: k8sDaemonSetsQuery,
|
||||
variables() {
|
||||
return {
|
||||
configuration: this.configuration,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return (
|
||||
data?.k8sDaemonSets?.map((daemonSet) => {
|
||||
return {
|
||||
name: daemonSet.metadata?.name,
|
||||
namespace: daemonSet.metadata?.namespace,
|
||||
status: calculateDaemonSetStatus(daemonSet),
|
||||
age: getAge(daemonSet.metadata?.creationTimestamp),
|
||||
labels: daemonSet.metadata?.labels,
|
||||
annotations: daemonSet.metadata?.annotations,
|
||||
kind: s__('KubernetesDashboard|DaemonSet'),
|
||||
};
|
||||
}) || []
|
||||
);
|
||||
},
|
||||
error(err) {
|
||||
this.errorMessage = err?.message;
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
k8sDaemonSets: [],
|
||||
errorMessage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
daemonSetsStats() {
|
||||
return [
|
||||
{
|
||||
value: this.countDaemonSetsByStatus(STATUS_READY),
|
||||
title: STATUS_LABELS[STATUS_READY],
|
||||
},
|
||||
{
|
||||
value: this.countDaemonSetsByStatus(STATUS_FAILED),
|
||||
title: STATUS_LABELS[STATUS_FAILED],
|
||||
},
|
||||
];
|
||||
},
|
||||
loading() {
|
||||
return this.$apollo.queries.k8sDaemonSets.loading;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
countDaemonSetsByStatus(status) {
|
||||
const filteredDaemonSets = this.k8sDaemonSets.filter((item) => item.status === status) || [];
|
||||
|
||||
return filteredDaemonSets.length;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<workload-layout
|
||||
:loading="loading"
|
||||
:error-message="errorMessage"
|
||||
:stats="daemonSetsStats"
|
||||
:items="k8sDaemonSets"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -2,8 +2,10 @@ export const PODS_ROUTE_NAME = 'pods';
|
|||
export const DEPLOYMENTS_ROUTE_NAME = 'deployments';
|
||||
export const STATEFUL_SETS_ROUTE_NAME = 'statefulSets';
|
||||
export const REPLICA_SETS_ROUTE_NAME = 'replicaSets';
|
||||
export const DAEMON_SETS_ROUTE_NAME = 'daemonSets';
|
||||
|
||||
export const PODS_ROUTE_PATH = '/pods';
|
||||
export const DEPLOYMENTS_ROUTE_PATH = '/deployments';
|
||||
export const STATEFUL_SETS_ROUTE_PATH = '/statefulsets';
|
||||
export const REPLICA_SETS_ROUTE_PATH = '/replicasets';
|
||||
export const DAEMON_SETS_ROUTE_PATH = '/daemonsets';
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import PodsPage from '../pages/pods_page.vue';
|
|||
import DeploymentsPage from '../pages/deployments_page.vue';
|
||||
import StatefulSetsPage from '../pages/stateful_sets_page.vue';
|
||||
import ReplicaSetsPage from '../pages/replica_sets_page.vue';
|
||||
import DaemonSetsPage from '../pages/daemon_sets_page.vue';
|
||||
import {
|
||||
PODS_ROUTE_NAME,
|
||||
PODS_ROUTE_PATH,
|
||||
|
|
@ -12,6 +13,8 @@ import {
|
|||
STATEFUL_SETS_ROUTE_PATH,
|
||||
REPLICA_SETS_ROUTE_NAME,
|
||||
REPLICA_SETS_ROUTE_PATH,
|
||||
DAEMON_SETS_ROUTE_NAME,
|
||||
DAEMON_SETS_ROUTE_PATH,
|
||||
} from './constants';
|
||||
|
||||
export default [
|
||||
|
|
@ -47,4 +50,12 @@ export default [
|
|||
title: s__('KubernetesDashboard|ReplicaSets'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: DAEMON_SETS_ROUTE_NAME,
|
||||
path: DAEMON_SETS_ROUTE_PATH,
|
||||
component: DaemonSetsPage,
|
||||
meta: {
|
||||
title: s__('KubernetesDashboard|DaemonSets'),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -69,9 +69,15 @@ export default {
|
|||
|
||||
<template>
|
||||
<section>
|
||||
<div class="gl-lg-display-flex gl-flex-direction-row gl-justify-content-space-between gl-pt-5">
|
||||
<div
|
||||
class="gl-lg-display-flex gl-flex-direction-row gl-py-5"
|
||||
:class="{
|
||||
'gl-justify-content-space-between': showSyntaxOptions,
|
||||
'gl-justify-content-end': !showSyntaxOptions,
|
||||
}"
|
||||
>
|
||||
<template v-if="showSyntaxOptions">
|
||||
<div class="gl-pb-6">
|
||||
<div>
|
||||
<gl-button
|
||||
category="tertiary"
|
||||
variant="link"
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemTaskMutation from '../graphql/update_work_item_task.mutation.graphql';
|
||||
|
||||
export function getUpdateWorkItemMutation({ input, workItemParentId }) {
|
||||
let mutation = updateWorkItemMutation;
|
||||
|
||||
const variables = {
|
||||
input,
|
||||
};
|
||||
|
||||
if (workItemParentId) {
|
||||
mutation = updateWorkItemTaskMutation;
|
||||
variables.input = {
|
||||
id: workItemParentId,
|
||||
taskData: input,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
mutation,
|
||||
variables,
|
||||
};
|
||||
}
|
||||
|
|
@ -134,11 +134,6 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
workItemParentId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
workItemTypes: {
|
||||
|
|
@ -328,7 +323,6 @@ export default {
|
|||
:data-testid="$options.stateToggleTestId"
|
||||
:work-item-id="workItemId"
|
||||
:work-item-state="workItemState"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
:work-item-type="workItemType"
|
||||
show-as-dropdown-item
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import {
|
||||
sprintfWorkItem,
|
||||
WIDGET_TYPE_ASSIGNEES,
|
||||
WIDGET_TYPE_HEALTH_STATUS,
|
||||
WIDGET_TYPE_HIERARCHY,
|
||||
|
|
@ -51,11 +50,6 @@ export default {
|
|||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
workItemParentId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
workItemType() {
|
||||
|
|
@ -67,15 +61,6 @@ export default {
|
|||
canDelete() {
|
||||
return this.workItem?.userPermissions?.deleteWorkItem;
|
||||
},
|
||||
canSetWorkItemMetadata() {
|
||||
return this.workItem?.userPermissions?.setWorkItemMetadata;
|
||||
},
|
||||
canAssignUnassignUser() {
|
||||
return this.workItemAssignees && this.canSetWorkItemMetadata;
|
||||
},
|
||||
confidentialTooltip() {
|
||||
return sprintfWorkItem(this.$options.i18n.confidentialTooltip, this.workItemType);
|
||||
},
|
||||
workItemAssignees() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import {
|
|||
WIDGET_TYPE_DESCRIPTION,
|
||||
WIDGET_TYPE_AWARD_EMOJI,
|
||||
WIDGET_TYPE_HIERARCHY,
|
||||
WORK_ITEM_TYPE_VALUE_ISSUE,
|
||||
WORK_ITEM_TYPE_VALUE_OBJECTIVE,
|
||||
WIDGET_TYPE_NOTES,
|
||||
WIDGET_TYPE_LINKED_ITEMS,
|
||||
|
|
@ -25,7 +24,6 @@ import {
|
|||
|
||||
import workItemUpdatedSubscription from '../graphql/work_item_updated.subscription.graphql';
|
||||
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemTaskMutation from '../graphql/update_work_item_task.mutation.graphql';
|
||||
import groupWorkItemByIidQuery from '../graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
|
||||
import { findHierarchyWidgetChildren } from '../utils';
|
||||
|
|
@ -83,11 +81,6 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
workItemParentId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -163,9 +156,6 @@ export default {
|
|||
workItemTypeId() {
|
||||
return this.workItem.workItemType?.id;
|
||||
},
|
||||
workItemBreadcrumbReference() {
|
||||
return this.workItemType ? `#${this.workItem.iid}` : '';
|
||||
},
|
||||
canUpdate() {
|
||||
return this.workItem.userPermissions?.updateWorkItem;
|
||||
},
|
||||
|
|
@ -184,26 +174,9 @@ export default {
|
|||
parentWorkItem() {
|
||||
return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY)?.parent;
|
||||
},
|
||||
parentWorkItemType() {
|
||||
return this.parentWorkItem?.workItemType?.name;
|
||||
},
|
||||
parentWorkItemIconName() {
|
||||
return this.parentWorkItem?.workItemType?.iconName;
|
||||
},
|
||||
parentWorkItemConfidentiality() {
|
||||
return this.parentWorkItem?.confidential;
|
||||
},
|
||||
parentWorkItemReference() {
|
||||
return this.parentWorkItem ? `${this.parentWorkItem.title} #${this.parentWorkItem.iid}` : '';
|
||||
},
|
||||
parentUrl() {
|
||||
// Once more types are moved to have Work Items involved
|
||||
// we need to handle this properly.
|
||||
if (this.parentWorkItemType === WORK_ITEM_TYPE_VALUE_ISSUE) {
|
||||
return `../../-/issues/${this.parentWorkItem?.iid}`;
|
||||
}
|
||||
return this.parentWorkItem?.webUrl;
|
||||
},
|
||||
workItemIconName() {
|
||||
return this.workItem.workItemType?.iconName;
|
||||
},
|
||||
|
|
@ -290,34 +263,21 @@ export default {
|
|||
},
|
||||
toggleConfidentiality(confidentialStatus) {
|
||||
this.updateInProgress = true;
|
||||
let updateMutation = updateWorkItemMutation;
|
||||
let inputVariables = {
|
||||
id: this.workItem.id,
|
||||
confidential: confidentialStatus,
|
||||
};
|
||||
|
||||
if (this.parentWorkItem) {
|
||||
updateMutation = updateWorkItemTaskMutation;
|
||||
inputVariables = {
|
||||
id: this.parentWorkItem.id,
|
||||
taskData: {
|
||||
id: this.workItem.id,
|
||||
confidential: confidentialStatus,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: updateMutation,
|
||||
mutation: updateWorkItemMutation,
|
||||
variables: {
|
||||
input: inputVariables,
|
||||
input: {
|
||||
id: this.workItem.id,
|
||||
confidential: confidentialStatus,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then(
|
||||
({
|
||||
data: {
|
||||
workItemUpdate: { errors, workItem, task },
|
||||
workItemUpdate: { errors, workItem },
|
||||
},
|
||||
}) => {
|
||||
if (errors?.length) {
|
||||
|
|
@ -325,7 +285,7 @@ export default {
|
|||
}
|
||||
|
||||
this.$emit('workItemUpdated', {
|
||||
confidential: workItem?.confidential || task?.confidential,
|
||||
confidential: workItem?.confidential,
|
||||
});
|
||||
},
|
||||
)
|
||||
|
|
@ -435,7 +395,6 @@ export default {
|
|||
:work-item-id="workItem.id"
|
||||
:work-item-title="workItem.title"
|
||||
:work-item-type="workItemType"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
:can-update="canUpdate"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
|
|
@ -465,7 +424,6 @@ export default {
|
|||
:work-item-create-note-email="workItem.createNoteEmail"
|
||||
:is-modal="isModal"
|
||||
:work-item-state="workItem.state"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
@deleteWorkItem="$emit('deleteWorkItem', { workItemType, workItemId: workItem.id })"
|
||||
@toggleWorkItemConfidentiality="toggleConfidentiality"
|
||||
@error="updateError = $event"
|
||||
|
|
@ -490,7 +448,6 @@ export default {
|
|||
:work-item-id="workItem.id"
|
||||
:work-item-title="workItem.title"
|
||||
:work-item-type="workItemType"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
:can-update="canUpdate"
|
||||
:use-h1="!isModal"
|
||||
@error="updateError = $event"
|
||||
|
|
@ -511,7 +468,6 @@ export default {
|
|||
:is-modal="isModal"
|
||||
:work-item="workItem"
|
||||
:is-sticky-header-showing="isStickyHeaderShowing"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
:work-item-notifications-subscribed="workItemNotificationsSubscribed"
|
||||
@hideStickyHeader="hideStickyHeader"
|
||||
@showStickyHeader="showStickyHeader"
|
||||
|
|
@ -530,7 +486,6 @@ export default {
|
|||
class="gl-border-b"
|
||||
:full-path="fullPath"
|
||||
:work-item="workItem"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
<work-item-description
|
||||
|
|
@ -605,7 +560,6 @@ export default {
|
|||
<work-item-attributes-wrapper
|
||||
:full-path="fullPath"
|
||||
:work-item="workItem"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
@error="updateError = $event"
|
||||
/>
|
||||
</aside>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { GlButton, GlDisclosureDropdownItem, GlLoadingIcon } from '@gitlab/ui';
|
|||
import * as Sentry from '~/sentry/sentry_browser_wrapper';
|
||||
import Tracking from '~/tracking';
|
||||
import { __ } from '~/locale';
|
||||
import { getUpdateWorkItemMutation } from '~/work_items/components/update_work_item';
|
||||
import {
|
||||
sprintfWorkItem,
|
||||
I18N_WORK_ITEM_ERROR_UPDATING,
|
||||
|
|
@ -12,6 +11,7 @@ import {
|
|||
STATE_EVENT_REOPEN,
|
||||
TRACKING_CATEGORY_SHOW,
|
||||
} from '../constants';
|
||||
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
@ -33,11 +33,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
workItemParentId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
showAsDropdownItem: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
@ -75,24 +70,19 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
async updateWorkItem() {
|
||||
const input = {
|
||||
id: this.workItemId,
|
||||
stateEvent: this.isWorkItemOpen ? STATE_EVENT_CLOSE : STATE_EVENT_REOPEN,
|
||||
};
|
||||
|
||||
this.updateInProgress = true;
|
||||
|
||||
try {
|
||||
this.track('updated_state');
|
||||
|
||||
const { mutation, variables } = getUpdateWorkItemMutation({
|
||||
workItemParentId: this.workItemParentId,
|
||||
input,
|
||||
});
|
||||
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation,
|
||||
variables,
|
||||
mutation: updateWorkItemMutation,
|
||||
variables: {
|
||||
input: {
|
||||
id: this.workItemId,
|
||||
stateEvent: this.isWorkItemOpen ? STATE_EVENT_CLOSE : STATE_EVENT_REOPEN,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const errors = data.workItemUpdate?.errors;
|
||||
|
|
@ -102,7 +92,6 @@ export default {
|
|||
}
|
||||
} catch (error) {
|
||||
const msg = sprintfWorkItem(I18N_WORK_ITEM_ERROR_UPDATING, this.workItemType);
|
||||
|
||||
this.$emit('error', msg);
|
||||
Sentry.captureException(error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,11 +30,6 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
workItemParentId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
updateInProgress: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
@ -126,7 +121,6 @@ export default {
|
|||
:work-item-reference="workItem.reference"
|
||||
:work-item-create-note-email="workItem.createNoteEmail"
|
||||
:work-item-state="workItem.state"
|
||||
:work-item-parent-id="workItemParentId"
|
||||
:is-modal="isModal"
|
||||
@deleteWorkItem="$emit('deleteWorkItem')"
|
||||
@toggleWorkItemConfidentiality="
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
WORK_ITEM_TITLE_MAX_LENGTH,
|
||||
I18N_MAX_CHARS_IN_WORK_ITEM_TITLE_MESSAGE,
|
||||
} from '../constants';
|
||||
import { getUpdateWorkItemMutation } from './update_work_item';
|
||||
import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql';
|
||||
import ItemTitle from './item_title.vue';
|
||||
|
||||
export default {
|
||||
|
|
@ -32,11 +32,6 @@ export default {
|
|||
required: false,
|
||||
default: '',
|
||||
},
|
||||
workItemParentId: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
canUpdate: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
@ -68,24 +63,19 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
const input = {
|
||||
id: this.workItemId,
|
||||
title: updatedTitle,
|
||||
};
|
||||
|
||||
this.updateInProgress = true;
|
||||
|
||||
try {
|
||||
this.track('updated_title');
|
||||
|
||||
const { mutation, variables } = getUpdateWorkItemMutation({
|
||||
workItemParentId: this.workItemParentId,
|
||||
input,
|
||||
});
|
||||
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation,
|
||||
variables,
|
||||
mutation: updateWorkItemMutation,
|
||||
variables: {
|
||||
input: {
|
||||
id: this.workItemId,
|
||||
title: updatedTitle,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const errors = data.workItemUpdate?.errors;
|
||||
|
|
|
|||
|
|
@ -54,9 +54,6 @@ export const i18n = {
|
|||
"WorkItem|This work item is not available. It either doesn't exist or you don't have permission to view it.",
|
||||
),
|
||||
updateError: s__('WorkItem|Something went wrong while updating the work item. Please try again.'),
|
||||
confidentialTooltip: s__(
|
||||
'WorkItem|Only project members with at least the Reporter role, the author, and assignees can view or be notified about this %{workItemType}.',
|
||||
),
|
||||
};
|
||||
|
||||
export const I18N_WORK_ITEM_ERROR_FETCHING_LABELS = s__(
|
||||
|
|
|
|||
|
|
@ -1,13 +0,0 @@
|
|||
#import "./work_item.fragment.graphql"
|
||||
|
||||
mutation workItemCreateFromTask($input: WorkItemCreateFromTaskInput!) {
|
||||
workItemCreateFromTask(input: $input) {
|
||||
workItem {
|
||||
...WorkItem
|
||||
}
|
||||
newWorkItem {
|
||||
...WorkItem
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
#import "./work_item.fragment.graphql"
|
||||
|
||||
mutation workItemUpdateTask($input: WorkItemUpdateTaskInput!) {
|
||||
workItemUpdate: workItemUpdateTask(input: $input) {
|
||||
errors
|
||||
workItem {
|
||||
id
|
||||
descriptionHtml
|
||||
}
|
||||
task {
|
||||
...WorkItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -234,7 +234,7 @@
|
|||
color: $skype;
|
||||
}
|
||||
|
||||
.twitter-icon {
|
||||
.x-icon {
|
||||
color: var(--gl-text-color, $gl-text-color);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,24 +65,7 @@ class Issue < ApplicationRecord
|
|||
belongs_to :moved_to, class_name: 'Issue', inverse_of: :moved_from
|
||||
has_one :moved_from, class_name: 'Issue', foreign_key: :moved_to_id, inverse_of: :moved_to
|
||||
|
||||
has_internal_id :iid, scope: :namespace, track_if: -> { !importing? }, init: ->(issue, scope) do
|
||||
# we need this init for the case where the IID allocation in internal_ids#last_value
|
||||
# is higher than the actual issues.max(iid) value for a given project. For instance
|
||||
# in case of an import where a batch of IIDs may be prealocated
|
||||
#
|
||||
# TODO: remove this once the UpdateIssuesInternalIdScope migration completes
|
||||
if issue
|
||||
[
|
||||
InternalId.where(project: issue.project, usage: :issues).pick(:last_value).to_i,
|
||||
issue.namespace&.issues&.maximum(:iid).to_i
|
||||
].max
|
||||
else
|
||||
[
|
||||
InternalId.where(**scope, usage: :issues).pick(:last_value).to_i,
|
||||
where(**scope).maximum(:iid).to_i
|
||||
].max
|
||||
end
|
||||
end
|
||||
has_internal_id :iid, scope: :namespace, track_if: -> { !importing? }
|
||||
|
||||
has_many :events, as: :target, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
|
||||
- if Feature.enabled?(:restyle_login_page, @project) && Gitlab::CurrentSettings.current_application_settings.terms
|
||||
%p.gl-px-5
|
||||
= html_escape(s_("SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}.")) % { link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe,
|
||||
= html_escape(s_("SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}.")) % { link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe,
|
||||
link_end: '</a>'.html_safe }
|
||||
|
||||
- if allow_signup?
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@
|
|||
%p.gl-text-gray-500.gl-mt-5.gl-mb-0
|
||||
- if Feature.enabled?(:restyle_login_page, @project)
|
||||
- if Gitlab.com?
|
||||
= html_escape(s_("SignUp|By clicking %{button_text} or registering through a third party you accept the GitLab%{link_start} Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}")) % { button_text: button_text,
|
||||
= html_escape(s_("SignUp|By clicking %{button_text} or registering through a third party you accept the GitLab%{link_start} Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}")) % { button_text: button_text,
|
||||
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }
|
||||
- else
|
||||
= html_escape(s_("SignUp|By clicking %{button_text} or registering through a third party you accept the%{link_start} Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}")) % { button_text: button_text,
|
||||
= html_escape(s_("SignUp|By clicking %{button_text} or registering through a third party you accept the%{link_start} Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}")) % { button_text: button_text,
|
||||
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }
|
||||
- else
|
||||
- if Gitlab.com?
|
||||
= html_escape(s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted the GitLab %{link_start}Terms of Use and Privacy Policy%{link_end}")) % { button_text: button_text,
|
||||
= html_escape(s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted the GitLab %{link_start}Terms of Use and Privacy Statement%{link_end}")) % { button_text: button_text,
|
||||
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }
|
||||
- else
|
||||
= html_escape(s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted the %{link_start}Terms of Use and Privacy Policy%{link_end}")) % { button_text: button_text,
|
||||
= html_escape(s_("SignUp|By clicking %{button_text}, I agree that I have read and accepted the %{link_start}Terms of Use and Privacy Statement%{link_end}")) % { button_text: button_text,
|
||||
link_start: "<a href='#{terms_path}' target='_blank' rel='noreferrer noopener'>".html_safe, link_end: '</a>'.html_safe }
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@
|
|||
- if @user.twitter.present?
|
||||
= render 'middle_dot_divider', breakpoint: 'sm' do
|
||||
= link_to twitter_url(@user), class: 'gl-hover-text-decoration-none', title: _("X (formerly Twitter)"), target: '_blank', rel: 'noopener noreferrer nofollow' do
|
||||
= sprite_icon('twitter', css_class: 'twitter-icon')
|
||||
= sprite_icon('x', css_class: 'x-icon')
|
||||
- if @user.discord.present?
|
||||
= render 'middle_dot_divider', breakpoint: 'sm' do
|
||||
= link_to discord_url(@user), class: 'gl-hover-text-decoration-none', title: "Discord", target: '_blank', rel: 'noopener noreferrer nofollow' do
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: gitlab_duo_chat_requests_to_ai_gateway
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138274
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433213
|
||||
milestone: '16.7'
|
||||
type: development
|
||||
group: group::ai framework
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddSourcePackageNameToSbomComponentVersions < Gitlab::Database::Migration[2.2]
|
||||
disable_ddl_transaction!
|
||||
|
||||
milestone '16.7'
|
||||
|
||||
def up
|
||||
with_lock_retries do
|
||||
add_column :sbom_component_versions, :source_package_name, :text, if_not_exists: true
|
||||
end
|
||||
|
||||
add_text_limit :sbom_component_versions, :source_package_name, 255
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
remove_column :sbom_component_versions, :source_package_name, if_exists: true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
557e640d30119599a2ca50cbe2b4e36f01b888df5a4679de362ae000ee23072b
|
||||
|
|
@ -23117,6 +23117,8 @@ CREATE TABLE sbom_component_versions (
|
|||
updated_at timestamp with time zone NOT NULL,
|
||||
component_id bigint NOT NULL,
|
||||
version text NOT NULL,
|
||||
source_package_name text,
|
||||
CONSTRAINT check_39636b9a8a CHECK ((char_length(source_package_name) <= 255)),
|
||||
CONSTRAINT check_e71cad08d3 CHECK ((char_length(version) <= 255))
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -860,7 +860,7 @@ component under test, with the `computed` property, for example). Remember to us
|
|||
We should test for events emitted in response to an action in our component. This testing
|
||||
verifies the correct events are being fired with the correct arguments.
|
||||
|
||||
For any DOM events we should use [`trigger`](https://v1.test-utils.vuejs.org/api/wrapper/#trigger)
|
||||
For any native DOM events we should use [`trigger`](https://v1.test-utils.vuejs.org/api/wrapper/#trigger)
|
||||
to fire out event.
|
||||
|
||||
```javascript
|
||||
|
|
@ -892,6 +892,20 @@ it('should fire the itemClicked event', () => {
|
|||
We should verify an event has been fired by asserting against the result of the
|
||||
[`emitted()`](https://v1.test-utils.vuejs.org/api/wrapper/#emitted) method.
|
||||
|
||||
It is a good practice to prefer to use `vm.$emit` over `trigger` when emitting events from child components.
|
||||
|
||||
Using `trigger` on the component means we treat it as a white box: we assume that the root element of child component has a native `click` event. Also, some tests fail in Vue3 mode when using `trigger` on child components.
|
||||
|
||||
```javascript
|
||||
const findButton = () => wrapper.findComponent(GlButton);
|
||||
|
||||
// bad
|
||||
findButton().trigger('click');
|
||||
|
||||
// good
|
||||
findButton().vm.$emit('click');
|
||||
```
|
||||
|
||||
## Vue.js Expert Role
|
||||
|
||||
You should only apply to be a Vue.js expert when your own merge requests and your reviews show:
|
||||
|
|
|
|||
|
|
@ -54,11 +54,6 @@ Only GitLab administrators can change enterprise users' primary email address to
|
|||
|
||||
Providing the ability to group Owners to change their enterprise users' primary email to an email with a non-verified domain is proposed in [issue 412966](https://gitlab.com/gitlab-org/gitlab/-/issues/412966).
|
||||
|
||||
## Dissociation of the user from their enterprise group
|
||||
|
||||
Changing an enterprise user's primary email to an email with a non-verified domain automatically disassociates them from their enterprise group.
|
||||
However, there are [primary email change restrictions](#primary-email-change).
|
||||
|
||||
## Verified domains for groups
|
||||
|
||||
The following automated processes use [verified domains](../project/pages/custom_domains_ssl_tls_certification/index.md) to run:
|
||||
|
|
@ -213,6 +208,10 @@ this information includes users' email addresses.
|
|||
|
||||
[Issue 391453](https://gitlab.com/gitlab-org/gitlab/-/issues/391453) proposes to change the criteria for access to email addresses from provisioned users to enterprise users.
|
||||
|
||||
### Remove enterprise management features from an account
|
||||
|
||||
Changing an enterprise user's primary email to any email with a non-verified domain automatically removes the enterprise badge from the account. This does not alter any account roles or permissions for the user, but does limit the group Owner's ability to manage this account.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Cannot disable two-factor authentication for an enterprise user
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ module Bitbucket
|
|||
merge_commit_sha: merge_commit_sha,
|
||||
target_branch_name: target_branch_name,
|
||||
target_branch_sha: target_branch_sha,
|
||||
source_and_target_project_different: source_and_target_project_different,
|
||||
reviewers: reviewers
|
||||
}
|
||||
end
|
||||
|
|
@ -89,6 +90,18 @@ module Bitbucket
|
|||
def target_branch
|
||||
raw['destination']
|
||||
end
|
||||
|
||||
def source_repo_uuid
|
||||
source_branch&.dig('repository', 'uuid')
|
||||
end
|
||||
|
||||
def target_repo_uuid
|
||||
target_branch&.dig('repository', 'uuid')
|
||||
end
|
||||
|
||||
def source_and_target_project_different
|
||||
source_repo_uuid != target_repo_uuid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ module Gitlab
|
|||
end
|
||||
|
||||
def execute
|
||||
return if skip
|
||||
|
||||
log_info(import_stage: 'import_pull_request', message: 'starting', iid: object[:iid])
|
||||
|
||||
description = ''
|
||||
|
|
@ -58,6 +60,15 @@ module Gitlab
|
|||
|
||||
attr_reader :object, :project, :formatter, :user_finder
|
||||
|
||||
def skip
|
||||
return false unless object[:source_and_target_project_different]
|
||||
|
||||
message = 'skipping because source and target projects are different'
|
||||
log_info(import_stage: 'import_pull_request', message: message, iid: object[:iid])
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
def author_line
|
||||
return '' if find_user_id
|
||||
|
||||
|
|
|
|||
|
|
@ -27874,6 +27874,12 @@ msgstr ""
|
|||
msgid "KubernetesDashboard|Annotations"
|
||||
msgstr ""
|
||||
|
||||
msgid "KubernetesDashboard|DaemonSet"
|
||||
msgstr ""
|
||||
|
||||
msgid "KubernetesDashboard|DaemonSets"
|
||||
msgstr ""
|
||||
|
||||
msgid "KubernetesDashboard|Dashboard"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -45646,19 +45652,19 @@ msgstr ""
|
|||
msgid "Sign-up restrictions"
|
||||
msgstr ""
|
||||
|
||||
msgid "SignUp|By clicking %{button_text} or registering through a third party you accept the GitLab%{link_start} Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}"
|
||||
msgid "SignUp|By clicking %{button_text} or registering through a third party you accept the GitLab%{link_start} Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "SignUp|By clicking %{button_text} or registering through a third party you accept the%{link_start} Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}"
|
||||
msgid "SignUp|By clicking %{button_text} or registering through a third party you accept the%{link_start} Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "SignUp|By clicking %{button_text}, I agree that I have read and accepted the %{link_start}Terms of Use and Privacy Policy%{link_end}"
|
||||
msgid "SignUp|By clicking %{button_text}, I agree that I have read and accepted the %{link_start}Terms of Use and Privacy Statement%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "SignUp|By clicking %{button_text}, I agree that I have read and accepted the GitLab %{link_start}Terms of Use and Privacy Policy%{link_end}"
|
||||
msgid "SignUp|By clicking %{button_text}, I agree that I have read and accepted the GitLab %{link_start}Terms of Use and Privacy Statement%{link_end}"
|
||||
msgstr ""
|
||||
|
||||
msgid "SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Policy and Cookie Policy%{link_end}."
|
||||
msgid "SignUp|By signing in you accept the %{link_start}Terms of Use and acknowledge the Privacy Statement and Cookie Policy%{link_end}."
|
||||
msgstr ""
|
||||
|
||||
msgid "SignUp|First name is too long (maximum is %{max_length} characters)."
|
||||
|
|
@ -55129,9 +55135,6 @@ msgstr ""
|
|||
msgid "WorkItem|Only %{MAX_WORK_ITEMS} items can be added at a time."
|
||||
msgstr ""
|
||||
|
||||
msgid "WorkItem|Only project members with at least the Reporter role, the author, and assignees can view or be notified about this %{workItemType}."
|
||||
msgstr ""
|
||||
|
||||
msgid "WorkItem|Open"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -227,6 +227,8 @@ function handle_retry_rspec_in_new_process() {
|
|||
}
|
||||
|
||||
function rspec_paralellized_job() {
|
||||
echo "[$(date '+%H:%M:%S')] Starting rspec_paralellized_job"
|
||||
|
||||
read -ra job_name <<< "${CI_JOB_NAME}"
|
||||
local test_tool="${job_name[0]}"
|
||||
local test_level="${job_name[1]}"
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ RSpec.describe 'Signup', :js, feature_category: :user_management do
|
|||
let(:terms_text) do
|
||||
<<~TEXT.squish
|
||||
By clicking Register or registering through a third party you accept the
|
||||
Terms of Use and acknowledge the Privacy Policy and Cookie Policy
|
||||
Terms of Use and acknowledge the Privacy Statement and Cookie Policy
|
||||
TEXT
|
||||
end
|
||||
|
||||
|
|
@ -383,7 +383,7 @@ RSpec.describe 'Signup', :js, feature_category: :user_management do
|
|||
let(:terms_text) do
|
||||
<<~TEXT.squish
|
||||
By clicking Register, I agree that I have read and accepted the Terms of
|
||||
Use and Privacy Policy
|
||||
Use and Privacy Statement
|
||||
TEXT
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -295,3 +295,59 @@ export const k8sReplicaSetsMock = [readyStatefulSet, readyStatefulSet, failedSta
|
|||
export const mockReplicaSetsTableItems = mockStatefulSetsTableItems.map((item) => {
|
||||
return { ...item, kind: 'ReplicaSet' };
|
||||
});
|
||||
|
||||
const readyDaemonSet = {
|
||||
status: { numberMisscheduled: 0, numberReady: 2, desiredNumberScheduled: 2 },
|
||||
metadata: {
|
||||
name: 'daemonSet-1',
|
||||
namespace: 'default',
|
||||
creationTimestamp: '2023-07-31T11:50:17Z',
|
||||
labels: {},
|
||||
annotations: {},
|
||||
},
|
||||
};
|
||||
|
||||
const failedDaemonSet = {
|
||||
status: { numberMisscheduled: 1, numberReady: 1, desiredNumberScheduled: 2 },
|
||||
metadata: {
|
||||
name: 'daemonSet-2',
|
||||
namespace: 'default',
|
||||
creationTimestamp: '2023-11-21T11:50:59Z',
|
||||
labels: {},
|
||||
annotations: {},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockDaemonSetsStats = [
|
||||
{
|
||||
title: 'Ready',
|
||||
value: 1,
|
||||
},
|
||||
{
|
||||
title: 'Failed',
|
||||
value: 1,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockDaemonSetsTableItems = [
|
||||
{
|
||||
name: 'daemonSet-1',
|
||||
namespace: 'default',
|
||||
status: 'Ready',
|
||||
age: '114d',
|
||||
labels: {},
|
||||
annotations: {},
|
||||
kind: 'DaemonSet',
|
||||
},
|
||||
{
|
||||
name: 'daemonSet-2',
|
||||
namespace: 'default',
|
||||
status: 'Failed',
|
||||
age: '1d',
|
||||
labels: {},
|
||||
annotations: {},
|
||||
kind: 'DaemonSet',
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sDaemonSetsMock = [readyDaemonSet, failedDaemonSet];
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import k8sDashboardPodsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_da
|
|||
import k8sDashboardDeploymentsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_deployments.query.graphql';
|
||||
import k8sDashboardStatefulSetsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_stateful_sets.query.graphql';
|
||||
import k8sDashboardReplicaSetsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_replica_sets.query.graphql';
|
||||
import k8sDashboardDaemonSetsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_daemon_sets.query.graphql';
|
||||
import {
|
||||
k8sPodsMock,
|
||||
k8sDeploymentsMock,
|
||||
k8sStatefulSetsMock,
|
||||
k8sReplicaSetsMock,
|
||||
k8sDaemonSetsMock,
|
||||
} from '../mock_data';
|
||||
|
||||
describe('~/frontend/environments/graphql/resolvers', () => {
|
||||
|
|
@ -370,4 +372,88 @@ describe('~/frontend/environments/graphql/resolvers', () => {
|
|||
).rejects.toThrow('API error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('k8sDaemonSets', () => {
|
||||
const client = { writeQuery: jest.fn() };
|
||||
|
||||
const mockWatcher = WatchApi.prototype;
|
||||
const mockDaemonSetsListWatcherFn = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve(mockWatcher);
|
||||
});
|
||||
|
||||
const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => {
|
||||
if (eventName === 'data') {
|
||||
callback([]);
|
||||
}
|
||||
});
|
||||
|
||||
const mockDaemonSetsListFn = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
items: k8sDaemonSetsMock,
|
||||
});
|
||||
});
|
||||
|
||||
const mockAllDaemonSetsListFn = jest.fn().mockImplementation(mockDaemonSetsListFn);
|
||||
|
||||
describe('when the DaemonSets data is present', () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(AppsV1Api.prototype, 'listAppsV1DaemonSetForAllNamespaces')
|
||||
.mockImplementation(mockAllDaemonSetsListFn);
|
||||
jest
|
||||
.spyOn(mockWatcher, 'subscribeToStream')
|
||||
.mockImplementation(mockDaemonSetsListWatcherFn);
|
||||
jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn);
|
||||
});
|
||||
|
||||
it('should request all DaemonSets from the cluster_client library and watch the events', async () => {
|
||||
const DaemonSets = await mockResolvers.Query.k8sDaemonSets(
|
||||
null,
|
||||
{
|
||||
configuration,
|
||||
},
|
||||
{ client },
|
||||
);
|
||||
|
||||
expect(mockAllDaemonSetsListFn).toHaveBeenCalled();
|
||||
expect(mockDaemonSetsListWatcherFn).toHaveBeenCalled();
|
||||
|
||||
expect(DaemonSets).toEqual(k8sDaemonSetsMock);
|
||||
});
|
||||
|
||||
it('should update cache with the new data when received from the library', async () => {
|
||||
await mockResolvers.Query.k8sDaemonSets(null, { configuration, namespace: '' }, { client });
|
||||
|
||||
expect(client.writeQuery).toHaveBeenCalledWith({
|
||||
query: k8sDashboardDaemonSetsQuery,
|
||||
variables: { configuration, namespace: '' },
|
||||
data: { k8sDaemonSets: [] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not watch DaemonSets from the cluster_client library when the DaemonSets data is not present', async () => {
|
||||
jest.spyOn(AppsV1Api.prototype, 'listAppsV1DaemonSetForAllNamespaces').mockImplementation(
|
||||
jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
items: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
await mockResolvers.Query.k8sDaemonSets(null, { configuration }, { client });
|
||||
|
||||
expect(mockDaemonSetsListWatcherFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw an error if the API call fails', async () => {
|
||||
jest
|
||||
.spyOn(AppsV1Api.prototype, 'listAppsV1DaemonSetForAllNamespaces')
|
||||
.mockRejectedValue(new Error('API error'));
|
||||
|
||||
await expect(
|
||||
mockResolvers.Query.k8sDaemonSets(null, { configuration }, { client }),
|
||||
).rejects.toThrow('API error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
getAge,
|
||||
calculateDeploymentStatus,
|
||||
calculateStatefulSetStatus,
|
||||
calculateDaemonSetStatus,
|
||||
} from '~/kubernetes_dashboard/helpers/k8s_integration_helper';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
|
||||
|
|
@ -72,4 +73,21 @@ describe('k8s_integration_helper', () => {
|
|||
expect(calculateStatefulSetStatus(item)).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('calculateDaemonSetStatus', () => {
|
||||
const ready = {
|
||||
status: { numberMisscheduled: 0, numberReady: 2, desiredNumberScheduled: 2 },
|
||||
};
|
||||
const failed = {
|
||||
status: { numberMisscheduled: 1, numberReady: 1, desiredNumberScheduled: 2 },
|
||||
};
|
||||
|
||||
it.each`
|
||||
condition | item | expected
|
||||
${'there are less numberReady than desiredNumberScheduled or the numberMisscheduled is present'} | ${failed} | ${'Failed'}
|
||||
${'there are the same amount of numberReady and desiredNumberScheduled'} | ${ready} | ${'Ready'}
|
||||
`('returns status as $expected when $condition', ({ item, expected }) => {
|
||||
expect(calculateDaemonSetStatus(item)).toBe(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import DaemonSetsPage from '~/kubernetes_dashboard/pages/daemon_sets_page.vue';
|
||||
import WorkloadLayout from '~/kubernetes_dashboard/components/workload_layout.vue';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
import {
|
||||
k8sDaemonSetsMock,
|
||||
mockDaemonSetsStats,
|
||||
mockDaemonSetsTableItems,
|
||||
} from '../graphql/mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('Kubernetes dashboard daemonSets page', () => {
|
||||
let wrapper;
|
||||
|
||||
const configuration = {
|
||||
basePath: 'kas/tunnel/url',
|
||||
baseOptions: {
|
||||
headers: { 'GitLab-Agent-Id': '1' },
|
||||
},
|
||||
};
|
||||
|
||||
const findWorkloadLayout = () => wrapper.findComponent(WorkloadLayout);
|
||||
|
||||
const createApolloProvider = () => {
|
||||
const mockResolvers = {
|
||||
Query: {
|
||||
k8sDaemonSets: jest.fn().mockReturnValue(k8sDaemonSetsMock),
|
||||
},
|
||||
};
|
||||
|
||||
return createMockApollo([], mockResolvers);
|
||||
};
|
||||
|
||||
const createWrapper = (apolloProvider = createApolloProvider()) => {
|
||||
wrapper = shallowMount(DaemonSetsPage, {
|
||||
provide: { configuration },
|
||||
apolloProvider,
|
||||
});
|
||||
};
|
||||
|
||||
describe('mounted', () => {
|
||||
it('renders WorkloadLayout component', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(findWorkloadLayout().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('sets loading prop for the WorkloadLayout', () => {
|
||||
createWrapper();
|
||||
|
||||
expect(findWorkloadLayout().props('loading')).toBe(true);
|
||||
});
|
||||
|
||||
it('removes loading prop from the WorkloadLayout when the list of pods loaded', async () => {
|
||||
createWrapper();
|
||||
await waitForPromises();
|
||||
|
||||
expect(findWorkloadLayout().props('loading')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when gets pods data', () => {
|
||||
useFakeDate(2023, 10, 23, 10, 10);
|
||||
|
||||
it('sets correct stats object for the WorkloadLayout', async () => {
|
||||
createWrapper();
|
||||
await waitForPromises();
|
||||
|
||||
expect(findWorkloadLayout().props('stats')).toEqual(mockDaemonSetsStats);
|
||||
});
|
||||
|
||||
it('sets correct table items object for the WorkloadLayout', async () => {
|
||||
createWrapper();
|
||||
await waitForPromises();
|
||||
|
||||
expect(findWorkloadLayout().props('items')).toMatchObject(mockDaemonSetsTableItems);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when gets an error from the cluster_client API', () => {
|
||||
const error = new Error('Error from the cluster_client API');
|
||||
const createErroredApolloProvider = () => {
|
||||
const mockResolvers = {
|
||||
Query: {
|
||||
k8sDaemonSets: jest.fn().mockRejectedValueOnce(error),
|
||||
},
|
||||
};
|
||||
|
||||
return createMockApollo([], mockResolvers);
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
createWrapper(createErroredApolloProvider());
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('sets errorMessage prop for the WorkloadLayout', () => {
|
||||
expect(findWorkloadLayout().props('errorMessage')).toBe(error.message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import { GlDropdown, GlSearchBoxByType, GlDropdownItem } from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
|
||||
import { nextTick } from 'vue';
|
||||
import DropdownWidget from '~/vue_shared/components/dropdown/dropdown_widget/dropdown_widget.vue';
|
||||
import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
|
||||
|
||||
describe('DropdownWidget component', () => {
|
||||
let wrapper;
|
||||
|
|
@ -27,11 +27,14 @@ describe('DropdownWidget component', () => {
|
|||
...props,
|
||||
},
|
||||
stubs: {
|
||||
GlDropdown,
|
||||
GlDropdown: stubComponent(GlDropdown, {
|
||||
methods: {
|
||||
hide: jest.fn(),
|
||||
},
|
||||
template: RENDER_ALL_SLOTS_TEMPLATE,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
jest.spyOn(findDropdown().vm, 'hide').mockImplementation();
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -61,7 +61,6 @@ describe('WorkItemDetailModal component', () => {
|
|||
expect(findWorkItemDetail().props()).toEqual({
|
||||
isModal: true,
|
||||
workItemIid: '1',
|
||||
workItemParentId: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ import { i18n } from '~/work_items/constants';
|
|||
import groupWorkItemByIidQuery from '~/work_items/graphql/group_work_item_by_iid.query.graphql';
|
||||
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_task.mutation.graphql';
|
||||
import workItemUpdatedSubscription from '~/work_items/graphql/work_item_updated.subscription.graphql';
|
||||
|
||||
import {
|
||||
|
|
@ -89,7 +88,7 @@ describe('WorkItemDetail component', () => {
|
|||
updateInProgress = false,
|
||||
workItemIid = '1',
|
||||
handler = successHandler,
|
||||
confidentialityMock = [updateWorkItemMutation, jest.fn()],
|
||||
mutationHandler,
|
||||
error = undefined,
|
||||
workItemsMvc2Enabled = false,
|
||||
linkedWorkItemsEnabled = false,
|
||||
|
|
@ -98,8 +97,8 @@ describe('WorkItemDetail component', () => {
|
|||
apolloProvider: createMockApollo([
|
||||
[workItemByIidQuery, handler],
|
||||
[groupWorkItemByIidQuery, groupSuccessHandler],
|
||||
[updateWorkItemMutation, mutationHandler],
|
||||
[workItemUpdatedSubscription, workItemUpdatedSubscriptionHandler],
|
||||
confidentialityMock,
|
||||
]),
|
||||
isLoggedIn: isLoggedIn(),
|
||||
propsData: {
|
||||
|
|
@ -230,119 +229,52 @@ describe('WorkItemDetail component', () => {
|
|||
|
||||
describe('confidentiality', () => {
|
||||
const errorMessage = 'Mutation failed';
|
||||
const confidentialWorkItem = workItemByIidResponseFactory({
|
||||
confidential: true,
|
||||
});
|
||||
const workItem = confidentialWorkItem.data.workspace.workItems.nodes[0];
|
||||
|
||||
// Mocks for work item without parent
|
||||
const withoutParentExpectedInputVars = { id, confidential: true };
|
||||
const toggleConfidentialityWithoutParentHandler = jest.fn().mockResolvedValue({
|
||||
const confidentialWorkItem = workItemByIidResponseFactory({ confidential: true });
|
||||
const mutationHandler = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
workItemUpdate: {
|
||||
workItem,
|
||||
workItem: confidentialWorkItem.data.workspace.workItems.nodes[0],
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
const withoutParentHandlerMock = jest
|
||||
.fn()
|
||||
.mockResolvedValue(workItemQueryResponseWithoutParent);
|
||||
const confidentialityWithoutParentMock = [
|
||||
updateWorkItemMutation,
|
||||
toggleConfidentialityWithoutParentHandler,
|
||||
];
|
||||
const confidentialityWithoutParentFailureMock = [
|
||||
updateWorkItemMutation,
|
||||
jest.fn().mockRejectedValue(new Error(errorMessage)),
|
||||
];
|
||||
|
||||
// Mocks for work item with parent
|
||||
const withParentExpectedInputVars = {
|
||||
id: mockParent.parent.id,
|
||||
taskData: { id, confidential: true },
|
||||
};
|
||||
const toggleConfidentialityWithParentHandler = jest.fn().mockResolvedValue({
|
||||
data: {
|
||||
workItemUpdate: {
|
||||
workItem: {
|
||||
id: workItem.id,
|
||||
descriptionHtml: workItem.description,
|
||||
},
|
||||
task: {
|
||||
workItem,
|
||||
confidential: true,
|
||||
},
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
it('sends updateInProgress props to child component', async () => {
|
||||
createComponent({ mutationHandler });
|
||||
await waitForPromises();
|
||||
|
||||
findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true);
|
||||
await nextTick();
|
||||
|
||||
expect(findCreatedUpdated().props('updateInProgress')).toBe(true);
|
||||
});
|
||||
const confidentialityWithParentMock = [
|
||||
updateWorkItemTaskMutation,
|
||||
toggleConfidentialityWithParentHandler,
|
||||
];
|
||||
const confidentialityWithParentFailureMock = [
|
||||
updateWorkItemTaskMutation,
|
||||
jest.fn().mockRejectedValue(new Error(errorMessage)),
|
||||
];
|
||||
|
||||
describe.each`
|
||||
context | handlerMock | confidentialityMock | confidentialityFailureMock | inputVariables
|
||||
${'no parent'} | ${withoutParentHandlerMock} | ${confidentialityWithoutParentMock} | ${confidentialityWithoutParentFailureMock} | ${withoutParentExpectedInputVars}
|
||||
${'parent'} | ${successHandler} | ${confidentialityWithParentMock} | ${confidentialityWithParentFailureMock} | ${withParentExpectedInputVars}
|
||||
`(
|
||||
'when work item has $context',
|
||||
({ handlerMock, confidentialityMock, confidentialityFailureMock, inputVariables }) => {
|
||||
it('sends updateInProgress props to child component', async () => {
|
||||
createComponent({
|
||||
handler: handlerMock,
|
||||
confidentialityMock,
|
||||
});
|
||||
it('emits workItemUpdated when mutation is successful', async () => {
|
||||
createComponent({ mutationHandler });
|
||||
await waitForPromises();
|
||||
|
||||
await waitForPromises();
|
||||
findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true);
|
||||
await waitForPromises();
|
||||
|
||||
findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true);
|
||||
expect(wrapper.emitted('workItemUpdated')).toEqual([[{ confidential: true }]]);
|
||||
expect(mutationHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
id: 'gid://gitlab/WorkItem/1',
|
||||
confidential: true,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
it('shows an alert when mutation fails', async () => {
|
||||
createComponent({ mutationHandler: jest.fn().mockRejectedValue(new Error(errorMessage)) });
|
||||
await waitForPromises();
|
||||
|
||||
expect(findCreatedUpdated().props('updateInProgress')).toBe(true);
|
||||
});
|
||||
findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true);
|
||||
await waitForPromises();
|
||||
|
||||
it('emits workItemUpdated when mutation is successful', async () => {
|
||||
createComponent({
|
||||
handler: handlerMock,
|
||||
confidentialityMock,
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true);
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.emitted('workItemUpdated')).toEqual([[{ confidential: true }]]);
|
||||
expect(confidentialityMock[1]).toHaveBeenCalledWith({
|
||||
input: inputVariables,
|
||||
});
|
||||
});
|
||||
|
||||
it('shows an alert when mutation fails', async () => {
|
||||
createComponent({
|
||||
handler: handlerMock,
|
||||
confidentialityMock: confidentialityFailureMock,
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
findWorkItemActions().vm.$emit('toggleWorkItemConfidentiality', true);
|
||||
await waitForPromises();
|
||||
expect(wrapper.emitted('workItemUpdated')).toBeUndefined();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(findAlert().exists()).toBe(true);
|
||||
expect(findAlert().text()).toBe(errorMessage);
|
||||
});
|
||||
},
|
||||
);
|
||||
expect(wrapper.emitted('workItemUpdated')).toBeUndefined();
|
||||
expect(findAlert().text()).toBe(errorMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe('description', () => {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ describe('WorkItemStickyHeader', () => {
|
|||
fullPath: '/test',
|
||||
isStickyHeaderShowing: true,
|
||||
workItemNotificationsSubscribed: true,
|
||||
workItemParentId: null,
|
||||
updateInProgress: false,
|
||||
parentWorkItemConfidentiality: false,
|
||||
showWorkItemCurrentUserTodos: true,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import ItemTitle from '~/work_items/components/item_title.vue';
|
|||
import WorkItemTitle from '~/work_items/components/work_item_title.vue';
|
||||
import { TRACKING_CATEGORY_SHOW } from '~/work_items/constants';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
import updateWorkItemTaskMutation from '~/work_items/graphql/update_work_item_task.mutation.graphql';
|
||||
import { updateWorkItemMutationResponse, workItemQueryResponse } from '../mock_data';
|
||||
|
||||
describe('WorkItemTitle component', () => {
|
||||
|
|
@ -20,22 +19,14 @@ describe('WorkItemTitle component', () => {
|
|||
|
||||
const findItemTitle = () => wrapper.findComponent(ItemTitle);
|
||||
|
||||
const createComponent = ({
|
||||
workItemParentId,
|
||||
mutationHandler = mutationSuccessHandler,
|
||||
canUpdate = true,
|
||||
} = {}) => {
|
||||
const createComponent = ({ mutationHandler = mutationSuccessHandler, canUpdate = true } = {}) => {
|
||||
const { id, title, workItemType } = workItemQueryResponse.data.workItem;
|
||||
wrapper = shallowMount(WorkItemTitle, {
|
||||
apolloProvider: createMockApollo([
|
||||
[updateWorkItemMutation, mutationHandler],
|
||||
[updateWorkItemTaskMutation, mutationHandler],
|
||||
]),
|
||||
apolloProvider: createMockApollo([[updateWorkItemMutation, mutationHandler]]),
|
||||
propsData: {
|
||||
workItemId: id,
|
||||
workItemTitle: title,
|
||||
workItemType: workItemType.name,
|
||||
workItemParentId,
|
||||
canUpdate,
|
||||
},
|
||||
});
|
||||
|
|
@ -77,27 +68,6 @@ describe('WorkItemTitle component', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('calls WorkItemTaskUpdate if passed workItemParentId prop', () => {
|
||||
const title = 'new title!';
|
||||
const workItemParentId = '1234';
|
||||
|
||||
createComponent({
|
||||
workItemParentId,
|
||||
});
|
||||
|
||||
findItemTitle().vm.$emit('title-changed', title);
|
||||
|
||||
expect(mutationSuccessHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
id: workItemParentId,
|
||||
taskData: {
|
||||
id: workItemQueryResponse.data.workItem.id,
|
||||
title,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call a mutation when the title has not changed', () => {
|
||||
createComponent();
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import CreateWorkItem from '~/work_items/pages/create_work_item.vue';
|
|||
import ItemTitle from '~/work_items/components/item_title.vue';
|
||||
import projectWorkItemTypesQuery from '~/work_items/graphql/project_work_item_types.query.graphql';
|
||||
import createWorkItemMutation from '~/work_items/graphql/create_work_item.mutation.graphql';
|
||||
import createWorkItemFromTaskMutation from '~/work_items/graphql/create_work_item_from_task.mutation.graphql';
|
||||
import { projectWorkItemTypesQueryResponse, createWorkItemMutationResponse } from '../mock_data';
|
||||
|
||||
jest.mock('~/lib/utils/uuids', () => ({ uuids: () => ['testuuid'] }));
|
||||
|
|
@ -42,7 +41,6 @@ describe('Create work item component', () => {
|
|||
[
|
||||
[projectWorkItemTypesQuery, queryHandler],
|
||||
[createWorkItemMutation, mutationHandler],
|
||||
[createWorkItemFromTaskMutation, mutationHandler],
|
||||
],
|
||||
{},
|
||||
{ typePolicies: { Project: { merge: true } } },
|
||||
|
|
|
|||
|
|
@ -49,7 +49,6 @@ describe('Work items root component', () => {
|
|||
|
||||
expect(findWorkItemDetail().props()).toEqual({
|
||||
isModal: false,
|
||||
workItemParentId: null,
|
||||
workItemIid: '1',
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -74,11 +74,13 @@ RSpec.describe Bitbucket::Representation::PullRequest, feature_category: :import
|
|||
'title' => 'title',
|
||||
'source' => {
|
||||
'branch' => { 'name' => 'source-branch-name' },
|
||||
'commit' => { 'hash' => 'source-commit-hash' }
|
||||
'commit' => { 'hash' => 'source-commit-hash' },
|
||||
'repository' => { 'uuid' => 'uuid' }
|
||||
},
|
||||
'destination' => {
|
||||
'branch' => { 'name' => 'destination-branch-name' },
|
||||
'commit' => { 'hash' => 'destination-commit-hash' }
|
||||
'commit' => { 'hash' => 'destination-commit-hash' },
|
||||
'repository' => { 'uuid' => 'uuid' }
|
||||
},
|
||||
'merge_commit' => { 'hash' => 'merge-commit-hash' },
|
||||
'reviewers' => [
|
||||
|
|
@ -101,6 +103,7 @@ RSpec.describe Bitbucket::Representation::PullRequest, feature_category: :import
|
|||
target_branch_sha: 'destination-commit-hash',
|
||||
title: 'title',
|
||||
updated_at: 'updated-at',
|
||||
source_and_target_project_different: false,
|
||||
reviewers: ['user-2']
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -77,6 +77,18 @@ RSpec.describe Gitlab::BitbucketImport::Importers::PullRequestImporter, :clean_g
|
|||
end
|
||||
end
|
||||
|
||||
context 'when the source and target projects are different' do
|
||||
let(:importer) { described_class.new(project, hash.merge(source_and_target_project_different: true)) }
|
||||
|
||||
it 'skips the import' do
|
||||
expect(Gitlab::BitbucketImport::Logger)
|
||||
.to receive(:info)
|
||||
.with(include(message: 'skipping because source and target projects are different', iid: anything))
|
||||
|
||||
expect { importer.execute }.not_to change { project.merge_requests.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the author does not have a bitbucket identity' do
|
||||
before do
|
||||
identity.update!(provider: :github)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,17 @@ RSpec.describe Gitlab::Tracking::EventDefinition do
|
|||
expect { described_class.definitions }.not_to raise_error
|
||||
end
|
||||
|
||||
it 'has no duplicated actions in InternalEventTracking events', :aggregate_failures do
|
||||
definitions_by_action = described_class.definitions
|
||||
.each_value.select { |d| d.category == 'InternalEventTracking' }
|
||||
.group_by(&:action)
|
||||
|
||||
definitions_by_action.each do |action, definitions|
|
||||
expect(definitions.size).to eq(1),
|
||||
"Multiple definitions use the action '#{action}': #{definitions.map(&:path).join(', ')}"
|
||||
end
|
||||
end
|
||||
|
||||
it 'has event definitions for all events used in Internal Events metric definitions', :aggregate_failures do
|
||||
from_metric_definitions = Gitlab::Usage::MetricDefinition.definitions
|
||||
.values
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ RSpec.describe 'devise/shared/_signup_box' do
|
|||
|
||||
let(:translation_com) do
|
||||
s_("SignUp|By clicking %{button_text} or registering through a third party you "\
|
||||
"accept the GitLab%{link_start} Terms of Use and acknowledge the Privacy Policy "\
|
||||
"accept the GitLab%{link_start} Terms of Use and acknowledge the Privacy Statement "\
|
||||
"and Cookie Policy%{link_end}")
|
||||
end
|
||||
|
||||
let(:translation_non_com) do
|
||||
s_("SignUp|By clicking %{button_text} or registering through a third party you "\
|
||||
"accept the%{link_start} Terms of Use and acknowledge the Privacy Policy and "\
|
||||
"accept the%{link_start} Terms of Use and acknowledge the Privacy Statement and "\
|
||||
"Cookie Policy%{link_end}")
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue