Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
e7b6c527c4
commit
c283bdb7bc
|
|
@ -1 +1 @@
|
|||
14.31.0
|
||||
14.32.0
|
||||
|
|
|
|||
2
Gemfile
2
Gemfile
|
|
@ -504,7 +504,7 @@ group :test do
|
|||
gem 'webmock', '~> 3.19.1' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'rails-controller-testing' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'concurrent-ruby', '~> 1.1' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'test-prof', '~> 1.3.0' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'test-prof', '~> 1.3.1' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'rspec_junit_formatter' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'guard-rspec' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
gem 'axe-core-rspec' # rubocop:todo Gemfile/MissingFeatureCategory
|
||||
|
|
|
|||
|
|
@ -643,7 +643,7 @@
|
|||
{"name":"term-ansicolor","version":"1.7.1","platform":"ruby","checksum":"92339ffec77c4bddc786a29385c91601dd52fc68feda23609bba0491229b05f7"},
|
||||
{"name":"terminal-table","version":"3.0.2","platform":"ruby","checksum":"f951b6af5f3e00203fb290a669e0a85c5dd5b051b3b023392ccfd67ba5abae91"},
|
||||
{"name":"terser","version":"1.0.2","platform":"ruby","checksum":"80c2e0bc7e2db4e12e8529658f9e0820e13d685ae67d745bf981f269743bb28e"},
|
||||
{"name":"test-prof","version":"1.3.0","platform":"ruby","checksum":"6092032f15810063a7b83595ce102398fbf3d684b9d3c71fb7b9c1d6464b2c17"},
|
||||
{"name":"test-prof","version":"1.3.1","platform":"ruby","checksum":"aa35cd06eae4e6545d7b1310181875e61ad0e9e08c31223fb1aafd50da187523"},
|
||||
{"name":"test_file_finder","version":"0.2.1","platform":"ruby","checksum":"a5e9b369d80c76aefbb609acf5e11d89a048f35e565de3cc261c20112f0fcdb3"},
|
||||
{"name":"text","version":"1.3.1","platform":"ruby","checksum":"2fbbbc82c1ce79c4195b13018a87cbb00d762bda39241bb3cdc32792759dd3f4"},
|
||||
{"name":"thor","version":"1.3.0","platform":"ruby","checksum":"1adc7f9e5b3655a68c71393fee8bd0ad088d14ee8e83a0b73726f23cbb3ca7c3"},
|
||||
|
|
|
|||
|
|
@ -1637,7 +1637,7 @@ GEM
|
|||
unicode-display_width (>= 1.1.1, < 3)
|
||||
terser (1.0.2)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
test-prof (1.3.0)
|
||||
test-prof (1.3.1)
|
||||
test_file_finder (0.2.1)
|
||||
faraday (>= 1.0, < 3.0, != 2.0.0)
|
||||
text (1.3.1)
|
||||
|
|
@ -2084,7 +2084,7 @@ DEPENDENCIES
|
|||
tanuki_emoji (~> 0.9)
|
||||
telesignenterprise (~> 2.2)
|
||||
terser (= 1.0.2)
|
||||
test-prof (~> 1.3.0)
|
||||
test-prof (~> 1.3.1)
|
||||
test_file_finder (~> 0.2.1)
|
||||
thrift (>= 0.16.0)
|
||||
timfel-krb5-auth (~> 0.8)
|
||||
|
|
|
|||
|
|
@ -80,10 +80,10 @@ export function getStatefulSetStatuses(items) {
|
|||
|
||||
export function getReplicaSetStatuses(items) {
|
||||
const failed = items.filter((item) => {
|
||||
return item.status?.readyReplicas < item.spec?.replicas;
|
||||
return calculateStatefulSetStatus(item) === STATUS_FAILED;
|
||||
});
|
||||
const ready = items.filter((item) => {
|
||||
return item.status?.readyReplicas === item.spec?.replicas;
|
||||
return calculateStatefulSetStatus(item) === STATUS_READY;
|
||||
});
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,12 @@ export const STATUSES = {
|
|||
|
||||
export const PROVIDERS = {
|
||||
GITHUB: 'github',
|
||||
BITBUCKET_SERVER: 'bitbucket_server',
|
||||
};
|
||||
|
||||
// Retrieved from value of `PAGE_LENGTH` in lib/bitbucket_server/paginator.rb
|
||||
export const BITBUCKET_SERVER_PAGE_LENGTH = 25;
|
||||
|
||||
const SCHEDULED_STATUS_ICON = {
|
||||
icon: 'status-scheduled',
|
||||
text: __('Pending'),
|
||||
|
|
|
|||
|
|
@ -105,10 +105,7 @@ export default {
|
|||
|
||||
mounted() {
|
||||
this.fetchJobs();
|
||||
|
||||
if (!this.paginatable) {
|
||||
this.fetchRepos();
|
||||
}
|
||||
this.fetchRepos();
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ export function initStoreFromElement(element) {
|
|||
importPath,
|
||||
cancelPath,
|
||||
defaultTargetNamespace,
|
||||
paginatable,
|
||||
} = element.dataset;
|
||||
|
||||
return createStore({
|
||||
|
|
@ -37,7 +36,6 @@ export function initStoreFromElement(element) {
|
|||
importPath,
|
||||
cancelPath,
|
||||
},
|
||||
hasPagination: parseBoolean(paginatable),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
|
|||
import { visitUrl, objectToQuery } from '~/lib/utils/url_utility';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import { isProjectImportable } from '../utils';
|
||||
import { PROVIDERS } from '../../constants';
|
||||
import { PROVIDERS, BITBUCKET_SERVER_PAGE_LENGTH } from '../../constants';
|
||||
import * as types from './mutation_types';
|
||||
|
||||
let eTagPoll;
|
||||
|
|
@ -33,6 +33,16 @@ const commitPaginationData = ({ state, commit, data }) => {
|
|||
const nextPage = state.pageInfo.page + 1;
|
||||
commit(types.SET_PAGE, nextPage);
|
||||
}
|
||||
|
||||
// Only BitBucket Server uses pagination with page length
|
||||
if (state.provider === PROVIDERS.BITBUCKET_SERVER) {
|
||||
const reposLength = data.providerRepos.length;
|
||||
if (reposLength > 0 && reposLength % BITBUCKET_SERVER_PAGE_LENGTH === 0) {
|
||||
commit(types.SET_HAS_NEXT_PAGE, true);
|
||||
} else {
|
||||
commit(types.SET_HAS_NEXT_PAGE, false);
|
||||
}
|
||||
}
|
||||
};
|
||||
const paginationParams = ({ state }) => {
|
||||
if (state.provider === PROVIDERS.GITHUB && state.pageInfo.endCursor) {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import state from './state';
|
|||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default ({ initialState, endpoints, hasPagination }) =>
|
||||
export default ({ initialState, endpoints }) =>
|
||||
new Vuex.Store({
|
||||
state: { ...state(), ...initialState },
|
||||
actions: actionsFactory({ endpoints, hasPagination }),
|
||||
actions: actionsFactory({ endpoints }),
|
||||
mutations,
|
||||
getters,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,3 +17,5 @@ export const SET_IMPORT_TARGET = 'SET_IMPORT_TARGET';
|
|||
export const SET_PAGE = 'SET_PAGE';
|
||||
|
||||
export const SET_PAGE_CURSORS = 'SET_PAGE_CURSORS';
|
||||
|
||||
export const SET_HAS_NEXT_PAGE = 'SET_HAS_NEXT_PAGE';
|
||||
|
|
|
|||
|
|
@ -143,4 +143,8 @@ export default {
|
|||
const { startCursor, endCursor, hasNextPage } = pageInfo;
|
||||
state.pageInfo = { ...state.pageInfo, startCursor, endCursor, hasNextPage };
|
||||
},
|
||||
|
||||
[types.SET_HAS_NEXT_PAGE](state, hasNextPage) {
|
||||
state.pageInfo.hasNextPage = hasNextPage;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,6 @@ export default () => ({
|
|||
page: 0,
|
||||
startCursor: null,
|
||||
endCursor: null,
|
||||
hasNextPage: true,
|
||||
hasNextPage: false,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import typeDefs from '~/environments/graphql/typedefs.graphql';
|
|||
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 { resolvers } from './resolvers';
|
||||
|
||||
export const apolloProvider = () => {
|
||||
|
|
@ -63,6 +64,25 @@ export const apolloProvider = () => {
|
|||
},
|
||||
});
|
||||
|
||||
cache.writeQuery({
|
||||
query: k8sReplicaSetsQuery,
|
||||
data: {
|
||||
metadata: {
|
||||
name: null,
|
||||
namespace: null,
|
||||
creationTimestamp: null,
|
||||
labels: null,
|
||||
annotations: null,
|
||||
},
|
||||
status: {
|
||||
readyReplicas: null,
|
||||
},
|
||||
spec: {
|
||||
replicas: null,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return new VueApollo({
|
||||
defaultClient,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
query getK8sDashboardReplicaSets($configuration: LocalConfiguration) {
|
||||
k8sReplicaSets(configuration: $configuration) @client {
|
||||
metadata {
|
||||
name
|
||||
namespace
|
||||
creationTimestamp
|
||||
labels
|
||||
annotations
|
||||
}
|
||||
status {
|
||||
readyReplicas
|
||||
}
|
||||
spec {
|
||||
replicas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import {
|
|||
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';
|
||||
|
||||
export default {
|
||||
k8sPods(_, { configuration }, { client }) {
|
||||
|
|
@ -91,4 +92,41 @@ export default {
|
|||
}
|
||||
});
|
||||
},
|
||||
|
||||
k8sReplicaSets(_, { configuration, namespace = '' }, { client }) {
|
||||
const config = new Configuration(configuration);
|
||||
|
||||
const appsV1api = new AppsV1Api(config);
|
||||
const deploymentsApi = namespace
|
||||
? appsV1api.listAppsV1NamespacedReplicaSet({ namespace })
|
||||
: appsV1api.listAppsV1ReplicaSetForAllNamespaces();
|
||||
return deploymentsApi
|
||||
.then((res) => {
|
||||
const watchPath = buildWatchPath({
|
||||
resource: 'replicasets',
|
||||
api: 'apis/apps/v1',
|
||||
namespace,
|
||||
});
|
||||
watchWorkloadItems({
|
||||
client,
|
||||
query: k8sDashboardReplicaSetsQuery,
|
||||
configuration,
|
||||
namespace,
|
||||
watchPath,
|
||||
queryField: 'k8sReplicaSets',
|
||||
mapFn: mapSetItem,
|
||||
});
|
||||
|
||||
const data = res?.items || [];
|
||||
|
||||
return data.map(mapSetItem);
|
||||
})
|
||||
.catch(async (err) => {
|
||||
try {
|
||||
await handleClusterError(err);
|
||||
} catch (error) {
|
||||
throw new Error(error.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import { getAge, calculateStatefulSetStatus } from '../helpers/k8s_integration_helper';
|
||||
import WorkloadLayout from '../components/workload_layout.vue';
|
||||
import k8sReplicaSetsQuery from '../graphql/queries/k8s_dashboard_replica_sets.query.graphql';
|
||||
import { STATUS_FAILED, STATUS_READY, STATUS_LABELS } from '../constants';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WorkloadLayout,
|
||||
},
|
||||
inject: ['configuration'],
|
||||
apollo: {
|
||||
k8sReplicaSets: {
|
||||
query: k8sReplicaSetsQuery,
|
||||
variables() {
|
||||
return {
|
||||
configuration: this.configuration,
|
||||
};
|
||||
},
|
||||
update(data) {
|
||||
return (
|
||||
data?.k8sReplicaSets?.map((replicaSet) => {
|
||||
return {
|
||||
name: replicaSet.metadata?.name,
|
||||
namespace: replicaSet.metadata?.namespace,
|
||||
status: calculateStatefulSetStatus(replicaSet),
|
||||
age: getAge(replicaSet.metadata?.creationTimestamp),
|
||||
labels: replicaSet.metadata?.labels,
|
||||
annotations: replicaSet.metadata?.annotations,
|
||||
kind: s__('KubernetesDashboard|ReplicaSet'),
|
||||
};
|
||||
}) || []
|
||||
);
|
||||
},
|
||||
error(err) {
|
||||
this.errorMessage = err?.message;
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
k8sReplicaSets: [],
|
||||
errorMessage: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
replicaSetsStats() {
|
||||
return [
|
||||
{
|
||||
value: this.countReplicaSetsByStatus(STATUS_READY),
|
||||
title: STATUS_LABELS[STATUS_READY],
|
||||
},
|
||||
{
|
||||
value: this.countReplicaSetsByStatus(STATUS_FAILED),
|
||||
title: STATUS_LABELS[STATUS_FAILED],
|
||||
},
|
||||
];
|
||||
},
|
||||
loading() {
|
||||
return this.$apollo.queries.k8sReplicaSets.loading;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
countReplicaSetsByStatus(phase) {
|
||||
const filteredReplicaSets = this.k8sReplicaSets.filter((item) => item.status === phase) || [];
|
||||
|
||||
return filteredReplicaSets.length;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<workload-layout
|
||||
:loading="loading"
|
||||
:error-message="errorMessage"
|
||||
:stats="replicaSetsStats"
|
||||
:items="k8sReplicaSets"
|
||||
/>
|
||||
</template>
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
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 PODS_ROUTE_PATH = '/pods';
|
||||
export const DEPLOYMENTS_ROUTE_PATH = '/deployments';
|
||||
export const STATEFUL_SETS_ROUTE_PATH = '/statefulsets';
|
||||
export const REPLICA_SETS_ROUTE_PATH = '/replicasets';
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { s__ } from '~/locale';
|
|||
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 {
|
||||
PODS_ROUTE_NAME,
|
||||
PODS_ROUTE_PATH,
|
||||
|
|
@ -9,6 +10,8 @@ import {
|
|||
DEPLOYMENTS_ROUTE_PATH,
|
||||
STATEFUL_SETS_ROUTE_NAME,
|
||||
STATEFUL_SETS_ROUTE_PATH,
|
||||
REPLICA_SETS_ROUTE_NAME,
|
||||
REPLICA_SETS_ROUTE_PATH,
|
||||
} from './constants';
|
||||
|
||||
export default [
|
||||
|
|
@ -36,4 +39,12 @@ export default [
|
|||
title: s__('KubernetesDashboard|StatefulSets'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: REPLICA_SETS_ROUTE_NAME,
|
||||
path: REPLICA_SETS_ROUTE_PATH,
|
||||
component: ReplicaSetsPage,
|
||||
meta: {
|
||||
title: s__('KubernetesDashboard|ReplicaSets'),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import CiIcon from './ci_icon.vue';
|
||||
|
||||
export default {
|
||||
component: CiIcon,
|
||||
title: 'vue_shared/ci_icon',
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
components: { CiIcon },
|
||||
props: Object.keys(argTypes),
|
||||
template: '<ci-icon v-bind="$props" />',
|
||||
});
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
status: {
|
||||
icon: 'status_success',
|
||||
text: 'Success',
|
||||
detailsPath: 'https://gitab.com/',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithText = Template.bind({});
|
||||
WithText.args = {
|
||||
status: {
|
||||
icon: 'status_success',
|
||||
text: 'Success',
|
||||
detailsPath: 'https://gitab.com/',
|
||||
},
|
||||
showStatusText: true,
|
||||
};
|
||||
|
|
@ -41,7 +41,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:moved_mr_sidebar, project)
|
||||
push_frontend_feature_flag(:sast_reports_in_inline_diff, project)
|
||||
push_frontend_feature_flag(:mr_experience_survey, project)
|
||||
push_force_frontend_feature_flag(:summarize_my_code_review, summarize_my_code_review_enabled?)
|
||||
push_frontend_feature_flag(:ci_job_failures_in_mr, project)
|
||||
push_frontend_feature_flag(:mr_pipelines_graphql, project)
|
||||
push_frontend_feature_flag(:notifications_todos_buttons, current_user)
|
||||
|
|
@ -49,12 +48,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
push_frontend_feature_flag(:merge_blocked_component, current_user)
|
||||
end
|
||||
|
||||
before_action only: [:edit] do
|
||||
if can?(current_user, :fill_in_merge_request_template, project)
|
||||
push_frontend_feature_flag(:fill_in_mr_template, project)
|
||||
end
|
||||
end
|
||||
|
||||
around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :discussions]
|
||||
|
||||
after_action :log_merge_request_show, only: [:show, :diffs]
|
||||
|
|
@ -638,16 +631,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
|
|||
Date.strptime(date, "%Y-%m-%d")&.to_time&.to_i if date
|
||||
rescue Date::Error, TypeError
|
||||
end
|
||||
|
||||
def summarize_my_code_review_enabled?
|
||||
namespace = project&.group&.root_ancestor
|
||||
return false if namespace.nil?
|
||||
|
||||
Feature.enabled?(:summarize_my_code_review, current_user) &&
|
||||
namespace.group_namespace? &&
|
||||
namespace.licensed_feature_available?(:summarize_my_mr_code_review) &&
|
||||
Gitlab::Llm::StageCheck.available?(namespace, :summarize_my_mr_code_review)
|
||||
end
|
||||
end
|
||||
|
||||
Projects::MergeRequestsController.prepend_mod_with('Projects::MergeRequestsController')
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
description: Install cluster application
|
||||
category: cluster:applications
|
||||
action: cluster application name
|
||||
label_description:
|
||||
property_description:
|
||||
value_description:
|
||||
extra_properties:
|
||||
identifiers:
|
||||
product_section: ops
|
||||
product_stage: monitor
|
||||
product_group: group::monitor
|
||||
milestone: "12.7"
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23000
|
||||
distributions:
|
||||
- ce
|
||||
- ee
|
||||
tiers:
|
||||
- free
|
||||
- premium
|
||||
- ultimate
|
||||
|
|
@ -31553,6 +31553,7 @@ Possible identifier types for a measurement.
|
|||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="valuestreamdashboardmetriccontributors"></a>`CONTRIBUTORS` | Contributor count. EXPERIMENTAL: Only available on the SaaS version of GitLab when the ClickHouse database backend is enabled. |
|
||||
| <a id="valuestreamdashboardmetricgroups"></a>`GROUPS` | Group count. |
|
||||
| <a id="valuestreamdashboardmetricissues"></a>`ISSUES` | Issue count. |
|
||||
| <a id="valuestreamdashboardmetricmerge_requests"></a>`MERGE_REQUESTS` | Merge request count. |
|
||||
|
|
|
|||
|
|
@ -240,6 +240,7 @@ When the user is authenticated and `simple` is not set this returns something li
|
|||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_job_token_scope_enabled": false,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"public_jobs": true,
|
||||
"build_timeout": 3600,
|
||||
"auto_cancel_pending_pipelines": "enabled",
|
||||
|
|
@ -416,6 +417,7 @@ GET /users/:user_id/projects
|
|||
"ci_forward_deployment_rollback_allowed": true,
|
||||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
|
|
@ -535,6 +537,7 @@ GET /users/:user_id/projects
|
|||
"ci_forward_deployment_rollback_allowed": true,
|
||||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
|
|
@ -1205,6 +1208,7 @@ GET /projects/:id
|
|||
"ci_forward_deployment_rollback_allowed": true,
|
||||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [
|
||||
{
|
||||
|
|
@ -2321,6 +2325,7 @@ Example response:
|
|||
"ci_forward_deployment_rollback_allowed": true,
|
||||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
|
|
@ -2453,6 +2458,7 @@ Example response:
|
|||
"ci_forward_deployment_rollback_allowed": true,
|
||||
"ci_allow_fork_pipelines_to_run_in_parent_project": true,
|
||||
"ci_separated_caches": true,
|
||||
"ci_restrict_pipeline_cancellation_role": "developer",
|
||||
"public_jobs": true,
|
||||
"shared_with_groups": [],
|
||||
"only_allow_merge_if_pipeline_succeeds": false,
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ RAILS_ENV=development bundle exec rake gitlab:duo:setup['<test-group-name>']
|
|||
1. You can use Rake task `rake gitlab:duo:enable_feature_flags` to enable all feature flags that are assigned to group AI Framework
|
||||
1. Set the required access token. To receive an access token:
|
||||
1. For Vertex, follow the [instructions below](#configure-gcp-vertex-access).
|
||||
1. For all other providers, like Anthropic, create an access request where `@m_gill`, `@wayne`, and `@timzallmann` are the tech stack owners.
|
||||
1. For Anthropic, create an access request
|
||||
|
||||
### Set up the embedding database
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,10 @@ If you already have a working GDK, you should
|
|||
|
||||
### Using Gitpod
|
||||
|
||||
If you want to contribute without the overhead of setting up a local development environment,
|
||||
you can use [Gitpod](../../integration/gitpod.md).
|
||||
Gitpod runs a virtual instance of the GDK.
|
||||
|
||||
Set aside about 15 minutes to launch the GDK in Gitpod.
|
||||
|
||||
1. Launch the GDK in [Gitpod](https://gitpod.io/#https://gitlab.com/gitlab-community/gitlab/-/tree/master/).
|
||||
|
|
@ -185,12 +189,6 @@ To confirm it was successful:
|
|||
If you get errors, run `gdk doctor` to troubleshoot. For more advanced troubleshooting, see
|
||||
[the troubleshooting docs](https://gitlab.com/gitlab-org/gitlab-development-kit/-/tree/main/doc/troubleshooting).
|
||||
|
||||
### Gitpod
|
||||
|
||||
If you want to contribute without the overhead of setting up a local development environment,
|
||||
you can use [Gitpod](../../integration/gitpod.md) instead.
|
||||
Gitpod runs a virtual instance of the GDK.
|
||||
|
||||
## Step 2: Change the code
|
||||
|
||||
[View an interactive demo of this step](https://gitlab.navattic.com/uu5a0dc5).
|
||||
|
|
@ -206,6 +204,9 @@ I want to change this text:
|
|||
Other settings on the page start with the word `Customize` and skip the `This setting allows you to` part.
|
||||
I'll update this phrase to match the others.
|
||||
|
||||
NOTE:
|
||||
As this text has already been [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116472) when developing this tutorial, you can instead search for `Customize the appearance of the syntax` to find the files that were changed.
|
||||
|
||||
1. Search the `gitlab-development-kit/gitlab` directory for the string `This setting allows you to customize`.
|
||||
|
||||
The results show one `.haml` file, two `.md` files, one `.pot` file, and
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ To run a DAST authenticated scan:
|
|||
#### Form authentication
|
||||
|
||||
- You are using either the [DAST proxy-based analyzer](proxy-based.md) or the [DAST browser-based analyzer](browser_based.md).
|
||||
- You know the URL of the login form of your application. Alternatively, you know how to go to the login form from the authentication URL (see [clicking to go to the login form](#clicking-to-go-to-the-login-form)).
|
||||
- You know the URL of the login form of your application. Alternatively, you know how to go to the login form from the authentication URL (see [clicking to go to the login form](#click-to-go-to-the-login-form)).
|
||||
- You know the [selectors](#finding-an-elements-selector) of the username and password HTML fields that DAST uses to input the respective values.
|
||||
- You know the element's [selector](#finding-an-elements-selector) that submits the login form when selected.
|
||||
|
||||
|
|
@ -80,6 +80,7 @@ To run a DAST authenticated scan:
|
|||
| `DAST_USERNAME` <sup>1</sup> | string | The username to authenticate to in the website. Example: `admin` |
|
||||
| `DAST_USERNAME_FIELD` <sup>1</sup> | [selector](#finding-an-elements-selector) | A selector describing the element used to enter the username on the login form. Example: `name:username` |
|
||||
| `DAST_AUTH_DISABLE_CLEAR_FIELDS` | boolean | Disables clearing of username and password fields before attempting manual login. Set to `false` by default. |
|
||||
| `DAST_AFTER_LOGIN_ACTIONS` | string | Comma separated list of actions to be run after login but before login verification. Currently supports "click" actions. Example: `click(on=id:change_to_bar_graph),click(on=css:input[name=username])` | |
|
||||
|
||||
1. Available to an on-demand proxy-based DAST scan.
|
||||
1. Not available to proxy-based scans.
|
||||
|
|
@ -191,7 +192,7 @@ authentication using the [single-step](#configuration-for-a-single-step-login-fo
|
|||
DAST supports authentication processes where a user is redirected to an external Identity Provider's site to log in.
|
||||
Check the [known limitations](#known-limitations) of DAST authentication to determine if your SSO authentication process is supported.
|
||||
|
||||
### Clicking to go to the login form
|
||||
### Click to go to the login form
|
||||
|
||||
Define `DAST_BROWSER_PATH_TO_LOGIN_FORM` to provide a path of elements to click on from the `DAST_AUTH_URL` so that DAST can access the
|
||||
login form. This method is suitable for applications that show the login form in a pop-up (modal) window or when the login form does not
|
||||
|
|
@ -210,6 +211,25 @@ dast:
|
|||
DAST_BROWSER_PATH_TO_LOGIN_FORM: "css:.navigation-menu,css:.login-menu-item"
|
||||
```
|
||||
|
||||
### Perform additional actions after submitting the username and password
|
||||
|
||||
Define `DAST_AFTER_LOGIN_ACTIONS` to provide a sequence of actions required to complete the login process after the username and password forms have been submitted. For example, this can be used to dismiss a modal dialog (such as a "keep me signed in?" prompt) that appears after the submit button is pressed.
|
||||
|
||||
DAST verifies authentication is successful and records authentication tokens once after-login actions have been executed.
|
||||
|
||||
For example:
|
||||
|
||||
```yaml
|
||||
include:
|
||||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
dast:
|
||||
variables:
|
||||
DAST_WEBSITE: "https://example.com"
|
||||
DAST_AUTH_URL: "https://example.com/login"
|
||||
DAST_AFTER_LOGIN_ACTIONS: "click(on=id:modal-yes)"
|
||||
```
|
||||
|
||||
### Excluding logout URLs
|
||||
|
||||
If DAST crawls the logout URL while running an authenticated scan, the user is logged out, resulting in the remainder of the scan being unauthenticated.
|
||||
|
|
|
|||
|
|
@ -61,8 +61,7 @@ This feature is an [Experiment](../../../policy/experiment-beta-support.md) on G
|
|||
When you've completed your review of a merge request and are ready to [submit your review](reviews/index.md#submit-a-review), generate a GitLab Duo Code review summary:
|
||||
|
||||
1. When you are ready to submit your review, select **Finish review**.
|
||||
1. Select **AI Actions** (**{tanuki}**).
|
||||
1. Select **Summarize my code review**.
|
||||
1. Select **Summarize my pending comments**.
|
||||
|
||||
The summary is displayed in the comment box. You can edit and refine the summary prior to submitting your review.
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module BitbucketServer
|
||||
class Paginator
|
||||
# Should be kept in-sync with `BITBUCKET_SERVER_PAGE_LENGTH` in app/assets/javascripts/import_entities/constants.js
|
||||
PAGE_LENGTH = 25
|
||||
|
||||
attr_reader :page_offset
|
||||
|
|
|
|||
|
|
@ -27919,6 +27919,12 @@ msgstr ""
|
|||
msgid "KubernetesDashboard|Ready"
|
||||
msgstr ""
|
||||
|
||||
msgid "KubernetesDashboard|ReplicaSet"
|
||||
msgstr ""
|
||||
|
||||
msgid "KubernetesDashboard|ReplicaSets"
|
||||
msgstr ""
|
||||
|
||||
msgid "KubernetesDashboard|Running"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ module QA
|
|||
end
|
||||
|
||||
def run_git(command_str, env: env_vars, max_attempts: 1)
|
||||
run(command_str, env: env, max_attempts: max_attempts, log_prefix: 'Git: ')
|
||||
run(command_str, env: env, max_attempts: max_attempts, sleep_internal: 10, log_prefix: 'Git: ')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
def run(command_str, env: [], max_attempts: 1, log_prefix: '')
|
||||
def run(command_str, env: [], max_attempts: 1, sleep_internal: 0, log_prefix: '')
|
||||
command = [*env, command_str, '2>&1'].compact.join(' ')
|
||||
result = nil
|
||||
|
||||
repeat_until(max_attempts: max_attempts, raise_on_failure: false) do
|
||||
repeat_until(max_attempts: max_attempts, sleep_interval: sleep_internal, raise_on_failure: false) do
|
||||
Runtime::Logger.debug "#{log_prefix}pwd=[#{Dir.pwd}], command=[#{command}]"
|
||||
output, status = Open3.capture2e(command)
|
||||
output.chomp!
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ RSpec.describe QA::Git::Repository do
|
|||
let(:repo_uri_with_credentials) { 'http://root@foo/bar.git' }
|
||||
let(:env_vars) { [%q(HOME="temp")] }
|
||||
let(:extra_env_vars) { [] }
|
||||
let(:run_params) { { env: env_vars + extra_env_vars, log_prefix: "Git: " } }
|
||||
let(:run_params) { { env: env_vars + extra_env_vars, sleep_internal: 10, log_prefix: "Git: " } }
|
||||
let(:repository) do
|
||||
described_class.new.tap do |r|
|
||||
r.uri = repo_uri
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ describe('ImportProjectsTable', () => {
|
|||
.filter((w) => w.props().variant === 'confirm')
|
||||
.at(0);
|
||||
const findImportAllModal = () => wrapper.findComponent({ ref: 'importAllModal' });
|
||||
const findIntersectionObserver = () => wrapper.findComponent(GlIntersectionObserver);
|
||||
|
||||
const importAllFn = jest.fn();
|
||||
const importAllModalShowFn = jest.fn();
|
||||
|
|
@ -203,41 +204,14 @@ describe('ImportProjectsTable', () => {
|
|||
describe('when paginatable is set to true', () => {
|
||||
const initState = {
|
||||
namespaces: [{ fullPath: 'path' }],
|
||||
pageInfo: { page: 1, hasNextPage: true },
|
||||
pageInfo: { page: 1, hasNextPage: false },
|
||||
repositories: [
|
||||
{ importSource: { id: 1 }, importedProject: null, importStatus: STATUSES.NONE },
|
||||
],
|
||||
};
|
||||
|
||||
describe('with hasNextPage true', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
state: initState,
|
||||
paginatable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not call fetchRepos on mount', () => {
|
||||
expect(fetchReposFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('renders intersection observer component', () => {
|
||||
expect(wrapper.findComponent(GlIntersectionObserver).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('calls fetchRepos when intersection observer appears', async () => {
|
||||
wrapper.findComponent(GlIntersectionObserver).vm.$emit('appear');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(fetchReposFn).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with hasNextPage false', () => {
|
||||
beforeEach(() => {
|
||||
initState.pageInfo.hasNextPage = false;
|
||||
|
||||
createComponent({
|
||||
state: initState,
|
||||
paginatable: true,
|
||||
|
|
@ -245,7 +219,30 @@ describe('ImportProjectsTable', () => {
|
|||
});
|
||||
|
||||
it('does not render intersection observer component', () => {
|
||||
expect(wrapper.findComponent(GlIntersectionObserver).exists()).toBe(false);
|
||||
expect(findIntersectionObserver().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with hasNextPage true', () => {
|
||||
beforeEach(() => {
|
||||
initState.pageInfo.hasNextPage = true;
|
||||
|
||||
createComponent({
|
||||
state: initState,
|
||||
paginatable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders intersection observer component', () => {
|
||||
expect(findIntersectionObserver().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('calls fetchRepos again when intersection observer appears', async () => {
|
||||
findIntersectionObserver().vm.$emit('appear');
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(fetchReposFn).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
SET_PAGE,
|
||||
SET_FILTER,
|
||||
SET_PAGE_CURSORS,
|
||||
SET_HAS_NEXT_PAGE,
|
||||
} from '~/import_entities/import_projects/store/mutation_types';
|
||||
import state from '~/import_entities/import_projects/store/state';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
|
|
@ -143,6 +144,44 @@ describe('import_projects store actions', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when provider is BITBUCKET_SERVER', () => {
|
||||
beforeEach(() => {
|
||||
localState.provider = PROVIDERS.BITBUCKET_SERVER;
|
||||
});
|
||||
|
||||
describe.each`
|
||||
reposLength | expectedHasNextPage
|
||||
${0} | ${false}
|
||||
${12} | ${false}
|
||||
${20} | ${false}
|
||||
${25} | ${true}
|
||||
`('when reposLength is $reposLength', ({ reposLength, expectedHasNextPage }) => {
|
||||
beforeEach(() => {
|
||||
payload.provider_repos = Array(reposLength).fill({});
|
||||
|
||||
mock.onGet(MOCK_ENDPOINT).reply(HTTP_STATUS_OK, payload);
|
||||
});
|
||||
|
||||
it('commits SET_HAS_NEXT_PAGE', () => {
|
||||
return testAction(
|
||||
fetchRepos,
|
||||
null,
|
||||
localState,
|
||||
[
|
||||
{ type: REQUEST_REPOS },
|
||||
{ type: SET_PAGE, payload: 1 },
|
||||
{ type: SET_HAS_NEXT_PAGE, payload: expectedHasNextPage },
|
||||
{
|
||||
type: RECEIVE_REPOS_SUCCESS,
|
||||
payload: convertObjectPropsToCamelCase(payload, { deep: true }),
|
||||
},
|
||||
],
|
||||
[],
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('commits REQUEST_REPOS, RECEIVE_REPOS_ERROR mutations on an unsuccessful request', () => {
|
||||
|
|
|
|||
|
|
@ -332,6 +332,16 @@ describe('import_projects store mutations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe(`${types.SET_HAS_NEXT_PAGE}`, () => {
|
||||
it('sets hasNextPage in pageInfo', () => {
|
||||
const NEW_HAS_NEXT_PAGE = true;
|
||||
state = { pageInfo: { hasNextPage: false } };
|
||||
|
||||
mutations[types.SET_HAS_NEXT_PAGE](state, NEW_HAS_NEXT_PAGE);
|
||||
expect(state.pageInfo.hasNextPage).toBe(NEW_HAS_NEXT_PAGE);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`${types.CANCEL_IMPORT_SUCCESS}`, () => {
|
||||
const payload = { repoId: 1 };
|
||||
|
||||
|
|
|
|||
|
|
@ -289,3 +289,9 @@ export const mockStatefulSetsTableItems = [
|
|||
kind: 'StatefulSet',
|
||||
},
|
||||
];
|
||||
|
||||
export const k8sReplicaSetsMock = [readyStatefulSet, readyStatefulSet, failedStatefulSet];
|
||||
|
||||
export const mockReplicaSetsTableItems = mockStatefulSetsTableItems.map((item) => {
|
||||
return { ...item, kind: 'ReplicaSet' };
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,13 @@ import { resolvers } from '~/kubernetes_dashboard/graphql/resolvers';
|
|||
import k8sDashboardPodsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_pods.query.graphql';
|
||||
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 { k8sPodsMock, k8sDeploymentsMock, k8sStatefulSetsMock } from '../mock_data';
|
||||
import k8sDashboardReplicaSetsQuery from '~/kubernetes_dashboard/graphql/queries/k8s_dashboard_replica_sets.query.graphql';
|
||||
import {
|
||||
k8sPodsMock,
|
||||
k8sDeploymentsMock,
|
||||
k8sStatefulSetsMock,
|
||||
k8sReplicaSetsMock,
|
||||
} from '../mock_data';
|
||||
|
||||
describe('~/frontend/environments/graphql/resolvers', () => {
|
||||
let mockResolvers;
|
||||
|
|
@ -276,4 +282,92 @@ describe('~/frontend/environments/graphql/resolvers', () => {
|
|||
).rejects.toThrow('API error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('k8sReplicaSets', () => {
|
||||
const client = { writeQuery: jest.fn() };
|
||||
|
||||
const mockWatcher = WatchApi.prototype;
|
||||
const mockReplicaSetsListWatcherFn = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve(mockWatcher);
|
||||
});
|
||||
|
||||
const mockOnDataFn = jest.fn().mockImplementation((eventName, callback) => {
|
||||
if (eventName === 'data') {
|
||||
callback([]);
|
||||
}
|
||||
});
|
||||
|
||||
const mockReplicaSetsListFn = jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
items: k8sReplicaSetsMock,
|
||||
});
|
||||
});
|
||||
|
||||
const mockAllReplicaSetsListFn = jest.fn().mockImplementation(mockReplicaSetsListFn);
|
||||
|
||||
describe('when the ReplicaSets data is present', () => {
|
||||
beforeEach(() => {
|
||||
jest
|
||||
.spyOn(AppsV1Api.prototype, 'listAppsV1ReplicaSetForAllNamespaces')
|
||||
.mockImplementation(mockAllReplicaSetsListFn);
|
||||
jest
|
||||
.spyOn(mockWatcher, 'subscribeToStream')
|
||||
.mockImplementation(mockReplicaSetsListWatcherFn);
|
||||
jest.spyOn(mockWatcher, 'on').mockImplementation(mockOnDataFn);
|
||||
});
|
||||
|
||||
it('should request all ReplicaSets from the cluster_client library and watch the events', async () => {
|
||||
const ReplicaSets = await mockResolvers.Query.k8sReplicaSets(
|
||||
null,
|
||||
{
|
||||
configuration,
|
||||
},
|
||||
{ client },
|
||||
);
|
||||
|
||||
expect(mockAllReplicaSetsListFn).toHaveBeenCalled();
|
||||
expect(mockReplicaSetsListWatcherFn).toHaveBeenCalled();
|
||||
|
||||
expect(ReplicaSets).toEqual(k8sReplicaSetsMock);
|
||||
});
|
||||
|
||||
it('should update cache with the new data when received from the library', async () => {
|
||||
await mockResolvers.Query.k8sReplicaSets(
|
||||
null,
|
||||
{ configuration, namespace: '' },
|
||||
{ client },
|
||||
);
|
||||
|
||||
expect(client.writeQuery).toHaveBeenCalledWith({
|
||||
query: k8sDashboardReplicaSetsQuery,
|
||||
variables: { configuration, namespace: '' },
|
||||
data: { k8sReplicaSets: [] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not watch ReplicaSets from the cluster_client library when the ReplicaSets data is not present', async () => {
|
||||
jest.spyOn(AppsV1Api.prototype, 'listAppsV1ReplicaSetForAllNamespaces').mockImplementation(
|
||||
jest.fn().mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
items: [],
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
await mockResolvers.Query.k8sReplicaSets(null, { configuration }, { client });
|
||||
|
||||
expect(mockReplicaSetsListWatcherFn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should throw an error if the API call fails', async () => {
|
||||
jest
|
||||
.spyOn(AppsV1Api.prototype, 'listAppsV1ReplicaSetForAllNamespaces')
|
||||
.mockRejectedValue(new Error('API error'));
|
||||
|
||||
await expect(
|
||||
mockResolvers.Query.k8sReplicaSets(null, { configuration }, { client }),
|
||||
).rejects.toThrow('API error');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 ReplicaSetsPage from '~/kubernetes_dashboard/pages/replica_sets_page.vue';
|
||||
import WorkloadLayout from '~/kubernetes_dashboard/components/workload_layout.vue';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
import {
|
||||
k8sReplicaSetsMock,
|
||||
mockStatefulSetsStats,
|
||||
mockReplicaSetsTableItems,
|
||||
} from '../graphql/mock_data';
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
describe('Kubernetes dashboard replicaSets 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: {
|
||||
k8sReplicaSets: jest.fn().mockReturnValue(k8sReplicaSetsMock),
|
||||
},
|
||||
};
|
||||
|
||||
return createMockApollo([], mockResolvers);
|
||||
};
|
||||
|
||||
const createWrapper = (apolloProvider = createApolloProvider()) => {
|
||||
wrapper = shallowMount(ReplicaSetsPage, {
|
||||
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(mockStatefulSetsStats);
|
||||
});
|
||||
|
||||
it('sets correct table items object for the WorkloadLayout', async () => {
|
||||
createWrapper();
|
||||
await waitForPromises();
|
||||
|
||||
expect(findWorkloadLayout().props('items')).toMatchObject(mockReplicaSetsTableItems);
|
||||
});
|
||||
});
|
||||
|
||||
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: {
|
||||
k8sReplicaSets: 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue