summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/pages/dashboard/merge_requests/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/group_members/index/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/new/group_path_validator.js2
-rw-r--r--app/assets/javascripts/pages/profiles/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js2
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js2
-rw-r--r--app/assets/javascripts/pages/projects/wikis/components/delete_wiki_modal.vue2
-rw-r--r--app/assets/javascripts/pages/sessions/new/username_validator.js2
-rw-r--r--app/assets/javascripts/pages/users/index.js2
-rw-r--r--app/controllers/projects/pages_controller.rb3
-rw-r--r--app/services/pages/delete_service.rb10
-rw-r--r--changelogs/unreleased/33880-add-api-to-disable-pages.yml5
-rw-r--r--doc/api/pages.md21
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/pages.rb26
-rw-r--r--qa/qa/resource/base.rb2
-rw-r--r--qa/qa/resource/user.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/email/trigger_email_notification_spec.rb2
-rw-r--r--qa/qa/vendor/saml_idp/page/login.rb4
-rw-r--r--qa/spec/spec_helper.rb2
-rw-r--r--spec/frontend/monitoring/utils_spec.js342
-rw-r--r--spec/javascripts/monitoring/utils_spec.js345
-rw-r--r--spec/requests/api/pages/pages_spec.rb71
-rw-r--r--spec/services/pages/delete_services_spec.rb27
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