From e72386771751fb22245bc6604fef236a2ee130cb Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 17 Dec 2019 18:07:48 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .eslintrc.yml | 3 + app/assets/javascripts/diffs/components/app.vue | 5 +- app/assets/javascripts/diffs/store/actions.js | 7 +- app/assets/javascripts/ide/stores/getters.js | 25 ++ .../pages/projects/snippets/show/index.js | 15 +- .../repository/components/breadcrumbs.vue | 4 +- .../repository/components/table/parent_row.vue | 2 +- .../repository/components/table/row.vue | 2 +- app/assets/javascripts/repository/router.js | 2 +- app/assets/javascripts/snippets/components/app.vue | 29 +- .../snippets/components/snippet_header.vue | 241 ++++++++++++ .../snippets/fragments/author.fragment.graphql | 8 + .../snippets/fragments/project.fragment.graphql | 6 + .../fragments/snippetBase.fragment.graphql | 13 + .../mutations/deleteSnippet.mutation.graphql | 5 + .../snippets/queries/getSnippet.query.graphql | 13 - .../queries/projectPermissions.query.graphql | 7 + .../snippets/queries/snippet.query.graphql | 15 + .../snippets/queries/userPermissions.query.graphql | 7 + .../components/diff_viewer/diff_viewer.vue | 4 +- app/assets/stylesheets/utilities.scss | 2 +- app/helpers/submodule_helper.rb | 2 + app/helpers/tree_helper.rb | 4 +- app/models/clusters/applications/knative.rb | 2 +- app/views/admin/users/_access_levels.html.haml | 2 +- app/views/dashboard/_snippets_head.html.haml | 3 +- app/views/dashboard/snippets/index.html.haml | 3 +- app/views/profiles/accounts/_providers.html.haml | 2 +- app/views/projects/snippets/index.html.haml | 3 +- app/views/projects/snippets/show.html.haml | 17 +- app/views/shared/empty_states/_snippets.html.haml | 3 +- app/views/snippets/_snippets.html.haml | 2 +- babel.config.js | 1 + changelogs/unreleased/39498-part-1.yml | 5 + .../ap-14330-external-personal-snippets.yml | 5 + .../unreleased/dz-scope-repository-routes.yml | 5 + changelogs/unreleased/knative-0-9-update.yml | 5 + changelogs/unreleased/service-desk-name.yml | 5 + config/prometheus/common_metrics.yml | 2 +- config/routes/project.rb | 3 +- config/routes/repository.rb | 2 +- .../20191213104838_add_service_desk_username.rb | 9 + ...20191209215316_knative_0_9_prometheus_update.rb | 13 + db/schema.rb | 3 +- doc/development/background_migrations.md | 10 +- doc/development/fe_guide/graphql.md | 50 +-- doc/user/permissions.md | 2 +- lib/banzai/filter/relative_link_filter.rb | 9 +- lib/gitlab/dependency_linker/godeps_json_linker.rb | 6 +- lib/gitlab/import_export/import_export.yml | 2 + lib/gitlab/middleware/go.rb | 2 +- lib/quality/test_level.rb | 9 +- locale/gitlab.pot | 21 ++ package.json | 1 + scripts/review_apps/base-config.yaml | 4 +- .../controllers/concerns/metrics_dashboard_spec.rb | 2 +- spec/controllers/projects/blame_controller_spec.rb | 2 +- spec/controllers/projects/blob_controller_spec.rb | 4 +- .../projects/branches_controller_spec.rb | 6 +- spec/controllers/projects/raw_controller_spec.rb | 2 +- .../serverless/functions_controller_spec.rb | 16 + spec/controllers/projects/tree_controller_spec.rb | 10 +- spec/controllers/projects_controller_spec.rb | 2 +- spec/features/dashboard/snippets_spec.rb | 34 ++ spec/features/discussion_comments/snippets_spec.rb | 1 + spec/features/issues/move_spec.rb | 1 + spec/features/issues/user_views_issues_spec.rb | 1 + .../user_sorts_merge_requests_spec.rb | 1 + .../user_views_open_merge_requests_spec.rb | 1 + spec/features/oauth_login_spec.rb | 1 + spec/features/participants_autocomplete_spec.rb | 2 + spec/features/projects/clusters/gcp_spec.rb | 1 + .../projects/files/user_edits_files_spec.rb | 1 + .../projects/snippets/create_snippet_spec.rb | 5 + spec/features/projects/snippets/show_spec.rb | 1 + .../snippets/user_comments_on_snippet_spec.rb | 1 + .../projects/snippets/user_deletes_snippet_spec.rb | 1 + .../projects/snippets/user_updates_snippet_spec.rb | 1 + spec/features/reportable_note/snippets_spec.rb | 1 + .../security/group/internal_access_spec.rb | 1 + .../features/security/group/private_access_spec.rb | 1 + spec/features/security/group/public_access_spec.rb | 1 + .../security/project/internal_access_spec.rb | 7 + .../security/project/private_access_spec.rb | 7 + .../security/project/public_access_spec.rb | 6 + spec/features/snippets/explore_spec.rb | 63 +++- spec/features/task_lists_spec.rb | 1 + spec/features/users/login_spec.rb | 1 + spec/finders/clusters_finder_spec.rb | 1 + spec/finders/group_descendants_finder_spec.rb | 1 + spec/finders/group_projects_finder_spec.rb | 2 + spec/finders/groups_finder_spec.rb | 2 + spec/finders/issues_finder_spec.rb | 1 + .../merge_request_target_project_finder_spec.rb | 1 + spec/finders/pipelines_finder_spec.rb | 1 + spec/finders/projects_finder_spec.rb | 2 + spec/finders/tags_finder_spec.rb | 1 + spec/frontend/fixtures/static/projects.json | 9 + spec/frontend/helpers/dom_shims/index.js | 1 + spec/frontend/helpers/dom_shims/inner_text.js | 11 + spec/frontend/ide/stores/getters_spec.js | 92 ++++- .../frontend/monitoring/charts/time_series_spec.js | 414 --------------------- .../monitoring/components/charts/column_spec.js | 66 ++++ .../components/charts/empty_chart_spec.js | 33 ++ .../monitoring/components/charts/heatmap_spec.js | 69 ++++ .../components/charts/single_stat_spec.js | 31 ++ .../components/charts/time_series_spec.js | 408 ++++++++++++++++++++ .../repository/components/table/parent_row_spec.js | 8 +- .../repository/components/table/row_spec.js | 2 +- spec/frontend/repository/router_spec.js | 10 +- spec/frontend/snippets/components/app_spec.js | 26 +- .../snippets/components/snippet_header_spec.js | 171 +++++++++ spec/frontend/test_setup.js | 12 - .../concerns/mutations/resolves_group_spec.rb | 1 + .../concerns/mutations/resolves_project_spec.rb | 1 + .../mutations/issues/set_confidential_spec.rb | 2 + spec/graphql/mutations/issues/set_due_date_spec.rb | 2 + .../mutations/merge_requests/set_assignees_spec.rb | 2 + .../mutations/merge_requests/set_labels_spec.rb | 2 + .../mutations/merge_requests/set_locked_spec.rb | 2 + .../mutations/merge_requests/set_milestone_spec.rb | 2 + .../merge_requests/set_subscription_spec.rb | 2 + .../mutations/merge_requests/set_wip_spec.rb | 2 + .../resolvers/concerns/resolves_pipelines_spec.rb | 1 + .../sentry_detailed_error_resolver_spec.rb | 1 + .../permission_types/base_permission_type_spec.rb | 1 + spec/helpers/avatars_helper_spec.rb | 1 + spec/helpers/blob_helper_spec.rb | 4 +- spec/helpers/events_helper_spec.rb | 1 + spec/helpers/groups_helper_spec.rb | 3 + spec/helpers/issuables_helper_spec.rb | 1 + spec/helpers/labels_helper_spec.rb | 1 + spec/helpers/markup_helper_spec.rb | 4 +- spec/helpers/merge_requests_helper_spec.rb | 1 + spec/helpers/projects_helper_spec.rb | 3 + spec/helpers/submodule_helper_spec.rb | 4 +- spec/javascripts/diffs/components/app_spec.js | 8 +- spec/javascripts/diffs/store/actions_spec.js | 10 +- spec/javascripts/monitoring/charts/column_spec.js | 62 --- .../monitoring/charts/empty_chart_spec.js | 33 -- spec/javascripts/monitoring/charts/heatmap_spec.js | 69 ---- .../monitoring/charts/single_stat_spec.js | 31 -- .../components/diff_viewer/diff_viewer_spec.js | 4 +- .../lib/banzai/filter/relative_link_filter_spec.rb | 36 +- spec/lib/gitlab/conflict/file_spec.rb | 2 +- spec/lib/gitlab/database_spec.rb | 1 + .../dependency_linker/godeps_json_linker_spec.rb | 4 +- spec/lib/gitlab/middleware/go_spec.rb | 2 +- .../queries/knative_invocation_query_spec.rb | 4 +- spec/lib/quality/test_level_spec.rb | 4 +- spec/mailers/notify_spec.rb | 12 + spec/models/clusters/applications/knative_spec.rb | 2 +- spec/models/project_spec.rb | 2 +- spec/policies/project_policy_spec.rb | 2 + spec/presenters/blob_presenter_spec.rb | 2 +- spec/presenters/label_presenter_spec.rb | 1 + spec/presenters/merge_request_presenter_spec.rb | 8 +- spec/presenters/project_presenter_spec.rb | 1 + spec/presenters/tree_entry_presenter_spec.rb | 2 +- spec/requests/api/award_emoji_spec.rb | 1 + spec/requests/api/boards_spec.rb | 1 + .../api/graphql/mutations/award_emojis/add_spec.rb | 1 + .../graphql/mutations/award_emojis/toggle_spec.rb | 1 + spec/requests/api/project_import_spec.rb | 1 + spec/requests/api/projects_spec.rb | 3 +- spec/requests/api/settings_spec.rb | 1 + spec/requests/api/snippets_spec.rb | 1 + spec/requests/api/users_spec.rb | 1 + spec/requests/git_http_spec.rb | 4 +- spec/routing/project_routing_spec.rb | 64 ++-- spec/serializers/blob_entity_spec.rb | 2 +- .../serializers/cluster_application_entity_spec.rb | 1 + spec/serializers/cluster_entity_spec.rb | 1 + spec/serializers/environment_entity_spec.rb | 1 + spec/serializers/group_child_entity_spec.rb | 1 + spec/serializers/group_child_serializer_spec.rb | 1 + .../merge_request_poll_widget_entity_spec.rb | 2 +- spec/serializers/note_entity_spec.rb | 1 + spec/serializers/project_note_entity_spec.rb | 1 + spec/serializers/user_entity_spec.rb | 1 + spec/support/helpers/kubernetes_helpers.rb | 54 ++- .../policies/project_policy_shared_examples.rb | 1 + spec/uploaders/file_mover_spec.rb | 1 + spec/validators/addressable_url_validator_spec.rb | 1 + spec/validators/devise_email_validator_spec.rb | 1 + spec/views/help/index.html.haml_spec.rb | 2 +- spec/workers/expire_job_cache_worker_spec.rb | 1 + spec/workers/expire_pipeline_cache_worker_spec.rb | 1 + .../notification_service_worker_spec.rb | 1 + spec/workers/repository_check/batch_worker_spec.rb | 1 + yarn.lock | 15 + 191 files changed, 1845 insertions(+), 864 deletions(-) create mode 100644 app/assets/javascripts/snippets/components/snippet_header.vue create mode 100644 app/assets/javascripts/snippets/fragments/author.fragment.graphql create mode 100644 app/assets/javascripts/snippets/fragments/project.fragment.graphql create mode 100644 app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql create mode 100644 app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql delete mode 100644 app/assets/javascripts/snippets/queries/getSnippet.query.graphql create mode 100644 app/assets/javascripts/snippets/queries/projectPermissions.query.graphql create mode 100644 app/assets/javascripts/snippets/queries/snippet.query.graphql create mode 100644 app/assets/javascripts/snippets/queries/userPermissions.query.graphql create mode 100644 changelogs/unreleased/39498-part-1.yml create mode 100644 changelogs/unreleased/ap-14330-external-personal-snippets.yml create mode 100644 changelogs/unreleased/dz-scope-repository-routes.yml create mode 100644 changelogs/unreleased/knative-0-9-update.yml create mode 100644 changelogs/unreleased/service-desk-name.yml create mode 100644 db/migrate/20191213104838_add_service_desk_username.rb create mode 100644 db/post_migrate/20191209215316_knative_0_9_prometheus_update.rb create mode 100644 spec/frontend/helpers/dom_shims/inner_text.js delete mode 100644 spec/frontend/monitoring/charts/time_series_spec.js create mode 100644 spec/frontend/monitoring/components/charts/column_spec.js create mode 100644 spec/frontend/monitoring/components/charts/empty_chart_spec.js create mode 100644 spec/frontend/monitoring/components/charts/heatmap_spec.js create mode 100644 spec/frontend/monitoring/components/charts/single_stat_spec.js create mode 100644 spec/frontend/monitoring/components/charts/time_series_spec.js create mode 100644 spec/frontend/snippets/components/snippet_header_spec.js delete mode 100644 spec/javascripts/monitoring/charts/column_spec.js delete mode 100644 spec/javascripts/monitoring/charts/empty_chart_spec.js delete mode 100644 spec/javascripts/monitoring/charts/heatmap_spec.js delete mode 100644 spec/javascripts/monitoring/charts/single_stat_spec.js diff --git a/.eslintrc.yml b/.eslintrc.yml index a55e65c2678..a8cbd9731a3 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -2,6 +2,7 @@ extends: - '@gitlab' - plugin:promise/recommended - plugin:no-jquery/slim + - plugin:no-jquery/deprecated-3.4 globals: __webpack_public_path__: true gl: false @@ -48,8 +49,10 @@ rules: no-jquery/no-animate: off # all offenses of no-jquery/no-animate-toggle are false positives ( $toast.show() ) no-jquery/no-animate-toggle: off + no-jquery/no-event-shorthand: off no-jquery/no-fade: off no-jquery/no-serialize: error + no-jquery/no-sizzle: off promise/always-return: off promise/no-callback-in-promise: off overrides: diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 8fc8a8d0495..8ea443814e9 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -220,9 +220,6 @@ export default { this.assignedDiscussions = false; this.fetchData(false); }, - isLatestVersion() { - return window.location.search.indexOf('diff_id') === -1; - }, startDiffRendering() { requestIdleCallback( () => { @@ -232,7 +229,7 @@ export default { ); }, fetchData(toggleTree = true) { - if (this.isLatestVersion() && this.glFeatures.diffsBatchLoad) { + if (this.glFeatures.diffsBatchLoad) { this.fetchDiffFilesMeta() .then(() => { if (toggleTree) this.hideTreeListIfJustOneFile(); diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 44672659f56..992b45c97ac 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -90,14 +90,13 @@ export const fetchDiffFiles = ({ state, commit }) => { }; export const fetchDiffFilesBatch = ({ commit, state }) => { - const baseUrl = `${state.endpointBatch}?per_page=${DIFFS_PER_PAGE}`; - const url = page => (page ? `${baseUrl}&page=${page}` : baseUrl); - commit(types.SET_BATCH_LOADING, true); const getBatch = page => axios - .get(url(page)) + .get(state.endpointBatch, { + params: { page, per_page: DIFFS_PER_PAGE, w: state.showWhitespace ? '0' : '1' }, + }) .then(({ data: { pagination, diff_files } }) => { commit(types.SET_DIFF_DATA_BATCH, { diff_files }); commit(types.SET_BATCH_LOADING, false); diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js index a176fd0aca8..bb8374b4e78 100644 --- a/app/assets/javascripts/ide/stores/getters.js +++ b/app/assets/javascripts/ide/stores/getters.js @@ -115,5 +115,30 @@ export const isOnDefaultBranch = (_state, getters) => export const canPushToBranch = (_state, getters) => getters.currentBranch && getters.currentBranch.can_push; +export const isFileDeletedAndReadded = (state, getters) => path => { + const stagedFile = getters.getStagedFile(path); + const file = state.entries[path]; + return Boolean(stagedFile && stagedFile.deleted && file.tempFile); +}; + +// checks if any diff exists in the staged or unstaged changes for this path +export const getDiffInfo = (state, getters) => path => { + const stagedFile = getters.getStagedFile(path); + const file = state.entries[path]; + const renamed = file.prevPath ? file.path !== file.prevPath : false; + const deletedAndReadded = getters.isFileDeletedAndReadded(path); + const deleted = deletedAndReadded ? false : file.deleted; + const tempFile = deletedAndReadded ? false : file.tempFile; + const changed = file.content !== (deletedAndReadded ? stagedFile.raw : file.raw); + + return { + exists: changed || renamed || deleted || tempFile, + changed, + renamed, + deleted, + tempFile, + }; +}; + // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/assets/javascripts/pages/projects/snippets/show/index.js b/app/assets/javascripts/pages/projects/snippets/show/index.js index c35b9c30058..738bf08f1bf 100644 --- a/app/assets/javascripts/pages/projects/snippets/show/index.js +++ b/app/assets/javascripts/pages/projects/snippets/show/index.js @@ -3,11 +3,16 @@ import ZenMode from '~/zen_mode'; import LineHighlighter from '~/line_highlighter'; import BlobViewer from '~/blob/viewer'; import snippetEmbed from '~/snippet/snippet_embed'; +import initSnippetsApp from '~/snippets'; document.addEventListener('DOMContentLoaded', () => { - new LineHighlighter(); // eslint-disable-line no-new - new BlobViewer(); // eslint-disable-line no-new - initNotes(); - new ZenMode(); // eslint-disable-line no-new - snippetEmbed(); + if (!gon.features.snippetsVue) { + new LineHighlighter(); // eslint-disable-line no-new + new BlobViewer(); // eslint-disable-line no-new + initNotes(); + new ZenMode(); // eslint-disable-line no-new + snippetEmbed(); + } else { + initSnippetsApp(); + } }); diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue index f6b9ea5d30d..e1382aa86d9 100644 --- a/app/assets/javascripts/repository/components/breadcrumbs.vue +++ b/app/assets/javascripts/repository/components/breadcrumbs.vue @@ -104,10 +104,10 @@ export default { return acc.concat({ name, path, - to: `/tree/${this.ref}${path}`, + to: `/-/tree/${this.ref}${path}`, }); }, - [{ name: this.projectShortPath, path: '/', to: `/tree/${this.ref}/` }], + [{ name: this.projectShortPath, path: '/', to: `/-/tree/${this.ref}/` }], ); }, canCreateMrFromFork() { diff --git a/app/assets/javascripts/repository/components/table/parent_row.vue b/app/assets/javascripts/repository/components/table/parent_row.vue index 3c39f404226..38aa672bc1c 100644 --- a/app/assets/javascripts/repository/components/table/parent_row.vue +++ b/app/assets/javascripts/repository/components/table/parent_row.vue @@ -15,7 +15,7 @@ export default { const splitArray = this.path.split('/'); splitArray.pop(); - return { path: `/tree/${this.commitRef}/${splitArray.join('/')}` }; + return { path: `/-/tree/${this.commitRef}/${splitArray.join('/')}` }; }, }, methods: { diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue index cf0457a2abf..57dd08c2142 100644 --- a/app/assets/javascripts/repository/components/table/row.vue +++ b/app/assets/javascripts/repository/components/table/row.vue @@ -84,7 +84,7 @@ export default { }, computed: { routerLinkTo() { - return this.isFolder ? { path: `/tree/${this.ref}/${this.path}` } : null; + return this.isFolder ? { path: `/-/tree/${this.ref}/${this.path}` } : null; }, iconName() { return `fa-${getIconName(this.type, this.path)}`; diff --git a/app/assets/javascripts/repository/router.js b/app/assets/javascripts/repository/router.js index ebf0a7091ea..fa544444be8 100644 --- a/app/assets/javascripts/repository/router.js +++ b/app/assets/javascripts/repository/router.js @@ -12,7 +12,7 @@ export default function createRouter(base, baseRef) { base: joinPaths(gon.relative_url_root || '', base), routes: [ { - path: `/tree/${baseRef}(/.*)?`, + path: `/-/tree/${baseRef}(/.*)?`, name: 'treePath', component: TreePage, props: route => ({ diff --git a/app/assets/javascripts/snippets/components/app.vue b/app/assets/javascripts/snippets/components/app.vue index e3d6cdd4606..bd2cb8e4595 100644 --- a/app/assets/javascripts/snippets/components/app.vue +++ b/app/assets/javascripts/snippets/components/app.vue @@ -1,10 +1,16 @@ diff --git a/app/assets/javascripts/snippets/components/snippet_header.vue b/app/assets/javascripts/snippets/components/snippet_header.vue new file mode 100644 index 00000000000..e8f1bfeaf43 --- /dev/null +++ b/app/assets/javascripts/snippets/components/snippet_header.vue @@ -0,0 +1,241 @@ + + diff --git a/app/assets/javascripts/snippets/fragments/author.fragment.graphql b/app/assets/javascripts/snippets/fragments/author.fragment.graphql new file mode 100644 index 00000000000..2684bd0fa37 --- /dev/null +++ b/app/assets/javascripts/snippets/fragments/author.fragment.graphql @@ -0,0 +1,8 @@ +fragment Author on Snippet { + author { + name, + avatarUrl, + username, + webUrl + } +} \ No newline at end of file diff --git a/app/assets/javascripts/snippets/fragments/project.fragment.graphql b/app/assets/javascripts/snippets/fragments/project.fragment.graphql new file mode 100644 index 00000000000..7d65789c67b --- /dev/null +++ b/app/assets/javascripts/snippets/fragments/project.fragment.graphql @@ -0,0 +1,6 @@ +fragment Project on Snippet { + project { + fullPath + webUrl + } +} \ No newline at end of file diff --git a/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql b/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql new file mode 100644 index 00000000000..57348a422ec --- /dev/null +++ b/app/assets/javascripts/snippets/fragments/snippetBase.fragment.graphql @@ -0,0 +1,13 @@ +fragment SnippetBase on Snippet { + id + title + description + createdAt + updatedAt + visibilityLevel + webUrl + userPermissions { + adminSnippet + updateSnippet + } +} \ No newline at end of file diff --git a/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql b/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql new file mode 100644 index 00000000000..0c829cbdee6 --- /dev/null +++ b/app/assets/javascripts/snippets/mutations/deleteSnippet.mutation.graphql @@ -0,0 +1,5 @@ +mutation DeleteSnippet($id: ID!) { + destroySnippet(input: {id: $id}) { + errors + } +} \ No newline at end of file diff --git a/app/assets/javascripts/snippets/queries/getSnippet.query.graphql b/app/assets/javascripts/snippets/queries/getSnippet.query.graphql deleted file mode 100644 index 5a5f0d05c5b..00000000000 --- a/app/assets/javascripts/snippets/queries/getSnippet.query.graphql +++ /dev/null @@ -1,13 +0,0 @@ -query getSnippet($ids: [ID!]) { - snippets(ids: $ids) { - edges { - node { - title - description - createdAt - updatedAt - visibility - } - } - } -} diff --git a/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql b/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql new file mode 100644 index 00000000000..288bd0889bf --- /dev/null +++ b/app/assets/javascripts/snippets/queries/projectPermissions.query.graphql @@ -0,0 +1,7 @@ +query CanCreateProjectSnippet($fullPath: ID!) { + project(fullPath: $fullPath) { + userPermissions { + createSnippet + } + } +} \ No newline at end of file diff --git a/app/assets/javascripts/snippets/queries/snippet.query.graphql b/app/assets/javascripts/snippets/queries/snippet.query.graphql new file mode 100644 index 00000000000..1cb2c86c4d8 --- /dev/null +++ b/app/assets/javascripts/snippets/queries/snippet.query.graphql @@ -0,0 +1,15 @@ +#import '../fragments/snippetBase.fragment.graphql' +#import '../fragments/project.fragment.graphql' +#import '../fragments/author.fragment.graphql' + +query GetSnippetQuery($ids: [ID!]) { + snippets(ids: $ids) { + edges { + node { + ...SnippetBase + ...Project + ...Author + } + } + } +} diff --git a/app/assets/javascripts/snippets/queries/userPermissions.query.graphql b/app/assets/javascripts/snippets/queries/userPermissions.query.graphql new file mode 100644 index 00000000000..f5b97b3d0f0 --- /dev/null +++ b/app/assets/javascripts/snippets/queries/userPermissions.query.graphql @@ -0,0 +1,7 @@ +query CanCreatePersonalSnippet { + currentUser { + userPermissions { + createSnippet + } + } +} \ No newline at end of file diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue index b874bedab36..bf3c3666300 100644 --- a/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/diff_viewer/diff_viewer.vue @@ -79,10 +79,10 @@ export default { return this.projectPath.indexOf('/') === 0 ? '' : `${gon.relative_url_root}/`; }, fullOldPath() { - return `${this.basePath}${this.projectPath}/raw/${this.oldSha}/${this.oldPath}`; + return `${this.basePath}${this.projectPath}/-/raw/${this.oldSha}/${this.oldPath}`; }, fullNewPath() { - return `${this.basePath}${this.projectPath}/raw/${this.newSha}/${this.newPath}`; + return `${this.basePath}${this.projectPath}/-/raw/${this.newSha}/${this.newPath}`; }, }, }; diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 7d25b6927f9..1f4bba5fc33 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -40,12 +40,12 @@ // Classes using mixins coming from @gitlab-ui // can be removed once https://gitlab.com/gitlab-org/gitlab/merge_requests/19021 has been merged -.gl-bg-blue-500 { @include gl-bg-blue-500; } .gl-bg-red-100 { @include gl-bg-red-100; } .gl-bg-orange-100 { @include gl-bg-orange-100; } .gl-bg-gray-100 { @include gl-bg-gray-100; } .gl-bg-green-100 { @include gl-bg-green-100;} +.gl-text-blue-500 { @include gl-text-blue-500; } .gl-text-gray-900 { @include gl-text-gray-900; } .gl-text-red-700 { @include gl-text-red-700; } .gl-text-orange-700 { @include gl-text-orange-700; } diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index 4b83988e8bb..e5400652458 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -43,6 +43,8 @@ module SubmoduleHelper elsif github_dot_com_url?(url) standard_links('github.com', namespace, project, submodule_item_id) elsif gitlab_dot_com_url?(url) + # This need to be replaced with /-/tree routing once one is landed on + # GitLab.com. Issue https://gitlab.com/gitlab-org/gitlab/issues/42764 standard_links('gitlab.com', namespace, project, submodule_item_id) else [sanitize_submodule_url(url), nil] diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index af1919eeb40..0b50b8b1130 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -38,13 +38,13 @@ module TreeHelper # many paths, as with a repository tree that has thousands of items. def fast_project_blob_path(project, blob_path) ActionDispatch::Journey::Router::Utils.escape_path( - File.join(relative_url_root, project.path_with_namespace, 'blob', blob_path) + File.join(relative_url_root, project.path_with_namespace, '-', 'blob', blob_path) ) end def fast_project_tree_path(project, tree_path) ActionDispatch::Journey::Router::Utils.escape_path( - File.join(relative_url_root, project.path_with_namespace, 'tree', tree_path) + File.join(relative_url_root, project.path_with_namespace, '-', 'tree', tree_path) ) end diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 89ffb72a22f..387503bee54 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Knative < ApplicationRecord - VERSION = '0.7.0' + VERSION = '0.9.0' REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts' METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml' FETCH_IP_ADDRESS_DELAY = 30.seconds diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml index bb1e22cc610..e3ab2e4f9bd 100644 --- a/app/views/admin/users/_access_levels.html.haml +++ b/app/views/admin/users/_access_levels.html.haml @@ -43,7 +43,7 @@ = f.check_box :external do External %p.light - External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects or groups. + External users cannot see internal or private projects unless access is explicitly granted. Also, external users cannot create projects, groups, or personal snippets. %row.hidden#warning_external_automatically_set.hidden .badge.badge-warning.text-white = _('Automatically marked as default internal user') diff --git a/app/views/dashboard/_snippets_head.html.haml b/app/views/dashboard/_snippets_head.html.haml index 34aca40d0d1..4958cdc3745 100644 --- a/app/views/dashboard/_snippets_head.html.haml +++ b/app/views/dashboard/_snippets_head.html.haml @@ -3,7 +3,8 @@ - if current_user && current_user.snippets.any? || @snippets.any? .page-title-controls - = link_to _("New snippet"), new_snippet_path, class: "btn btn-success", title: _("New snippet") + - if can?(current_user, :create_personal_snippet) + = link_to _("New snippet"), new_snippet_path, class: "btn btn-success", title: _("New snippet") .top-area %ul.nav-links.nav.nav-tabs diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml index 2caa8e0cac4..44a9270971a 100644 --- a/app/views/dashboard/snippets/index.html.haml +++ b/app/views/dashboard/snippets/index.html.haml @@ -1,6 +1,7 @@ - @hide_top_links = true - page_title "Snippets" - header_title "Snippets", dashboard_snippets_path +- button_path = new_snippet_path if can?(current_user, :create_personal_snippet) = render 'dashboard/snippets_head' - if current_user.snippets.exists? @@ -9,4 +10,4 @@ - if current_user.snippets.exists? = render partial: 'shared/snippets/list', locals: { link_project: true } - else - = render 'shared/empty_states/snippets', button_path: new_snippet_path + = render 'shared/empty_states/snippets', button_path: button_path diff --git a/app/views/profiles/accounts/_providers.html.haml b/app/views/profiles/accounts/_providers.html.haml index dc17de1b220..a87191d0fa4 100644 --- a/app/views/profiles/accounts/_providers.html.haml +++ b/app/views/profiles/accounts/_providers.html.haml @@ -16,6 +16,6 @@ %a.provider-btn = s_('Profiles|Active') - elsif link_allowed - = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn gl-bg-blue-500' do + = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn gl-text-blue-500' do = s_('Profiles|Connect') = render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: group_saml_identities diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index 7682d01a5a1..0ce18d83d57 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -8,8 +8,7 @@ - if can?(current_user, :create_project_snippet, @project) .nav-controls - - if can?(current_user, :create_project_snippet, @project) - = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet") + = link_to _("New snippet"), new_project_snippet_path(@project), class: "btn btn-success", title: _("New snippet") = render 'shared/snippets/list' - else diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index f495b4eaf30..768e4422206 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -3,13 +3,16 @@ - breadcrumb_title @snippet.to_reference - page_title "#{@snippet.title} (#{@snippet.to_reference})", _("Snippets") -= render 'shared/snippets/header' +- if Feature.enabled?(:snippets_vue) + #js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id} } +- else + = render 'shared/snippets/header' -.project-snippets - %article.file-holder.snippet-file-content - = render 'shared/snippets/blob' + .project-snippets + %article.file-holder.snippet-file-content + = render 'shared/snippets/blob' - .row-content-block.top-block.content-component-block - = render 'award_emoji/awards_block', awardable: @snippet, inline: true + .row-content-block.top-block.content-component-block + = render 'award_emoji/awards_block', awardable: @snippet, inline: true - #notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => true + #notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => true diff --git a/app/views/shared/empty_states/_snippets.html.haml b/app/views/shared/empty_states/_snippets.html.haml index a1a16b9d067..889a470d6ec 100644 --- a/app/views/shared/empty_states/_snippets.html.haml +++ b/app/views/shared/empty_states/_snippets.html.haml @@ -11,7 +11,8 @@ %p = s_('SnippetsEmptyState|They can be either public or private.') .text-center - = link_to s_('SnippetsEmptyState|New snippet'), button_path, class: 'btn btn-success', title: s_('SnippetsEmptyState|New snippet'), id: 'new_snippet_link' + - if button_path + = link_to s_('SnippetsEmptyState|New snippet'), button_path, class: 'btn btn-success', title: s_('SnippetsEmptyState|New snippet'), id: 'new_snippet_link' - unless current_page?(dashboard_snippets_path) = link_to s_('SnippetsEmptyState|Explore public snippets'), explore_snippets_path, class: 'btn btn-default', title: s_('SnippetsEmptyState|Explore public snippets') - else diff --git a/app/views/snippets/_snippets.html.haml b/app/views/snippets/_snippets.html.haml index dab247da251..69b19c0def9 100644 --- a/app/views/snippets/_snippets.html.haml +++ b/app/views/snippets/_snippets.html.haml @@ -3,7 +3,7 @@ - current_user_empty_message_header = s_('UserProfile|You haven\'t created any snippets.') - current_user_empty_message_description = s_('UserProfile|Snippets in GitLab can either be private, internal, or public.') - primary_button_label = _('New snippet') -- primary_button_link = new_snippet_path +- primary_button_link = new_snippet_path if can?(current_user, :create_personal_snippet) - visitor_empty_message = s_('UserProfile|No snippets found.') .snippets-list-holder diff --git a/babel.config.js b/babel.config.js index 79c401ed975..91c58cf46bf 100644 --- a/babel.config.js +++ b/babel.config.js @@ -21,6 +21,7 @@ const plugins = [ '@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-json-strings', '@babel/plugin-proposal-private-methods', + '@babel/plugin-proposal-optional-chaining', ]; // add code coverage tooling if necessary diff --git a/changelogs/unreleased/39498-part-1.yml b/changelogs/unreleased/39498-part-1.yml new file mode 100644 index 00000000000..fb410993424 --- /dev/null +++ b/changelogs/unreleased/39498-part-1.yml @@ -0,0 +1,5 @@ +--- +title: "!21542 Part 1: Add new utils for Web IDE store" +merge_request: 21673 +author: +type: fixed diff --git a/changelogs/unreleased/ap-14330-external-personal-snippets.yml b/changelogs/unreleased/ap-14330-external-personal-snippets.yml new file mode 100644 index 00000000000..86cd3f0ab75 --- /dev/null +++ b/changelogs/unreleased/ap-14330-external-personal-snippets.yml @@ -0,0 +1,5 @@ +--- +title: Match external user new snippet button visibility to permissions +merge_request: 21718 +author: +type: fixed diff --git a/changelogs/unreleased/dz-scope-repository-routes.yml b/changelogs/unreleased/dz-scope-repository-routes.yml new file mode 100644 index 00000000000..302227dde57 --- /dev/null +++ b/changelogs/unreleased/dz-scope-repository-routes.yml @@ -0,0 +1,5 @@ +--- +title: Move repository routes under - scope +merge_request: 20455 +author: +type: deprecated diff --git a/changelogs/unreleased/knative-0-9-update.yml b/changelogs/unreleased/knative-0-9-update.yml new file mode 100644 index 00000000000..d25e9e90efe --- /dev/null +++ b/changelogs/unreleased/knative-0-9-update.yml @@ -0,0 +1,5 @@ +--- +title: Update Knative to 0.9.0 +merge_request: 21361 +author: cab105 +type: added diff --git a/changelogs/unreleased/service-desk-name.yml b/changelogs/unreleased/service-desk-name.yml new file mode 100644 index 00000000000..f3d32e6c6cd --- /dev/null +++ b/changelogs/unreleased/service-desk-name.yml @@ -0,0 +1,5 @@ +--- +title: Added migration which adds service desk username column +merge_request: 21733 +author: +type: added diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml index 795243fab49..314ee44ed71 100644 --- a/config/prometheus/common_metrics.yml +++ b/config/prometheus/common_metrics.yml @@ -209,6 +209,6 @@ panel_groups: weight: 1 metrics: - id: system_metrics_knative_function_invocation_count - query_range: 'sum(ceil(rate(istio_requests_total{destination_service_namespace="%{kube_namespace}", destination_app=~"%{function_name}.*"}[1m])*60))' + query_range: 'sum(ceil(rate(istio_requests_total{destination_service_namespace="%{kube_namespace}", destination_service=~"%{function_name}.*"}[1m])*60))' label: invocations / minute unit: requests diff --git a/config/routes/project.rb b/config/routes/project.rb index d2abc73f7cc..6c2dcafd1ab 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -554,7 +554,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do :forks, :group_links, :import, :avatar, :mirror, :cycle_analytics, :mattermost, :variables, :triggers, :environments, :protected_environments, :error_tracking, - :serverless, :clusters, :audit_events, :wikis, :merge_requests) + :serverless, :clusters, :audit_events, :wikis, :merge_requests, + :blob, :tree, :raw, :blame, :commits, :create_dir, :find_file, :files) end # rubocop: disable Cop/PutProjectRoutesUnderScope diff --git a/config/routes/repository.rb b/config/routes/repository.rb index 4815575ba9f..e59168c6af4 100644 --- a/config/routes/repository.rb +++ b/config/routes/repository.rb @@ -65,7 +65,7 @@ scope format: false do resources :protected_tags, only: [:index, :show, :create, :update, :destroy] end - scope constraints: { id: /[^\0]+/ } do + scope path: '-', constraints: { id: /[^\0]+/ } do scope controller: :blob do get '/new/*id', action: :new, as: :new_blob post '/create/*id', action: :create, as: :create_blob diff --git a/db/migrate/20191213104838_add_service_desk_username.rb b/db/migrate/20191213104838_add_service_desk_username.rb new file mode 100644 index 00000000000..945bdb67fa1 --- /dev/null +++ b/db/migrate/20191213104838_add_service_desk_username.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddServiceDeskUsername < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_column :service_desk_settings, :outgoing_name, :string, limit: 255 + end +end diff --git a/db/post_migrate/20191209215316_knative_0_9_prometheus_update.rb b/db/post_migrate/20191209215316_knative_0_9_prometheus_update.rb new file mode 100644 index 00000000000..52127f71cd0 --- /dev/null +++ b/db/post_migrate/20191209215316_knative_0_9_prometheus_update.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class Knative09PrometheusUpdate < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def up + ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute + end + + def down + # no-op + end +end diff --git a/db/schema.rb b/db/schema.rb index d3e9f66c388..67d305a3505 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_12_16_183532) do +ActiveRecord::Schema.define(version: 2019_12_17_160632) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -3663,6 +3663,7 @@ ActiveRecord::Schema.define(version: 2019_12_16_183532) do create_table "service_desk_settings", primary_key: "project_id", id: :bigint, default: nil, force: :cascade do |t| t.string "issue_template_key", limit: 255 + t.string "outgoing_name", limit: 255 end create_table "services", id: :serial, force: :cascade do |t| diff --git a/doc/development/background_migrations.md b/doc/development/background_migrations.md index 5e16f83b63c..8a1db615022 100644 --- a/doc/development/background_migrations.md +++ b/doc/development/background_migrations.md @@ -10,11 +10,6 @@ migrations automatically reschedule themselves for a later point in time. ## When To Use Background Migrations -> **Note:** -> When adding background migrations _you must_ make sure they are announced in the -> monthly release post along with an estimate of how long it will take to complete -> the migrations. - In the vast majority of cases you will want to use a regular Rails migration instead. Background migrations should be used when migrating _data_ in tables that have so many rows this process would take hours when performed in a @@ -34,6 +29,11 @@ Some examples where background migrations can be useful: - Populating one column based on JSON stored in another column. - Migrating data that depends on the output of external services (e.g. an API). +> **Note:** +> If the background migration is part of an important upgrade, make sure it's announced +> in the release post. Discuss with your Project Manager if you're not sure the migration falls +> into this category. + ## Isolation Background migrations must be isolated and can not use application code (e.g. diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md index 40df5f3265a..40b9fdef76e 100644 --- a/doc/development/fe_guide/graphql.md +++ b/doc/development/fe_guide/graphql.md @@ -39,43 +39,31 @@ To distinguish queries from mutations and fragments, the following naming conven - `addUser.mutation.graphql` for mutations; - `basicUser.fragment.graphql` for fragments. -GraphQL: - -- Queries are stored in `(ee/)app/assets/javascripts/` under the feature. For example, `respository/queries`. Frontend components can use these stored queries. -- Mutations are stored in - `(ee/)app/assets/javascripts//.mutation.graphql`. - ### Fragments -Fragments are a way to make your complex GraphQL queries more readable and re-usable. -They can be stored in a separate file and imported. +Fragments are a way to make your complex GraphQL queries more readable and re-usable. Here is an example of GraphQL fragment: -For example, a fragment that references another fragment: - -```ruby -fragment BaseEpic on Epic { +```javascript +fragment DesignListItem on Design { id - iid - title - webPath - relativePosition - userPermissions { - adminEpic - createEpic - } + image + event + filename + notesCount } +``` -fragment EpicNode on Epic { - ...BaseEpic - state - reference(full: true) - relationPath - createdAt - closedAt - hasChildren - hasIssues - group { - fullPath +Fragments can be stored in separate files, imported and used in queries, mutations or other fragments. + +```javascript +#import "./designList.fragment.graphql" +#import "./diffRefs.fragment.graphql" + +fragment DesignItem on Design { + ...DesignListItem + fullPath + diffRefs { + ...DesignDiffRefs } } ``` diff --git a/doc/user/permissions.md b/doc/user/permissions.md index ed58c23a22f..9cbf4fd6192 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -253,7 +253,7 @@ project and should only have access to that project. External users: -- Cannot create groups or projects. +- Cannot create groups, projects, or personal snippets. - Can only access projects to which they are explicitly granted access, thus hiding all other internal or private ones from them (like being logged out). diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 583b0081319..2581a179c79 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -152,11 +152,18 @@ module Banzai def rebuild_relative_uri(uri) file_path = nested_file_path_if_exists(uri) + resource_type = uri_type(file_path) + + # Repository routes are under /-/ scope now. + # Since we craft a path without using route helpers we must + # ensure - is added here. + prefix = '-' if %w(tree blob raw commits).include?(resource_type.to_s) uri.path = [ relative_url_root, project.full_path, - uri_type(file_path), + prefix, + resource_type, Addressable::URI.escape(ref).gsub('#', '%23'), Addressable::URI.escape(file_path) ].compact.join('/').squeeze('/').chomp('/') diff --git a/lib/gitlab/dependency_linker/godeps_json_linker.rb b/lib/gitlab/dependency_linker/godeps_json_linker.rb index d24c137793e..9166e9091ac 100644 --- a/lib/gitlab/dependency_linker/godeps_json_linker.rb +++ b/lib/gitlab/dependency_linker/godeps_json_linker.rb @@ -12,10 +12,12 @@ module Gitlab def link_dependencies link_json('ImportPath') do |path| case path + when %r{\A(?github\.com/#{REPO_REGEX})/(?.+)\z} + "https://#{$~[:repo]}/tree/master/#{$~[:path]}" when %r{\A(?gitlab\.com/#{NESTED_REPO_REGEX})\.git/(?.+)\z}, - %r{\A(?git(lab|hub)\.com/#{REPO_REGEX})/(?.+)\z} + %r{\A(?gitlab\.com/#{REPO_REGEX})/(?.+)\z} - "https://#{$~[:repo]}/tree/master/#{$~[:path]}" + "https://#{$~[:repo]}/-/tree/master/#{$~[:path]}" when /\Agolang\.org/ "https://godoc.org/#{path}" else diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index bf92de73b48..ba1ccd77604 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -220,6 +220,8 @@ excluded_attributes: - :encrypted_token - :encrypted_token_iv - :enabled + service_desk_setting: + - :outgoing_name methods: notes: diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index f207d91235f..53508938c49 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -60,7 +60,7 @@ module Gitlab end meta_import_tag = tag :meta, name: 'go-import', content: "#{import_prefix} git #{repository_url}" - meta_source_tag = tag :meta, name: 'go-source', content: "#{import_prefix} #{project_url} #{project_url}/tree/#{branch}{/dir} #{project_url}/blob/#{branch}{/dir}/{file}#L{line}" + meta_source_tag = tag :meta, name: 'go-source', content: "#{import_prefix} #{project_url} #{project_url}/-/tree/#{branch}{/dir} #{project_url}/-/blob/#{branch}{/dir}/{file}#L{line}" head_tag = content_tag :head, meta_import_tag + meta_source_tag html_tag = content_tag :html, head_tag + body_tag [html_tag, 200] diff --git a/lib/quality/test_level.rb b/lib/quality/test_level.rb index 90a8096cc2b..84470a73b1b 100644 --- a/lib/quality/test_level.rb +++ b/lib/quality/test_level.rb @@ -5,6 +5,10 @@ module Quality UnknownTestLevelError = Class.new(StandardError) TEST_LEVEL_FOLDERS = { + migration: %w[ + migrations + lib/gitlab/background_migration + ], unit: %w[ bin config @@ -19,7 +23,6 @@ module Quality initializers javascripts lib - migrations models policies presenters @@ -36,10 +39,6 @@ module Quality workers elastic_integration ], - migration: %w[ - migrations - lib/gitlab/background_migration - ], integration: %w[ controllers mailers diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6301a72f48a..be2ba9d22fb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4994,12 +4994,18 @@ msgstr "" msgid "Could not create group" msgstr "" +msgid "Could not create issue" +msgstr "" + msgid "Could not create project" msgstr "" msgid "Could not delete chat nickname %{chat_name}." msgstr "" +msgid "Could not fetch projects" +msgstr "" + msgid "Could not remove the trigger." msgstr "" @@ -5644,6 +5650,12 @@ msgstr "" msgid "Delete list" msgstr "" +msgid "Delete snippet" +msgstr "" + +msgid "Delete snippet?" +msgstr "" + msgid "Delete source branch" msgstr "" @@ -10001,6 +10013,9 @@ msgstr "" msgid "IssuesAnalytics|Total:" msgstr "" +msgid "Issue|Title" +msgstr "" + msgid "It must have a header row and at least two columns: the first column is the issue title and the second column is the issue description. The separator is automatically detected." msgstr "" @@ -10709,6 +10724,9 @@ msgstr "" msgid "Loading issues" msgstr "" +msgid "Loading snippet" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -11724,6 +11742,9 @@ msgstr "" msgid "New issue" msgstr "" +msgid "New issue title" +msgstr "" + msgid "New label" msgstr "" diff --git a/package.json b/package.json index aa11e35d3e6..098cdd534a0 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@babel/core": "^7.6.2", "@babel/plugin-proposal-class-properties": "^7.5.5", "@babel/plugin-proposal-json-strings": "^7.2.0", + "@babel/plugin-proposal-optional-chaining": "^7.7.5", "@babel/plugin-proposal-private-methods": "^7.6.0", "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-syntax-import-meta": "^7.2.0", diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml index e8a21451e02..999f730efc5 100644 --- a/scripts/review_apps/base-config.yaml +++ b/scripts/review_apps/base-config.yaml @@ -44,7 +44,7 @@ gitlab: memory: 37.5M maxReplicas: 3 hpa: - targetAverageValue: 130m + targetAverageValue: 500m deployment: livenessProbe: timeoutSeconds: 5 @@ -56,6 +56,8 @@ gitlab: limits: cpu: 975m memory: 1450M + hpa: + targetAverageValue: 650m task-runner: resources: requests: diff --git a/spec/controllers/concerns/metrics_dashboard_spec.rb b/spec/controllers/concerns/metrics_dashboard_spec.rb index ff2b6fbb8ec..662fb386208 100644 --- a/spec/controllers/concerns/metrics_dashboard_spec.rb +++ b/spec/controllers/concerns/metrics_dashboard_spec.rb @@ -72,7 +72,7 @@ describe MetricsDashboard do it 'includes project_blob_path only for project dashboards' do expect(system_dashboard['project_blob_path']).to be_nil - expect(project_dashboard['project_blob_path']).to eq("/#{project.namespace.path}/#{project.name}/blob/master/.gitlab/dashboards/test.yml") + expect(project_dashboard['project_blob_path']).to eq("/#{project.namespace.path}/#{project.name}/-/blob/master/.gitlab/dashboards/test.yml") end describe 'project permissions' do diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb index dd7c0f45dc2..ac8394e3cd4 100644 --- a/spec/controllers/projects/blame_controller_spec.rb +++ b/spec/controllers/projects/blame_controller_spec.rb @@ -36,7 +36,7 @@ describe Projects::BlameController do it 'redirects' do expect(subject) - .to redirect_to("/#{project.full_path}/tree/master") + .to redirect_to("/#{project.full_path}/-/tree/master") end end diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 78599935910..aa1dcdd9f1d 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -33,7 +33,7 @@ describe Projects::BlobController do it 'redirects' do expect(subject) - .to redirect_to("/#{project.full_path}/tree/master") + .to redirect_to("/#{project.full_path}/-/tree/master") end end @@ -115,7 +115,7 @@ describe Projects::BlobController do it 'redirects' do expect(subject) - .to redirect_to("/#{project.full_path}/tree/markdown/doc") + .to redirect_to("/#{project.full_path}/-/tree/markdown/doc") end end end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 4f8ab6a5def..bf7ef9dab0c 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -37,7 +37,7 @@ describe Projects::BranchesController do let(:ref) { "master" } it 'redirects' do expect(subject) - .to redirect_to("/#{project.full_path}/tree/merge_branch") + .to redirect_to("/#{project.full_path}/-/tree/merge_branch") end end @@ -46,7 +46,7 @@ describe Projects::BranchesController do let(:ref) { "master" } it 'redirects' do expect(subject) - .to redirect_to("/#{project.full_path}/tree/alert('merge');") + .to redirect_to("/#{project.full_path}/-/tree/alert('merge');") end end @@ -88,7 +88,7 @@ describe Projects::BranchesController do } expect(subject) - .to redirect_to("/#{project.full_path}/tree/1-feature-branch") + .to redirect_to("/#{project.full_path}/-/tree/1-feature-branch") end it 'posts a system note' do diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb index bdf1c1a84d3..391b7ad1648 100644 --- a/spec/controllers/projects/raw_controller_spec.rb +++ b/spec/controllers/projects/raw_controller_spec.rb @@ -69,7 +69,7 @@ describe Projects::RawController do env: :raw_blob_request_limit, remote_ip: '0.0.0.0', request_method: 'GET', - path: "/#{project.full_path}/raw/#{file_path}" + path: "/#{project.full_path}/-/raw/#{file_path}" } expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb index 73fb0fad646..33d66f4ac5a 100644 --- a/spec/controllers/projects/serverless/functions_controller_spec.rb +++ b/spec/controllers/projects/serverless/functions_controller_spec.rb @@ -149,6 +149,14 @@ describe Projects::Serverless::FunctionsController do include_examples 'GET #show with valid data' end + + context 'on Knative 0.9.0' do + before do + prepare_knative_stubs(knative_09_service(knative_stub_options)) + end + + include_examples 'GET #show with valid data' + end end end @@ -210,6 +218,14 @@ describe Projects::Serverless::FunctionsController do include_examples 'GET #index with data' end + + context 'on Knative 0.9.0' do + before do + prepare_knative_stubs(knative_09_service(knative_stub_options)) + end + + include_examples 'GET #index with data' + end end def prepare_knative_stubs(knative_service) diff --git a/spec/controllers/projects/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb index c0c11db5dd6..f89881af5e2 100644 --- a/spec/controllers/projects/tree_controller_spec.rb +++ b/spec/controllers/projects/tree_controller_spec.rb @@ -45,7 +45,7 @@ describe Projects::TreeController do it 'redirects' do expect(subject) - .to redirect_to("/#{project.full_path}/tree/master") + .to redirect_to("/#{project.full_path}/-/tree/master") end end @@ -60,7 +60,7 @@ describe Projects::TreeController do it 'redirects' do expect(subject) - .to redirect_to("/#{project.full_path}/tree/empty-branch") + .to redirect_to("/#{project.full_path}/-/tree/empty-branch") end end @@ -125,7 +125,7 @@ describe Projects::TreeController do let(:id) { 'master/README.md' } it 'redirects' do - redirect_url = "/#{project.full_path}/blob/master/README.md" + redirect_url = "/#{project.full_path}/-/blob/master/README.md" expect(subject) .to redirect_to(redirect_url) end @@ -153,7 +153,7 @@ describe Projects::TreeController do it 'redirects to the new directory' do expect(subject) - .to redirect_to("/#{project.full_path}/tree/#{branch_name}/#{path}") + .to redirect_to("/#{project.full_path}/-/tree/#{branch_name}/#{path}") expect(flash[:notice]).to eq('The directory has been successfully created.') end end @@ -164,7 +164,7 @@ describe Projects::TreeController do it 'does not allow overwriting of existing files' do expect(subject) - .to redirect_to("/#{project.full_path}/tree/master") + .to redirect_to("/#{project.full_path}/-/tree/master") expect(flash[:alert]).to eq('A file with this name already exists') end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 5259c612bbd..a1dff0881b6 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -943,7 +943,7 @@ describe ProjectsController do end it 'renders JSON body with image links expanded' do - expanded_path = "/#{project_with_repo.full_path}/raw/master/files/images/logo-white.png" + expanded_path = "/#{project_with_repo.full_path}/-/raw/master/files/images/logo-white.png" post :preview_markdown, params: preview_markdown_params diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb index 4fb01995cb0..ff3eb58931d 100644 --- a/spec/features/dashboard/snippets_spec.rb +++ b/spec/features/dashboard/snippets_spec.rb @@ -6,6 +6,7 @@ describe 'Dashboard snippets' do context 'when the project has snippets' do let(:project) { create(:project, :public) } let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) } + before do allow(Snippet).to receive(:default_per_page).and_return(1) sign_in(project.owner) @@ -13,10 +14,16 @@ describe 'Dashboard snippets' do end it_behaves_like 'paginated snippets' + + it 'shows new snippet button in header' do + parent_element = page.find('.page-title-controls') + expect(parent_element).to have_link('New snippet') + end end context 'when there are no project snippets', :js do let(:project) { create(:project, :public) } + before do sign_in(project.owner) visit dashboard_snippets_path @@ -28,6 +35,11 @@ describe 'Dashboard snippets' do expect(element).to have_content("Snippets are small pieces of code or notes that you want to keep.") expect(element.find('.svg-content img')['src']).to have_content('illustrations/snippets_empty') end + + it 'shows new snippet button in main content area' do + parent_element = page.find('.row.empty-state') + expect(parent_element).to have_link('New snippet') + end end context 'filtering by visibility' do @@ -76,4 +88,26 @@ describe 'Dashboard snippets' do expect(page).to have_content(snippets[0].title) end end + + context 'as an external user' do + let(:user) { create(:user, :external) } + before do + sign_in(user) + visit dashboard_snippets_path + end + + context 'without snippets' do + it 'hides new snippet button' do + expect(page).not_to have_link('New snippet') + end + end + + context 'with snippets' do + let!(:snippets) { create(:personal_snippet, author: user) } + + it 'hides new snippet button' do + expect(page).not_to have_link('New snippet') + end + end + end end diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb index 082f35050c0..0dccb7f5bb3 100644 --- a/spec/features/discussion_comments/snippets_spec.rb +++ b/spec/features/discussion_comments/snippets_spec.rb @@ -8,6 +8,7 @@ describe 'Thread Comments Snippet', :js do let(:snippet) { create(:project_snippet, :private, project: project, author: user) } before do + stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 1122504248f..7126707affd 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -70,6 +70,7 @@ describe 'issue move to another project' do context 'user does not have permission to move the issue to a project', :js do let!(:private_project) { create(:project, :private) } let(:another_project) { create(:project) } + before do another_project.add_guest(user) end diff --git a/spec/features/issues/user_views_issues_spec.rb b/spec/features/issues/user_views_issues_spec.rb index b986991f38f..8f174472f49 100644 --- a/spec/features/issues/user_views_issues_spec.rb +++ b/spec/features/issues/user_views_issues_spec.rb @@ -6,6 +6,7 @@ describe "User views issues" do let!(:closed_issue) { create(:closed_issue, project: project) } let!(:open_issue1) { create(:issue, project: project) } let!(:open_issue2) { create(:issue, project: project) } + set(:user) { create(:user) } shared_examples "opens issue from list" do diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb index ca3e24d7036..3c217786d43 100644 --- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb @@ -9,6 +9,7 @@ describe 'User sorts merge requests' do let!(:merge_request2) do create(:merge_request_with_diffs, source_project: project, target_project: project, source_branch: 'merge-test') end + set(:user) { create(:user) } set(:group) { create(:group) } set(:group_member) { create(:group_member, :maintainer, user: user, group: group) } diff --git a/spec/features/merge_requests/user_views_open_merge_requests_spec.rb b/spec/features/merge_requests/user_views_open_merge_requests_spec.rb index cefac9690ce..932090bdbce 100644 --- a/spec/features/merge_requests/user_views_open_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_views_open_merge_requests_spec.rb @@ -113,6 +113,7 @@ describe 'User views open merge requests' do context 'when project is internal' do let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } + set(:project) { create(:project, :internal, :repository) } context 'when signed in' do diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb index 4a1e8598131..29e9b0c313a 100644 --- a/spec/features/oauth_login_spec.rb +++ b/spec/features/oauth_login_spec.rb @@ -33,6 +33,7 @@ describe 'OAuth Login', :js, :allow_forgery_protection do let(:remember_me) { false } let(:user) { create(:omniauth_user, extern_uid: uid, provider: provider.to_s) } let(:two_factor_user) { create(:omniauth_user, :two_factor, extern_uid: uid, provider: provider.to_s) } + provider == :salesforce ? let(:additional_info) { { extra: { email_verified: true } } } : let(:additional_info) { {} } before do diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index a7bba124716..fdedd319116 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -34,6 +34,7 @@ describe 'Member autocomplete', :js do context 'adding a new note on a Issue' do let(:noteable) { create(:issue, author: author, project: project) } + before do visit project_issue_path(project, noteable) end @@ -47,6 +48,7 @@ describe 'Member autocomplete', :js do create(:merge_request, source_project: project, target_project: project, author: author) end + before do visit project_merge_request_path(project, noteable) end diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 7c8b2640e89..142aaaffb7d 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -194,6 +194,7 @@ describe 'Gcp Cluster', :js, :do_not_mock_admin_mode do context 'when third party offers are disabled' do let(:admin) { create(:admin) } + before do stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false') sign_in(admin) diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb index 374a7fb7936..cc428dce2a8 100644 --- a/spec/features/projects/files/user_edits_files_spec.rb +++ b/spec/features/projects/files/user_edits_files_spec.rb @@ -193,6 +193,7 @@ describe 'Projects > Files > User edits files', :js do context 'when the user already had a fork of the project', :js do let!(:forked_project) { fork_project(project2, user, namespace: user.namespace, repository: true) } + before do visit(project2_tree_path_root_ref) wait_for_requests diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb index 684793ce116..ad65e04473c 100644 --- a/spec/features/projects/snippets/create_snippet_spec.rb +++ b/spec/features/projects/snippets/create_snippet_spec.rb @@ -18,6 +18,7 @@ describe 'Projects > Snippets > Create Snippet', :js do context 'when a user is authenticated' do before do + stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) @@ -76,6 +77,10 @@ describe 'Projects > Snippets > Create Snippet', :js do end context 'when a user is not authenticated' do + before do + stub_feature_flags(snippets_vue: false) + end + it 'shows a public snippet on the index page but not the New snippet button' do snippet = create(:project_snippet, :public, project: project) diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb index e448309356d..9be226c017f 100644 --- a/spec/features/projects/snippets/show_spec.rb +++ b/spec/features/projects/snippets/show_spec.rb @@ -8,6 +8,7 @@ describe 'Projects > Snippets > Project snippet', :js do let(:snippet) { create(:project_snippet, project: project, file_name: file_name, content: content) } before do + stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) end diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb index 239d19d35d1..11707378996 100644 --- a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb +++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb @@ -8,6 +8,7 @@ describe 'Projects > Snippets > User comments on a snippet', :js do let(:user) { create(:user) } before do + stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/projects/snippets/user_deletes_snippet_spec.rb b/spec/features/projects/snippets/user_deletes_snippet_spec.rb index 1b56d7bf26d..7e337710e19 100644 --- a/spec/features/projects/snippets/user_deletes_snippet_spec.rb +++ b/spec/features/projects/snippets/user_deletes_snippet_spec.rb @@ -8,6 +8,7 @@ describe 'Projects > Snippets > User deletes a snippet' do let(:user) { create(:user) } before do + stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/projects/snippets/user_updates_snippet_spec.rb b/spec/features/projects/snippets/user_updates_snippet_spec.rb index c7ff4f89fd6..93a5b4a7262 100644 --- a/spec/features/projects/snippets/user_updates_snippet_spec.rb +++ b/spec/features/projects/snippets/user_updates_snippet_spec.rb @@ -8,6 +8,7 @@ describe 'Projects > Snippets > User updates a snippet' do let(:user) { create(:user) } before do + stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb index c2c853cdb05..bd37675315f 100644 --- a/spec/features/reportable_note/snippets_spec.rb +++ b/spec/features/reportable_note/snippets_spec.rb @@ -7,6 +7,7 @@ describe 'Reportable note on snippets', :js do let(:project) { create(:project) } before do + stub_feature_flags(snippets_vue: false) project.add_maintainer(user) sign_in(user) end diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index a182b6b9d57..8dd15789cd1 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -53,6 +53,7 @@ describe 'Internal Group access' do describe 'GET /groups/:path/merge_requests' do let(:project) { create(:project, :internal, :repository, group: group) } + subject { merge_requests_group_path(group) } it { is_expected.to be_allowed_for(:admin) } diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index 5e3e9824aaa..0720302b03b 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -53,6 +53,7 @@ describe 'Private Group access' do describe 'GET /groups/:path/merge_requests' do let(:project) { create(:project, :private, :repository, group: group) } + subject { merge_requests_group_path(group) } it { is_expected.to be_allowed_for(:admin) } diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index efc84205980..0a18a0ff812 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -53,6 +53,7 @@ describe 'Public Group access' do describe 'GET /groups/:path/merge_requests' do let(:project) { create(:project, :public, :repository, group: group) } + subject { merge_requests_group_path(group) } it { is_expected.to be_allowed_for(:admin) } diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 20a320e5b92..5c74b566ef0 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -129,6 +129,7 @@ describe "Internal Project Access" do describe "GET /:project_path/blob" do let(:commit) { project.repository.commit } + subject { project_blob_path(project, File.join(commit.id, '.gitignore')) } it { is_expected.to be_allowed_for(:admin) } @@ -186,6 +187,7 @@ describe "Internal Project Access" do describe "GET /:project_path/issues/:id/edit" do let(:issue) { create(:issue, project: project) } + subject { edit_project_issue_path(project, issue) } it { is_expected.to be_allowed_for(:admin) } @@ -327,6 +329,7 @@ describe "Internal Project Access" do describe "GET /:project_path/pipelines/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } + subject { project_pipeline_path(project, pipeline) } it { is_expected.to be_allowed_for(:admin) } @@ -379,6 +382,7 @@ describe "Internal Project Access" do describe "GET /:project_path/builds/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } + subject { project_job_path(project, build.id) } context "when allowed for public and internal" do @@ -417,6 +421,7 @@ describe "Internal Project Access" do describe 'GET /:project_path/builds/:id/trace' do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } + subject { trace_project_job_path(project, build.id) } context 'when allowed for public and internal' do @@ -482,6 +487,7 @@ describe "Internal Project Access" do describe "GET /:project_path/-/environments/:id" do let(:environment) { create(:environment, project: project) } + subject { project_environment_path(project, environment) } it { is_expected.to be_allowed_for(:admin) } @@ -497,6 +503,7 @@ describe "Internal Project Access" do describe "GET /:project_path/-/environments/:id/deployments" do let(:environment) { create(:environment, project: project) } + subject { project_environment_deployments_path(project, environment) } it { is_expected.to be_allowed_for(:admin) } diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 62f9a96305d..2404b7cc69d 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -129,6 +129,7 @@ describe "Private Project Access" do describe "GET /:project_path/blob" do let(:commit) { project.repository.commit } + subject { project_blob_path(project, File.join(commit.id, '.gitignore')) } it { is_expected.to be_allowed_for(:admin) } @@ -186,6 +187,7 @@ describe "Private Project Access" do describe "GET /:project_path/issues/:id/edit" do let(:issue) { create(:issue, project: project) } + subject { edit_project_issue_path(project, issue) } it { is_expected.to be_allowed_for(:admin) } @@ -311,6 +313,7 @@ describe "Private Project Access" do describe "GET /:project_path/pipelines/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } + subject { project_pipeline_path(project, pipeline) } it { is_expected.to be_allowed_for(:admin) } @@ -365,6 +368,7 @@ describe "Private Project Access" do describe "GET /:project_path/builds/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } + subject { project_job_path(project, build.id) } it { is_expected.to be_allowed_for(:admin) } @@ -398,6 +402,7 @@ describe "Private Project Access" do describe 'GET /:project_path/builds/:id/trace' do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } + subject { trace_project_job_path(project, build.id) } it { is_expected.to be_allowed_for(:admin) } @@ -443,6 +448,7 @@ describe "Private Project Access" do describe "GET /:project_path/-/environments/:id" do let(:environment) { create(:environment, project: project) } + subject { project_environment_path(project, environment) } it { is_expected.to be_allowed_for(:admin) } @@ -458,6 +464,7 @@ describe "Private Project Access" do describe "GET /:project_path/-/environments/:id/deployments" do let(:environment) { create(:environment, project: project) } + subject { project_environment_deployments_path(project, environment) } it { is_expected.to be_allowed_for(:admin) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 317c7bae084..8e07af61c8b 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -143,6 +143,7 @@ describe "Public Project Access" do describe "GET /:project_path/pipelines/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } + subject { project_pipeline_path(project, pipeline) } it { is_expected.to be_allowed_for(:admin) } @@ -195,6 +196,7 @@ describe "Public Project Access" do describe "GET /:project_path/builds/:id" do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } + subject { project_job_path(project, build.id) } context "when allowed for public" do @@ -233,6 +235,7 @@ describe "Public Project Access" do describe 'GET /:project_path/builds/:id/trace' do let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, pipeline: pipeline) } + subject { trace_project_job_path(project, build.id) } context 'when allowed for public' do @@ -298,6 +301,7 @@ describe "Public Project Access" do describe "GET /:project_path/-/environments/:id" do let(:environment) { create(:environment, project: project) } + subject { project_environment_path(project, environment) } it { is_expected.to be_allowed_for(:admin) } @@ -313,6 +317,7 @@ describe "Public Project Access" do describe "GET /:project_path/-/environments/:id/deployments" do let(:environment) { create(:environment, project: project) } + subject { project_environment_deployments_path(project, environment) } it { is_expected.to be_allowed_for(:admin) } @@ -399,6 +404,7 @@ describe "Public Project Access" do describe "GET /:project_path/issues/:id/edit" do let(:issue) { create(:issue, project: project) } + subject { edit_project_issue_path(project, issue) } it { is_expected.to be_allowed_for(:admin) } diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb index 57e91fea709..2075742eafb 100644 --- a/spec/features/snippets/explore_spec.rb +++ b/spec/features/snippets/explore_spec.rb @@ -6,30 +6,59 @@ describe 'Explore Snippets' do let!(:public_snippet) { create(:personal_snippet, :public) } let!(:internal_snippet) { create(:personal_snippet, :internal) } let!(:private_snippet) { create(:personal_snippet, :private) } + let(:user) { nil } - it 'User should see snippets that are not private' do - sign_in create(:user) + before do + sign_in(user) if user visit explore_snippets_path - - expect(page).to have_content(public_snippet.title) - expect(page).to have_content(internal_snippet.title) - expect(page).not_to have_content(private_snippet.title) end - it 'External user should see only public snippets' do - sign_in create(:user, :external) - visit explore_snippets_path + context 'User' do + let(:user) { create(:user) } + + it 'see snippets that are not private' do + expect(page).to have_content(public_snippet.title) + expect(page).to have_content(internal_snippet.title) + expect(page).not_to have_content(private_snippet.title) + end - expect(page).to have_content(public_snippet.title) - expect(page).not_to have_content(internal_snippet.title) - expect(page).not_to have_content(private_snippet.title) + it 'shows new snippet button in header' do + parent_element = page.find('.page-title-controls') + expect(parent_element).to have_link('New snippet') + end end - it 'Not authenticated user should see only public snippets' do - visit explore_snippets_path + context 'External user' do + let(:user) { create(:user, :external) } + + it 'see only public snippets' do + expect(page).to have_content(public_snippet.title) + expect(page).not_to have_content(internal_snippet.title) + expect(page).not_to have_content(private_snippet.title) + end + + context 'without snippets' do + before do + Snippet.delete_all + end + + it 'hides new snippet button' do + expect(page).not_to have_link('New snippet') + end + end + + context 'with snippets' do + it 'hides new snippet button' do + expect(page).not_to have_link('New snippet') + end + end + end - expect(page).to have_content(public_snippet.title) - expect(page).not_to have_content(internal_snippet.title) - expect(page).not_to have_content(private_snippet.title) + context 'Not authenticated user' do + it 'see only public snippets' do + expect(page).to have_content(public_snippet.title) + expect(page).not_to have_content(internal_snippet.title) + expect(page).not_to have_content(private_snippet.title) + end end end diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index acffc4ce580..11429f16f42 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -146,6 +146,7 @@ describe 'Task Lists' do describe 'for Notes' do let!(:issue) { create(:issue, author: user, project: project) } + describe 'multiple tasks' do let!(:note) do create(:note, note: markdown, noteable: issue, diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index b7c54bb6de8..5f4f92e547c 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -406,6 +406,7 @@ describe 'Login' do describe 'with required two-factor authentication enabled' do let(:user) { create(:user) } + # TODO: otp_grace_period_started_at context 'global setting' do diff --git a/spec/finders/clusters_finder_spec.rb b/spec/finders/clusters_finder_spec.rb index f6ea8347f67..5dde616f679 100644 --- a/spec/finders/clusters_finder_spec.rb +++ b/spec/finders/clusters_finder_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe ClustersFinder do let(:project) { create(:project) } + set(:user) { create(:user) } describe '#execute' do diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb index 17875a9b9ab..ee8606e474e 100644 --- a/spec/finders/group_descendants_finder_spec.rb +++ b/spec/finders/group_descendants_finder_spec.rb @@ -6,6 +6,7 @@ describe GroupDescendantsFinder do let(:user) { create(:user) } let(:group) { create(:group) } let(:params) { {} } + subject(:finder) do described_class.new(current_user: user, parent_group: group, params: params) end diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index 0a7ca5211e1..89fc1e380dc 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -132,11 +132,13 @@ describe GroupProjectsFinder do context "only shared" do let(:options) { { only_shared: true } } + it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) } end context "only owned" do let(:options) { { only_owned: true } } + it { is_expected.to eq([private_project, public_project]) } end diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index 741a89a270b..939b818f165 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -111,6 +111,7 @@ describe GroupsFinder do context 'authorized to private project' do context 'project one level deep' do let!(:subproject) { create(:project, :private, namespace: private_subgroup) } + before do subproject.add_guest(user) end @@ -129,6 +130,7 @@ describe GroupsFinder do context 'project two levels deep' do let!(:private_subsubgroup) { create(:group, :private, parent: private_subgroup) } let!(:subsubproject) { create(:project, :private, namespace: private_subsubgroup) } + before do subsubproject.add_guest(user) end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 6c10a617279..c52ee89006b 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -786,6 +786,7 @@ describe IssuesFinder do describe '#with_confidentiality_access_check' do let(:guest) { create(:user) } + set(:authorized_user) { create(:user) } set(:project) { create(:project, namespace: authorized_user.namespace) } set(:public_issue) { create(:issue, project: project) } diff --git a/spec/finders/merge_request_target_project_finder_spec.rb b/spec/finders/merge_request_target_project_finder_spec.rb index 7fef16d1040..4d2e4c5318c 100644 --- a/spec/finders/merge_request_target_project_finder_spec.rb +++ b/spec/finders/merge_request_target_project_finder_spec.rb @@ -6,6 +6,7 @@ describe MergeRequestTargetProjectFinder do include ProjectForksHelper let(:user) { create(:user) } + subject(:finder) { described_class.new(current_user: user, source_project: forked_project) } shared_examples 'finding related projects' do diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index 116088f5141..c8a4ea799c3 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -6,6 +6,7 @@ describe PipelinesFinder do let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } let(:params) { {} } + subject { described_class.new(project, current_user, params).execute } describe "#execute" do diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index a9344cd593a..bf97e3cf7db 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -181,6 +181,7 @@ describe ProjectsFinder, :do_not_mock_admin_mode do describe 'filter by non_public' do let(:params) { { non_public: true } } + before do private_project.add_developer(current_user) end @@ -190,6 +191,7 @@ describe ProjectsFinder, :do_not_mock_admin_mode do describe 'filter by starred' do let(:params) { { starred: true } } + before do current_user.toggle_star(public_project) end diff --git a/spec/finders/tags_finder_spec.rb b/spec/finders/tags_finder_spec.rb index 582d82bbf79..b9de2d29895 100644 --- a/spec/finders/tags_finder_spec.rb +++ b/spec/finders/tags_finder_spec.rb @@ -96,6 +96,7 @@ describe TagsFinder do context 'filter and sort' do let(:tags_to_compare) { %w[v1.0.0 v1.1.0] } + subject { described_class.new(repository, params).execute.select { |tag| tags_to_compare.include?(tag.name) } } context 'when sort by updated_desc' do diff --git a/spec/frontend/fixtures/static/projects.json b/spec/frontend/fixtures/static/projects.json index d92d3acdea0..f28d9899099 100644 --- a/spec/frontend/fixtures/static/projects.json +++ b/spec/frontend/fixtures/static/projects.json @@ -99,6 +99,15 @@ "access_level": 50, "notification_level": 3 } + }, + "_links": { + "self": "https://gitlab.com/api/v4/projects/278964", + "issues": "https://gitlab.com/api/v4/projects/278964/issues", + "merge_requests": "https://gitlab.com/api/v4/projects/278964/merge_requests", + "repo_branches": "https://gitlab.com/api/v4/projects/278964/repository/branches", + "labels": "https://gitlab.com/api/v4/projects/278964/labels", + "events": "https://gitlab.com/api/v4/projects/278964/events", + "members": "https://gitlab.com/api/v4/projects/278964/members" } }, { "id": 7, diff --git a/spec/frontend/helpers/dom_shims/index.js b/spec/frontend/helpers/dom_shims/index.js index 40256398e6d..1fc5130cefc 100644 --- a/spec/frontend/helpers/dom_shims/index.js +++ b/spec/frontend/helpers/dom_shims/index.js @@ -1 +1,2 @@ import './get_client_rects'; +import './inner_text'; diff --git a/spec/frontend/helpers/dom_shims/inner_text.js b/spec/frontend/helpers/dom_shims/inner_text.js new file mode 100644 index 00000000000..2b8201eed31 --- /dev/null +++ b/spec/frontend/helpers/dom_shims/inner_text.js @@ -0,0 +1,11 @@ +// workaround for JSDOM not supporting innerText +// see https://github.com/jsdom/jsdom/issues/1245 +Object.defineProperty(global.Element.prototype, 'innerText', { + get() { + return this.textContent; + }, + set(value) { + this.textContext = value; + }, + configurable: true, // make it so that it doesn't blow chunks on re-running tests with things like --watch +}); diff --git a/spec/frontend/ide/stores/getters_spec.js b/spec/frontend/ide/stores/getters_spec.js index d196f6f79d5..21c5e886738 100644 --- a/spec/frontend/ide/stores/getters_spec.js +++ b/spec/frontend/ide/stores/getters_spec.js @@ -1,12 +1,14 @@ import * as getters from '~/ide/stores/getters'; -import state from '~/ide/stores/state'; +import { createStore } from '~/ide/stores'; import { file } from '../helpers'; describe('IDE store getters', () => { let localState; + let localStore; beforeEach(() => { - localState = state(); + localStore = createStore(); + localState = localStore.state; }); describe('activeFile', () => { @@ -310,4 +312,90 @@ describe('IDE store getters', () => { expect(getters.canPushToBranch({}, localGetters)).toBeFalsy(); }); }); + + describe('isFileDeletedAndReadded', () => { + const f = { ...file('sample'), content: 'sample', raw: 'sample' }; + + it.each([ + { + entry: { ...f, tempFile: true }, + staged: { ...f, deleted: true }, + output: true, + }, + { + entry: { ...f, content: 'changed' }, + staged: { ...f, content: 'changed' }, + output: false, + }, + { + entry: { ...f, content: 'changed' }, + output: false, + }, + ])( + 'checks staged and unstaged files to see if a file was deleted and readded (case %#)', + ({ entry, staged, output }) => { + Object.assign(localState, { + entries: { + [entry.path]: entry, + }, + stagedFiles: [], + }); + + if (staged) localState.stagedFiles.push(staged); + + expect(localStore.getters.isFileDeletedAndReadded(entry.path)).toBe(output); + }, + ); + }); + + describe('getDiffInfo', () => { + const f = { ...file('sample'), content: 'sample', raw: 'sample' }; + it.each([ + { + entry: { ...f, tempFile: true }, + staged: { ...f, deleted: true }, + output: { deleted: false, changed: false, tempFile: false }, + }, + { + entry: { ...f, tempFile: true, content: 'changed', raw: '' }, + staged: { ...f, deleted: true }, + output: { deleted: false, changed: true, tempFile: false }, + }, + { + entry: { ...f, content: 'changed' }, + output: { changed: true }, + }, + { + entry: { ...f, content: 'sample' }, + staged: { ...f, content: 'changed' }, + output: { changed: false }, + }, + { + entry: { ...f, deleted: true }, + output: { deleted: true, changed: false }, + }, + { + entry: { ...f, prevPath: 'old_path' }, + output: { renamed: true, changed: false }, + }, + { + entry: { ...f, prevPath: 'old_path', content: 'changed' }, + output: { renamed: true, changed: true }, + }, + ])( + 'compares changes in a file entry and returns a resulting diff info (case %#)', + ({ entry, staged, output }) => { + Object.assign(localState, { + entries: { + [entry.path]: entry, + }, + stagedFiles: [], + }); + + if (staged) localState.stagedFiles.push(staged); + + expect(localStore.getters.getDiffInfo(entry.path)).toEqual(expect.objectContaining(output)); + }, + ); + }); }); diff --git a/spec/frontend/monitoring/charts/time_series_spec.js b/spec/frontend/monitoring/charts/time_series_spec.js deleted file mode 100644 index 128c4bc49f1..00000000000 --- a/spec/frontend/monitoring/charts/time_series_spec.js +++ /dev/null @@ -1,414 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { setTestTimeout } from 'helpers/timeout'; -import { GlLink } from '@gitlab/ui'; -import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; -import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper'; -import { createStore } from '~/monitoring/stores'; -import TimeSeries from '~/monitoring/components/charts/time_series.vue'; -import * as types from '~/monitoring/stores/mutation_types'; -import { - deploymentData, - metricsGroupsAPIResponse, - mockedQueryResultPayload, - mockProjectDir, - mockHost, -} from '../mock_data'; - -import * as iconUtils from '~/lib/utils/icon_utils'; - -const mockSvgPathContent = 'mockSvgPathContent'; -const mockWidgets = 'mockWidgets'; - -jest.mock('~/lib/utils/icon_utils', () => ({ - getSvgIconPathContent: jest.fn().mockImplementation( - () => - new Promise(resolve => { - resolve(mockSvgPathContent); - }), - ), -})); - -describe('Time series component', () => { - let mockGraphData; - let makeTimeSeriesChart; - let store; - - beforeEach(() => { - setTestTimeout(1000); - - store = createStore(); - - store.commit( - `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, - metricsGroupsAPIResponse, - ); - - store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); - - // Mock data contains 2 panel groups, with 1 and 2 panels respectively - store.commit( - `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, - mockedQueryResultPayload, - ); - - // Pick the second panel group and the first panel in it - [mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels; - - makeTimeSeriesChart = (graphData, type) => - shallowMount(TimeSeries, { - propsData: { - graphData: { ...graphData, type }, - deploymentData: store.state.monitoringDashboard.deploymentData, - projectPath: `${mockHost}${mockProjectDir}`, - }, - slots: { - default: mockWidgets, - }, - sync: false, - store, - attachToDocument: true, - }); - }); - - describe('general functions', () => { - let timeSeriesChart; - - beforeEach(done => { - timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); - timeSeriesChart.vm.$nextTick(done); - }); - - it('renders chart title', () => { - expect(timeSeriesChart.find('.js-graph-title').text()).toBe(mockGraphData.title); - }); - - it('contains graph widgets from slot', () => { - expect(timeSeriesChart.find('.js-graph-widgets').text()).toBe(mockWidgets); - }); - - it('allows user to override max value label text using prop', () => { - timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' }); - - expect(timeSeriesChart.props().legendMaxText).toBe('legendMaxText'); - }); - - it('allows user to override average value label text using prop', () => { - timeSeriesChart.setProps({ legendAverageText: 'averageText' }); - - expect(timeSeriesChart.props().legendAverageText).toBe('averageText'); - }); - - describe('methods', () => { - describe('formatTooltipText', () => { - let mockDate; - let mockCommitUrl; - let generateSeriesData; - - beforeEach(() => { - mockDate = deploymentData[0].created_at; - mockCommitUrl = deploymentData[0].commitUrl; - generateSeriesData = type => ({ - seriesData: [ - { - seriesName: timeSeriesChart.vm.chartData[0].name, - componentSubType: type, - value: [mockDate, 5.55555], - dataIndex: 0, - }, - ], - value: mockDate, - }); - }); - - it('does not throw error if data point is outside the zoom range', () => { - const seriesDataWithoutValue = generateSeriesData('line'); - expect( - timeSeriesChart.vm.formatTooltipText({ - ...seriesDataWithoutValue, - seriesData: seriesDataWithoutValue.seriesData.map(data => ({ - ...data, - value: undefined, - })), - }), - ).toBeUndefined(); - }); - - describe('when series is of line type', () => { - beforeEach(done => { - timeSeriesChart.vm.formatTooltipText(generateSeriesData('line')); - timeSeriesChart.vm.$nextTick(done); - }); - - it('formats tooltip title', () => { - expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); - }); - - it('formats tooltip content', () => { - const name = 'Pod average'; - const value = '5.556'; - const dataIndex = 0; - const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel); - - expect(seriesLabel.vm.color).toBe(''); - expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); - expect(timeSeriesChart.vm.tooltip.content).toEqual([ - { name, value, dataIndex, color: undefined }, - ]); - - expect( - shallowWrapperContainsSlotText( - timeSeriesChart.find(GlAreaChart), - 'tooltipContent', - value, - ), - ).toBe(true); - }); - }); - - describe('when series is of scatter type, for deployments', () => { - beforeEach(() => { - timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter')); - }); - - it('formats tooltip title', () => { - expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); - }); - - it('formats tooltip sha', () => { - expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9'); - }); - - it('formats tooltip commit url', () => { - expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl); - }); - }); - }); - - describe('setSvg', () => { - const mockSvgName = 'mockSvgName'; - - beforeEach(done => { - timeSeriesChart.vm.setSvg(mockSvgName); - timeSeriesChart.vm.$nextTick(done); - }); - - it('gets svg path content', () => { - expect(iconUtils.getSvgIconPathContent).toHaveBeenCalledWith(mockSvgName); - }); - - it('sets svg path content', () => { - timeSeriesChart.vm.$nextTick(() => { - expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); - }); - }); - - it('contains an svg object within an array to properly render icon', () => { - timeSeriesChart.vm.$nextTick(() => { - expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([ - { - handleIcon: `path://${mockSvgPathContent}`, - }, - ]); - }); - }); - }); - - describe('onResize', () => { - const mockWidth = 233; - - beforeEach(() => { - jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ - width: mockWidth, - })); - timeSeriesChart.vm.onResize(); - }); - - it('sets area chart width', () => { - expect(timeSeriesChart.vm.width).toBe(mockWidth); - }); - }); - }); - - describe('computed', () => { - describe('chartData', () => { - let chartData; - const seriesData = () => chartData[0]; - - beforeEach(() => { - ({ chartData } = timeSeriesChart.vm); - }); - - it('utilizes all data points', () => { - const { values } = mockGraphData.metrics[0].result[0]; - - expect(chartData.length).toBe(1); - expect(seriesData().data.length).toBe(values.length); - }); - - it('creates valid data', () => { - const { data } = seriesData(); - - expect( - data.filter( - ([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number', - ).length, - ).toBe(data.length); - }); - - it('formats line width correctly', () => { - expect(chartData[0].lineStyle.width).toBe(2); - }); - }); - - describe('chartOptions', () => { - describe('are extended by `option`', () => { - const mockSeriesName = 'Extra series 1'; - const mockOption = { - option1: 'option1', - option2: 'option2', - }; - - it('arbitrary options', () => { - timeSeriesChart.setProps({ - option: mockOption, - }); - - expect(timeSeriesChart.vm.chartOptions).toEqual(expect.objectContaining(mockOption)); - }); - - it('additional series', () => { - timeSeriesChart.setProps({ - option: { - series: [ - { - name: mockSeriesName, - }, - ], - }, - }); - - const optionSeries = timeSeriesChart.vm.chartOptions.series; - - expect(optionSeries.length).toEqual(2); - expect(optionSeries[0].name).toEqual(mockSeriesName); - }); - }); - - describe('yAxis formatter', () => { - let format; - - beforeEach(() => { - format = timeSeriesChart.vm.chartOptions.yAxis.axisLabel.formatter; - }); - - it('rounds to 3 decimal places', () => { - expect(format(0.88888)).toBe('0.889'); - }); - }); - }); - - describe('scatterSeries', () => { - it('utilizes deployment data', () => { - expect(timeSeriesChart.vm.scatterSeries.data).toEqual([ - ['2019-07-16T10:14:25.589Z', 0], - ['2019-07-16T11:14:25.589Z', 0], - ['2019-07-16T12:14:25.589Z', 0], - ]); - - expect(timeSeriesChart.vm.scatterSeries.symbolSize).toBe(14); - }); - }); - - describe('yAxisLabel', () => { - it('constructs a label for the chart y-axis', () => { - expect(timeSeriesChart.vm.yAxisLabel).toBe('Memory Used per Pod'); - }); - }); - }); - - afterEach(() => { - timeSeriesChart.destroy(); - }); - }); - - describe('wrapped components', () => { - const glChartComponents = [ - { - chartType: 'area-chart', - component: GlAreaChart, - }, - { - chartType: 'line-chart', - component: GlLineChart, - }, - ]; - - glChartComponents.forEach(dynamicComponent => { - describe(`GitLab UI: ${dynamicComponent.chartType}`, () => { - let timeSeriesAreaChart; - let glChart; - - beforeEach(done => { - timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType); - glChart = timeSeriesAreaChart.find(dynamicComponent.component); - timeSeriesAreaChart.vm.$nextTick(done); - }); - - afterEach(() => { - timeSeriesAreaChart.destroy(); - }); - - it('is a Vue instance', () => { - expect(glChart.exists()).toBe(true); - expect(glChart.isVueInstance()).toBe(true); - }); - - it('receives data properties needed for proper chart render', () => { - const props = glChart.props(); - - expect(props.data).toBe(timeSeriesAreaChart.vm.chartData); - expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions); - expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText); - expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds); - }); - - it('recieves a tooltip title', done => { - const mockTitle = 'mockTitle'; - timeSeriesAreaChart.vm.tooltip.title = mockTitle; - - timeSeriesAreaChart.vm.$nextTick(() => { - expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', mockTitle)).toBe(true); - done(); - }); - }); - - describe('when tooltip is showing deployment data', () => { - const mockSha = 'mockSha'; - const commitUrl = `${mockProjectDir}/commit/${mockSha}`; - - beforeEach(done => { - timeSeriesAreaChart.vm.tooltip.isDeployment = true; - timeSeriesAreaChart.vm.$nextTick(done); - }); - - it('uses deployment title', () => { - expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', 'Deployed')).toBe(true); - }); - - it('renders clickable commit sha in tooltip content', done => { - timeSeriesAreaChart.vm.tooltip.sha = mockSha; - timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl; - - timeSeriesAreaChart.vm.$nextTick(() => { - const commitLink = timeSeriesAreaChart.find(GlLink); - - expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); - expect(commitLink.attributes('href')).toEqual(commitUrl); - done(); - }); - }); - }); - }); - }); - }); -}); diff --git a/spec/frontend/monitoring/components/charts/column_spec.js b/spec/frontend/monitoring/components/charts/column_spec.js new file mode 100644 index 00000000000..b4539801e0f --- /dev/null +++ b/spec/frontend/monitoring/components/charts/column_spec.js @@ -0,0 +1,66 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { GlColumnChart } from '@gitlab/ui/dist/charts'; +import ColumnChart from '~/monitoring/components/charts/column.vue'; + +const localVue = createLocalVue(); + +jest.mock('~/lib/utils/icon_utils', () => ({ + getSvgIconPathContent: jest.fn().mockResolvedValue('mockSvgPathContent'), +})); + +describe('Column component', () => { + let columnChart; + + beforeEach(() => { + columnChart = shallowMount(localVue.extend(ColumnChart), { + propsData: { + graphData: { + metrics: [ + { + x_label: 'Time', + y_label: 'Usage', + result: [ + { + metric: {}, + values: [ + [1495700554.925, '8.0390625'], + [1495700614.925, '8.0390625'], + [1495700674.925, '8.0390625'], + ], + }, + ], + }, + ], + }, + containerWidth: 100, + }, + sync: false, + localVue, + }); + }); + + afterEach(() => { + columnChart.destroy(); + }); + + describe('wrapped components', () => { + describe('GitLab UI column chart', () => { + let glColumnChart; + + beforeEach(() => { + glColumnChart = columnChart.find(GlColumnChart); + }); + + it('is a Vue instance', () => { + expect(glColumnChart.isVueInstance()).toBe(true); + }); + + it('receives data properties needed for proper chart render', () => { + const props = glColumnChart.props(); + + expect(props.data).toBe(columnChart.vm.chartData); + expect(props.option).toBe(columnChart.vm.chartOptions); + }); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/charts/empty_chart_spec.js b/spec/frontend/monitoring/components/charts/empty_chart_spec.js new file mode 100644 index 00000000000..06822126b59 --- /dev/null +++ b/spec/frontend/monitoring/components/charts/empty_chart_spec.js @@ -0,0 +1,33 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import EmptyChart from '~/monitoring/components/charts/empty_chart.vue'; + +const localVue = createLocalVue(); + +describe('Empty Chart component', () => { + let emptyChart; + const graphTitle = 'Memory Usage'; + + beforeEach(() => { + emptyChart = shallowMount(localVue.extend(EmptyChart), { + propsData: { + graphTitle, + }, + sync: false, + localVue, + }); + }); + + afterEach(() => { + emptyChart.destroy(); + }); + + it('render the chart title', () => { + expect(emptyChart.find({ ref: 'graphTitle' }).text()).toBe(graphTitle); + }); + + describe('Computed props', () => { + it('sets the height for the svg container', () => { + expect(emptyChart.vm.svgContainerStyle.height).toBe('300px'); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/charts/heatmap_spec.js b/spec/frontend/monitoring/components/charts/heatmap_spec.js new file mode 100644 index 00000000000..5e2c1932e9e --- /dev/null +++ b/spec/frontend/monitoring/components/charts/heatmap_spec.js @@ -0,0 +1,69 @@ +import { shallowMount } from '@vue/test-utils'; +import { GlHeatmap } from '@gitlab/ui/dist/charts'; +import Heatmap from '~/monitoring/components/charts/heatmap.vue'; +import { graphDataPrometheusQueryRangeMultiTrack } from '../../mock_data'; + +describe('Heatmap component', () => { + let heatmapChart; + let store; + + beforeEach(() => { + heatmapChart = shallowMount(Heatmap, { + propsData: { + graphData: graphDataPrometheusQueryRangeMultiTrack, + containerWidth: 100, + }, + store, + }); + }); + + afterEach(() => { + heatmapChart.destroy(); + }); + + describe('wrapped components', () => { + describe('GitLab UI heatmap chart', () => { + let glHeatmapChart; + + beforeEach(() => { + glHeatmapChart = heatmapChart.find(GlHeatmap); + }); + + it('is a Vue instance', () => { + expect(glHeatmapChart.isVueInstance()).toBe(true); + }); + + it('should display a label on the x axis', () => { + expect(heatmapChart.vm.xAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.x_label); + }); + + it('should display a label on the y axis', () => { + expect(heatmapChart.vm.yAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.y_label); + }); + + // According to the echarts docs https://echarts.apache.org/en/option.html#series-heatmap.data + // each row of the heatmap chart is represented by an array inside another parent array + // e.g. [[0, 0, 10]], the format represents the column, the row and finally the value + // corresponding to the cell + + it('should return chartData with a length of x by y, with a length of 3 per array', () => { + const row = heatmapChart.vm.chartData[0]; + + expect(row.length).toBe(3); + expect(heatmapChart.vm.chartData.length).toBe(30); + }); + + it('returns a series of labels for the x axis', () => { + const { xAxisLabels } = heatmapChart.vm; + + expect(xAxisLabels.length).toBe(5); + }); + + it('returns a series of labels for the y axis', () => { + const { yAxisLabels } = heatmapChart.vm; + + expect(yAxisLabels.length).toBe(6); + }); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/charts/single_stat_spec.js b/spec/frontend/monitoring/components/charts/single_stat_spec.js new file mode 100644 index 00000000000..78bcc400787 --- /dev/null +++ b/spec/frontend/monitoring/components/charts/single_stat_spec.js @@ -0,0 +1,31 @@ +import { shallowMount, createLocalVue } from '@vue/test-utils'; +import SingleStatChart from '~/monitoring/components/charts/single_stat.vue'; +import { graphDataPrometheusQuery } from '../../mock_data'; + +const localVue = createLocalVue(); + +describe('Single Stat Chart component', () => { + let singleStatChart; + + beforeEach(() => { + singleStatChart = shallowMount(localVue.extend(SingleStatChart), { + propsData: { + graphData: graphDataPrometheusQuery, + }, + sync: false, + localVue, + }); + }); + + afterEach(() => { + singleStatChart.destroy(); + }); + + describe('computed', () => { + describe('engineeringNotation', () => { + it('should interpolate the value and unit props', () => { + expect(singleStatChart.vm.engineeringNotation).toBe('91MB'); + }); + }); + }); +}); diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js new file mode 100644 index 00000000000..098b3408e67 --- /dev/null +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -0,0 +1,408 @@ +import { shallowMount } from '@vue/test-utils'; +import { setTestTimeout } from 'helpers/timeout'; +import { GlLink } from '@gitlab/ui'; +import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts'; +import { shallowWrapperContainsSlotText } from 'helpers/vue_test_utils_helper'; +import { createStore } from '~/monitoring/stores'; +import TimeSeries from '~/monitoring/components/charts/time_series.vue'; +import * as types from '~/monitoring/stores/mutation_types'; +import { + deploymentData, + metricsGroupsAPIResponse, + mockedQueryResultPayload, + mockProjectDir, + mockHost, +} from '../../mock_data'; +import * as iconUtils from '~/lib/utils/icon_utils'; + +const mockWidgets = 'mockWidgets'; + +const mockSvgPathContent = 'mockSvgPathContent'; +jest.mock('~/lib/utils/icon_utils', () => ({ + getSvgIconPathContent: jest.fn().mockImplementation(() => Promise.resolve(mockSvgPathContent)), +})); + +describe('Time series component', () => { + let mockGraphData; + let makeTimeSeriesChart; + let store; + + beforeEach(() => { + setTestTimeout(1000); + + store = createStore(); + + store.commit( + `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, + metricsGroupsAPIResponse, + ); + + store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData); + + // Mock data contains 2 panel groups, with 1 and 2 panels respectively + store.commit( + `monitoringDashboard/${types.RECEIVE_METRIC_RESULT_SUCCESS}`, + mockedQueryResultPayload, + ); + + // Pick the second panel group and the first panel in it + [mockGraphData] = store.state.monitoringDashboard.dashboard.panel_groups[1].panels; + + makeTimeSeriesChart = (graphData, type) => + shallowMount(TimeSeries, { + propsData: { + graphData: { ...graphData, type }, + deploymentData: store.state.monitoringDashboard.deploymentData, + projectPath: `${mockHost}${mockProjectDir}`, + }, + slots: { + default: mockWidgets, + }, + sync: false, + store, + attachToDocument: true, + }); + }); + + describe('general functions', () => { + let timeSeriesChart; + + beforeEach(done => { + timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart'); + timeSeriesChart.vm.$nextTick(done); + }); + + it('renders chart title', () => { + expect(timeSeriesChart.find('.js-graph-title').text()).toBe(mockGraphData.title); + }); + + it('contains graph widgets from slot', () => { + expect(timeSeriesChart.find('.js-graph-widgets').text()).toBe(mockWidgets); + }); + + it('allows user to override max value label text using prop', () => { + timeSeriesChart.setProps({ legendMaxText: 'legendMaxText' }); + + expect(timeSeriesChart.props().legendMaxText).toBe('legendMaxText'); + }); + + it('allows user to override average value label text using prop', () => { + timeSeriesChart.setProps({ legendAverageText: 'averageText' }); + + expect(timeSeriesChart.props().legendAverageText).toBe('averageText'); + }); + + describe('methods', () => { + describe('formatTooltipText', () => { + let mockDate; + let mockCommitUrl; + let generateSeriesData; + + beforeEach(() => { + mockDate = deploymentData[0].created_at; + mockCommitUrl = deploymentData[0].commitUrl; + generateSeriesData = type => ({ + seriesData: [ + { + seriesName: timeSeriesChart.vm.chartData[0].name, + componentSubType: type, + value: [mockDate, 5.55555], + dataIndex: 0, + }, + ], + value: mockDate, + }); + }); + + it('does not throw error if data point is outside the zoom range', () => { + const seriesDataWithoutValue = generateSeriesData('line'); + expect( + timeSeriesChart.vm.formatTooltipText({ + ...seriesDataWithoutValue, + seriesData: seriesDataWithoutValue.seriesData.map(data => ({ + ...data, + value: undefined, + })), + }), + ).toBeUndefined(); + }); + + describe('when series is of line type', () => { + beforeEach(done => { + timeSeriesChart.vm.formatTooltipText(generateSeriesData('line')); + timeSeriesChart.vm.$nextTick(done); + }); + + it('formats tooltip title', () => { + expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); + }); + + it('formats tooltip content', () => { + const name = 'Pod average'; + const value = '5.556'; + const dataIndex = 0; + const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel); + + expect(seriesLabel.vm.color).toBe(''); + expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true); + expect(timeSeriesChart.vm.tooltip.content).toEqual([ + { name, value, dataIndex, color: undefined }, + ]); + + expect( + shallowWrapperContainsSlotText( + timeSeriesChart.find(GlAreaChart), + 'tooltipContent', + value, + ), + ).toBe(true); + }); + }); + + describe('when series is of scatter type, for deployments', () => { + beforeEach(() => { + timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter')); + }); + + it('formats tooltip title', () => { + expect(timeSeriesChart.vm.tooltip.title).toBe('16 Jul 2019, 10:14AM'); + }); + + it('formats tooltip sha', () => { + expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9'); + }); + + it('formats tooltip commit url', () => { + expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl); + }); + }); + }); + + describe('setSvg', () => { + const mockSvgName = 'mockSvgName'; + + beforeEach(done => { + timeSeriesChart.vm.setSvg(mockSvgName); + timeSeriesChart.vm.$nextTick(done); + }); + + it('gets svg path content', () => { + expect(iconUtils.getSvgIconPathContent).toHaveBeenCalledWith(mockSvgName); + }); + + it('sets svg path content', () => { + timeSeriesChart.vm.$nextTick(() => { + expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`); + }); + }); + + it('contains an svg object within an array to properly render icon', () => { + timeSeriesChart.vm.$nextTick(() => { + expect(timeSeriesChart.vm.chartOptions.dataZoom).toEqual([ + { + handleIcon: `path://${mockSvgPathContent}`, + }, + ]); + }); + }); + }); + + describe('onResize', () => { + const mockWidth = 233; + + beforeEach(() => { + jest.spyOn(Element.prototype, 'getBoundingClientRect').mockImplementation(() => ({ + width: mockWidth, + })); + timeSeriesChart.vm.onResize(); + }); + + it('sets area chart width', () => { + expect(timeSeriesChart.vm.width).toBe(mockWidth); + }); + }); + }); + + describe('computed', () => { + describe('chartData', () => { + let chartData; + const seriesData = () => chartData[0]; + + beforeEach(() => { + ({ chartData } = timeSeriesChart.vm); + }); + + it('utilizes all data points', () => { + const { values } = mockGraphData.metrics[0].result[0]; + + expect(chartData.length).toBe(1); + expect(seriesData().data.length).toBe(values.length); + }); + + it('creates valid data', () => { + const { data } = seriesData(); + + expect( + data.filter( + ([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number', + ).length, + ).toBe(data.length); + }); + + it('formats line width correctly', () => { + expect(chartData[0].lineStyle.width).toBe(2); + }); + }); + + describe('chartOptions', () => { + describe('are extended by `option`', () => { + const mockSeriesName = 'Extra series 1'; + const mockOption = { + option1: 'option1', + option2: 'option2', + }; + + it('arbitrary options', () => { + timeSeriesChart.setProps({ + option: mockOption, + }); + + expect(timeSeriesChart.vm.chartOptions).toEqual(expect.objectContaining(mockOption)); + }); + + it('additional series', () => { + timeSeriesChart.setProps({ + option: { + series: [ + { + name: mockSeriesName, + }, + ], + }, + }); + + const optionSeries = timeSeriesChart.vm.chartOptions.series; + + expect(optionSeries.length).toEqual(2); + expect(optionSeries[0].name).toEqual(mockSeriesName); + }); + }); + + describe('yAxis formatter', () => { + let format; + + beforeEach(() => { + format = timeSeriesChart.vm.chartOptions.yAxis.axisLabel.formatter; + }); + + it('rounds to 3 decimal places', () => { + expect(format(0.88888)).toBe('0.889'); + }); + }); + }); + + describe('scatterSeries', () => { + it('utilizes deployment data', () => { + expect(timeSeriesChart.vm.scatterSeries.data).toEqual([ + ['2019-07-16T10:14:25.589Z', 0], + ['2019-07-16T11:14:25.589Z', 0], + ['2019-07-16T12:14:25.589Z', 0], + ]); + + expect(timeSeriesChart.vm.scatterSeries.symbolSize).toBe(14); + }); + }); + + describe('yAxisLabel', () => { + it('constructs a label for the chart y-axis', () => { + expect(timeSeriesChart.vm.yAxisLabel).toBe('Memory Used per Pod'); + }); + }); + }); + + afterEach(() => { + timeSeriesChart.destroy(); + }); + }); + + describe('wrapped components', () => { + const glChartComponents = [ + { + chartType: 'area-chart', + component: GlAreaChart, + }, + { + chartType: 'line-chart', + component: GlLineChart, + }, + ]; + + glChartComponents.forEach(dynamicComponent => { + describe(`GitLab UI: ${dynamicComponent.chartType}`, () => { + let timeSeriesAreaChart; + let glChart; + + beforeEach(done => { + timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType); + glChart = timeSeriesAreaChart.find(dynamicComponent.component); + timeSeriesAreaChart.vm.$nextTick(done); + }); + + afterEach(() => { + timeSeriesAreaChart.destroy(); + }); + + it('is a Vue instance', () => { + expect(glChart.exists()).toBe(true); + expect(glChart.isVueInstance()).toBe(true); + }); + + it('receives data properties needed for proper chart render', () => { + const props = glChart.props(); + + expect(props.data).toBe(timeSeriesAreaChart.vm.chartData); + expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions); + expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText); + expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds); + }); + + it('recieves a tooltip title', done => { + const mockTitle = 'mockTitle'; + timeSeriesAreaChart.vm.tooltip.title = mockTitle; + + timeSeriesAreaChart.vm.$nextTick(() => { + expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', mockTitle)).toBe(true); + done(); + }); + }); + + describe('when tooltip is showing deployment data', () => { + const mockSha = 'mockSha'; + const commitUrl = `${mockProjectDir}/commit/${mockSha}`; + + beforeEach(done => { + timeSeriesAreaChart.vm.tooltip.isDeployment = true; + timeSeriesAreaChart.vm.$nextTick(done); + }); + + it('uses deployment title', () => { + expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', 'Deployed')).toBe(true); + }); + + it('renders clickable commit sha in tooltip content', done => { + timeSeriesAreaChart.vm.tooltip.sha = mockSha; + timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl; + + timeSeriesAreaChart.vm.$nextTick(() => { + const commitLink = timeSeriesAreaChart.find(GlLink); + + expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); + expect(commitLink.attributes('href')).toEqual(commitUrl); + done(); + }); + }); + }); + }); + }); + }); +}); diff --git a/spec/frontend/repository/components/table/parent_row_spec.js b/spec/frontend/repository/components/table/parent_row_spec.js index 7020055271f..63f84b2597b 100644 --- a/spec/frontend/repository/components/table/parent_row_spec.js +++ b/spec/frontend/repository/components/table/parent_row_spec.js @@ -30,8 +30,8 @@ describe('Repository parent row component', () => { it.each` path | to - ${'app'} | ${'/tree/master/'} - ${'app/assets'} | ${'/tree/master/app'} + ${'app'} | ${'/-/tree/master/'} + ${'app/assets'} | ${'/-/tree/master/app'} `('renders link in $path to $to', ({ path, to }) => { factory(path); @@ -46,7 +46,7 @@ describe('Repository parent row component', () => { vm.find('td').trigger('click'); expect($router.push).toHaveBeenCalledWith({ - path: '/tree/master/app', + path: '/-/tree/master/app', }); }); @@ -58,7 +58,7 @@ describe('Repository parent row component', () => { vm.find('a').trigger('click'); expect($router.push).not.toHaveBeenCalledWith({ - path: '/tree/master/app', + path: '/-/tree/master/app', }); }); }); diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js index 94fa8b1e363..3114b6a2eaa 100644 --- a/spec/frontend/repository/components/table/row_spec.js +++ b/spec/frontend/repository/components/table/row_spec.js @@ -83,7 +83,7 @@ describe('Repository table row component', () => { vm.trigger('click'); if (pushes) { - expect($router.push).toHaveBeenCalledWith({ path: '/tree/master/test' }); + expect($router.push).toHaveBeenCalledWith({ path: '/-/tree/master/test' }); } else { expect($router.push).not.toHaveBeenCalled(); } diff --git a/spec/frontend/repository/router_spec.js b/spec/frontend/repository/router_spec.js index f61a0ccd1e6..1efd74a30c2 100644 --- a/spec/frontend/repository/router_spec.js +++ b/spec/frontend/repository/router_spec.js @@ -4,11 +4,11 @@ import createRouter from '~/repository/router'; describe('Repository router spec', () => { it.each` - path | component | componentName - ${'/'} | ${IndexPage} | ${'IndexPage'} - ${'/tree/master'} | ${TreePage} | ${'TreePage'} - ${'/tree/master/app/assets'} | ${TreePage} | ${'TreePage'} - ${'/tree/123/app/assets'} | ${null} | ${'null'} + path | component | componentName + ${'/'} | ${IndexPage} | ${'IndexPage'} + ${'/-/tree/master'} | ${TreePage} | ${'TreePage'} + ${'/-/tree/master/app/assets'} | ${TreePage} | ${'TreePage'} + ${'/-/tree/123/app/assets'} | ${null} | ${'null'} `('sets component as $componentName for path "$path"', ({ path, component }) => { const router = createRouter('', 'master'); diff --git a/spec/frontend/snippets/components/app_spec.js b/spec/frontend/snippets/components/app_spec.js index 535e71b6da7..f2800f9e6af 100644 --- a/spec/frontend/snippets/components/app_spec.js +++ b/spec/frontend/snippets/components/app_spec.js @@ -1,19 +1,22 @@ import SnippetApp from '~/snippets/components/app.vue'; +import SnippetHeader from '~/snippets/components/snippet_header.vue'; +import { GlLoadingIcon } from '@gitlab/ui'; + import { createLocalVue, shallowMount } from '@vue/test-utils'; describe('Snippet view app', () => { let wrapper; - let snippetDataMock; const localVue = createLocalVue(); const defaultProps = { - snippetGid: 'gid://gitlab/PersonalSnippet/35', + snippetGid: 'gid://gitlab/PersonalSnippet/42', }; - function createComponent({ props = defaultProps, snippetData = {} } = {}) { - snippetDataMock = jest.fn(); + function createComponent({ props = defaultProps, loading = false } = {}) { const $apollo = { queries: { - snippetData: snippetDataMock, + snippet: { + loading, + }, }, }; @@ -25,17 +28,18 @@ describe('Snippet view app', () => { ...props, }, }); - - wrapper.setData({ - snippetData, - }); } afterEach(() => { wrapper.destroy(); }); - it('renders itself', () => { + it('renders loader while the query is in flight', () => { + createComponent({ loading: true }); + expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + }); + + it('renders SnippetHeader component after the query is finished', () => { createComponent(); - expect(wrapper.find('.js-snippet-view').exists()).toBe(true); + expect(wrapper.find(SnippetHeader).exists()).toBe(true); }); }); diff --git a/spec/frontend/snippets/components/snippet_header_spec.js b/spec/frontend/snippets/components/snippet_header_spec.js new file mode 100644 index 00000000000..8847a3a6938 --- /dev/null +++ b/spec/frontend/snippets/components/snippet_header_spec.js @@ -0,0 +1,171 @@ +import SnippetHeader from '~/snippets/components/snippet_header.vue'; +import DeleteSnippetMutation from '~/snippets/mutations/deleteSnippet.mutation.graphql'; +import { ApolloMutation } from 'vue-apollo'; +import { GlButton, GlModal } from '@gitlab/ui'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; + +describe('Snippet header component', () => { + let wrapper; + const localVue = createLocalVue(); + const snippet = { + snippet: { + id: 'gid://gitlab/PersonalSnippet/50', + title: 'The property of Thor', + visibilityLevel: 'private', + webUrl: 'http://personal.dev.null/42', + userPermissions: { + adminSnippet: true, + updateSnippet: true, + reportSnippet: false, + }, + project: null, + author: { + name: 'Thor Odinson', + }, + }, + }; + const mutationVariables = { + mutation: DeleteSnippetMutation, + variables: { + id: snippet.snippet.id, + }, + }; + const errorMsg = 'Foo bar'; + const err = { message: errorMsg }; + + const resolveMutate = jest.fn(() => Promise.resolve()); + const rejectMutation = jest.fn(() => Promise.reject(err)); + + const mutationTypes = { + RESOLVE: resolveMutate, + REJECT: rejectMutation, + }; + + function createComponent({ + loading = false, + permissions = {}, + mutationRes = mutationTypes.RESOLVE, + } = {}) { + const defaultProps = Object.assign({}, snippet); + if (permissions) { + Object.assign(defaultProps.snippet.userPermissions, { + ...permissions, + }); + } + const $apollo = { + queries: { + canCreateSnippet: { + loading, + }, + }, + mutate: mutationRes, + }; + + wrapper = shallowMount(SnippetHeader, { + sync: false, + mocks: { $apollo }, + localVue, + propsData: { + ...defaultProps, + }, + stubs: { + ApolloMutation, + }, + }); + } + + afterEach(() => { + wrapper.destroy(); + }); + + it('renders itself', () => { + createComponent(); + expect(wrapper.find('.detail-page-header').exists()).toBe(true); + }); + + it('renders action buttons based on permissions', () => { + createComponent({ + permissions: { + adminSnippet: false, + updateSnippet: false, + }, + }); + expect(wrapper.findAll(GlButton).length).toEqual(0); + + createComponent({ + permissions: { + adminSnippet: true, + updateSnippet: false, + }, + }); + expect(wrapper.findAll(GlButton).length).toEqual(1); + + createComponent({ + permissions: { + adminSnippet: true, + updateSnippet: true, + }, + }); + expect(wrapper.findAll(GlButton).length).toEqual(2); + + createComponent({ + permissions: { + adminSnippet: true, + updateSnippet: true, + }, + }); + wrapper.setData({ + canCreateSnippet: true, + }); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.findAll(GlButton).length).toEqual(3); + }); + }); + + it('renders modal for deletion of a snippet', () => { + createComponent(); + expect(wrapper.find(GlModal).exists()).toBe(true); + }); + + describe('Delete mutation', () => { + const { location } = window; + + beforeEach(() => { + delete window.location; + window.location = { + pathname: '', + }; + }); + + afterEach(() => { + window.location = location; + }); + + it('dispatches a mutation to delete the snippet with correct variables', () => { + createComponent(); + wrapper.vm.deleteSnippet(); + expect(mutationTypes.RESOLVE).toHaveBeenCalledWith(mutationVariables); + }); + + it('sets error message if mutation fails', () => { + createComponent({ mutationRes: mutationTypes.REJECT }); + expect(Boolean(wrapper.vm.errorMessage)).toBe(false); + + wrapper.vm.deleteSnippet(); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.errorMessage).toEqual(errorMsg); + }); + }); + + it('closes modal and redirects to snippets listing in case of successful mutation', () => { + createComponent(); + wrapper.vm.closeDeleteModal = jest.fn(); + + wrapper.vm.deleteSnippet(); + return wrapper.vm.$nextTick().then(() => { + expect(wrapper.vm.closeDeleteModal).toHaveBeenCalled(); + expect(window.location.pathname).toEqual('dashboard/snippets'); + }); + }); + }); +}); diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js index 4636de6b8b6..ab42dbe7cd1 100644 --- a/spec/frontend/test_setup.js +++ b/spec/frontend/test_setup.js @@ -34,18 +34,6 @@ Vue.config.productionTip = false; Vue.use(Translate); -// workaround for JSDOM not supporting innerText -// see https://github.com/jsdom/jsdom/issues/1245 -Object.defineProperty(global.Element.prototype, 'innerText', { - get() { - return this.textContent; - }, - set(value) { - this.textContext = value; - }, - configurable: true, // make it so that it doesn't blow chunks on re-running tests with things like --watch -}); - // convenience wrapper for migration from Karma Object.assign(global, { getJSONFixture, diff --git a/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb index 897b8f4e9ef..fcc717f83a2 100644 --- a/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb +++ b/spec/graphql/mutations/concerns/mutations/resolves_group_spec.rb @@ -10,6 +10,7 @@ describe Mutations::ResolvesGroup do end let(:context) { double } + subject(:mutation) { mutation_class.new(object: nil, context: context) } it 'uses the GroupsResolver to resolve groups by path' do diff --git a/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb b/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb index 09d1f66a2c7..918e5fb016e 100644 --- a/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb +++ b/spec/graphql/mutations/concerns/mutations/resolves_project_spec.rb @@ -10,6 +10,7 @@ describe Mutations::ResolvesProject do end let(:context) { double } + subject(:mutation) { mutation_class.new(object: nil, context: context) } it 'uses the ProjectsResolver to resolve projects by path' do diff --git a/spec/graphql/mutations/issues/set_confidential_spec.rb b/spec/graphql/mutations/issues/set_confidential_spec.rb index 05b787eb5ca..a8f1fcdf7f1 100644 --- a/spec/graphql/mutations/issues/set_confidential_spec.rb +++ b/spec/graphql/mutations/issues/set_confidential_spec.rb @@ -5,11 +5,13 @@ require 'spec_helper' describe Mutations::Issues::SetConfidential do let(:issue) { create(:issue) } let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } describe '#resolve' do let(:confidential) { true } let(:mutated_issue) { subject[:issue] } + subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, confidential: confidential) } it 'raises an error if the resource is not accessible to the user' do diff --git a/spec/graphql/mutations/issues/set_due_date_spec.rb b/spec/graphql/mutations/issues/set_due_date_spec.rb index 9a1f0925fe3..b45a7b460cd 100644 --- a/spec/graphql/mutations/issues/set_due_date_spec.rb +++ b/spec/graphql/mutations/issues/set_due_date_spec.rb @@ -5,11 +5,13 @@ require 'spec_helper' describe Mutations::Issues::SetDueDate do let(:issue) { create(:issue) } let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } describe '#resolve' do let(:due_date) { 2.days.since } let(:mutated_issue) { subject[:issue] } + subject { mutation.resolve(project_path: issue.project.full_path, iid: issue.iid, due_date: due_date) } it 'raises an error if the resource is not accessible to the user' do diff --git a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb index e8da0e25b7d..2033ab57a0d 100644 --- a/spec/graphql/mutations/merge_requests/set_assignees_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_assignees_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Mutations::MergeRequests::SetAssignees do let(:merge_request) { create(:merge_request) } let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } describe '#resolve' do @@ -12,6 +13,7 @@ describe Mutations::MergeRequests::SetAssignees do let(:assignee2) { create(:user) } let(:assignee_usernames) { [assignee.username] } let(:mutated_merge_request) { subject[:merge_request] } + subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, assignee_usernames: assignee_usernames) } before do diff --git a/spec/graphql/mutations/merge_requests/set_labels_spec.rb b/spec/graphql/mutations/merge_requests/set_labels_spec.rb index 3729251bab7..f7c04a57f68 100644 --- a/spec/graphql/mutations/merge_requests/set_labels_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_labels_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe Mutations::MergeRequests::SetLabels do let(:merge_request) { create(:merge_request) } let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } describe '#resolve' do @@ -12,6 +13,7 @@ describe Mutations::MergeRequests::SetLabels do let(:label2) { create(:label, project: merge_request.project) } let(:label_ids) { [label.to_global_id] } let(:mutated_merge_request) { subject[:merge_request] } + subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, label_ids: label_ids) } it 'raises an error if the resource is not accessible to the user' do diff --git a/spec/graphql/mutations/merge_requests/set_locked_spec.rb b/spec/graphql/mutations/merge_requests/set_locked_spec.rb index 51249854378..d35430abff1 100644 --- a/spec/graphql/mutations/merge_requests/set_locked_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_locked_spec.rb @@ -5,11 +5,13 @@ require 'spec_helper' describe Mutations::MergeRequests::SetLocked do let(:merge_request) { create(:merge_request) } let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } describe '#resolve' do let(:locked) { true } let(:mutated_merge_request) { subject[:merge_request] } + subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, locked: locked) } it 'raises an error if the resource is not accessible to the user' do diff --git a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb index c2792a4bc25..d79b0a995d7 100644 --- a/spec/graphql/mutations/merge_requests/set_milestone_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_milestone_spec.rb @@ -5,11 +5,13 @@ require 'spec_helper' describe Mutations::MergeRequests::SetMilestone do let(:merge_request) { create(:merge_request) } let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } describe '#resolve' do let(:milestone) { create(:milestone, project: merge_request.project) } let(:mutated_merge_request) { subject[:merge_request] } + subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, milestone: milestone) } it 'raises an error if the resource is not accessible to the user' do diff --git a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb index 116a77abcc0..286de6c0c97 100644 --- a/spec/graphql/mutations/merge_requests/set_subscription_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_subscription_spec.rb @@ -6,11 +6,13 @@ describe Mutations::MergeRequests::SetSubscription do let(:merge_request) { create(:merge_request) } let(:project) { merge_request.project } let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } describe '#resolve' do let(:subscribe) { true } let(:mutated_merge_request) { subject[:merge_request] } + subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, subscribed_state: subscribe) } it 'raises an error if the resource is not accessible to the user' do diff --git a/spec/graphql/mutations/merge_requests/set_wip_spec.rb b/spec/graphql/mutations/merge_requests/set_wip_spec.rb index c4accab9e46..490994c4577 100644 --- a/spec/graphql/mutations/merge_requests/set_wip_spec.rb +++ b/spec/graphql/mutations/merge_requests/set_wip_spec.rb @@ -5,11 +5,13 @@ require 'spec_helper' describe Mutations::MergeRequests::SetWip do let(:merge_request) { create(:merge_request) } let(:user) { create(:user) } + subject(:mutation) { described_class.new(object: nil, context: { current_user: user }) } describe '#resolve' do let(:wip) { true } let(:mutated_merge_request) { subject[:merge_request] } + subject { mutation.resolve(project_path: merge_request.project.full_path, iid: merge_request.iid, wip: wip) } it 'raises an error if the resource is not accessible to the user' do diff --git a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb index fa031af4013..b59561ebdd4 100644 --- a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb +++ b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb @@ -16,6 +16,7 @@ describe ResolvesPipelines do end let(:current_user) { create(:user) } + set(:project) { create(:project, :private) } set(:pipeline) { create(:ci_pipeline, project: project) } set(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) } diff --git a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb index 5e7f18636ec..4490b54d1f7 100644 --- a/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb +++ b/spec/graphql/resolvers/error_tracking/sentry_detailed_error_resolver_spec.rb @@ -20,6 +20,7 @@ describe Resolvers::ErrorTracking::SentryDetailedErrorResolver do describe '#resolve' do let(:args) { { id: issue_global_id(1234) } } + it 'fetches the data via the sentry API' do resolve_error(args) diff --git a/spec/graphql/types/permission_types/base_permission_type_spec.rb b/spec/graphql/types/permission_types/base_permission_type_spec.rb index a45102e5b50..aa6b4320006 100644 --- a/spec/graphql/types/permission_types/base_permission_type_spec.rb +++ b/spec/graphql/types/permission_types/base_permission_type_spec.rb @@ -6,6 +6,7 @@ describe Types::PermissionTypes::BasePermissionType do let(:permitable) { double('permittable') } let(:current_user) { build(:user) } let(:context) { { current_user: current_user } } + subject(:test_type) do Class.new(described_class) do graphql_name 'TestClass' diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index 77182a59c1c..8b6817efcc4 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -193,6 +193,7 @@ describe AvatarsHelper do describe '#user_avatar_without_link' do let(:options) { { user: user } } + subject { helper.user_avatar_without_link(options) } it 'displays user avatar' do diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index 4996e27c2e6..2d0c4723648 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -56,7 +56,7 @@ describe BlobHelper do stub_feature_flags(web_ide_default: false) link = helper.edit_blob_button(project, 'master', 'README.md') - expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md") + expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/-/edit/master/README.md") end it 'returns a link with a Web IDE route' do @@ -69,7 +69,7 @@ describe BlobHelper do stub_feature_flags(web_ide_default: false) link = helper.edit_blob_button(project, 'master', 'README.md', link_opts: { mr_id: 10 }) - expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/edit/master/README.md?mr_id=10") + expect(Capybara.string(link).find_link('Edit')[:href]).to eq("/#{project.full_path}/-/edit/master/README.md?mr_id=10") end end diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index 63a37a1f113..fa553710274 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe EventsHelper do describe '#event_commit_title' do let(:message) { 'foo & bar ' + 'A' * 70 + '\n' + 'B' * 80 } + subject { helper.event_commit_title(message) } it 'returns the first line, truncated to 70 chars' do diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 8b33277ea18..ac2f028f937 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -127,6 +127,7 @@ describe GroupsHelper do sub_sub_owner: sub_sub_owner } end + subject { helper.share_with_group_lock_help_text(sub_subgroup) } where(:root_share_with_group_locked, :subgroup_share_with_group_locked, :sub_subgroup_share_with_group_locked, :current_user, :help_text, :linked_ancestor) do @@ -194,6 +195,7 @@ describe GroupsHelper do describe '#group_container_registry_nav' do let(:group) { create(:group, :public) } let(:user) { create(:user) } + before do stub_container_registry_config(enabled: true) allow(helper).to receive(:current_user) { user } @@ -229,6 +231,7 @@ describe GroupsHelper do describe '#group_sidebar_links' do let(:group) { create(:group, :public) } let(:user) { create(:user) } + before do group.add_owner(user) allow(helper).to receive(:current_user) { user } diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 9249c625135..a39110d0aa7 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -278,6 +278,7 @@ describe IssuablesHelper do describe '#assignee_sidebar_data' do let(:user) { create(:user) } let(:merge_request) { nil } + subject { helper.assignee_sidebar_data(user, merge_request: merge_request) } it 'returns hash of assignee data' do diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index e2dff05cfaa..7ad554fd618 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -274,6 +274,7 @@ describe LabelsHelper do describe '#label_status_tooltip' do let(:status) { 'unsubscribed'.inquiry } + subject { label_status_tooltip(label.present(issuable_subject: nil), status) } context 'with a project label' do diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb index 5ca5f5703cf..6227a5f5662 100644 --- a/spec/helpers/markup_helper_spec.rb +++ b/spec/helpers/markup_helper_spec.rb @@ -44,6 +44,7 @@ describe MarkupHelper do describe "override default project" do let(:actual) { issue.to_reference } + set(:second_project) { create(:project, :public) } set(:second_issue) { create(:issue, project: second_project) } @@ -55,6 +56,7 @@ describe MarkupHelper do describe 'uploads' do let(:text) { "![ImageTest](/uploads/test.png)" } + set(:group) { create(:group) } subject { helper.markdown(text) } @@ -101,7 +103,7 @@ describe MarkupHelper do let(:requested_path) { 'files/images/README.md' } it 'returns the correct HTML for the image' do - expanded_path = "/#{project.full_path}/raw/master/files/images/#{image_file}" + expanded_path = "/#{project.full_path}/-/raw/master/files/images/#{image_file}" expect(subject.css('a')[0].attr('href')).to eq(expanded_path) expect(subject.css('img')[0].attr('data-src')).to eq(expanded_path) diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb index 6bd567eab57..8db8c37038e 100644 --- a/spec/helpers/merge_requests_helper_spec.rb +++ b/spec/helpers/merge_requests_helper_spec.rb @@ -54,6 +54,7 @@ describe MergeRequestsHelper do describe '#format_mr_branch_names' do describe 'within the same project' do let(:merge_request) { create(:merge_request) } + subject { format_mr_branch_names(merge_request) } it { is_expected.to eq([merge_request.source_branch, merge_request.target_branch]) } diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 0db54599ede..46228d0d1c2 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -313,6 +313,7 @@ describe ProjectsHelper do describe '#link_to_project' do let(:group) { create(:group, name: 'group name with space') } let(:project) { create(:project, group: group, name: 'project name with space') } + subject { link_to_project(project) } it 'returns an HTML link to the project' do @@ -544,6 +545,7 @@ describe ProjectsHelper do describe '#git_user_name' do let(:user) { double(:user, name: 'John "A" Doe53') } + before do allow(helper).to receive(:current_user).and_return(user) end @@ -566,6 +568,7 @@ describe ProjectsHelper do context 'user logged in' do let(:user) { create(:user) } + before do allow(helper).to receive(:current_user).and_return(user) end diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index fcfce0eaf31..9046c1e9fb4 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -95,7 +95,7 @@ describe SubmoduleHelper do allow(repo).to receive(:project).and_return(project) stub_url('./') - expect(subject).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/tree/hash"]) + expect(subject).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/-/tree/hash"]) end end @@ -166,7 +166,7 @@ describe SubmoduleHelper do allow(repo).to receive(:submodule_url_for).and_return(relative_path) result = subject - expect(result).to eq([expected_path, "#{expected_path}/tree/#{submodule_item.id}"]) + expect(result).to eq([expected_path, "#{expected_path}/-/tree/#{submodule_item.id}"]) end it 'handles project under same group' do diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js index b21cab58b4e..48e1ed18a2f 100644 --- a/spec/javascripts/diffs/components/app_spec.js +++ b/spec/javascripts/diffs/components/app_spec.js @@ -92,16 +92,16 @@ describe('diffs/components/app', () => { }); }); - it('calls fetchDiffFiles if diffsBatchLoad is enabled, and not latest version', () => { + it('calls batch methods if diffsBatchLoad is enabled, and not latest version', () => { wrapper.vm.glFeatures.diffsBatchLoad = true; wrapper.vm.isLatestVersion = () => false; wrapper.vm.fetchData(false); - expect(wrapper.vm.fetchDiffFiles).toHaveBeenCalled(); + expect(wrapper.vm.fetchDiffFiles).not.toHaveBeenCalled(); wrapper.vm.$nextTick(() => { expect(wrapper.vm.startRenderDiffsQueue).toHaveBeenCalled(); - expect(wrapper.vm.fetchDiffFilesMeta).not.toHaveBeenCalled(); - expect(wrapper.vm.fetchDiffFilesBatch).not.toHaveBeenCalled(); + expect(wrapper.vm.fetchDiffFilesMeta).toHaveBeenCalled(); + expect(wrapper.vm.fetchDiffFilesBatch).toHaveBeenCalled(); }); }); diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index 589bd0e649a..b23334d38dc 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -147,13 +147,15 @@ describe('DiffsStoreActions', () => { describe('fetchDiffFilesBatch', () => { it('should fetch batch diff files', done => { const endpointBatch = '/fetch/diffs_batch'; - const batch1 = `${endpointBatch}?per_page=${DIFFS_PER_PAGE}`; - const batch2 = `${endpointBatch}?per_page=${DIFFS_PER_PAGE}&page=2`; const mock = new MockAdapter(axios); const res1 = { diff_files: [], pagination: { next_page: 2 } }; const res2 = { diff_files: [], pagination: {} }; - mock.onGet(batch1).reply(200, res1); - mock.onGet(batch2).reply(200, res2); + mock + .onGet(endpointBatch, { params: { page: undefined, per_page: DIFFS_PER_PAGE, w: '1' } }) + .reply(200, res1); + mock + .onGet(endpointBatch, { params: { page: 2, per_page: DIFFS_PER_PAGE, w: '1' } }) + .reply(200, res2); testAction( fetchDiffFilesBatch, diff --git a/spec/javascripts/monitoring/charts/column_spec.js b/spec/javascripts/monitoring/charts/column_spec.js deleted file mode 100644 index 9676617e8e1..00000000000 --- a/spec/javascripts/monitoring/charts/column_spec.js +++ /dev/null @@ -1,62 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { GlColumnChart } from '@gitlab/ui/dist/charts'; -import ColumnChart from '~/monitoring/components/charts/column.vue'; - -const localVue = createLocalVue(); - -describe('Column component', () => { - let columnChart; - - beforeEach(() => { - columnChart = shallowMount(localVue.extend(ColumnChart), { - propsData: { - graphData: { - metrics: [ - { - x_label: 'Time', - y_label: 'Usage', - result: [ - { - metric: {}, - values: [ - [1495700554.925, '8.0390625'], - [1495700614.925, '8.0390625'], - [1495700674.925, '8.0390625'], - ], - }, - ], - }, - ], - }, - containerWidth: 100, - }, - sync: false, - localVue, - }); - }); - - afterEach(() => { - columnChart.destroy(); - }); - - describe('wrapped components', () => { - describe('GitLab UI column chart', () => { - let glColumnChart; - - beforeEach(() => { - glColumnChart = columnChart.find(GlColumnChart); - }); - - it('is a Vue instance', () => { - expect(glColumnChart.isVueInstance()).toBe(true); - }); - - it('receives data properties needed for proper chart render', () => { - const props = glColumnChart.props(); - - expect(props.data).toBe(columnChart.vm.chartData); - expect(props.option).toBe(columnChart.vm.chartOptions); - }); - }); - }); -}); diff --git a/spec/javascripts/monitoring/charts/empty_chart_spec.js b/spec/javascripts/monitoring/charts/empty_chart_spec.js deleted file mode 100644 index 06822126b59..00000000000 --- a/spec/javascripts/monitoring/charts/empty_chart_spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import EmptyChart from '~/monitoring/components/charts/empty_chart.vue'; - -const localVue = createLocalVue(); - -describe('Empty Chart component', () => { - let emptyChart; - const graphTitle = 'Memory Usage'; - - beforeEach(() => { - emptyChart = shallowMount(localVue.extend(EmptyChart), { - propsData: { - graphTitle, - }, - sync: false, - localVue, - }); - }); - - afterEach(() => { - emptyChart.destroy(); - }); - - it('render the chart title', () => { - expect(emptyChart.find({ ref: 'graphTitle' }).text()).toBe(graphTitle); - }); - - describe('Computed props', () => { - it('sets the height for the svg container', () => { - expect(emptyChart.vm.svgContainerStyle.height).toBe('300px'); - }); - }); -}); diff --git a/spec/javascripts/monitoring/charts/heatmap_spec.js b/spec/javascripts/monitoring/charts/heatmap_spec.js deleted file mode 100644 index 9a98fc6fb05..00000000000 --- a/spec/javascripts/monitoring/charts/heatmap_spec.js +++ /dev/null @@ -1,69 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import { GlHeatmap } from '@gitlab/ui/dist/charts'; -import Heatmap from '~/monitoring/components/charts/heatmap.vue'; -import { graphDataPrometheusQueryRangeMultiTrack } from '../mock_data'; - -describe('Heatmap component', () => { - let heatmapChart; - let store; - - beforeEach(() => { - heatmapChart = shallowMount(Heatmap, { - propsData: { - graphData: graphDataPrometheusQueryRangeMultiTrack, - containerWidth: 100, - }, - store, - }); - }); - - afterEach(() => { - heatmapChart.destroy(); - }); - - describe('wrapped components', () => { - describe('GitLab UI heatmap chart', () => { - let glHeatmapChart; - - beforeEach(() => { - glHeatmapChart = heatmapChart.find(GlHeatmap); - }); - - it('is a Vue instance', () => { - expect(glHeatmapChart.isVueInstance()).toBe(true); - }); - - it('should display a label on the x axis', () => { - expect(heatmapChart.vm.xAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.x_label); - }); - - it('should display a label on the y axis', () => { - expect(heatmapChart.vm.yAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.y_label); - }); - - // According to the echarts docs https://echarts.apache.org/en/option.html#series-heatmap.data - // each row of the heatmap chart is represented by an array inside another parent array - // e.g. [[0, 0, 10]], the format represents the column, the row and finally the value - // corresponding to the cell - - it('should return chartData with a length of x by y, with a length of 3 per array', () => { - const row = heatmapChart.vm.chartData[0]; - - expect(row.length).toBe(3); - expect(heatmapChart.vm.chartData.length).toBe(30); - }); - - it('returns a series of labels for the x axis', () => { - const { xAxisLabels } = heatmapChart.vm; - - expect(xAxisLabels.length).toBe(5); - }); - - it('returns a series of labels for the y axis', () => { - const { yAxisLabels } = heatmapChart.vm; - - expect(yAxisLabels.length).toBe(6); - }); - }); - }); -}); diff --git a/spec/javascripts/monitoring/charts/single_stat_spec.js b/spec/javascripts/monitoring/charts/single_stat_spec.js deleted file mode 100644 index 6adca0b0eed..00000000000 --- a/spec/javascripts/monitoring/charts/single_stat_spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import SingleStatChart from '~/monitoring/components/charts/single_stat.vue'; -import { graphDataPrometheusQuery } from '../mock_data'; - -const localVue = createLocalVue(); - -describe('Single Stat Chart component', () => { - let singleStatChart; - - beforeEach(() => { - singleStatChart = shallowMount(localVue.extend(SingleStatChart), { - propsData: { - graphData: graphDataPrometheusQuery, - }, - sync: false, - localVue, - }); - }); - - afterEach(() => { - singleStatChart.destroy(); - }); - - describe('computed', () => { - describe('engineeringNotation', () => { - it('should interpolate the value and unit props', () => { - expect(singleStatChart.vm.engineeringNotation).toBe('91MB'); - }); - }); - }); -}); diff --git a/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js b/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js index c743f1f6ad7..6a83790093a 100644 --- a/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js +++ b/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js @@ -38,11 +38,11 @@ describe('DiffViewer', () => { setTimeout(() => { expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe( - `//raw/DEF/${RED_BOX_IMAGE_URL}`, + `//-/raw/DEF/${RED_BOX_IMAGE_URL}`, ); expect(vm.$el.querySelector('.added img').getAttribute('src')).toBe( - `//raw/ABC/${GREEN_BOX_IMAGE_URL}`, + `//-/raw/ABC/${GREEN_BOX_IMAGE_URL}`, ); done(); diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 1efca647b8b..500df24ece8 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -153,7 +153,7 @@ describe Banzai::Filter::RelativeLinkFilter do it 'rebuilds absolute URL for a file in the repo' do doc = filter(link('/doc/api/README.md')) expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + .to eq "/#{project_path}/-/blob/#{ref}/doc/api/README.md" end it 'does not modify relative URLs in system notes' do @@ -171,13 +171,13 @@ describe Banzai::Filter::RelativeLinkFilter do it 'rebuilds relative URL for a file in the repo' do doc = filter(link('doc/api/README.md')) expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + .to eq "/#{project_path}/-/blob/#{ref}/doc/api/README.md" end it 'rebuilds relative URL for a file in the repo with leading ./' do doc = filter(link('./doc/api/README.md')) expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + .to eq "/#{project_path}/-/blob/#{ref}/doc/api/README.md" end it 'rebuilds relative URL for a file in the repo up one directory' do @@ -185,7 +185,7 @@ describe Banzai::Filter::RelativeLinkFilter do doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md') expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + .to eq "/#{project_path}/-/blob/#{ref}/doc/api/README.md" end it 'rebuilds relative URL for a file in the repo up multiple directories' do @@ -193,7 +193,7 @@ describe Banzai::Filter::RelativeLinkFilter do doc = filter(relative_link, requested_path: 'doc/foo/bar/baz/README.md') expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + .to eq "/#{project_path}/-/blob/#{ref}/doc/api/README.md" end it 'rebuilds relative URL for a file in the repository root' do @@ -201,47 +201,47 @@ describe Banzai::Filter::RelativeLinkFilter do doc = filter(relative_link, requested_path: 'doc/some-file.md') expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/blob/#{ref}/README.md" + .to eq "/#{project_path}/-/blob/#{ref}/README.md" end it 'rebuilds relative URL for a file in the repo with an anchor' do doc = filter(link('README.md#section')) expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/blob/#{ref}/README.md#section" + .to eq "/#{project_path}/-/blob/#{ref}/README.md#section" end it 'rebuilds relative URL for a directory in the repo' do doc = filter(link('doc/api/')) expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/tree/#{ref}/doc/api" + .to eq "/#{project_path}/-/tree/#{ref}/doc/api" end it 'rebuilds relative URL for an image in the repo' do doc = filter(image('files/images/logo-black.png')) expect(doc.at_css('img')['src']) - .to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png" + .to eq "/#{project_path}/-/raw/#{ref}/files/images/logo-black.png" end it 'rebuilds relative URL for link to an image in the repo' do doc = filter(link('files/images/logo-black.png')) expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/raw/#{ref}/files/images/logo-black.png" + .to eq "/#{project_path}/-/raw/#{ref}/files/images/logo-black.png" end it 'rebuilds relative URL for a video in the repo' do doc = filter(video('files/videos/intro.mp4'), commit: project.commit('video'), ref: 'video') expect(doc.at_css('video')['src']) - .to eq "/#{project_path}/raw/video/files/videos/intro.mp4" + .to eq "/#{project_path}/-/raw/video/files/videos/intro.mp4" end it 'rebuilds relative URL for audio in the repo' do doc = filter(audio('files/audio/sample.wav'), commit: project.commit('audio'), ref: 'audio') expect(doc.at_css('audio')['src']) - .to eq "/#{project_path}/raw/audio/files/audio/sample.wav" + .to eq "/#{project_path}/-/raw/audio/files/audio/sample.wav" end it 'does not modify relative URL with an anchor only' do @@ -268,7 +268,7 @@ describe Banzai::Filter::RelativeLinkFilter do allow_any_instance_of(described_class).to receive(:uri_type).and_return(:raw) doc = filter(image(escaped)) - expect(doc.at_css('img')['src']).to eq "/#{project_path}/raw/#{Addressable::URI.escape(ref)}/#{escaped}" + expect(doc.at_css('img')['src']).to eq "/#{project_path}/-/raw/#{Addressable::URI.escape(ref)}/#{escaped}" end context 'when requested path is a file in the repo' do @@ -276,7 +276,7 @@ describe Banzai::Filter::RelativeLinkFilter do it 'rebuilds URL relative to the containing directory' do doc = filter(link('users.md')) - expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md" + expect(doc.at_css('a')['href']).to eq "/#{project_path}/-/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md" end end @@ -285,7 +285,7 @@ describe Banzai::Filter::RelativeLinkFilter do it 'rebuilds URL relative to the directory' do doc = filter(link('users.md')) - expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md" + expect(doc.at_css('a')['href']).to eq "/#{project_path}/-/blob/#{Addressable::URI.escape(ref)}/doc/api/users.md" end end @@ -296,7 +296,7 @@ describe Banzai::Filter::RelativeLinkFilter do it 'correctly escapes the ref' do doc = filter(link('.gitkeep')) - expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/foo/bar/.gitkeep" + expect(doc.at_css('a')['href']).to eq "/#{project_path}/-/blob/#{Addressable::URI.escape(ref)}/foo/bar/.gitkeep" end end @@ -315,7 +315,7 @@ describe Banzai::Filter::RelativeLinkFilter do doc = filter(link(path)) expect(doc.at_css('a')['href']) - .to eq "/#{project_path}/raw/#{ref_escaped}/files/images/logo-black.png" + .to eq "/#{project_path}/-/raw/#{ref_escaped}/files/images/logo-black.png" end end @@ -326,7 +326,7 @@ describe Banzai::Filter::RelativeLinkFilter do it 'does not escape the space twice' do doc = filter(link('README.md')) - expect(doc.at_css('a')['href']).to eq "/#{project_path}/blob/#{Addressable::URI.escape(ref)}/with%20space/README.md" + expect(doc.at_css('a')['href']).to eq "/#{project_path}/-/blob/#{Addressable::URI.escape(ref)}/with%20space/README.md" end end end diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb index adf5a232a75..7ecc42ea497 100644 --- a/spec/lib/gitlab/conflict/file_spec.rb +++ b/spec/lib/gitlab/conflict/file_spec.rb @@ -258,7 +258,7 @@ FILE describe '#as_json' do it 'includes the blob path for the file' do expect(conflict_file.as_json[:blob_path]) - .to eq("/#{project.full_path}/blob/#{our_commit.oid}/files/ruby/regex.rb") + .to eq("/#{project.full_path}/-/blob/#{our_commit.oid}/files/ruby/regex.rb") end it 'includes the blob icon for the file' do diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb index a033316a6d9..3db8900ed8e 100644 --- a/spec/lib/gitlab/database_spec.rb +++ b/spec/lib/gitlab/database_spec.rb @@ -324,6 +324,7 @@ describe Gitlab::Database do context 'with version < 9.5' do let(:version) { 9.4 } + it 'refuses setting the upsert' do expect(connection) .not_to receive(:execute) diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb index 9f8542a76c9..7128689e362 100644 --- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb @@ -75,8 +75,8 @@ describe Gitlab::DependencyLinker::GodepsJsonLinker do end it 'links GitLab projects' do - expect(subject).to include(link('gitlab.com/group/project/path', 'https://gitlab.com/group/project/tree/master/path')) - expect(subject).to include(link('gitlab.com/group/subgroup/project.git/path', 'https://gitlab.com/group/subgroup/project/tree/master/path')) + expect(subject).to include(link('gitlab.com/group/project/path', 'https://gitlab.com/group/project/-/tree/master/path')) + expect(subject).to include(link('gitlab.com/group/subgroup/project.git/path', 'https://gitlab.com/group/subgroup/project/-/tree/master/path')) end it 'links Golang packages' do diff --git a/spec/lib/gitlab/middleware/go_spec.rb b/spec/lib/gitlab/middleware/go_spec.rb index 2b90035d148..99c2a364dfc 100644 --- a/spec/lib/gitlab/middleware/go_spec.rb +++ b/spec/lib/gitlab/middleware/go_spec.rb @@ -241,7 +241,7 @@ describe Gitlab::Middleware::Go do project_url = "http://#{Gitlab.config.gitlab.host}/#{path}" expect(response[0]).to eq(200) expect(response[1]['Content-Type']).to eq('text/html') - expected_body = %{go get #{Gitlab.config.gitlab.url}/#{path}} + expected_body = %{go get #{Gitlab.config.gitlab.url}/#{path}} expect(response[2].body).to eq([expected_body]) end end diff --git a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb index 0936d895453..fa2dccc7c92 100644 --- a/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb +++ b/spec/lib/gitlab/prometheus/queries/knative_invocation_query_spec.rb @@ -16,12 +16,12 @@ describe Gitlab::Prometheus::Queries::KnativeInvocationQuery do create(:prometheus_metric, :common, identifier: :system_metrics_knative_function_invocation_count, - query: 'sum(ceil(rate(istio_requests_total{destination_service_namespace="%{kube_namespace}", destination_app=~"%{function_name}.*"}[1m])*60))') + query: 'sum(ceil(rate(istio_requests_total{destination_service_namespace="%{kube_namespace}", destination_service=~"%{function_name}.*"}[1m])*60))') end it 'has the query, but no data' do expect(client).to receive(:query_range).with( - 'sum(ceil(rate(istio_requests_total{destination_service_namespace="test-ns", destination_app=~"test-name.*"}[1m])*60))', + 'sum(ceil(rate(istio_requests_total{destination_service_namespace="test-ns", destination_service=~"test-name.*"}[1m])*60))', hash_including(:start, :stop) ) diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb index c85994402dd..13817bdcc72 100644 --- a/spec/lib/quality/test_level_spec.rb +++ b/spec/lib/quality/test_level_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a pattern' do expect(subject.pattern(:unit)) - .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb") + .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb") end end @@ -82,7 +82,7 @@ RSpec.describe Quality::TestLevel do context 'when level is unit' do it 'returns a regexp' do expect(subject.regexp(:unit)) - .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)}) + .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)}) end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index ad256c10964..e4a7d62eb02 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -109,6 +109,7 @@ describe Notify do describe 'that are reassigned' do let(:previous_assignee) { create(:user, name: 'Previous Assignee') } + subject { described_class.reassigned_issue_email(recipient.id, issue.id, [previous_assignee.id], current_user.id) } it_behaves_like 'a multiple recipients email' @@ -207,6 +208,7 @@ describe Notify do describe 'status changed' do let(:status) { 'closed' } + subject { described_class.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) } it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do @@ -235,6 +237,7 @@ describe Notify do describe 'moved to another project' do let(:new_issue) { create(:issue) } + subject { described_class.issue_moved_email(recipient, issue, new_issue, current_user) } context 'when a user has permissions to access the new issue' do @@ -334,6 +337,7 @@ describe Notify do describe 'that are reassigned' do let(:previous_assignee) { create(:user, name: 'Previous Assignee') } + subject { described_class.reassigned_merge_request_email(recipient.id, merge_request.id, [previous_assignee.id], current_user.id) } it_behaves_like 'a multiple recipients email' @@ -426,6 +430,7 @@ describe Notify do describe 'status changed' do let(:status) { 'reopened' } + subject { described_class.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) } it_behaves_like 'an answer to an existing thread with reply-by-email enabled' do @@ -454,6 +459,7 @@ describe Notify do describe 'that are merged' do let(:merge_author) { create(:user) } + subject { described_class.merged_merge_request_email(recipient.id, merge_request.id, merge_author.id) } it_behaves_like 'a multiple recipients email' @@ -698,6 +704,7 @@ describe Notify do describe 'project was moved' do let(:recipient) { user } + subject { described_class.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } it_behaves_like 'an email sent to a user' @@ -725,6 +732,7 @@ describe Notify do project.request_access(user) project.requesters.find_by(user_id: user.id) end + subject { described_class.member_access_requested_email('project', project_member.id, recipient.id) } it_behaves_like 'an email sent from GitLab' @@ -750,6 +758,7 @@ describe Notify do project.request_access(user) project.requesters.find_by(user_id: user.id) end + subject { described_class.member_access_denied_email('project', project.id, user.id) } it_behaves_like 'an email sent from GitLab' @@ -769,6 +778,7 @@ describe Notify do let(:owner) { create(:user, name: "Chang O'Keefe") } let(:project) { create(:project, :public, namespace: owner.namespace) } let(:project_member) { create(:project_member, project: project, user: user) } + subject { described_class.member_access_granted_email('project', project_member.id) } it_behaves_like 'an email sent from GitLab' @@ -1190,6 +1200,7 @@ describe Notify do group.request_access(user) group.requesters.find_by(user_id: user.id) end + subject { described_class.member_access_requested_email('group', group_member.id, recipient.id) } it_behaves_like 'an email sent from GitLab' @@ -1216,6 +1227,7 @@ describe Notify do group.requesters.find_by(user_id: user.id) end let(:recipient) { user } + subject { described_class.member_access_denied_email('group', group.id, user.id) } it_behaves_like 'an email sent from GitLab' diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb index 5b45dc078de..c1057af5f80 100644 --- a/spec/models/clusters/applications/knative_spec.rb +++ b/spec/models/clusters/applications/knative_spec.rb @@ -123,7 +123,7 @@ describe Clusters::Applications::Knative do subject { knative.install_command } it 'is initialized with latest version' do - expect(subject.version).to eq('0.7.0') + expect(subject.version).to eq('0.9.0') end it_behaves_like 'a command' diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 99d7e4d156f..7170003857e 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -697,7 +697,7 @@ describe Project do let(:project) { create(:project, :repository) } it 'returns the README' do - expect(project.readme_url).to eq("#{project.web_url}/blob/master/README.md") + expect(project.readme_url).to eq("#{project.web_url}/-/blob/master/README.md") end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 7465a616a83..188eafadfc1 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -307,6 +307,7 @@ describe ProjectPolicy do context 'for a guest in a private project' do let(:project) { create(:project, :private) } + subject { described_class.new(guest, project) } it 'disallows the guest from reading the merge request and merge request iid' do @@ -320,6 +321,7 @@ describe ProjectPolicy do describe 'for unconfirmed user' do let(:unconfirmed_user) { create(:user, confirmed_at: nil) } + subject { described_class.new(unconfirmed_user, project) } it 'disallows to modify pipelines' do diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb index 8680e8b9b45..ee7bfd1256d 100644 --- a/spec/presenters/blob_presenter_spec.rb +++ b/spec/presenters/blob_presenter_spec.rb @@ -21,7 +21,7 @@ describe BlobPresenter, :seed_helper do subject { described_class.new(blob) } - it { expect(subject.web_url).to eq("http://localhost/#{project.full_path}/blob/#{blob.commit_id}/#{blob.path}") } + it { expect(subject.web_url).to eq("http://localhost/#{project.full_path}/-/blob/#{blob.commit_id}/#{blob.path}") } end describe '#highlight' do diff --git a/spec/presenters/label_presenter_spec.rb b/spec/presenters/label_presenter_spec.rb index d566da7c872..e9f9657490d 100644 --- a/spec/presenters/label_presenter_spec.rb +++ b/spec/presenters/label_presenter_spec.rb @@ -41,6 +41,7 @@ describe LabelPresenter do describe '#filter_path' do context 'with group as context subject' do let(:label_in_group) { build_stubbed(:label, project: project).present(issuable_subject: group) } + subject { label_in_group.filter_path } it { is_expected.to eq(issues_group_path(group, label_name: [label_in_group.title])) } diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb index 6ce6f84cf61..025f083ab27 100644 --- a/spec/presenters/merge_request_presenter_spec.rb +++ b/spec/presenters/merge_request_presenter_spec.rb @@ -334,7 +334,7 @@ describe MergeRequestPresenter do allow(resource).to receive(:target_branch_exists?) { true } is_expected - .to eq("/#{resource.target_project.full_path}/commits/#{resource.target_branch}") + .to eq("/#{resource.target_project.full_path}/-/commits/#{resource.target_branch}") end end @@ -358,7 +358,7 @@ describe MergeRequestPresenter do allow(resource).to receive(:source_branch_exists?) { true } is_expected - .to eq("/#{resource.source_project.full_path}/commits/#{resource.source_branch}") + .to eq("/#{resource.source_project.full_path}/-/commits/#{resource.source_branch}") end end @@ -382,7 +382,7 @@ describe MergeRequestPresenter do allow(resource).to receive(:target_branch_exists?) { true } is_expected - .to eq("/#{resource.target_project.full_path}/tree/#{resource.target_branch}") + .to eq("/#{resource.target_project.full_path}/-/tree/#{resource.target_branch}") end end @@ -496,7 +496,7 @@ describe MergeRequestPresenter do allow(resource).to receive(:source_branch_exists?) { true } is_expected - .to eq("#{resource.source_branch}") + .to eq("#{resource.source_branch}") end it 'escapes html, when source_branch does not exist' do diff --git a/spec/presenters/project_presenter_spec.rb b/spec/presenters/project_presenter_spec.rb index ce095d2225f..318024bacd6 100644 --- a/spec/presenters/project_presenter_spec.rb +++ b/spec/presenters/project_presenter_spec.rb @@ -436,6 +436,7 @@ describe ProjectPresenter do describe '#repo_statistics_buttons' do let(:presenter) { described_class.new(project, current_user: user) } + subject(:empty_repo_statistics_buttons) { presenter.empty_repo_statistics_buttons } before do diff --git a/spec/presenters/tree_entry_presenter_spec.rb b/spec/presenters/tree_entry_presenter_spec.rb index d74ee5dc28f..0c29fe3e5ff 100644 --- a/spec/presenters/tree_entry_presenter_spec.rb +++ b/spec/presenters/tree_entry_presenter_spec.rb @@ -11,6 +11,6 @@ describe TreeEntryPresenter do let(:presenter) { described_class.new(tree) } describe '.web_url' do - it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/tree/#{tree.commit_id}/#{tree.path}") } + it { expect(presenter.web_url).to eq("http://localhost/#{project.full_path}/-/tree/#{tree.commit_id}/#{tree.path}") } end end diff --git a/spec/requests/api/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb index 80040cddd4d..19a34314bb8 100644 --- a/spec/requests/api/award_emoji_spec.rb +++ b/spec/requests/api/award_emoji_spec.rb @@ -9,6 +9,7 @@ describe API::AwardEmoji do set(:award_emoji) { create(:award_emoji, awardable: issue, user: user) } let!(:merge_request) { create(:merge_request, source_project: project, target_project: project) } let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request, user: user) } + set(:note) { create(:note, project: project, noteable: issue) } before do diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index 8a67e956165..510ef9d7d0a 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -69,6 +69,7 @@ describe API::Boards do set(:group) { create(:group) } set(:board_parent) { create(:group, parent: group ) } let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" } + set(:board) { create(:board, group: board_parent) } it 'creates a new board list for ancestor group labels' do diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb index 5b910d5bfe0..b24981873c8 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb @@ -75,6 +75,7 @@ describe 'Adding an AwardEmoji' do describe 'marking Todos as done' do let(:user) { current_user} + subject { post_graphql_mutation(mutation, current_user: user) } include_examples 'creating award emojis marks Todos as done' diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb index ae628d3e56c..5e2c0e668a5 100644 --- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb +++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb @@ -83,6 +83,7 @@ describe 'Toggling an AwardEmoji' do describe 'marking Todos as done' do let(:user) { current_user} + subject { post_graphql_mutation(mutation, current_user: user) } include_examples 'creating award emojis marks Todos as done' diff --git a/spec/requests/api/project_import_spec.rb b/spec/requests/api/project_import_spec.rb index 866adbd424e..186f0f52a46 100644 --- a/spec/requests/api/project_import_spec.rb +++ b/spec/requests/api/project_import_spec.rb @@ -7,6 +7,7 @@ describe API::ProjectImport do let(:user) { create(:user) } let(:file) { File.join('spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } let(:namespace) { create(:group) } + before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) stub_uploads_object_storage(FileUploader) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cda2dd7d2f4..eb3d5e698d8 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -715,7 +715,7 @@ describe API::Projects do post api('/projects', user), params: project - expect(json_response['readme_url']).to eql("#{Gitlab.config.gitlab.url}/#{json_response['namespace']['full_path']}/somewhere/blob/master/README.md") + expect(json_response['readme_url']).to eql("#{Gitlab.config.gitlab.url}/#{json_response['namespace']['full_path']}/somewhere/-/blob/master/README.md") end it 'sets tag list to a project' do @@ -1882,6 +1882,7 @@ describe API::Projects do describe "POST /projects/:id/share" do let(:group) { create(:group) } + before do group.add_developer(user) end diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb index 6599e4b8f23..af86ba86303 100644 --- a/spec/requests/api/settings_spec.rb +++ b/spec/requests/api/settings_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe API::Settings, 'Settings' do let(:user) { create(:user) } + set(:admin) { create(:admin) } describe "GET /application/settings" do diff --git a/spec/requests/api/snippets_spec.rb b/spec/requests/api/snippets_spec.rb index eb9d0d38bcb..f32be7a8765 100644 --- a/spec/requests/api/snippets_spec.rb +++ b/spec/requests/api/snippets_spec.rb @@ -375,6 +375,7 @@ describe API::Snippets do describe 'DELETE /snippets/:id' do let!(:public_snippet) { create(:personal_snippet, :public, author: user) } + it 'deletes snippet' do expect do delete api("/snippets/#{public_snippet.id}", user) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 29088a42fbe..0a22a09b8a6 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -2124,6 +2124,7 @@ describe API::Users do describe 'GET /user/status' do let(:path) { '/user/status' } + it_behaves_like 'rendering user status' end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 42b4bd71b88..7424b7ee8d0 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -834,7 +834,7 @@ describe 'Git HTTP requests' do Blob.decorate(Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt'), project) end - get "/#{project.full_path}/blob/master/info/refs" + get "/#{project.full_path}/-/blob/master/info/refs" end it "returns the file" do @@ -844,7 +844,7 @@ describe 'Git HTTP requests' do context "when the file does not exist" do before do - get "/#{project.full_path}/blob/master/info/refs" + get "/#{project.full_path}/-/blob/master/info/refs" end it "redirects" do diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 96956d85de4..7816b77d14c 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -454,11 +454,14 @@ describe 'project routing' do it_behaves_like 'RESTful project resources' do let(:actions) { [:show] } let(:controller) { 'commits' } + let(:controller_path) { '/-/commits' } end it 'to #show' do - expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') + expect(get('/gitlab/gitlabhq/-/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/commits/master.atom", "/gitlab/gitlabhq/-/commits/master.atom" end # project_project_members GET /:project_id/project_members(.:format) project_members#index @@ -550,62 +553,68 @@ describe 'project routing' do # project_blame GET /:project_id/blame/:id(.:format) blame#show {id: /[^\0]+/, project_id: /[^\/]+/} describe Projects::BlameController, 'routing' do it 'to #show' do - expect(get('/gitlab/gitlabhq/blame/master/app/models/project.rb')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') - expect(get('/gitlab/gitlabhq/blame/master/files.scss')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + expect(get('/gitlab/gitlabhq/-/blame/master/app/models/project.rb')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/-/blame/master/files.scss')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') newline_file = "new\n\nline.txt" url_encoded_newline_file = ERB::Util.url_encode(newline_file) - assert_routing({ path: "/gitlab/gitlabhq/blame/master/#{url_encoded_newline_file}", + assert_routing({ path: "/gitlab/gitlabhq/-/blame/master/#{url_encoded_newline_file}", method: :get }, { controller: 'projects/blame', action: 'show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "master/#{newline_file}" }) end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/blame/master/readme.md", "/gitlab/gitlabhq/-/blame/master/readme.md" end # project_blob GET /:project_id/blob/:id(.:format) blob#show {id: /[^\0]+/, project_id: /[^\/]+/} describe Projects::BlobController, 'routing' do it 'to #show' do - expect(get('/gitlab/gitlabhq/blob/master/app/models/project.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') - expect(get('/gitlab/gitlabhq/blob/master/app/models/compare.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/compare.rb') - expect(get('/gitlab/gitlabhq/blob/master/app/models/diff.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/diff.js') - expect(get('/gitlab/gitlabhq/blob/master/files.scss')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') - expect(get('/gitlab/gitlabhq/blob/master/blob/index.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/blob/index.js') - expect(get('/gitlab/gitlabhq/blob/blob/master/blob/index.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'blob/master/blob/index.js') + expect(get('/gitlab/gitlabhq/-/blob/master/app/models/project.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/-/blob/master/app/models/compare.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/compare.rb') + expect(get('/gitlab/gitlabhq/-/blob/master/app/models/diff.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/diff.js') + expect(get('/gitlab/gitlabhq/-/blob/master/files.scss')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + expect(get('/gitlab/gitlabhq/-/blob/master/blob/index.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/blob/index.js') + expect(get('/gitlab/gitlabhq/-/blob/blob/master/blob/index.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'blob/master/blob/index.js') newline_file = "new\n\nline.txt" url_encoded_newline_file = ERB::Util.url_encode(newline_file) - assert_routing({ path: "/gitlab/gitlabhq/blob/blob/master/blob/#{url_encoded_newline_file}", + assert_routing({ path: "/gitlab/gitlabhq/-/blob/blob/master/blob/#{url_encoded_newline_file}", method: :get }, { controller: 'projects/blob', action: 'show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "blob/master/blob/#{newline_file}" }) end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/blob/master/readme.md", "/gitlab/gitlabhq/-/blob/master/readme.md" end # project_tree GET /:project_id/tree/:id(.:format) tree#show {id: /[^\0]+/, project_id: /[^\/]+/} describe Projects::TreeController, 'routing' do it 'to #show' do - expect(get('/gitlab/gitlabhq/tree/master/app/models/project.rb')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') - expect(get('/gitlab/gitlabhq/tree/master/files.scss')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') - expect(get('/gitlab/gitlabhq/tree/master/tree/files')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/tree/files') - expect(get('/gitlab/gitlabhq/tree/tree/master/tree/files')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'tree/master/tree/files') + expect(get('/gitlab/gitlabhq/-/tree/master/app/models/project.rb')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/-/tree/master/files.scss')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + expect(get('/gitlab/gitlabhq/-/tree/master/tree/files')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/tree/files') + expect(get('/gitlab/gitlabhq/-/tree/tree/master/tree/files')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'tree/master/tree/files') newline_file = "new\n\nline.txt" url_encoded_newline_file = ERB::Util.url_encode(newline_file) - assert_routing({ path: "/gitlab/gitlabhq/tree/master/#{url_encoded_newline_file}", + assert_routing({ path: "/gitlab/gitlabhq/-/tree/master/#{url_encoded_newline_file}", method: :get }, { controller: 'projects/tree', action: 'show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "master/#{newline_file}" }) end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/tree/master/app", "/gitlab/gitlabhq/-/tree/master/app" end # project_find_file GET /:namespace_id/:project_id/find_file/*id(.:format) projects/find_file#show {:id=>/[^\0]+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/html/} # project_files GET /:namespace_id/:project_id/files/*id(.:format) projects/find_file#list {:id=>/(?:[^.]|\.(?!json$))+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?/json/} describe Projects::FindFileController, 'routing' do it 'to #show' do - expect(get('/gitlab/gitlabhq/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + expect(get('/gitlab/gitlabhq/-/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') newline_file = "new\n\nline.txt" url_encoded_newline_file = ERB::Util.url_encode(newline_file) - assert_routing({ path: "/gitlab/gitlabhq/find_file/#{url_encoded_newline_file}", + assert_routing({ path: "/gitlab/gitlabhq/-/find_file/#{url_encoded_newline_file}", method: :get }, { controller: 'projects/find_file', action: 'show', namespace_id: 'gitlab', project_id: 'gitlabhq', @@ -613,26 +622,29 @@ describe 'project routing' do end it 'to #list' do - expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.json') + expect(get('/gitlab/gitlabhq/-/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.json') newline_file = "new\n\nline.txt" url_encoded_newline_file = ERB::Util.url_encode(newline_file) - assert_routing({ path: "/gitlab/gitlabhq/files/#{url_encoded_newline_file}", + assert_routing({ path: "/gitlab/gitlabhq/-/files/#{url_encoded_newline_file}", method: :get }, { controller: 'projects/find_file', action: 'list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "#{newline_file}" }) end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/find_file/master", "/gitlab/gitlabhq/-/find_file/master" + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/files/master.json", "/gitlab/gitlabhq/-/files/master.json" end describe Projects::BlobController, 'routing' do it 'to #edit' do - expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to( + expect(get('/gitlab/gitlabhq/-/edit/master/app/models/project.rb')).to( route_to('projects/blob#edit', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb')) newline_file = "new\n\nline.txt" url_encoded_newline_file = ERB::Util.url_encode(newline_file) - assert_routing({ path: "/gitlab/gitlabhq/edit/master/docs/#{url_encoded_newline_file}", + assert_routing({ path: "/gitlab/gitlabhq/-/edit/master/docs/#{url_encoded_newline_file}", method: :get }, { controller: 'projects/blob', action: 'edit', namespace_id: 'gitlab', project_id: 'gitlabhq', @@ -640,13 +652,13 @@ describe 'project routing' do end it 'to #preview' do - expect(post('/gitlab/gitlabhq/preview/master/app/models/project.rb')).to( + expect(post('/gitlab/gitlabhq/-/preview/master/app/models/project.rb')).to( route_to('projects/blob#preview', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb')) newline_file = "new\n\nline.txt" url_encoded_newline_file = ERB::Util.url_encode(newline_file) - assert_routing({ path: "/gitlab/gitlabhq/edit/master/docs/#{url_encoded_newline_file}", + assert_routing({ path: "/gitlab/gitlabhq/-/edit/master/docs/#{url_encoded_newline_file}", method: :get }, { controller: 'projects/blob', action: 'edit', namespace_id: 'gitlab', project_id: 'gitlabhq', @@ -659,12 +671,14 @@ describe 'project routing' do it 'to #show' do newline_file = "new\n\nline.txt" url_encoded_newline_file = ERB::Util.url_encode(newline_file) - assert_routing({ path: "/gitlab/gitlabhq/raw/master/#{url_encoded_newline_file}", + assert_routing({ path: "/gitlab/gitlabhq/-/raw/master/#{url_encoded_newline_file}", method: :get }, { controller: 'projects/raw', action: 'show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: "master/#{newline_file}" }) end + + it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/raw/master/app/models/project.rb", "/gitlab/gitlabhq/-/raw/master/app/models/project.rb" end # project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/} diff --git a/spec/serializers/blob_entity_spec.rb b/spec/serializers/blob_entity_spec.rb index 7e3a0a87bd5..3cd967ed44c 100644 --- a/spec/serializers/blob_entity_spec.rb +++ b/spec/serializers/blob_entity_spec.rb @@ -23,7 +23,7 @@ describe BlobEntity do mode: "100644", readable_text: true, icon: "file-text-o", - url: "/#{project.full_path}/blob/master/bar/branch-test.txt" + url: "/#{project.full_path}/-/blob/master/bar/branch-test.txt" }) end end diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb index cb9325986d7..c700c150461 100644 --- a/spec/serializers/cluster_application_entity_spec.rb +++ b/spec/serializers/cluster_application_entity_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe ClusterApplicationEntity do describe '#as_json' do let(:application) { build(:clusters_applications_helm, version: '0.1.1') } + subject { described_class.new(application).as_json } it 'has name' do diff --git a/spec/serializers/cluster_entity_spec.rb b/spec/serializers/cluster_entity_spec.rb index 22b9166f922..e3826a7221d 100644 --- a/spec/serializers/cluster_entity_spec.rb +++ b/spec/serializers/cluster_entity_spec.rb @@ -39,6 +39,7 @@ describe ClusterEntity do context 'when no application has been installed' do let(:cluster) { create(:cluster) } + subject { described_class.new(cluster).as_json[:applications]} it 'contains helm as not_installable' do diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb index e4d1d7f9049..f392ecea959 100644 --- a/spec/serializers/environment_entity_spec.rb +++ b/spec/serializers/environment_entity_spec.rb @@ -9,6 +9,7 @@ describe EnvironmentEntity do end let(:environment) { create(:environment) } + subject { entity.as_json } it 'exposes latest deployment' do diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb index 198a55d5433..cae263e7fd5 100644 --- a/spec/serializers/group_child_entity_spec.rb +++ b/spec/serializers/group_child_entity_spec.rb @@ -9,6 +9,7 @@ describe GroupChildEntity do let(:user) { create(:user) } let(:request) { double('request') } let(:entity) { described_class.new(object, request: request) } + subject(:json) { entity.as_json } before do diff --git a/spec/serializers/group_child_serializer_spec.rb b/spec/serializers/group_child_serializer_spec.rb index 128e06cd172..92f083ed23e 100644 --- a/spec/serializers/group_child_serializer_spec.rb +++ b/spec/serializers/group_child_serializer_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe GroupChildSerializer do let(:request) { double('request') } let(:user) { create(:user) } + subject(:serializer) { described_class.new(current_user: user) } describe '#represent' do diff --git a/spec/serializers/merge_request_poll_widget_entity_spec.rb b/spec/serializers/merge_request_poll_widget_entity_spec.rb index ea9052b4046..0593dd527cc 100644 --- a/spec/serializers/merge_request_poll_widget_entity_spec.rb +++ b/spec/serializers/merge_request_poll_widget_entity_spec.rb @@ -60,7 +60,7 @@ describe MergeRequestPollWidgetEntity do project.add_developer(user) expect(subject[:new_blob_path]) - .to eq("/#{resource.project.full_path}/new/#{resource.source_branch}") + .to eq("/#{resource.project.full_path}/-/new/#{resource.source_branch}") end end diff --git a/spec/serializers/note_entity_spec.rb b/spec/serializers/note_entity_spec.rb index cec07cda063..f37fffb5048 100644 --- a/spec/serializers/note_entity_spec.rb +++ b/spec/serializers/note_entity_spec.rb @@ -10,6 +10,7 @@ describe NoteEntity do let(:entity) { described_class.new(note, request: request) } let(:note) { create(:note) } let(:user) { create(:user) } + subject { entity.as_json } it_behaves_like 'note entity' diff --git a/spec/serializers/project_note_entity_spec.rb b/spec/serializers/project_note_entity_spec.rb index 7c76f230781..469bf2b86de 100644 --- a/spec/serializers/project_note_entity_spec.rb +++ b/spec/serializers/project_note_entity_spec.rb @@ -10,6 +10,7 @@ describe ProjectNoteEntity do let(:entity) { described_class.new(note, request: request) } let(:note) { create(:note) } let(:user) { create(:user) } + subject { entity.as_json } it_behaves_like 'note entity' diff --git a/spec/serializers/user_entity_spec.rb b/spec/serializers/user_entity_spec.rb index edb49757b38..71107daf6ac 100644 --- a/spec/serializers/user_entity_spec.rb +++ b/spec/serializers/user_entity_spec.rb @@ -7,6 +7,7 @@ describe UserEntity do let(:entity) { described_class.new(user) } let(:user) { create(:user) } + subject { entity.as_json } it 'exposes user name and login' do diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb index a3b527e0ffe..ad4ae93a027 100644 --- a/spec/support/helpers/kubernetes_helpers.rb +++ b/spec/support/helpers/kubernetes_helpers.rb @@ -351,7 +351,7 @@ module KubernetesHelpers def kube_knative_services_body(**options) { "kind" => "List", - "items" => [knative_07_service(options)] + "items" => [knative_09_service(options)] } end @@ -539,6 +539,58 @@ module KubernetesHelpers "podcount" => 0 } end + # noinspection RubyStringKeysInHashInspection + def knative_09_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') + { "apiVersion" => "serving.knative.dev/v1alpha1", + "kind" => "Service", + "metadata" => + { "annotations" => + { "serving.knative.dev/creator" => "system:serviceaccount:#{namespace}:#{namespace}-service-account", + "serving.knative.dev/lastModifier" => "system:serviceaccount:#{namespace}:#{namespace}-service-account" }, + "creationTimestamp" => "2019-10-22T21:19:13Z", + "generation" => 1, + "labels" => { "service" => name }, + "name" => name, + "namespace" => namespace, + "resourceVersion" => "289726", + "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}", + "uid" => "988349fa-f511-11e9-9ea1-42010a80005e" }, + "spec" => { + "template" => { + "metadata" => { + "annotations" => { "Description" => description }, + "creationTimestamp" => "2019-10-22T21:19:12Z", + "labels" => { "service" => name } + }, + "spec" => { + "containers" => [{ + "env" => + [{ "name" => "timestamp", "value" => "2019-10-22 21:19:12" }], + "image" => "image_name", + "name" => "user-container", + "resources" => {} + }], + "timeoutSeconds" => 300 + } + }, + "traffic" => [{ "latestRevision" => true, "percent" => 100 }] + }, + "status" => + { "address" => { "url" => "http://#{name}.#{namespace}.svc.cluster.local" }, + "conditions" => + [{ "lastTransitionTime" => "2019-10-22T21:20:15Z", "status" => "True", "type" => "ConfigurationsReady" }, + { "lastTransitionTime" => "2019-10-22T21:20:15Z", "status" => "True", "type" => "Ready" }, + { "lastTransitionTime" => "2019-10-22T21:20:15Z", "status" => "True", "type" => "RoutesReady" }], + "latestCreatedRevisionName" => "#{name}-92tsj", + "latestReadyRevisionName" => "#{name}-92tsj", + "observedGeneration" => 1, + "traffic" => [{ "latestRevision" => true, "percent" => 100, "revisionName" => "#{name}-92tsj" }], + "url" => "http://#{name}.#{namespace}.#{domain}" }, + "environment_scope" => environment, + "cluster_id" => 5, + "podcount" => 0 } + end + # noinspection RubyStringKeysInHashInspection def knative_05_service(name: 'kubetest', namespace: 'default', domain: 'example.com', description: 'a knative service', environment: 'production') { "apiVersion" => "serving.knative.dev/v1alpha1", diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index 37f8bc14e20..1831fc10628 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -152,6 +152,7 @@ end RSpec.shared_examples 'project policies as developer' do context 'abilities for non-public projects' do let(:project) { create(:project, namespace: owner.namespace) } + subject { described_class.new(developer, project) } it do diff --git a/spec/uploaders/file_mover_spec.rb b/spec/uploaders/file_mover_spec.rb index c4a315b3fd6..9f053f03b0e 100644 --- a/spec/uploaders/file_mover_spec.rb +++ b/spec/uploaders/file_mover_spec.rb @@ -22,6 +22,7 @@ describe FileMover do end let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') } + subject { described_class.new(temp_file_path, from_model: user, to_model: snippet).execute } describe '#execute' do diff --git a/spec/validators/addressable_url_validator_spec.rb b/spec/validators/addressable_url_validator_spec.rb index 6927a1f67a1..e8a44f7a12a 100644 --- a/spec/validators/addressable_url_validator_spec.rb +++ b/spec/validators/addressable_url_validator_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe AddressableUrlValidator do let!(:badge) { build(:badge, link_url: 'http://www.example.com') } + subject { validator.validate(badge) } include_examples 'url validator examples', described_class::DEFAULT_OPTIONS[:schemes] diff --git a/spec/validators/devise_email_validator_spec.rb b/spec/validators/devise_email_validator_spec.rb index 7860b659bd3..1dbf3f66cfd 100644 --- a/spec/validators/devise_email_validator_spec.rb +++ b/spec/validators/devise_email_validator_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe DeviseEmailValidator do let!(:user) { build(:user, public_email: 'test@example.com') } + subject { validator.validate(user) } describe 'validations' do diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb index 474a294ce0d..98040da9d2c 100644 --- a/spec/views/help/index.html.haml_spec.rb +++ b/spec/views/help/index.html.haml_spec.rb @@ -40,7 +40,7 @@ describe 'help/index' do render expect(rendered).to match '8.0.2' - expect(rendered).to have_link('abcdefg', href: %r{https://gitlab.com/gitlab-org/(gitlab|gitlab-foss)/commits/abcdefg}) + expect(rendered).to have_link('abcdefg', href: %r{https://gitlab.com/gitlab-org/(gitlab|gitlab-foss)/-/commits/abcdefg}) end end end diff --git a/spec/workers/expire_job_cache_worker_spec.rb b/spec/workers/expire_job_cache_worker_spec.rb index 6ac285ca944..eeab304d926 100644 --- a/spec/workers/expire_job_cache_worker_spec.rb +++ b/spec/workers/expire_job_cache_worker_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe ExpireJobCacheWorker do set(:pipeline) { create(:ci_empty_pipeline) } let(:project) { pipeline.project } + subject { described_class.new } describe '#perform' do diff --git a/spec/workers/expire_pipeline_cache_worker_spec.rb b/spec/workers/expire_pipeline_cache_worker_spec.rb index 5652f5e8685..e162a227a66 100644 --- a/spec/workers/expire_pipeline_cache_worker_spec.rb +++ b/spec/workers/expire_pipeline_cache_worker_spec.rb @@ -6,6 +6,7 @@ describe ExpirePipelineCacheWorker do let(:user) { create(:user) } let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } + subject { described_class.new } describe '#perform' do diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb index 0729c5f9ffb..14a9f0ed8b7 100644 --- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb +++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe MailScheduler::NotificationServiceWorker do let(:worker) { described_class.new } let(:method) { 'new_key' } + set(:key) { create(:personal_key) } def serialize(*args) diff --git a/spec/workers/repository_check/batch_worker_spec.rb b/spec/workers/repository_check/batch_worker_spec.rb index 051c6a5d141..12e7d1879d0 100644 --- a/spec/workers/repository_check/batch_worker_spec.rb +++ b/spec/workers/repository_check/batch_worker_spec.rb @@ -4,6 +4,7 @@ require 'spec_helper' describe RepositoryCheck::BatchWorker do let(:shard_name) { 'default' } + subject { described_class.new } before do diff --git a/yarn.lock b/yarn.lock index fbc6733fea4..aa45c5dc6b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -278,6 +278,14 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" +"@babel/plugin-proposal-optional-chaining@^7.7.5": + version "7.7.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.7.5.tgz#f0835f044cef85b31071a924010a2a390add11d4" + integrity sha512-sOwFqT8JSchtJeDD+CjmWCaiFoLxY4Ps7NjvwHC/U7l4e9i5pTRNt8nDMIFSOUL+ncFbYSwruHM8WknYItWdXw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-chaining" "^7.7.4" + "@babel/plugin-proposal-private-methods@^7.6.0": version "7.6.0" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.6.0.tgz#19ddc493c7b5d47afdd4291e740c609a83c9fae4" @@ -337,6 +345,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-syntax-optional-chaining@^7.7.4": + version "7.7.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.7.4.tgz#c91fdde6de85d2eb8906daea7b21944c3610c901" + integrity sha512-2MqYD5WjZSbJdUagnJvIdSfkb/ucOC9/1fRJxm7GAxY6YQLWlUvkfxoNbUPcPLHJyetKUDQ4+yyuUyAoc0HriA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-transform-arrow-functions@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550" -- cgit v1.2.3