summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-04 18:08:35 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-04 18:08:35 +0000
commite15501a5e1f54249434167c0198dab775bdc4a1f (patch)
tree15615908225f23633fa269c063de38d38f88c38a
parent856e2c64ee69b055b31a8ebbeee616f13a46505e (diff)
downloadgitlab-ce-e15501a5e1f54249434167c0198dab775bdc4a1f.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml8
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml1
-rw-r--r--.gitlab/ci/review-apps/qa.gitlab-ci.yml62
-rw-r--r--CHANGELOG.md11
-rw-r--r--app/assets/javascripts/api/user_api.js6
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_table.vue6
-rw-r--r--app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue1
-rw-r--r--app/assets/javascripts/import_entities/import_groups/constants.js2
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/api.js10
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue4
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/app.vue22
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue6
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue10
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/constants.js3
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/index.js7
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue2
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue13
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/actions.js22
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/index.js11
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/mutations.js16
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/state.js11
-rw-r--r--app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue16
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/actions.js6
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/constants.js1
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/getters.js4
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js1
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/mutations.js14
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/state.js1
-rw-r--r--app/helpers/jira_connect_helper.rb3
-rw-r--r--config/settings.rb6
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md8
-rw-r--r--doc/development/documentation/styleguide/word_list.md19
-rw-r--r--doc/integration/mattermost/index.md1
-rw-r--r--lib/gitlab/experimentation/controller_concern.rb4
-rw-r--r--locale/gitlab.pot6
-rw-r--r--qa/qa/resource/merge_request.rb10
-rw-r--r--qa/qa/support/knapsack_report.rb1
-rw-r--r--qa/tasks/knapsack.rake7
-rw-r--r--spec/config/settings_spec.rb2
-rw-r--r--spec/frontend/import_entities/import_groups/components/import_table_spec.js30
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js9
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/app_spec.js37
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js7
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js7
-rw-r--r--spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js60
-rw-r--r--spec/frontend/jira_connect/subscriptions/store/actions_spec.js63
-rw-r--r--spec/frontend/jira_connect/subscriptions/store/mutations_spec.js17
-rw-r--r--spec/frontend/pipelines/test_reports/stores/actions_spec.js7
-rw-r--r--spec/frontend/pipelines/test_reports/stores/mutations_spec.js21
-rw-r--r--spec/frontend/pipelines/test_reports/test_suite_table_spec.js57
-rw-r--r--spec/helpers/jira_connect_helper_spec.rb1
-rw-r--r--spec/lib/gitlab/experimentation/controller_concern_spec.rb4
53 files changed, 516 insertions, 151 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 4767f100cf4..7787c28d318 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -114,14 +114,16 @@
policy: push
.qa-ruby-gems-cache: &qa-ruby-gems-cache
- key: "qa-ruby-gems-${DEBIAN_VERSION}"
+ key:
+ files:
+ - qa/Gemfile.lock
paths:
- - qa/vendor/ruby/
+ - qa/vendor/ruby
policy: pull
.qa-ruby-gems-cache-push: &qa-ruby-gems-cache-push
<<: *qa-ruby-gems-cache
- policy: push # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
+ policy: pull-push
.setup-test-env-cache:
cache:
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index 8881a4c486d..93cd43fbdee 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -1,4 +1,5 @@
.qa-job-base:
+ image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:debian-bullseye-ruby-2.7-bundler-2.3-git-2.33-lfs-2.9-chrome-99-docker-20.10.14-gcloud-383-kubectl-1.23
extends:
- .default-retry
- .qa-cache
diff --git a/.gitlab/ci/review-apps/qa.gitlab-ci.yml b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
index a185d40664e..ba5e8c9b112 100644
--- a/.gitlab/ci/review-apps/qa.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/qa.gitlab-ci.yml
@@ -5,18 +5,9 @@ include:
- /ci/allure-report.yml
- /ci/knapsack-report.yml
-.review-qa-base:
- extends:
- - .use-docker-in-docker
- image:
- name: ${QA_IMAGE}
- entrypoint: [""]
- stage: qa
- needs: ["review-deploy"]
+.test_variables:
variables:
QA_DEBUG: "true"
- QA_CAN_TEST_GIT_PROTOCOL_V2: "false"
- QA_CAN_TEST_PRAEFECT: "false"
QA_GENERATE_ALLURE_REPORT: "true"
GITLAB_USERNAME: "root"
GITLAB_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
@@ -24,19 +15,40 @@ include:
GITLAB_ADMIN_PASSWORD: "${REVIEW_APPS_ROOT_PASSWORD}"
GITLAB_QA_ADMIN_ACCESS_TOKEN: "${REVIEW_APPS_ROOT_TOKEN}"
GITHUB_ACCESS_TOKEN: "${REVIEW_APPS_QA_GITHUB_ACCESS_TOKEN}"
- SIGNUP_DISABLED: "true"
+
+.review-qa-base:
+ extends:
+ - .use-docker-in-docker
+ - .qa-cache
+ - .test_variables
+ image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:debian-bullseye-ruby-2.7-bundler-2.3-git-2.33-lfs-2.9-chrome-99-docker-20.10.14-gcloud-383-kubectl-1.23
+ stage: qa
+ needs: ["review-deploy"]
+ variables:
+ DOCKER_HOST: tcp://docker:2376
+ DOCKER_TLS_CERTDIR: /certs
+ DOCKER_CERT_PATH: /certs/client
+ DOCKER_TLS_VERIFY: 1
+ BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES: "true"
+ BUNDLE_PATH: vendor
before_script:
- # Use $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA so that GitLab image built in omnibus-gitlab-mirror and QA image are in sync.
- export EE_LICENSE="$(cat $REVIEW_APPS_EE_LICENSE_FILE)"
- - if [ -n "$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA" ]; then
- git checkout -f ${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA};
- fi
- - export CI_ENVIRONMENT_URL="$(cat environment_url.txt)"
- - echo "${CI_ENVIRONMENT_URL}"
- - cd qa
+ - export QA_GITLAB_URL="$(cat environment_url.txt)"
+ - cd qa && bundle install
script:
- qa_run_status=0
- - bin/test "${QA_SCENARIO}" "${CI_ENVIRONMENT_URL}" -- --color --format documentation --format RspecJunitFormatter --out tmp/rspec.xml || qa_run_status=$?
+ - |
+ bundle exec rake "knapsack:rspec[\
+ ${RSPEC_TAGS} \
+ --tag ~orchestrated \
+ --tag ~transient \
+ --tag ~skip_signup_disabled \
+ --tag ~requires_git_protocol_v2 \
+ --force-color \
+ --order random \
+ --format documentation \
+ --format RspecJunitFormatter --out tmp/rspec.xml \
+ ]" || qa_run_status=$?
- if [ ${qa_run_status} -ne 0 ]; then
release_sha=$(echo "${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-${CI_COMMIT_SHA}}" | cut -c1-11);
echo "Errors can be found at https://sentry.gitlab.net/gitlab/gitlab-review-apps/releases/${release_sha}/all-events/.";
@@ -63,21 +75,20 @@ review-qa-smoke:
extends:
- .review-qa-base
- .review:rules:review-qa-smoke
- retry: 1 # This is confusing but this means "2 runs at max".
+ retry: 1
variables:
QA_RUN_TYPE: review-qa-smoke
- QA_SCENARIO: Test::Instance::Smoke
-
+ RSPEC_TAGS: --tag smoke
review-qa-reliable:
extends:
- .review-qa-base
- .review:rules:review-qa-reliable
- parallel: 10
retry: 1
+ parallel: 10
variables:
QA_RUN_TYPE: review-qa-reliable
- QA_SCENARIO: Test::Instance::Reliable
+ RSPEC_TAGS: --tag reliable
review-qa-all:
extends:
@@ -86,8 +97,7 @@ review-qa-all:
parallel: 5
variables:
QA_RUN_TYPE: review-qa-all
- QA_SCENARIO: Test::Instance::All
- QA_SKIP_SMOKE_RELIABLE: "true"
+ RSPEC_TAGS: --tag ~reliable --tag ~smoke
review-performance:
extends:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99f3b27bdc0..d3adbbe049e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,17 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 14.10.2 (2022-05-04)
+
+### Fixed (2 changes)
+
+- [Resolve "Fork relationship is not respected for certain projects"](gitlab-org/gitlab@881099bc27d9696ea3b9bcc2a1e43c3207ee4bb3) ([merge request](gitlab-org/gitlab!86476))
+- [Fix mappings errors for ES6.8](gitlab-org/gitlab@5caac54a746a331d828d4e3ce24273cd6173c86f) ([merge request](gitlab-org/gitlab!86476)) **GitLab Enterprise Edition**
+
+### Other (1 change)
+
+- [Add documentation for mr settings audit events part 1](gitlab-org/gitlab@95bfdae5a677de5ac9d0d5ceccd42e88ca4f99c4) ([merge request](gitlab-org/gitlab!86476))
+
## 14.10.1 (2022-04-29)
### Security (14 changes)
diff --git a/app/assets/javascripts/api/user_api.js b/app/assets/javascripts/api/user_api.js
index c743b18d572..c362253f52e 100644
--- a/app/assets/javascripts/api/user_api.js
+++ b/app/assets/javascripts/api/user_api.js
@@ -12,6 +12,7 @@ const USER_PROJECTS_PATH = '/api/:version/users/:id/projects';
const USER_POST_STATUS_PATH = '/api/:version/user/status';
const USER_FOLLOW_PATH = '/api/:version/users/:id/follow';
const USER_UNFOLLOW_PATH = '/api/:version/users/:id/unfollow';
+const CURRENT_USER_PATH = '/api/:version/user';
export function getUsers(query, options) {
const url = buildApiUrl(USERS_PATH);
@@ -81,3 +82,8 @@ export function unfollowUser(userId) {
const url = buildApiUrl(USER_UNFOLLOW_PATH).replace(':id', encodeURIComponent(userId));
return axios.post(url);
}
+
+export function getCurrentUser(options) {
+ const url = buildApiUrl(CURRENT_USER_PATH);
+ return axios.get(url, { ...options });
+}
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
index ca04824c663..ce401862cc1 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
@@ -25,7 +25,7 @@ import importGroupsMutation from '../graphql/mutations/import_groups.mutation.gr
import updateImportStatusMutation from '../graphql/mutations/update_import_status.mutation.graphql';
import availableNamespacesQuery from '../graphql/queries/available_namespaces.query.graphql';
import bulkImportSourceGroupsQuery from '../graphql/queries/bulk_import_source_groups.query.graphql';
-import { NEW_NAME_FIELD, i18n } from '../constants';
+import { NEW_NAME_FIELD, ROOT_NAMESPACE, i18n } from '../constants';
import { StatusPoller } from '../services/status_poller';
import { isFinished, isAvailableForImport, isNameValid, isSameTarget } from '../utils';
import ImportActionsCell from './import_actions_cell.vue';
@@ -430,10 +430,10 @@ export default {
return this.importTargets[group.id];
}
- const defaultTargetNamespace = this.availableNamespaces[0] ?? { fullPath: '', id: null };
+ const defaultTargetNamespace = this.availableNamespaces[0] ?? ROOT_NAMESPACE;
let importTarget;
if (group.lastImportTarget) {
- const targetNamespace = this.availableNamespaces.find(
+ const targetNamespace = [ROOT_NAMESPACE, ...this.availableNamespaces].find(
(ns) => ns.fullPath === group.lastImportTarget.targetNamespace,
);
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue b/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue
index 344a6e45370..4fbbd5b239c 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_target_cell.vue
@@ -57,6 +57,7 @@ export default {
toggle-class="gl-rounded-top-right-none! gl-rounded-bottom-right-none!"
class="gl-h-7 gl-flex-grow-1"
data-qa-selector="target_namespace_selector_dropdown"
+ data-testid="target-namespace-selector"
>
<gl-dropdown-item @click="$emit('update-target-namespace', { fullPath: '', id: null })">{{
s__('BulkImport|No parent')
diff --git a/app/assets/javascripts/import_entities/import_groups/constants.js b/app/assets/javascripts/import_entities/import_groups/constants.js
index ac1466238d0..32137308684 100644
--- a/app/assets/javascripts/import_entities/import_groups/constants.js
+++ b/app/assets/javascripts/import_entities/import_groups/constants.js
@@ -18,3 +18,5 @@ export const i18n = {
};
export const NEW_NAME_FIELD = 'newName';
+
+export const ROOT_NAMESPACE = { fullPath: '', id: null };
diff --git a/app/assets/javascripts/jira_connect/subscriptions/api.js b/app/assets/javascripts/jira_connect/subscriptions/api.js
index 14947b6c835..de67703356f 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/api.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/api.js
@@ -29,3 +29,13 @@ export const fetchGroups = async (groupsPath, { page, perPage, search }) => {
},
});
};
+
+export const fetchSubscriptions = async (subscriptionsPath) => {
+ const jwt = await getJwt();
+
+ return axios.get(subscriptionsPath, {
+ params: {
+ jwt,
+ },
+ });
+};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue
index 1fc40e5c0d6..d77fd5652b2 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue
@@ -12,7 +12,7 @@ export default {
GroupItemName,
},
inject: {
- subscriptionsPath: {
+ addSubscriptionsPath: {
default: '',
},
},
@@ -36,7 +36,7 @@ export default {
onClick() {
this.isLoading = true;
- addSubscription(this.subscriptionsPath, this.group.full_path)
+ addSubscription(this.addSubscriptionsPath, this.group.full_path)
.then(() => {
persistAlert({
title: s__('Integrations|Namespace successfully linked'),
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
index 9fdca530aee..f8e0a406c6f 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
@@ -1,7 +1,7 @@
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { isEmpty } from 'lodash';
-import { mapState, mapMutations } from 'vuex';
+import { mapState, mapMutations, mapActions } from 'vuex';
import { retrieveAlert } from '~/jira_connect/subscriptions/utils';
import AccessorUtilities from '~/lib/utils/accessor';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
@@ -30,8 +30,8 @@ export default {
usersPath: {
default: '',
},
- subscriptions: {
- default: [],
+ subscriptionsPath: {
+ default: '',
},
},
data() {
@@ -40,7 +40,7 @@ export default {
};
},
computed: {
- ...mapState(['alert']),
+ ...mapState(['alert', 'subscriptions']),
shouldShowAlert() {
return Boolean(this.alert?.message);
},
@@ -64,16 +64,30 @@ export default {
created() {
this.setInitialAlert();
},
+ mounted() {
+ this.fetchSubscriptionsOauth();
+ },
methods: {
...mapMutations({
setAlert: SET_ALERT,
}),
+ ...mapActions(['fetchSubscriptions']),
+ /**
+ * Fetch subscriptions from the REST API,
+ * if the jiraConnectOauth flag is enabled.
+ */
+ fetchSubscriptionsOauth() {
+ if (!this.isOauthEnabled) return;
+
+ this.fetchSubscriptions(this.subscriptionsPath);
+ },
setInitialAlert() {
const { linkUrl, title, message, variant } = retrieveAlert() || {};
this.setAlert({ linkUrl, title, message, variant });
},
onSignInOauth(user) {
this.user = user;
+ this.fetchSubscriptionsOauth();
},
onSignInError() {
this.setAlert({
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
index dfed57df7d6..3ee243aad47 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
@@ -8,7 +8,7 @@ import {
} from '~/jira_connect/subscriptions/constants';
import { setUrlParams } from '~/lib/utils/url_utility';
import AccessorUtilities from '~/lib/utils/accessor';
-
+import { getCurrentUser } from '~/rest_api';
import { createCodeVerifier, createCodeChallenge } from '../pkce';
export default {
@@ -40,6 +40,7 @@ export default {
// Build the initial OAuth authorization URL
const { oauth_authorize_url: oauthAuthorizeURL } = this.oauthMetadata;
+
const oauthAuthorizeURLWithChallenge = setUrlParams(
{
code_challenge: codeChallenge,
@@ -73,6 +74,7 @@ export default {
const code = event.data?.code;
try {
const accessToken = await this.getOAuthToken(code);
+
await this.loadUser(accessToken);
} catch (e) {
this.handleError();
@@ -97,7 +99,7 @@ export default {
return data.access_token;
},
async loadUser(accessToken) {
- const { data } = await axios.get('/api/v4/user', {
+ const { data } = await getCurrentUser({
headers: { Authorization: `Bearer ${accessToken}` },
});
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
index 0251728c896..4c039be9ba5 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
@@ -1,7 +1,7 @@
<script>
import { GlButton, GlTableLite } from '@gitlab/ui';
import { isEmpty } from 'lodash';
-import { mapMutations } from 'vuex';
+import { mapMutations, mapState } from 'vuex';
import { removeSubscription } from '~/jira_connect/subscriptions/api';
import { reloadPage } from '~/jira_connect/subscriptions/utils';
import { __, s__ } from '~/locale';
@@ -16,11 +16,6 @@ export default {
GroupItemName,
TimeagoTooltip,
},
- inject: {
- subscriptions: {
- default: [],
- },
- },
data() {
return {
loadingItem: null,
@@ -45,6 +40,9 @@ export default {
i18n: {
unlinkError: s__('Integrations|Failed to unlink namespace. Please try again.'),
},
+ computed: {
+ ...mapState(['subscriptions']),
+ },
methods: {
...mapMutations({
setAlert: SET_ALERT,
diff --git a/app/assets/javascripts/jira_connect/subscriptions/constants.js b/app/assets/javascripts/jira_connect/subscriptions/constants.js
index d30ebdbb487..df3cf5b1381 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/constants.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/constants.js
@@ -8,6 +8,9 @@ export const ADD_NAMESPACE_MODAL_ID = 'add-namespace-modal';
export const I18N_DEFAULT_SIGN_IN_BUTTON_TEXT = s__('Integrations|Sign in to GitLab');
export const I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE = s__('Integrations|Failed to sign in to GitLab.');
+export const I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE = s__(
+ 'Integrations|Failed to load subscriptions.',
+);
const OAUTH_WINDOW_SIZE = 800;
export const OAUTH_WINDOW_OPTIONS = [
diff --git a/app/assets/javascripts/jira_connect/subscriptions/index.js b/app/assets/javascripts/jira_connect/subscriptions/index.js
index 3b584b5fe98..8e9f73538b9 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/index.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/index.js
@@ -9,8 +9,6 @@ import JiraConnectApp from './components/app.vue';
import createStore from './store';
import { sizeToParent } from './utils';
-const store = createStore();
-
export function initJiraConnect() {
const el = document.querySelector('.js-jira-connect-app');
if (!el) {
@@ -24,6 +22,7 @@ export function initJiraConnect() {
const {
groupsPath,
subscriptions,
+ addSubscriptionsPath,
subscriptionsPath,
usersPath,
gitlabUserPath,
@@ -31,12 +30,14 @@ export function initJiraConnect() {
} = el.dataset;
sizeToParent();
+ const store = createStore({ subscriptions: JSON.parse(subscriptions) });
+
return new Vue({
el,
store,
provide: {
groupsPath,
- subscriptions: JSON.parse(subscriptions),
+ addSubscriptionsPath,
subscriptionsPath,
usersPath,
gitlabUserPath,
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue
index a1868e11b53..91b66c87694 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue
@@ -40,7 +40,7 @@ export default {
<div>
<h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
<div v-if="hasSubscriptions">
- <div class="gl-display-flex gl-justify-content-end">
+ <div class="gl-display-flex gl-justify-content-end gl-mb-3">
<sign-in-oauth-button
v-if="useSignInOauthButton"
@sign-in="$emit('sign-in-oauth', $event)"
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue
index e49c764ebc5..b1c1ae73e14 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue
@@ -1,5 +1,7 @@
<script>
-import { GlEmptyState } from '@gitlab/ui';
+import { mapState } from 'vuex';
+import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
+
import SubscriptionsList from '../components/subscriptions_list.vue';
import AddNamespaceButton from '../components/add_namespace_button.vue';
@@ -7,6 +9,7 @@ export default {
name: 'SubscriptionsPage',
components: {
GlEmptyState,
+ GlLoadingIcon,
SubscriptionsList,
AddNamespaceButton,
},
@@ -16,6 +19,9 @@ export default {
required: true,
},
},
+ computed: {
+ ...mapState(['subscriptionsLoading', 'subscriptionsError']),
+ },
};
</script>
@@ -23,8 +29,9 @@ export default {
<div>
<h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
- <div v-if="hasSubscriptions">
- <div class="gl-display-flex gl-justify-content-end">
+ <gl-loading-icon v-if="subscriptionsLoading" size="md" />
+ <div v-else-if="hasSubscriptions && !subscriptionsError">
+ <div class="gl-display-flex gl-justify-content-end gl-mb-3">
<add-namespace-button />
</div>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/actions.js b/app/assets/javascripts/jira_connect/subscriptions/store/actions.js
new file mode 100644
index 00000000000..44241535e76
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/actions.js
@@ -0,0 +1,22 @@
+import { fetchSubscriptions as fetchSubscriptionsREST } from '~/jira_connect/subscriptions/api';
+import { I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE } from '../constants';
+import {
+ SET_SUBSCRIPTIONS,
+ SET_SUBSCRIPTIONS_LOADING,
+ SET_SUBSCRIPTIONS_ERROR,
+ SET_ALERT,
+} from './mutation_types';
+
+export const fetchSubscriptions = async ({ commit }, subscriptionsPath) => {
+ commit(SET_SUBSCRIPTIONS_LOADING, true);
+
+ try {
+ const data = await fetchSubscriptionsREST(subscriptionsPath);
+ commit(SET_SUBSCRIPTIONS, data.data.subscriptions);
+ } catch {
+ commit(SET_SUBSCRIPTIONS_ERROR, true);
+ commit(SET_ALERT, { message: I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE, variant: 'danger' });
+ } finally {
+ commit(SET_SUBSCRIPTIONS_LOADING, false);
+ }
+};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/index.js b/app/assets/javascripts/jira_connect/subscriptions/store/index.js
index de830e3891a..abad1920bcc 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/index.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/index.js
@@ -1,12 +1,15 @@
import Vue from 'vue';
import Vuex from 'vuex';
+import * as actions from './actions';
import mutations from './mutations';
-import state from './state';
+import createState from './state';
Vue.use(Vuex);
-export default () =>
- new Vuex.Store({
+export default function createStore(initialState) {
+ return new Vuex.Store({
mutations,
- state,
+ actions,
+ state: createState(initialState),
});
+}
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js b/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js
index 15f36b824d9..f954c22c906 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js
@@ -1 +1,4 @@
export const SET_ALERT = 'SET_ALERT';
+export const SET_SUBSCRIPTIONS = 'SET_SUBSCRIPTIONS';
+export const SET_SUBSCRIPTIONS_LOADING = 'SET_SUBSCRIPTIONS_LOADING';
+export const SET_SUBSCRIPTIONS_ERROR = 'SET_SUBSCRIPTIONS_ERROR';
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js b/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js
index 2a25e0fe25f..d5c4864243b 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js
@@ -1,7 +1,21 @@
-import { SET_ALERT } from './mutation_types';
+import {
+ SET_ALERT,
+ SET_SUBSCRIPTIONS,
+ SET_SUBSCRIPTIONS_LOADING,
+ SET_SUBSCRIPTIONS_ERROR,
+} from './mutation_types';
export default {
[SET_ALERT](state, { title, message, variant, linkUrl } = {}) {
state.alert = { title, message, variant, linkUrl };
},
+ [SET_SUBSCRIPTIONS](state, subscriptions = []) {
+ state.subscriptions = subscriptions;
+ },
+ [SET_SUBSCRIPTIONS_LOADING](state, subscriptionsLoading) {
+ state.subscriptionsLoading = subscriptionsLoading;
+ },
+ [SET_SUBSCRIPTIONS_ERROR](state, subscriptionsError) {
+ state.subscriptionsError = subscriptionsError;
+ },
};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/state.js b/app/assets/javascripts/jira_connect/subscriptions/store/state.js
index c807df03f00..2243e70afb1 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/state.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/state.js
@@ -1,3 +1,8 @@
-export default () => ({
- alert: undefined,
-});
+export default function createState({ subscriptions = [], subscriptionsLoading = false } = {}) {
+ return {
+ alert: undefined,
+ subscriptions,
+ subscriptionsLoading,
+ subscriptionsError: false,
+ };
+}
diff --git a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
index 51373e712ff..9b0e6560c53 100644
--- a/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
+++ b/app/assets/javascripts/pipelines/components/test_reports/test_suite_table.vue
@@ -35,7 +35,7 @@ export default {
},
computed: {
...mapState(['pageInfo']),
- ...mapGetters(['getSuiteTests', 'getSuiteTestCount']),
+ ...mapGetters(['getSuiteTests', 'getSuiteTestCount', 'getSuiteArtifactsExpired']),
hasSuites() {
return this.getSuiteTests.length > 0;
},
@@ -80,7 +80,8 @@ export default {
<div
v-for="(testCase, index) in getSuiteTests"
:key="index"
- class="gl-responsive-table-row rounded align-items-md-start mt-xs-3 js-case-row"
+ class="gl-responsive-table-row rounded align-items-md-start"
+ data-testid="test-case-row"
>
<div class="table-section section-20 section-wrap">
<div role="rowheader" class="table-mobile-header">{{ __('Suite') }}</div>
@@ -157,7 +158,16 @@ export default {
</div>
<div v-else>
- <p class="js-no-test-cases">{{ s__('TestReports|There are no test cases to display.') }}</p>
+ <p data-testid="no-test-cases">
+ {{ s__('TestReports|There are no test cases to display.') }}
+ </p>
+ <p v-if="getSuiteArtifactsExpired" data-testid="artifacts-expired">
+ {{
+ s__(
+ 'TestReports|Test details are populated by job artifacts. The job artifacts from this pipeline are expired.',
+ )
+ }}
+ </p>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
index b7f590a7b3c..f0556f3d12e 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/actions.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js
@@ -38,11 +38,7 @@ export const fetchTestSuite = ({ state, commit, dispatch }, index) => {
return axios
.get(state.suiteEndpoint, { params: { build_ids } })
.then(({ data }) => commit(types.SET_SUITE, { suite: data, index }))
- .catch(() => {
- createFlash({
- message: s__('TestReports|There was an error fetching the test suite.'),
- });
- })
+ .catch((error) => commit(types.SET_SUITE_ERROR, error))
.finally(() => {
dispatch('toggleLoading');
});
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/constants.js b/app/assets/javascripts/pipelines/stores/test_reports/constants.js
new file mode 100644
index 00000000000..8eebfb6b208
--- /dev/null
+++ b/app/assets/javascripts/pipelines/stores/test_reports/constants.js
@@ -0,0 +1 @@
+export const ARTIFACTS_EXPIRED_ERROR_MESSAGE = 'Test report artifacts have expired';
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/getters.js b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
index 03680de0fa9..e6a88bb4175 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/getters.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/getters.js
@@ -1,4 +1,5 @@
import { addIconStatus, formatFilePath, formattedTime } from './utils';
+import { ARTIFACTS_EXPIRED_ERROR_MESSAGE } from './constants';
export const getTestSuites = (state) => {
const { test_suites: testSuites = [] } = state.testReports;
@@ -29,3 +30,6 @@ export const getSuiteTests = (state) => {
};
export const getSuiteTestCount = (state) => getSelectedSuite(state)?.test_cases?.length || 0;
+
+export const getSuiteArtifactsExpired = (state) =>
+ state.errorMessage === ARTIFACTS_EXPIRED_ERROR_MESSAGE;
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
index 803f6bf60b1..7651a2f4327 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/mutation_types.js
@@ -2,4 +2,5 @@ export const SET_PAGE = 'SET_PAGE';
export const SET_SELECTED_SUITE_INDEX = 'SET_SELECTED_SUITE_INDEX';
export const SET_SUMMARY = 'SET_SUMMARY';
export const SET_SUITE = 'SET_SUITE';
+export const SET_SUITE_ERROR = 'SET_SUITE_ERROR';
export const TOGGLE_LOADING = 'TOGGLE_LOADING';
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
index cf0bf8483dd..68ee063dda7 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/mutations.js
@@ -1,3 +1,5 @@
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
import * as types from './mutation_types';
export default {
@@ -13,6 +15,18 @@ export default {
state.testReports.test_suites[index] = { ...suite, hasFullSuite: true };
},
+ [types.SET_SUITE_ERROR](state, error) {
+ const errorMessage = error.response?.data?.errors;
+
+ if (errorMessage) {
+ state.errorMessage = errorMessage;
+ } else {
+ createFlash({
+ message: s__('TestReports|There was an error fetching the test suite.'),
+ });
+ }
+ },
+
[types.SET_SELECTED_SUITE_INDEX](state, selectedSuiteIndex) {
Object.assign(state, { selectedSuiteIndex });
},
diff --git a/app/assets/javascripts/pipelines/stores/test_reports/state.js b/app/assets/javascripts/pipelines/stores/test_reports/state.js
index 0ee6f53fa58..3ec9418c14e 100644
--- a/app/assets/javascripts/pipelines/stores/test_reports/state.js
+++ b/app/assets/javascripts/pipelines/stores/test_reports/state.js
@@ -5,6 +5,7 @@ export default ({ blobPath = '', summaryEndpoint = '', suiteEndpoint = '' }) =>
testReports: {},
selectedSuiteIndex: null,
isLoading: false,
+ errorMessage: null,
pageInfo: {
page: 1,
perPage: 20,
diff --git a/app/helpers/jira_connect_helper.rb b/app/helpers/jira_connect_helper.rb
index 67b85b26f9e..30f29e002b8 100644
--- a/app/helpers/jira_connect_helper.rb
+++ b/app/helpers/jira_connect_helper.rb
@@ -7,7 +7,8 @@ module JiraConnectHelper
{
groups_path: api_v4_groups_path(params: { min_access_level: Gitlab::Access::MAINTAINER, skip_groups: skip_groups }),
subscriptions: subscriptions.map { |s| serialize_subscription(s) }.to_json,
- subscriptions_path: jira_connect_subscriptions_path,
+ add_subscriptions_path: jira_connect_subscriptions_path,
+ subscriptions_path: jira_connect_subscriptions_path(format: :json),
users_path: current_user ? nil : jira_connect_users_path, # users_path is used to determine if user is signed in
gitlab_user_path: current_user ? user_path(current_user) : nil,
oauth_metadata: Feature.enabled?(:jira_connect_oauth, current_user) ? jira_connect_oauth_data.to_json : nil
diff --git a/config/settings.rb b/config/settings.rb
index 1860ea0f659..df67fdc8c53 100644
--- a/config/settings.rb
+++ b/config/settings.rb
@@ -195,9 +195,9 @@ class Settings < Settingslogic
# Set a default UUID for the case when the UUID hasn't been initialized.
uuid = Gitlab::CurrentSettings.uuid || 'uuid-not-set'
- minute = Digest::MD5.hexdigest(uuid + 'minute').to_i(16) % 60
- hour = Digest::MD5.hexdigest(uuid + 'hour').to_i(16) % 24
- day_of_week = Digest::MD5.hexdigest(uuid).to_i(16) % 7
+ minute = Digest::SHA256.hexdigest(uuid + 'minute').to_i(16) % 60
+ hour = Digest::SHA256.hexdigest(uuid + 'hour').to_i(16) % 24
+ day_of_week = Digest::SHA256.hexdigest(uuid).to_i(16) % 7
"#{minute} #{hour} * * #{day_of_week}"
end
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index 6953d859768..a3240a6041b 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -134,11 +134,11 @@ This overview is brief. Refer to the above instructions for more context.
## Use `gitlab-sshd` instead of OpenSSH
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299109) in GitLab 14.5 as an **Alpha** release.
-> - [Changed](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/6321) to a **Beta** release in GitLab 15.0.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299109) in GitLab 14.5 as an **Alpha** release for self-managed customers.
-NOTE:
-`gitlab-sshd` is in [**Beta**](../../policy/alpha-beta-support.md#beta-features).
+WARNING:
+`gitlab-sshd` is in [**Alpha**](../../policy/alpha-beta-support.md#alpha-features).
+It is not ready for production use.
`gitlab-sshd` is [a standalone SSH server](https://gitlab.com/gitlab-org/gitlab-shell/-/tree/main/internal/sshd)
written in Go. It is provided as a part of the `gitlab-shell` package. It has a lower memory
diff --git a/doc/development/documentation/styleguide/word_list.md b/doc/development/documentation/styleguide/word_list.md
index e8ff7b8b865..9f222f157a1 100644
--- a/doc/development/documentation/styleguide/word_list.md
+++ b/doc/development/documentation/styleguide/word_list.md
@@ -533,6 +533,25 @@ Instead of:
Do not use **list** when referring to a [**dropdown list**](#dropdown-list).
Use the full phrase **dropdown list** instead.
+## license
+
+When writing about licenses:
+
+- Do not use variations such as **cloud license**, **offline license**, or **legacy license**.
+- Do not use interchangeably with **subscription**:
+ - A license grants users access to the subscription they purchased, and contains information such as the number of seats and subscription dates.
+ - A subscription is the subscription tier that the user purchases.
+
+Use:
+
+ - Add a license to your instance.
+ - Purchase a subscription.
+
+Instead of:
+
+ - Buy a license.
+ - Purchase a license.
+
## log in, log on
Do not use **log in** or **log on**. Use [sign in](#sign-in) instead. If the user interface has **Log in**, you can use it.
diff --git a/doc/integration/mattermost/index.md b/doc/integration/mattermost/index.md
index c8e2df1f88f..5ea723abba9 100644
--- a/doc/integration/mattermost/index.md
+++ b/doc/integration/mattermost/index.md
@@ -95,6 +95,7 @@ mattermost_external_url 'http://mattermost.example.com'
gitlab_rails['enable'] = false
redis['enable'] = false
postgres_exporter['enable'] = false
+grafana['enable'] = false
```
Then follow the appropriate steps in the [Authorize GitLab Mattermost section](#authorize-gitlab-mattermost). Last, to enable
diff --git a/lib/gitlab/experimentation/controller_concern.rb b/lib/gitlab/experimentation/controller_concern.rb
index a68e2db4dac..b09d67b8d5f 100644
--- a/lib/gitlab/experimentation/controller_concern.rb
+++ b/lib/gitlab/experimentation/controller_concern.rb
@@ -146,9 +146,9 @@ module Gitlab
return experimentation_subject_id if subject.blank?
if subject.respond_to?(:to_global_id)
- Digest::MD5.hexdigest(subject.to_global_id.to_s)
+ Digest::SHA256.hexdigest(subject.to_global_id.to_s)
else
- Digest::MD5.hexdigest(subject.to_s)
+ Digest::SHA256.hexdigest(subject.to_s)
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a361fb733a2..36ca4f8f69f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -20705,6 +20705,9 @@ msgstr ""
msgid "Integrations|Failed to load namespaces. Please try again."
msgstr ""
+msgid "Integrations|Failed to load subscriptions."
+msgstr ""
+
msgid "Integrations|Failed to sign in to GitLab."
msgstr ""
@@ -37746,6 +37749,9 @@ msgstr ""
msgid "TestReports|No test cases were found in the test report."
msgstr ""
+msgid "TestReports|Test details are populated by job artifacts. The job artifacts from this pipeline are expired."
+msgstr ""
+
msgid "TestReports|Tests"
msgstr ""
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index e3d139d59ae..0a92553690f 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -78,7 +78,7 @@ module QA
end
def fabricate!
- return fabricate_large_merge_request if Runtime::Scenario.large_setup?
+ return fabricate_large_merge_request if large_setup?
populate_target_and_source_if_required
@@ -100,7 +100,7 @@ module QA
end
def fabricate_via_api!
- return fabricate_large_merge_request if Runtime::Scenario.large_setup?
+ return fabricate_large_merge_request if large_setup?
resource_web_url(api_get)
rescue ResourceNotFoundError, NoValueError # rescue if iid not populated
@@ -208,6 +208,12 @@ module QA
private
+ def large_setup?
+ Runtime::Scenario.large_setup?
+ rescue ArgumentError
+ false
+ end
+
def transform_api_resource(api_resource)
raise ResourceNotFoundError if api_resource.blank?
diff --git a/qa/qa/support/knapsack_report.rb b/qa/qa/support/knapsack_report.rb
index 0ec53461e3f..afe60664bec 100644
--- a/qa/qa/support/knapsack_report.rb
+++ b/qa/qa/support/knapsack_report.rb
@@ -100,6 +100,7 @@ module QA
# @return [void]
def setup_environment!
ENV["KNAPSACK_TEST_FILE_PATTERN"] ||= "qa/specs/features/**/*_spec.rb"
+ ENV["KNAPSACK_TEST_DIR"] = "qa/specs"
ENV["KNAPSACK_REPORT_PATH"] = report_path
end
diff --git a/qa/tasks/knapsack.rake b/qa/tasks/knapsack.rake
index c3a2deab7f3..f50716e03bc 100644
--- a/qa/tasks/knapsack.rake
+++ b/qa/tasks/knapsack.rake
@@ -3,7 +3,12 @@
namespace :knapsack do
desc "Run tests with knapsack runner"
task :rspec, [:rspec_args] do |_, args|
- raise "This environment is not compatible with knapsack runner!" unless QA::Runtime::Env.knapsack?
+ unless QA::Runtime::Env.knapsack?
+ QA::Runtime::Logger.info("This environment is not compatible with parallel knapsack execution!")
+ QA::Runtime::Logger.info("Falling back to standard execution")
+
+ exec(%Q[bundle exec rspec #{args[:rspec_args]}])
+ end
QA::Support::KnapsackReport.configure!
Knapsack::Runners::RSpecRunner.run(args[:rspec_args])
diff --git a/spec/config/settings_spec.rb b/spec/config/settings_spec.rb
index 0c2465678f9..1de0e7e6c26 100644
--- a/spec/config/settings_spec.rb
+++ b/spec/config/settings_spec.rb
@@ -118,7 +118,7 @@ RSpec.describe Settings do
allow(Gitlab::CurrentSettings)
.to receive(:uuid) { 'd9e2f4e8-db1f-4e51-b03d-f427e1965c4a'}
- expect(described_class.send(:cron_for_service_ping)).to eq('21 18 * * 4')
+ expect(described_class.send(:cron_for_service_ping)).to eq('44 10 * * 4')
end
it 'returns min, hour, day in the valid range' do
diff --git a/spec/frontend/import_entities/import_groups/components/import_table_spec.js b/spec/frontend/import_entities/import_groups/components/import_table_spec.js
index 3bef793f2c3..1939e43e5dc 100644
--- a/spec/frontend/import_entities/import_groups/components/import_table_spec.js
+++ b/spec/frontend/import_entities/import_groups/components/import_table_spec.js
@@ -9,7 +9,7 @@ import createFlash from '~/flash';
import httpStatus from '~/lib/utils/http_status';
import axios from '~/lib/utils/axios_utils';
import { STATUSES } from '~/import_entities/constants';
-import { i18n } from '~/import_entities/import_groups/constants';
+import { i18n, ROOT_NAMESPACE } from '~/import_entities/import_groups/constants';
import ImportTable from '~/import_entities/import_groups/components/import_table.vue';
import importGroupsMutation from '~/import_entities/import_groups/graphql/mutations/import_groups.mutation.graphql';
import PaginationLinks from '~/vue_shared/components/pagination_links.vue';
@@ -45,6 +45,8 @@ describe('import table', () => {
const findImportButtons = () =>
wrapper.findAll('button').wrappers.filter((w) => w.text() === 'Import');
const findPaginationDropdown = () => wrapper.find('[data-testid="page-size"]');
+ const findTargetNamespaceDropdown = (rowWrapper) =>
+ rowWrapper.find('[data-testid="target-namespace-selector"]');
const findPaginationDropdownText = () => findPaginationDropdown().find('button').text();
const findSelectionCount = () => wrapper.find('[data-test-id="selection-count"]');
@@ -137,6 +139,32 @@ describe('import table', () => {
expect(wrapper.findAll('tbody tr')).toHaveLength(FAKE_GROUPS.length);
});
+ it('correctly maintains root namespace as last import target', async () => {
+ createComponent({
+ bulkImportSourceGroups: () => ({
+ nodes: [
+ {
+ ...generateFakeEntry({ id: 1, status: STATUSES.FINISHED }),
+ lastImportTarget: {
+ id: 1,
+ targetNamespace: ROOT_NAMESPACE.fullPath,
+ newName: 'does-not-matter',
+ },
+ },
+ ],
+ pageInfo: FAKE_PAGE_INFO,
+ versionValidation: FAKE_VERSION_VALIDATION,
+ }),
+ });
+
+ await waitForPromises();
+ const firstRow = wrapper.find('tbody tr');
+ const targetNamespaceDropdownButton = findTargetNamespaceDropdown(firstRow).find(
+ '[aria-haspopup]',
+ );
+ expect(targetNamespaceDropdownButton.text()).toBe('No parent');
+ });
+
it('does not render status string when result list is empty', async () => {
createComponent({
bulkImportSourceGroups: jest.fn().mockResolvedValue({
diff --git a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js
index 3d7bf7acb41..3cdb21dbe95 100644
--- a/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item_spec.js
@@ -13,7 +13,7 @@ jest.mock('~/jira_connect/subscriptions/utils');
describe('GroupsListItem', () => {
let wrapper;
- const mockSubscriptionPath = 'subscriptionPath';
+ const mockAddSubscriptionsPath = '/addSubscriptionsPath';
const createComponent = ({ mountFn = shallowMount } = {}) => {
wrapper = mountFn(GroupsListItem, {
@@ -21,7 +21,7 @@ describe('GroupsListItem', () => {
group: mockGroup1,
},
provide: {
- subscriptionsPath: mockSubscriptionPath,
+ addSubscriptionsPath: mockAddSubscriptionsPath,
},
});
};
@@ -70,7 +70,10 @@ describe('GroupsListItem', () => {
await waitForPromises();
- expect(addSubscriptionSpy).toHaveBeenCalledWith(mockSubscriptionPath, mockGroup1.full_path);
+ expect(addSubscriptionSpy).toHaveBeenCalledWith(
+ mockAddSubscriptionsPath,
+ mockGroup1.full_path,
+ );
expect(persistAlert).toHaveBeenCalledWith({
linkUrl: '/help/integration/jira_development_panel.html#use-the-integration',
message:
diff --git a/spec/frontend/jira_connect/subscriptions/components/app_spec.js b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
index f9dcf5ade24..a70c2f6c5f7 100644
--- a/spec/frontend/jira_connect/subscriptions/components/app_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/app_spec.js
@@ -12,6 +12,7 @@ import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
import { I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE } from '~/jira_connect/subscriptions/constants';
import { __ } from '~/locale';
import AccessorUtilities from '~/lib/utils/accessor';
+import * as api from '~/jira_connect/subscriptions/api';
import { mockSubscription } from '../mock_data';
jest.mock('~/jira_connect/subscriptions/utils', () => ({
@@ -31,7 +32,8 @@ describe('JiraConnectApp', () => {
const findBrowserSupportAlert = () => wrapper.findComponent(BrowserSupportAlert);
const createComponent = ({ provide, mountFn = shallowMountExtended } = {}) => {
- store = createStore();
+ store = createStore({ subscriptions: [mockSubscription] });
+ jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = mountFn(JiraConnectApp, {
store,
@@ -53,7 +55,6 @@ describe('JiraConnectApp', () => {
createComponent({
provide: {
usersPath,
- subscriptions: [mockSubscription],
},
});
});
@@ -79,14 +80,13 @@ describe('JiraConnectApp', () => {
createComponent({
provide: {
usersPath: '/user',
- subscriptions: [],
},
});
const userLink = findUserLink();
expect(userLink.exists()).toBe(true);
expect(userLink.props()).toEqual({
- hasSubscriptions: false,
+ hasSubscriptions: true,
user: null,
userSignedIn: false,
});
@@ -167,7 +167,6 @@ describe('JiraConnectApp', () => {
createComponent({
provide: {
usersPath: '/mock',
- subscriptions: [],
},
});
findSignInPage().vm.$emit('sign-in-oauth', mockUser);
@@ -193,7 +192,6 @@ describe('JiraConnectApp', () => {
createComponent({
provide: {
usersPath: '/mock',
- subscriptions: [],
},
});
findSignInPage().vm.$emit('error');
@@ -235,4 +233,31 @@ describe('JiraConnectApp', () => {
});
},
);
+
+ describe('when `jiraConnectOauth` feature flag is enabled', () => {
+ const mockSubscriptionsPath = '/mockSubscriptionsPath';
+
+ beforeEach(() => {
+ jest.spyOn(api, 'fetchSubscriptions').mockResolvedValue({ data: { subscriptions: [] } });
+
+ createComponent({
+ provide: {
+ glFeatures: { jiraConnectOauth: true },
+ subscriptionsPath: mockSubscriptionsPath,
+ },
+ });
+ });
+
+ describe('when component mounts', () => {
+ it('dispatches `fetchSubscriptions` action', async () => {
+ expect(store.dispatch).toHaveBeenCalledWith('fetchSubscriptions', mockSubscriptionsPath);
+ });
+ });
+
+ describe('when oauth button emits `sign-in-oauth` event', () => {
+ it('dispatches `fetchSubscriptions` action', () => {
+ expect(store.dispatch).toHaveBeenCalledWith('fetchSubscriptions', mockSubscriptionsPath);
+ });
+ });
+ });
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
index 18274cd4362..1002c48afe0 100644
--- a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
@@ -11,9 +11,12 @@ import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
import httpStatus from '~/lib/utils/http_status';
import AccessorUtilities from '~/lib/utils/accessor';
+import { getCurrentUser } from '~/rest_api';
jest.mock('~/lib/utils/accessor');
jest.mock('~/jira_connect/subscriptions/utils');
+jest.mock('~/jira_connect/subscriptions/api');
+jest.mock('~/rest_api');
jest.mock('~/jira_connect/subscriptions/pkce', () => ({
createCodeVerifier: jest.fn().mockReturnValue('mock-verifier'),
createCodeChallenge: jest.fn().mockResolvedValue('mock-challenge'),
@@ -147,7 +150,7 @@ describe('SignInOauthButton', () => {
mockAxios
.onPost(mockOauthMetadata.oauth_token_url)
.replyOnce(httpStatus.OK, { access_token: mockAccessToken });
- mockAxios.onGet('/api/v4/user').replyOnce(httpStatus.OK, mockUser);
+ getCurrentUser.mockResolvedValue({ data: mockUser });
window.dispatchEvent(new MessageEvent('message', mockEvent));
@@ -162,7 +165,7 @@ describe('SignInOauthButton', () => {
});
it('executes GET request to fetch user data', () => {
- expect(axios.get).toHaveBeenCalledWith('/api/v4/user', {
+ expect(getCurrentUser).toHaveBeenCalledWith({
headers: { Authorization: `Bearer ${mockAccessToken}` },
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js b/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
index 2aad533f677..2d7c58fc278 100644
--- a/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/subscriptions_list_spec.js
@@ -20,12 +20,11 @@ describe('SubscriptionsList', () => {
let store;
const createComponent = () => {
- store = createStore();
+ store = createStore({
+ subscriptions: [mockSubscription],
+ });
wrapper = mount(SubscriptionsList, {
- provide: {
- subscriptions: [mockSubscription],
- },
store,
});
};
diff --git a/spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js b/spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js
index 31a58042a05..4956af76ead 100644
--- a/spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/pages/subscriptions_page_spec.js
@@ -1,4 +1,4 @@
-import { GlEmptyState } from '@gitlab/ui';
+import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SubscriptionsPage from '~/jira_connect/subscriptions/pages/subscriptions_page.vue';
import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
@@ -10,15 +10,16 @@ describe('SubscriptionsPage', () => {
let store;
const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
+ const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
- const createComponent = ({ props } = {}) => {
- store = createStore();
+ const createComponent = ({ props, initialState } = {}) => {
+ store = createStore(initialState);
wrapper = shallowMount(SubscriptionsPage, {
store,
- propsData: props,
+ propsData: { hasSubscriptions: false, ...props },
stubs: {
GlEmptyState,
},
@@ -31,29 +32,40 @@ describe('SubscriptionsPage', () => {
describe('template', () => {
describe.each`
- scenario | expectSubscriptionsList | expectEmptyState
- ${'with subscriptions'} | ${true} | ${false}
- ${'without subscriptions'} | ${false} | ${true}
- `('$scenario', ({ expectEmptyState, expectSubscriptionsList }) => {
- beforeEach(() => {
- createComponent({
- props: {
- hasSubscriptions: expectSubscriptionsList,
- },
+ scenario | subscriptionsLoading | hasSubscriptions | expectSubscriptionsList | expectEmptyState
+ ${'with subscriptions loading'} | ${true} | ${false} | ${false} | ${false}
+ ${'with subscriptions'} | ${false} | ${true} | ${true} | ${false}
+ ${'without subscriptions'} | ${false} | ${false} | ${false} | ${true}
+ `(
+ '$scenario',
+ ({ subscriptionsLoading, hasSubscriptions, expectEmptyState, expectSubscriptionsList }) => {
+ beforeEach(() => {
+ createComponent({
+ initialState: { subscriptionsLoading },
+ props: {
+ hasSubscriptions,
+ },
+ });
});
- });
- it('renders button to add namespace', () => {
- expect(findAddNamespaceButton().exists()).toBe(true);
- });
+ it(`${
+ subscriptionsLoading ? 'does not render' : 'renders'
+ } button to add namespace`, () => {
+ expect(findAddNamespaceButton().exists()).toBe(!subscriptionsLoading);
+ });
- it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
- expect(findEmptyState().exists()).toBe(expectEmptyState);
- });
+ it(`${subscriptionsLoading ? 'renders' : 'does not render'} GlLoadingIcon`, () => {
+ expect(findGlLoadingIcon().exists()).toBe(subscriptionsLoading);
+ });
- it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
- expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
- });
- });
+ it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
+ expect(findEmptyState().exists()).toBe(expectEmptyState);
+ });
+
+ it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
+ expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
+ });
+ },
+ );
});
});
diff --git a/spec/frontend/jira_connect/subscriptions/store/actions_spec.js b/spec/frontend/jira_connect/subscriptions/store/actions_spec.js
new file mode 100644
index 00000000000..fbc814155b0
--- /dev/null
+++ b/spec/frontend/jira_connect/subscriptions/store/actions_spec.js
@@ -0,0 +1,63 @@
+import testAction from 'helpers/vuex_action_helper';
+
+import * as types from '~/jira_connect/subscriptions/store/mutation_types';
+import { fetchSubscriptions } from '~/jira_connect/subscriptions/store/actions';
+import state from '~/jira_connect/subscriptions/store/state';
+import * as api from '~/jira_connect/subscriptions/api';
+import { I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE } from '~/jira_connect/subscriptions/constants';
+
+describe('JiraConnect actions', () => {
+ let mockedState;
+
+ beforeEach(() => {
+ mockedState = state();
+ });
+
+ describe('fetchSubscriptions', () => {
+ const mockUrl = '/mock-url';
+
+ describe('when API request is successful', () => {
+ it('should commit SET_SUBSCRIPTIONS_LOADING and SET_SUBSCRIPTIONS mutations', async () => {
+ jest.spyOn(api, 'fetchSubscriptions').mockResolvedValue({ data: { subscriptions: [] } });
+
+ await testAction(
+ fetchSubscriptions,
+ mockUrl,
+ mockedState,
+ [
+ { type: types.SET_SUBSCRIPTIONS_LOADING, payload: true },
+ { type: types.SET_SUBSCRIPTIONS, payload: [] },
+ { type: types.SET_SUBSCRIPTIONS_LOADING, payload: false },
+ ],
+ [],
+ );
+
+ expect(api.fetchSubscriptions).toHaveBeenCalledWith(mockUrl);
+ });
+ });
+
+ describe('when API request fails', () => {
+ it('should commit SET_SUBSCRIPTIONS_LOADING, SET_SUBSCRIPTIONS_ERROR and SET_ALERT mutations', async () => {
+ jest.spyOn(api, 'fetchSubscriptions').mockRejectedValue();
+
+ await testAction(
+ fetchSubscriptions,
+ mockUrl,
+ mockedState,
+ [
+ { type: types.SET_SUBSCRIPTIONS_LOADING, payload: true },
+ { type: types.SET_SUBSCRIPTIONS_ERROR, payload: true },
+ {
+ type: types.SET_ALERT,
+ payload: { message: I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE, variant: 'danger' },
+ },
+ { type: types.SET_SUBSCRIPTIONS_LOADING, payload: false },
+ ],
+ [],
+ );
+
+ expect(api.fetchSubscriptions).toHaveBeenCalledWith(mockUrl);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/jira_connect/subscriptions/store/mutations_spec.js b/spec/frontend/jira_connect/subscriptions/store/mutations_spec.js
index 84a33dbf0b5..b5069a9b98d 100644
--- a/spec/frontend/jira_connect/subscriptions/store/mutations_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/store/mutations_spec.js
@@ -25,4 +25,21 @@ describe('JiraConnect store mutations', () => {
});
});
});
+
+ describe('SET_SUBSCRIPTIONS_LOADING', () => {
+ it('sets subscriptions loading flag', () => {
+ mutations.SET_SUBSCRIPTIONS_LOADING(localState, true);
+
+ expect(localState.subscriptionsLoading).toBe(true);
+ });
+ });
+
+ describe('SET_SUBSCRIPTIONS', () => {
+ it('sets subscriptions loading flag', () => {
+ const mockSubscriptions = [{ name: 'test' }];
+ mutations.SET_SUBSCRIPTIONS(localState, mockSubscriptions);
+
+ expect(localState.subscriptions).toBe(mockSubscriptions);
+ });
+ });
});
diff --git a/spec/frontend/pipelines/test_reports/stores/actions_spec.js b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
index d5acb115bc1..74a9d8c354f 100644
--- a/spec/frontend/pipelines/test_reports/stores/actions_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/actions_spec.js
@@ -82,17 +82,16 @@ describe('Actions TestReports Store', () => {
);
});
- it('should create flash on API error', async () => {
+ it('should call SET_SUITE_ERROR on error', () => {
const index = 0;
- await testAction(
+ return testAction(
actions.fetchTestSuite,
index,
{ ...state, testReports, suiteEndpoint: null },
- [],
+ [{ type: types.SET_SUITE_ERROR, payload: expect.any(Error) }],
[{ type: 'toggleLoading' }, { type: 'toggleLoading' }],
);
- expect(createFlash).toHaveBeenCalled();
});
describe('when we already have the suite data', () => {
diff --git a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
index f2dbeec6a06..6ab479a257c 100644
--- a/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
+++ b/spec/frontend/pipelines/test_reports/stores/mutations_spec.js
@@ -1,6 +1,9 @@
import testReports from 'test_fixtures/pipelines/test_report.json';
import * as types from '~/pipelines/stores/test_reports/mutation_types';
import mutations from '~/pipelines/stores/test_reports/mutations';
+import createFlash from '~/flash';
+
+jest.mock('~/flash.js');
describe('Mutations TestReports Store', () => {
let mockState;
@@ -44,6 +47,24 @@ describe('Mutations TestReports Store', () => {
});
});
+ describe('set suite error', () => {
+ it('should set the error message in state if provided', () => {
+ const message = 'Test report artifacts have expired';
+
+ mutations[types.SET_SUITE_ERROR](mockState, {
+ response: { data: { errors: message } },
+ });
+
+ expect(mockState.errorMessage).toBe(message);
+ });
+
+ it('should show a flash message otherwise', () => {
+ mutations[types.SET_SUITE_ERROR](mockState, {});
+
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+
describe('set selected suite index', () => {
it('should set selectedSuiteIndex', () => {
const selectedSuiteIndex = 0;
diff --git a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
index 97241e14129..dc72fa31ace 100644
--- a/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
+++ b/spec/frontend/pipelines/test_reports/test_suite_table_spec.js
@@ -1,12 +1,13 @@
import { GlButton, GlFriendlyWrap, GlLink, GlPagination } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import testReports from 'test_fixtures/pipelines/test_report.json';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import SuiteTable from '~/pipelines/components/test_reports/test_suite_table.vue';
import { TestStatus } from '~/pipelines/constants';
import * as getters from '~/pipelines/stores/test_reports/getters';
import { formatFilePath } from '~/pipelines/stores/test_reports/utils';
+import { ARTIFACTS_EXPIRED_ERROR_MESSAGE } from '~/pipelines/stores/test_reports/constants';
import skippedTestCases from './mock_data';
Vue.use(Vuex);
@@ -23,13 +24,14 @@ describe('Test reports suite table', () => {
const testCases = testSuite.test_cases;
const blobPath = '/test/blob/path';
- const noCasesMessage = () => wrapper.find('.js-no-test-cases');
- const allCaseRows = () => wrapper.findAll('.js-case-row');
- const findCaseRowAtIndex = (index) => wrapper.findAll('.js-case-row').at(index);
+ const noCasesMessage = () => wrapper.findByTestId('no-test-cases');
+ const artifactsExpiredMessage = () => wrapper.findByTestId('artifacts-expired');
+ const allCaseRows = () => wrapper.findAllByTestId('test-case-row');
+ const findCaseRowAtIndex = (index) => wrapper.findAllByTestId('test-case-row').at(index);
const findLinkForRow = (row) => row.find(GlLink);
const findIconForRow = (row, status) => row.find(`.ci-status-icon-${status}`);
- const createComponent = (suite = testSuite, perPage = 20) => {
+ const createComponent = ({ suite = testSuite, perPage = 20, errorMessage } = {}) => {
store = new Vuex.Store({
state: {
blobPath,
@@ -41,11 +43,12 @@ describe('Test reports suite table', () => {
page: 1,
perPage,
},
+ errorMessage,
},
getters,
});
- wrapper = shallowMount(SuiteTable, {
+ wrapper = shallowMountExtended(SuiteTable, {
store,
stubs: { GlFriendlyWrap },
});
@@ -55,12 +58,18 @@ describe('Test reports suite table', () => {
wrapper.destroy();
});
- describe('should not render', () => {
- beforeEach(() => createComponent([]));
+ it('should render a message when there are no test cases', () => {
+ createComponent({ suite: [] });
- it('a table when there are no test cases', () => {
- expect(noCasesMessage().exists()).toBe(true);
- });
+ expect(noCasesMessage().exists()).toBe(true);
+ expect(artifactsExpiredMessage().exists()).toBe(false);
+ });
+
+ it('should render a message when artifacts have expired', () => {
+ createComponent({ suite: [], errorMessage: ARTIFACTS_EXPIRED_ERROR_MESSAGE });
+
+ expect(noCasesMessage().exists()).toBe(true);
+ expect(artifactsExpiredMessage().exists()).toBe(true);
});
describe('when a test suite is supplied', () => {
@@ -102,7 +111,7 @@ describe('Test reports suite table', () => {
const perPage = 2;
beforeEach(() => {
- createComponent(testSuite, perPage);
+ createComponent({ testSuite, perPage });
});
it('renders one page of test cases', () => {
@@ -117,11 +126,13 @@ describe('Test reports suite table', () => {
describe('when a test case classname property is null', () => {
it('still renders all test cases', () => {
createComponent({
- ...testSuite,
- test_cases: testSuite.test_cases.map((testCase) => ({
- ...testCase,
- classname: null,
- })),
+ testSuite: {
+ ...testSuite,
+ test_cases: testSuite.test_cases.map((testCase) => ({
+ ...testCase,
+ classname: null,
+ })),
+ },
});
expect(allCaseRows()).toHaveLength(testCases.length);
@@ -131,11 +142,13 @@ describe('Test reports suite table', () => {
describe('when a test case name property is null', () => {
it('still renders all test cases', () => {
createComponent({
- ...testSuite,
- test_cases: testSuite.test_cases.map((testCase) => ({
- ...testCase,
- name: null,
- })),
+ testSuite: {
+ ...testSuite,
+ test_cases: testSuite.test_cases.map((testCase) => ({
+ ...testCase,
+ name: null,
+ })),
+ },
});
expect(allCaseRows()).toHaveLength(testCases.length);
diff --git a/spec/helpers/jira_connect_helper_spec.rb b/spec/helpers/jira_connect_helper_spec.rb
index 1c1b2a22b7c..169a5c0076a 100644
--- a/spec/helpers/jira_connect_helper_spec.rb
+++ b/spec/helpers/jira_connect_helper_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe JiraConnectHelper do
it 'includes Jira Connect app attributes' do
is_expected.to include(
:groups_path,
+ :add_subscriptions_path,
:subscriptions_path,
:users_path,
:subscriptions,
diff --git a/spec/lib/gitlab/experimentation/controller_concern_spec.rb b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
index 435a0d56301..799884d7a74 100644
--- a/spec/lib/gitlab/experimentation/controller_concern_spec.rb
+++ b/spec/lib/gitlab/experimentation/controller_concern_spec.rb
@@ -274,7 +274,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
action: 'start',
property: 'control_group',
value: 1,
- label: Digest::MD5.hexdigest('abc'),
+ label: Digest::SHA256.hexdigest('abc'),
user: user
)
end
@@ -289,7 +289,7 @@ RSpec.describe Gitlab::Experimentation::ControllerConcern, type: :controller do
action: 'start',
property: 'control_group',
value: 1,
- label: Digest::MD5.hexdigest('somestring'),
+ label: Digest::SHA256.hexdigest('somestring'),
user: user
)
end