diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-09 09:08:00 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-12-09 09:08:00 +0000 |
commit | aa10b541b6a3fbc7fa712abcc59d073fc8dc620a (patch) | |
tree | cde565fd177bcfd44ea892d6ebbdfcab4fb7cd35 | |
parent | a5ba0dd8c6a4221f010a1fabb50aa1239314e3ef (diff) | |
download | gitlab-ce-aa10b541b6a3fbc7fa712abcc59d073fc8dc620a.tar.gz |
Add latest changes from gitlab-org/gitlab@master
28 files changed, 522 insertions, 367 deletions
diff --git a/app/assets/javascripts/pages/dashboard/merge_requests/index.js b/app/assets/javascripts/pages/dashboard/merge_requests/index.js index ff758fcb4fe..24d7b592948 100644 --- a/app/assets/javascripts/pages/dashboard/merge_requests/index.js +++ b/app/assets/javascripts/pages/dashboard/merge_requests/index.js @@ -1,6 +1,6 @@ +import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests'; import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; -import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; diff --git a/app/assets/javascripts/pages/groups/group_members/index/index.js b/app/assets/javascripts/pages/groups/group_members/index/index.js index e4f4c3b574e..e77a7cf8e0a 100644 --- a/app/assets/javascripts/pages/groups/group_members/index/index.js +++ b/app/assets/javascripts/pages/groups/group_members/index/index.js @@ -1,7 +1,7 @@ /* eslint-disable no-new */ -import memberExpirationDate from '~/member_expiration_date'; import Members from 'ee_else_ce/members'; +import memberExpirationDate from '~/member_expiration_date'; import UsersSelect from '~/users_select'; document.addEventListener('DOMContentLoaded', () => { diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js index 090e1a2bc6d..4f15f5ec58c 100644 --- a/app/assets/javascripts/pages/groups/issues/index.js +++ b/app/assets/javascripts/pages/groups/issues/index.js @@ -1,9 +1,9 @@ +import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; import initIssuablesList from '~/issuables_list'; import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar'; import { FILTERED_SEARCH } from '~/pages/constants'; -import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; import initManualOrdering from '~/manual_ordering'; const ISSUE_BULK_UPDATE_PREFIX = 'issue_'; diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js index 7520cfb6da0..13c5c350c24 100644 --- a/app/assets/javascripts/pages/groups/merge_requests/index.js +++ b/app/assets/javascripts/pages/groups/merge_requests/index.js @@ -1,8 +1,8 @@ +import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests'; import projectSelect from '~/project_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; -import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests'; import { FILTERED_SEARCH } from '~/pages/constants'; const ISSUABLE_BULK_UPDATE_PREFIX = 'merge_request_'; diff --git a/app/assets/javascripts/pages/groups/new/group_path_validator.js b/app/assets/javascripts/pages/groups/new/group_path_validator.js index 2021ad117e8..f1e7ff87e5a 100644 --- a/app/assets/javascripts/pages/groups/new/group_path_validator.js +++ b/app/assets/javascripts/pages/groups/new/group_path_validator.js @@ -1,6 +1,6 @@ +import _ from 'underscore'; import InputValidator from '~/validators/input_validator'; -import _ from 'underscore'; import fetchGroupPathAvailability from './fetch_group_path_availability'; import flash from '~/flash'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js index 13cb0d6f74b..ad003181728 100644 --- a/app/assets/javascripts/pages/profiles/show/index.js +++ b/app/assets/javascripts/pages/profiles/show/index.js @@ -1,7 +1,7 @@ import $ from 'jquery'; -import createFlash from '~/flash'; import GfmAutoComplete from 'ee_else_ce/gfm_auto_complete'; import emojiRegex from 'emoji-regex'; +import createFlash from '~/flash'; import EmojiMenu from './emoji_menu'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js index 96e47187fed..34c7ee2e603 100644 --- a/app/assets/javascripts/pages/projects/issues/form.js +++ b/app/assets/javascripts/pages/projects/issues/form.js @@ -1,8 +1,8 @@ /* eslint-disable no-new */ import $ from 'jquery'; -import GLForm from '~/gl_form'; import IssuableForm from 'ee_else_ce/issuable_form'; +import GLForm from '~/gl_form'; import LabelsSelect from '~/labels_select'; import MilestoneSelect from '~/milestone_select'; import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js index c73ebb31eb3..bf54ca972b2 100644 --- a/app/assets/javascripts/pages/projects/issues/index/index.js +++ b/app/assets/javascripts/pages/projects/issues/index/index.js @@ -1,12 +1,12 @@ /* eslint-disable no-new */ +import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; import IssuableIndex from '~/issuable_index'; import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import UsersSelect from '~/users_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; -import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys'; import initManualOrdering from '~/manual_ordering'; document.addEventListener('DOMContentLoaded', () => { diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js index 0bcca22e40f..8f93cbb2a42 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js @@ -1,8 +1,8 @@ +import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests'; import IssuableIndex from '~/issuable_index'; import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import UsersSelect from '~/users_select'; import initFilteredSearch from '~/pages/search/init_filtered_search'; -import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests'; import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys'; import { FILTERED_SEARCH } from '~/pages/constants'; import { ISSUABLE_INDEX } from '~/pages/projects/constants'; diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js index e51ab79a51d..76d72efb11b 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js +++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js @@ -1,10 +1,10 @@ /* eslint-disable no-new */ import $ from 'jquery'; +import IssuableForm from 'ee_else_ce/issuable_form'; import Diff from '~/diff'; import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation'; import GLForm from '~/gl_form'; -import IssuableForm from 'ee_else_ce/issuable_form'; import LabelsSelect from '~/labels_select'; import MilestoneSelect from '~/milestone_select'; import IssuableTemplateSelectors from '~/templates/issuable_template_selectors'; diff --git a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue index b0c9ca3ec0d..2176309ac84 100644 --- a/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue +++ b/app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue @@ -1,7 +1,7 @@ <script> import _ from 'underscore'; -import { s__, sprintf } from '~/locale'; import { GlModal, GlModalDirective } from '@gitlab/ui'; +import { s__, sprintf } from '~/locale'; export default { components: { diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js index 36d1e773134..25be71d9ed4 100644 --- a/app/assets/javascripts/pages/sessions/new/username_validator.js +++ b/app/assets/javascripts/pages/sessions/new/username_validator.js @@ -1,6 +1,6 @@ +import _ from 'underscore'; import InputValidator from '~/validators/input_validator'; -import _ from 'underscore'; import axios from '~/lib/utils/axios_utils'; import flash from '~/flash'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/pages/users/index.js b/app/assets/javascripts/pages/users/index.js index a191df00dfa..cfc6dc61f9f 100644 --- a/app/assets/javascripts/pages/users/index.js +++ b/app/assets/javascripts/pages/users/index.js @@ -1,6 +1,6 @@ import $ from 'jquery'; -import UserCallout from '~/user_callout'; import Cookies from 'js-cookie'; +import UserCallout from '~/user_callout'; import UserTabs from './user_tabs'; function initUserProfile(action) { diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb index 722fc30b3ff..f1e591ea1ec 100644 --- a/app/controllers/projects/pages_controller.rb +++ b/app/controllers/projects/pages_controller.rb @@ -15,8 +15,7 @@ class Projects::PagesController < Projects::ApplicationController # rubocop: enable CodeReuse/ActiveRecord def destroy - project.remove_pages - project.pages_domains.destroy_all # rubocop: disable DestroyAll + ::Pages::DeleteService.new(@project, current_user).execute respond_to do |format| format.html do diff --git a/app/services/pages/delete_service.rb b/app/services/pages/delete_service.rb new file mode 100644 index 00000000000..d4de6bb750d --- /dev/null +++ b/app/services/pages/delete_service.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Pages + class DeleteService < BaseService + def execute + project.remove_pages + project.pages_domains.destroy_all # rubocop: disable DestroyAll + end + end +end diff --git a/changelogs/unreleased/33880-add-api-to-disable-pages.yml b/changelogs/unreleased/33880-add-api-to-disable-pages.yml new file mode 100644 index 00000000000..5be872addd0 --- /dev/null +++ b/changelogs/unreleased/33880-add-api-to-disable-pages.yml @@ -0,0 +1,5 @@ +--- +title: Add API endpoint to unpublish GitLab Pages +merge_request: 19781 +author: +type: added diff --git a/doc/api/pages.md b/doc/api/pages.md new file mode 100644 index 00000000000..0babca61650 --- /dev/null +++ b/doc/api/pages.md @@ -0,0 +1,21 @@ +# Pages API + +Endpoints for managing [GitLab Pages](https://about.gitlab.com/product/pages/). + +The GitLab Pages feature must be enabled to use these endpoints. Find out more about [administering](../administration/pages/index.md) and [using](../user/project/pages/index.md) the feature. + +## Unpublish pages + +Remove pages. The user must have admin priviledges. + +```text +DELETE /projects/:id/pages +``` + +| Attribute | Type | Required | Description | +| --------- | -------------- | -------- | ---------------------------------------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | + +```bash +curl --request 'DELETE' --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/2/pages +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 6949cfa8e49..56eccb036b6 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -137,6 +137,7 @@ module API mount ::API::Discussions mount ::API::ResourceLabelEvents mount ::API::NotificationSettings + mount ::API::Pages mount ::API::PagesDomains mount ::API::Pipelines mount ::API::PipelineSchedules diff --git a/lib/api/pages.rb b/lib/api/pages.rb new file mode 100644 index 00000000000..e049493b10d --- /dev/null +++ b/lib/api/pages.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module API + class Pages < Grape::API + before do + require_pages_config_enabled! + authenticated_with_full_private_access! + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do + desc 'Unpublish pages' do + detail 'This feature was introduced in GitLab 12.6' + end + delete ':id/pages' do + authorize! :remove_pages, user_project + + status 204 + + ::Pages::DeleteService.new(user_project, current_user).execute + end + end + end +end diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb index ae20ca1a98e..3bb62703290 100644 --- a/qa/qa/resource/base.rb +++ b/qa/qa/resource/base.rb @@ -64,7 +64,7 @@ module QA end def visit! - Runtime::Logger.debug(%Q[Visiting #{self.class.name} at "#{web_url}"]) if Runtime::Env.debug? + Runtime::Logger.debug(%Q[Visiting #{self.class.name} at "#{web_url}"]) Support::Retrier.retry_until do visit(web_url) diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index bdbe5f3ef51..9544a3e80b3 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -79,7 +79,7 @@ module QA def api_delete super - QA::Runtime::Logger.debug("Deleted user '#{username}'") if Runtime::Env.debug? + QA::Runtime::Logger.debug("Deleted user '#{username}'") end def api_delete_path diff --git a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb index 57063e91532..90290b4f2a0 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb @@ -24,7 +24,7 @@ module QA # Wait for Action Mailer to deliver messages mailhog_json = Support::Retrier.retry_until(sleep_interval: 1) do - Runtime::Logger.debug(%Q[retrieving "#{QA::Runtime::MailHog.api_messages_url}"]) if Runtime::Env.debug? + Runtime::Logger.debug(%Q[retrieving "#{QA::Runtime::MailHog.api_messages_url}"]) mailhog_response = get QA::Runtime::MailHog.api_messages_url diff --git a/qa/qa/vendor/saml_idp/page/login.rb b/qa/qa/vendor/saml_idp/page/login.rb index 9ebcabe15fc..041b4a0feee 100644 --- a/qa/qa/vendor/saml_idp/page/login.rb +++ b/qa/qa/vendor/saml_idp/page/login.rb @@ -8,7 +8,7 @@ module QA module Page class Login < Page::Base def login(username, password) - QA::Runtime::Logger.debug("Logging into SAMLIdp with username: #{username} and password:#{password}") if QA::Runtime::Env.debug? + QA::Runtime::Logger.debug("Logging into SAMLIdp with username: #{username} and password:#{password}") fill_in 'username', with: username fill_in 'password', with: password @@ -21,7 +21,7 @@ module QA def login_required? login_required = page.has_text?('Enter your username and password') - QA::Runtime::Logger.debug("login_required: #{login_required}") if QA::Runtime::Env.debug? + QA::Runtime::Logger.debug("login_required: #{login_required}") login_required end end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index 36a1b901c65..3a26ed89e9c 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -20,7 +20,7 @@ RSpec.configure do |config| QA::Specs::Helpers::Quarantine.configure_rspec config.before do |example| - QA::Runtime::Logger.debug("\nStarting test: #{example.full_description}\n") if QA::Runtime::Env.debug? + QA::Runtime::Logger.debug("\nStarting test: #{example.full_description}\n") end config.after(:context) do diff --git a/spec/frontend/monitoring/utils_spec.js b/spec/frontend/monitoring/utils_spec.js index 1e8d5753885..9b1a331e3b5 100644 --- a/spec/frontend/monitoring/utils_spec.js +++ b/spec/frontend/monitoring/utils_spec.js @@ -1,6 +1,12 @@ import * as monitoringUtils from '~/monitoring/utils'; +import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; +import { + graphDataPrometheusQuery, + graphDataPrometheusQueryRange, + anomalyMockGraphData, +} from './mock_data'; -describe('Snowplow Events', () => { +describe('monitoring/utils', () => { const generatedLink = 'http://chart.link.com'; const chartTitle = 'Some metric chart'; @@ -51,4 +57,338 @@ describe('Snowplow Events', () => { }); }); }); + + describe('getTimeDiff', () => { + function secondsBetween({ start, end }) { + return (new Date(end) - new Date(start)) / 1000; + } + + function minutesBetween(timeRange) { + return secondsBetween(timeRange) / 60; + } + + function hoursBetween(timeRange) { + return minutesBetween(timeRange) / 60; + } + + it('defaults to an 8 hour (28800s) difference', () => { + const params = monitoringUtils.getTimeDiff(); + + expect(hoursBetween(params)).toEqual(8); + }); + + it('accepts time window as an argument', () => { + const params = monitoringUtils.getTimeDiff('thirtyMinutes'); + + expect(minutesBetween(params)).toEqual(30); + }); + + it('returns a value for every defined time window', () => { + const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours'); + + nonDefaultWindows.forEach(timeWindow => { + const params = monitoringUtils.getTimeDiff(timeWindow); + + // Ensure we're not returning the default + expect(hoursBetween(params)).not.toEqual(8); + }); + }); + }); + + describe('getTimeWindow', () => { + [ + { + args: [ + { + start: '2019-10-01T18:27:47.000Z', + end: '2019-10-01T21:27:47.000Z', + }, + ], + expected: timeWindowsKeyNames.threeHours, + }, + { + args: [ + { + start: '2019-10-01T28:27:47.000Z', + end: '2019-10-01T21:27:47.000Z', + }, + ], + expected: null, + }, + { + args: [ + { + start: '', + end: '', + }, + ], + expected: null, + }, + { + args: [ + { + start: null, + end: null, + }, + ], + expected: null, + }, + { + args: [{}], + expected: null, + }, + ].forEach(({ args, expected }) => { + it(`returns "${expected}" with args=${JSON.stringify(args)}`, () => { + expect(monitoringUtils.getTimeWindow(...args)).toEqual(expected); + }); + }); + }); + + describe('graphDataValidatorForValues', () => { + /* + * When dealing with a metric using the query format, e.g. + * query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024' + * the validator will look for the `value` key instead of `values` + */ + it('validates data with the query format', () => { + const validGraphData = monitoringUtils.graphDataValidatorForValues( + true, + graphDataPrometheusQuery, + ); + + expect(validGraphData).toBe(true); + }); + + /* + * When dealing with a metric using the query?range format, e.g. + * query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', + * the validator will look for the `values` key instead of `value` + */ + it('validates data with the query_range format', () => { + const validGraphData = monitoringUtils.graphDataValidatorForValues( + false, + graphDataPrometheusQueryRange, + ); + + expect(validGraphData).toBe(true); + }); + }); + + describe('stringToISODate', () => { + ['', 'null', undefined, 'abc'].forEach(input => { + it(`throws error for invalid input like ${input}`, done => { + try { + monitoringUtils.stringToISODate(input); + } catch (e) { + expect(e).toBeDefined(); + done(); + } + }); + }); + [ + { + input: '2019-09-09 01:01:01', + output: '2019-09-09T01:01:01Z', + }, + { + input: '2019-09-09 00:00:00', + output: '2019-09-09T00:00:00Z', + }, + { + input: '2019-09-09 23:59:59', + output: '2019-09-09T23:59:59Z', + }, + { + input: '2019-09-09', + output: '2019-09-09T00:00:00Z', + }, + ].forEach(({ input, output }) => { + it(`returns ${output} from ${input}`, () => { + expect(monitoringUtils.stringToISODate(input)).toBe(output); + }); + }); + }); + + describe('ISODateToString', () => { + [ + { + input: new Date('2019-09-09T00:00:00.000Z'), + output: '2019-09-09 00:00:00', + }, + { + input: new Date('2019-09-09T07:00:00.000Z'), + output: '2019-09-09 07:00:00', + }, + ].forEach(({ input, output }) => { + it(`ISODateToString return ${output} for ${input}`, () => { + expect(monitoringUtils.ISODateToString(input)).toBe(output); + }); + }); + }); + + describe('truncateZerosInDateTime', () => { + [ + { + input: '', + output: '', + }, + { + input: '2019-10-10', + output: '2019-10-10', + }, + { + input: '2019-10-10 00:00:01', + output: '2019-10-10 00:00:01', + }, + { + input: '2019-10-10 00:00:00', + output: '2019-10-10', + }, + ].forEach(({ input, output }) => { + it(`truncateZerosInDateTime return ${output} for ${input}`, () => { + expect(monitoringUtils.truncateZerosInDateTime(input)).toBe(output); + }); + }); + }); + + describe('isValidDate', () => { + [ + { + input: '2019-09-09T00:00:00.000Z', + output: true, + }, + { + input: '2019-09-09T000:00.000Z', + output: false, + }, + { + input: 'a2019-09-09T000:00.000Z', + output: false, + }, + { + input: '2019-09-09T', + output: false, + }, + { + input: '2019-09-09', + output: true, + }, + { + input: '2019-9-9', + output: true, + }, + { + input: '2019-9-', + output: true, + }, + { + input: '2019--', + output: false, + }, + { + input: '2019', + output: true, + }, + { + input: '', + output: false, + }, + { + input: null, + output: false, + }, + ].forEach(({ input, output }) => { + it(`isValidDate return ${output} for ${input}`, () => { + expect(monitoringUtils.isValidDate(input)).toBe(output); + }); + }); + }); + + describe('isDateTimePickerInputValid', () => { + [ + { + input: null, + output: false, + }, + { + input: '', + output: false, + }, + { + input: 'xxxx-xx-xx', + output: false, + }, + { + input: '9999-99-19', + output: false, + }, + { + input: '2019-19-23', + output: false, + }, + { + input: '2019-09-23', + output: true, + }, + { + input: '2019-09-23 x', + output: false, + }, + { + input: '2019-09-29 0:0:0', + output: false, + }, + { + input: '2019-09-29 00:00:00', + output: true, + }, + { + input: '2019-09-29 24:24:24', + output: false, + }, + { + input: '2019-09-29 23:24:24', + output: true, + }, + { + input: '2019-09-29 23:24:24 ', + output: false, + }, + ].forEach(({ input, output }) => { + it(`returns ${output} for ${input}`, () => { + expect(monitoringUtils.isDateTimePickerInputValid(input)).toBe(output); + }); + }); + }); + + describe('graphDataValidatorForAnomalyValues', () => { + let oneMetric; + let threeMetrics; + let fourMetrics; + beforeEach(() => { + oneMetric = graphDataPrometheusQuery; + threeMetrics = anomalyMockGraphData; + + const metrics = [...threeMetrics.metrics]; + metrics.push(threeMetrics.metrics[0]); + fourMetrics = { + ...anomalyMockGraphData, + metrics, + }; + }); + /* + * Anomaly charts can accept results for exactly 3 metrics, + */ + it('validates passes with the right query format', () => { + expect(monitoringUtils.graphDataValidatorForAnomalyValues(threeMetrics)).toBe(true); + }); + + it('validation fails for wrong format, 1 metric', () => { + expect(monitoringUtils.graphDataValidatorForAnomalyValues(oneMetric)).toBe(false); + }); + + it('validation fails for wrong format, more than 3 metrics', () => { + expect(monitoringUtils.graphDataValidatorForAnomalyValues(fourMetrics)).toBe(false); + }); + }); }); diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js deleted file mode 100644 index 3459b44c7ec..00000000000 --- a/spec/javascripts/monitoring/utils_spec.js +++ /dev/null @@ -1,345 +0,0 @@ -import { - getTimeDiff, - getTimeWindow, - graphDataValidatorForValues, - isDateTimePickerInputValid, - truncateZerosInDateTime, - stringToISODate, - ISODateToString, - isValidDate, - graphDataValidatorForAnomalyValues, -} from '~/monitoring/utils'; -import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants'; -import { - graphDataPrometheusQuery, - graphDataPrometheusQueryRange, - anomalyMockGraphData, -} from './mock_data'; - -describe('getTimeDiff', () => { - function secondsBetween({ start, end }) { - return (new Date(end) - new Date(start)) / 1000; - } - - function minutesBetween(timeRange) { - return secondsBetween(timeRange) / 60; - } - - function hoursBetween(timeRange) { - return minutesBetween(timeRange) / 60; - } - - it('defaults to an 8 hour (28800s) difference', () => { - const params = getTimeDiff(); - - expect(hoursBetween(params)).toEqual(8); - }); - - it('accepts time window as an argument', () => { - const params = getTimeDiff('thirtyMinutes'); - - expect(minutesBetween(params)).toEqual(30); - }); - - it('returns a value for every defined time window', () => { - const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours'); - - nonDefaultWindows.forEach(timeWindow => { - const params = getTimeDiff(timeWindow); - - // Ensure we're not returning the default - expect(hoursBetween(params)).not.toEqual(8); - }); - }); -}); - -describe('getTimeWindow', () => { - [ - { - args: [ - { - start: '2019-10-01T18:27:47.000Z', - end: '2019-10-01T21:27:47.000Z', - }, - ], - expected: timeWindowsKeyNames.threeHours, - }, - { - args: [ - { - start: '2019-10-01T28:27:47.000Z', - end: '2019-10-01T21:27:47.000Z', - }, - ], - expected: null, - }, - { - args: [ - { - start: '', - end: '', - }, - ], - expected: null, - }, - { - args: [ - { - start: null, - end: null, - }, - ], - expected: null, - }, - { - args: [{}], - expected: null, - }, - ].forEach(({ args, expected }) => { - it(`returns "${expected}" with args=${JSON.stringify(args)}`, () => { - expect(getTimeWindow(...args)).toEqual(expected); - }); - }); -}); - -describe('graphDataValidatorForValues', () => { - /* - * When dealing with a metric using the query format, e.g. - * query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024' - * the validator will look for the `value` key instead of `values` - */ - it('validates data with the query format', () => { - const validGraphData = graphDataValidatorForValues(true, graphDataPrometheusQuery); - - expect(validGraphData).toBe(true); - }); - - /* - * When dealing with a metric using the query?range format, e.g. - * query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024', - * the validator will look for the `values` key instead of `value` - */ - it('validates data with the query_range format', () => { - const validGraphData = graphDataValidatorForValues(false, graphDataPrometheusQueryRange); - - expect(validGraphData).toBe(true); - }); -}); - -describe('stringToISODate', () => { - ['', 'null', undefined, 'abc'].forEach(input => { - it(`throws error for invalid input like ${input}`, done => { - try { - stringToISODate(input); - } catch (e) { - expect(e).toBeDefined(); - done(); - } - }); - }); - [ - { - input: '2019-09-09 01:01:01', - output: '2019-09-09T01:01:01Z', - }, - { - input: '2019-09-09 00:00:00', - output: '2019-09-09T00:00:00Z', - }, - { - input: '2019-09-09 23:59:59', - output: '2019-09-09T23:59:59Z', - }, - { - input: '2019-09-09', - output: '2019-09-09T00:00:00Z', - }, - ].forEach(({ input, output }) => { - it(`returns ${output} from ${input}`, () => { - expect(stringToISODate(input)).toBe(output); - }); - }); -}); - -describe('ISODateToString', () => { - [ - { - input: new Date('2019-09-09T00:00:00.000Z'), - output: '2019-09-09 00:00:00', - }, - { - input: new Date('2019-09-09T07:00:00.000Z'), - output: '2019-09-09 07:00:00', - }, - ].forEach(({ input, output }) => { - it(`ISODateToString return ${output} for ${input}`, () => { - expect(ISODateToString(input)).toBe(output); - }); - }); -}); - -describe('truncateZerosInDateTime', () => { - [ - { - input: '', - output: '', - }, - { - input: '2019-10-10', - output: '2019-10-10', - }, - { - input: '2019-10-10 00:00:01', - output: '2019-10-10 00:00:01', - }, - { - input: '2019-10-10 00:00:00', - output: '2019-10-10', - }, - ].forEach(({ input, output }) => { - it(`truncateZerosInDateTime return ${output} for ${input}`, () => { - expect(truncateZerosInDateTime(input)).toBe(output); - }); - }); -}); - -describe('isValidDate', () => { - [ - { - input: '2019-09-09T00:00:00.000Z', - output: true, - }, - { - input: '2019-09-09T000:00.000Z', - output: false, - }, - { - input: 'a2019-09-09T000:00.000Z', - output: false, - }, - { - input: '2019-09-09T', - output: false, - }, - { - input: '2019-09-09', - output: true, - }, - { - input: '2019-9-9', - output: true, - }, - { - input: '2019-9-', - output: true, - }, - { - input: '2019--', - output: false, - }, - { - input: '2019', - output: true, - }, - { - input: '', - output: false, - }, - { - input: null, - output: false, - }, - ].forEach(({ input, output }) => { - it(`isValidDate return ${output} for ${input}`, () => { - expect(isValidDate(input)).toBe(output); - }); - }); -}); - -describe('isDateTimePickerInputValid', () => { - [ - { - input: null, - output: false, - }, - { - input: '', - output: false, - }, - { - input: 'xxxx-xx-xx', - output: false, - }, - { - input: '9999-99-19', - output: false, - }, - { - input: '2019-19-23', - output: false, - }, - { - input: '2019-09-23', - output: true, - }, - { - input: '2019-09-23 x', - output: false, - }, - { - input: '2019-09-29 0:0:0', - output: false, - }, - { - input: '2019-09-29 00:00:00', - output: true, - }, - { - input: '2019-09-29 24:24:24', - output: false, - }, - { - input: '2019-09-29 23:24:24', - output: true, - }, - { - input: '2019-09-29 23:24:24 ', - output: false, - }, - ].forEach(({ input, output }) => { - it(`returns ${output} for ${input}`, () => { - expect(isDateTimePickerInputValid(input)).toBe(output); - }); - }); -}); - -describe('graphDataValidatorForAnomalyValues', () => { - let oneMetric; - let threeMetrics; - let fourMetrics; - beforeEach(() => { - oneMetric = graphDataPrometheusQuery; - threeMetrics = anomalyMockGraphData; - - const metrics = [...threeMetrics.metrics]; - metrics.push(threeMetrics.metrics[0]); - fourMetrics = { - ...anomalyMockGraphData, - metrics, - }; - }); - /* - * Anomaly charts can accept results for exactly 3 metrics, - */ - it('validates passes with the right query format', () => { - expect(graphDataValidatorForAnomalyValues(threeMetrics)).toBe(true); - }); - - it('validation fails for wrong format, 1 metric', () => { - expect(graphDataValidatorForAnomalyValues(oneMetric)).toBe(false); - }); - - it('validation fails for wrong format, more than 3 metrics', () => { - expect(graphDataValidatorForAnomalyValues(fourMetrics)).toBe(false); - }); -}); diff --git a/spec/requests/api/pages/pages_spec.rb b/spec/requests/api/pages/pages_spec.rb new file mode 100644 index 00000000000..2085c509eff --- /dev/null +++ b/spec/requests/api/pages/pages_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe API::Pages do + let_it_be(:project) { create(:project, path: 'my.project', pages_https_only: false) } + let_it_be(:admin) { create(:admin) } + let_it_be(:user) { create(:user) } + + before do + project.add_maintainer(user) + project.mark_pages_as_deployed + end + + describe 'DELETE /projects/:id/pages' do + context 'when Pages is disabled' do + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(false) + end + + it_behaves_like '404 response' do + let(:request) { delete api("/projects/#{project.id}/pages", admin)} + end + end + + context 'when Pages is enabled' do + before do + allow(Gitlab.config.pages).to receive(:enabled).and_return(true) + end + + context 'when Pages are deployed' do + it 'returns 204' do + delete api("/projects/#{project.id}/pages", admin) + + expect(response).to have_gitlab_http_status(204) + end + + it 'removes the pages' do + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return true + expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, project.namespace.full_path, anything) + + delete api("/projects/#{project.id}/pages", admin ) + + expect(project.reload.pages_metadatum.deployed?).to be(false) + end + end + + context 'when pages are not deployed' do + before do + project.mark_pages_as_not_deployed + end + + it 'returns 204' do + delete api("/projects/#{project.id}/pages", admin) + + expect(response).to have_gitlab_http_status(204) + end + end + + context 'when there is no project' do + it 'returns 404' do + id = -1 + + delete api("/projects/#{id}/pages", admin) + + expect(response).to have_gitlab_http_status(404) + end + end + end + end +end diff --git a/spec/services/pages/delete_services_spec.rb b/spec/services/pages/delete_services_spec.rb new file mode 100644 index 00000000000..c253f294e80 --- /dev/null +++ b/spec/services/pages/delete_services_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Pages::DeleteService do + let_it_be(:project) { create(:project, path: "my.project")} + let_it_be(:admin) { create(:admin) } + let_it_be(:domain) { create(:pages_domain, project: project) } + let_it_be(:service) { described_class.new(project, admin)} + + it 'deletes published pages' do + expect_any_instance_of(Gitlab::PagesTransfer).to receive(:rename_project).and_return true + expect(PagesWorker).to receive(:perform_in).with(5.minutes, :remove, project.namespace.full_path, anything) + + service.execute + + expect(project.reload.pages_metadatum.deployed?).to be(false) + end + + it 'deletes all domains' do + expect(project.pages_domains.count).to be 1 + + service.execute + + expect(project.reload.pages_domains.count).to be 0 + end +end |