diff options
18 files changed, 307 insertions, 220 deletions
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js index 32fd0990374..1336b6a5461 100644 --- a/app/assets/javascripts/lib/utils/url_utility.js +++ b/app/assets/javascripts/lib/utils/url_utility.js @@ -2,8 +2,8 @@ import { join as joinPaths } from 'path'; // Returns an array containing the value(s) of the // of the key passed as an argument -export function getParameterValues(sParam) { - const sPageURL = decodeURIComponent(window.location.search.substring(1)); +export function getParameterValues(sParam, url = window.location) { + const sPageURL = decodeURIComponent(new URL(url).search.substring(1)); return sPageURL.split('&').reduce((acc, urlParam) => { const sParameterName = urlParam.split('='); diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue index 2feb545199b..5892c18ac91 100644 --- a/app/assets/javascripts/monitoring/components/dashboard.vue +++ b/app/assets/javascripts/monitoring/components/dashboard.vue @@ -18,8 +18,8 @@ import MonitorSingleStatChart from './charts/single_stat.vue'; import PanelType from './panel_type.vue'; import GraphGroup from './graph_group.vue'; import EmptyState from './empty_state.vue'; -import { sidebarAnimationDuration, timeWindows, timeWindowsKeyNames } from '../constants'; -import { getTimeDiff } from '../utils'; +import { sidebarAnimationDuration, timeWindows } from '../constants'; +import { getTimeDiff, getTimeWindow } from '../utils'; let sidebarMutationObserver; @@ -147,6 +147,7 @@ export default { selectedTimeWindow: '', selectedTimeWindowKey: '', formIsValid: null, + timeWindows: {}, }; }, computed: { @@ -184,17 +185,6 @@ export default { currentDashboard: this.currentDashboard, projectPath: this.projectPath, }); - - this.timeWindows = timeWindows; - this.selectedTimeWindowKey = - _.escape(getParameterValues('time_window')[0]) || timeWindowsKeyNames.eightHours; - - // Set default time window if the selectedTimeWindowKey is bogus - if (!Object.keys(this.timeWindows).includes(this.selectedTimeWindowKey)) { - this.selectedTimeWindowKey = timeWindowsKeyNames.eightHours; - } - - this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey]; }, beforeDestroy() { if (sidebarMutationObserver) { @@ -205,7 +195,20 @@ export default { if (!this.hasMetrics) { this.setGettingStartedEmptyState(); } else { - this.fetchData(getTimeDiff(this.selectedTimeWindow)); + const defaultRange = getTimeDiff(); + const start = getParameterValues('start')[0] || defaultRange.start; + const end = getParameterValues('end')[0] || defaultRange.end; + + const range = { + start, + end, + }; + + this.timeWindows = timeWindows; + this.selectedTimeWindowKey = getTimeWindow(range); + this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey]; + + this.fetchData(range); sidebarMutationObserver = new MutationObserver(this.onSidebarMutation); sidebarMutationObserver.observe(document.querySelector('.layout-page'), { @@ -259,7 +262,8 @@ export default { return this.timeWindows[key] === this.selectedTimeWindow; }, setTimeWindowParameter(key) { - return `?time_window=${key}`; + const { start, end } = getTimeDiff(key); + return `?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`; }, groupHasData(group) { return this.chartsWithData(group.metrics).length > 0; diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue index e17f03de0fd..9e85b0633fe 100644 --- a/app/assets/javascripts/monitoring/components/embed.vue +++ b/app/assets/javascripts/monitoring/components/embed.vue @@ -1,8 +1,9 @@ <script> import { mapActions, mapState } from 'vuex'; +import { getParameterValues, removeParams } from '~/lib/utils/url_utility'; import GraphGroup from './graph_group.vue'; import MonitorAreaChart from './charts/area.vue'; -import { sidebarAnimationDuration, timeWindowsKeyNames, timeWindows } from '../constants'; +import { sidebarAnimationDuration } from '../constants'; import { getTimeDiff } from '../utils'; let sidebarMutationObserver; @@ -19,10 +20,17 @@ export default { }, }, data() { + const defaultRange = getTimeDiff(); + const start = getParameterValues('start', this.dashboardUrl)[0] || defaultRange.start; + const end = getParameterValues('end', this.dashboardUrl)[0] || defaultRange.end; + + const params = { + start, + end, + }; + return { - params: { - ...getTimeDiff(timeWindows[timeWindowsKeyNames.eightHours]), - }, + params, elWidth: 0, }; }, @@ -73,7 +81,7 @@ export default { prometheusEndpointEnabled: true, }); this.setEndpoints({ - dashboardEndpoint: this.dashboardUrl, + dashboardEndpoint: removeParams(['start', 'end'], this.dashboardUrl), }); this.setShowErrorBanner(false); }, diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js index 605c95e6da5..d7d89522732 100644 --- a/app/assets/javascripts/monitoring/constants.js +++ b/app/assets/javascripts/monitoring/constants.js @@ -21,11 +21,19 @@ export const timeWindows = { oneWeek: __('1 week'), }; -export const timeWindowsKeyNames = { - thirtyMinutes: 'thirtyMinutes', - threeHours: 'threeHours', - eightHours: 'eightHours', - oneDay: 'oneDay', - threeDays: 'threeDays', - oneWeek: 'oneWeek', +export const secondsIn = { + thirtyMinutes: 60 * 30, + threeHours: 60 * 60 * 3, + eightHours: 60 * 60 * 8, + oneDay: 60 * 60 * 24 * 1, + threeDays: 60 * 60 * 24 * 3, + oneWeek: 60 * 60 * 24 * 7 * 1, }; + +export const timeWindowsKeyNames = Object.keys(secondsIn).reduce( + (otherTimeWindows, timeWindow) => ({ + ...otherTimeWindows, + [timeWindow]: timeWindow, + }), + {}, +); diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js index 245cc2eaca3..0cbad179f17 100644 --- a/app/assets/javascripts/monitoring/stores/actions.js +++ b/app/assets/javascripts/monitoring/stores/actions.js @@ -151,7 +151,7 @@ function fetchPrometheusResult(prometheusEndpoint, params) { */ export const fetchPrometheusMetric = ({ commit }, { metric, params }) => { const { start, end } = params; - const timeDiff = end - start; + const timeDiff = (new Date(end) - new Date(start)) / 1000; const minStep = 60; const queryDataPoints = 600; diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js index 478e2b3d06c..46b01f753f8 100644 --- a/app/assets/javascripts/monitoring/utils.js +++ b/app/assets/javascripts/monitoring/utils.js @@ -1,35 +1,24 @@ -import { timeWindows } from './constants'; +import { secondsIn, timeWindowsKeyNames } from './constants'; -/** - * method that converts a predetermined time window to minutes - * defaults to 8 hours as the default option - * @param {String} timeWindow - The time window to convert to minutes - * @returns {number} The time window in minutes - */ -const getTimeDifferenceSeconds = timeWindow => { - switch (timeWindow) { - case timeWindows.thirtyMinutes: - return 60 * 30; - case timeWindows.threeHours: - return 60 * 60 * 3; - case timeWindows.oneDay: - return 60 * 60 * 24 * 1; - case timeWindows.threeDays: - return 60 * 60 * 24 * 3; - case timeWindows.oneWeek: - return 60 * 60 * 24 * 7 * 1; - default: - return 60 * 60 * 8; - } -}; +export const getTimeDiff = timeWindow => { + const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds + const difference = secondsIn[timeWindow] || secondsIn.eightHours; + const start = end - difference; -export const getTimeDiff = selectedTimeWindow => { - const end = Date.now() / 1000; // convert milliseconds to seconds - const start = end - getTimeDifferenceSeconds(selectedTimeWindow); - - return { start, end }; + return { + start: new Date(start * 1000).toISOString(), + end: new Date(end * 1000).toISOString(), + }; }; +export const getTimeWindow = ({ start, end }) => + Object.entries(secondsIn).reduce((acc, [timeRange, value]) => { + if (end - start === value) { + return timeRange; + } + return acc; + }, timeWindowsKeyNames.eightHours); + /** * This method is used to validate if the graph data format for a chart component * that needs a time series as a response from a prometheus query (query_range) is diff --git a/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml b/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml new file mode 100644 index 00000000000..aaf0ddfa48d --- /dev/null +++ b/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml @@ -0,0 +1,5 @@ +--- +title: Allow links to metrics dashboard at a specific time +merge_request: 31283 +author: +type: added diff --git a/changelogs/unreleased/sh-add-missing-csp-report-uri.yml b/changelogs/unreleased/sh-add-missing-csp-report-uri.yml new file mode 100644 index 00000000000..656eb8e9c37 --- /dev/null +++ b/changelogs/unreleased/sh-add-missing-csp-report-uri.yml @@ -0,0 +1,5 @@ +--- +title: Add missing report-uri to CSP config +merge_request: 31593 +author: +type: fixed diff --git a/doc/api/README.md b/doc/api/README.md index 9f23af3f723..0195ce3912a 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -4,149 +4,10 @@ Automate GitLab via a simple and powerful API. The main GitLab API is a [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) API. Therefore, documentation in this section assumes knowledge of REST concepts. -## API resources - -Available API resources can be grouped in the following contexts: - -- [Projects](#project-resources). -- [Groups](#group-resources). -- [Standalone](#standalone-resources). - -See also: - -- [V3 to V4](v3_to_v4.md). -- Adding [deploy keys for multiple projects](deploy_key_multiple_projects.md). - -### Project resources - -The following API resources are available in the project context: - -| Resource | Available endpoints | -|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) | -| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` | -| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` | -| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` | -| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` | -| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) | -| [Dependencies](dependencies.md) **(ULTIMATE)** | `/projects/:id/dependencies` -| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) | -| [Deployments](deployments.md) | `/projects/:id/deployments` | -| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) | -| [Environments](environments.md) | `/projects/:id/environments` | -| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) | -| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) | -| [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) | -| [Issue boards](boards.md) | `/projects/:id/boards` | -| [Issue links](issue_links.md) **(STARTER)** | `/projects/:id/issues/.../links` | -| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` | -| [Labels](labels.md) | `/projects/:id/labels` | -| [Managed licenses](managed_licenses.md) **(ULTIMATE)** | `/projects/:id/managed_licenses` | -| [Members](members.md) | `/projects/:id/members` (also available for groups) | -| [Merge request approvals](merge_request_approvals.md) **(STARTER)** | `/projects/:id/approvals`, `/projects/:id/merge_requests/.../approvals` | -| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) | -| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` (also available for groups) | -| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) | -| [Packages](packages.md) **(PREMIUM)** | `/projects/:id/packages` | -| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) | -| [Pipelines](pipelines.md) | `/projects/:id/pipelines` | -| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` | -| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` | -| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) | -| [Project badges](project_badges.md) | `/projects/:id/badges` | -| [Project clusters](project_clusters.md) | `/projects/:id/clusters` | -| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` | -| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` | -| [Project milestones](milestones.md) | `/projects/:id/milestones` | -| [Project snippets](project_snippets.md) | `/projects/:id/snippets` | -| [Project templates](project_templates.md) | `/projects/:id/templates` | -| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` | -| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` | -| [Releases](releases/index.md) | `/projects/:id/releases` | -| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` | -| [Repositories](repositories.md) | `/projects/:id/repository` | -| [Repository files](repository_files.md) | `/projects/:id/repository/files` | -| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` | -| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) | -| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) | -| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) | -| [Services](services.md) | `/projects/:id/services` | -| [Tags](tags.md) | `/projects/:id/repository/tags` | -| [Vulnerabilities](vulnerabilities.md) **(ULTIMATE)** | `/projects/:id/vulnerabilities` (also available for groups) | -| [Wikis](wikis.md) | `/projects/:id/wikis` | - -### Group resources - -The following API resources are available in the group context: - -| Resource | Available endpoints | -|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------| -| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) | -| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) | -| [Discussions](discussions.md) (threaded comments) **(ULTIMATE)** | `/groups/:id/epics/.../discussions` (also available for projects) | -| [Epic issues](epic_issues.md) **(ULTIMATE)** | `/groups/:id/epics/.../issues` | -| [Epic links](epic_links.md) **(ULTIMATE)** | `/groups/:id/epics/.../epics` | -| [Epics](epics.md) **(ULTIMATE)** | `/groups/:id/epics` | -| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` | -| [Group badges](group_badges.md) | `/groups/:id/badges` | -| [Group issue boards](group_boards.md) | `/groups/:id/boards` | -| [Group labels](group_labels.md) | `/groups/:id/labels` | -| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` | -| [Group milestones](group_milestones.md) | `/groups/:id/milestones` | -| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) | -| [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) | -| [Members](members.md) | `/groups/:id/members` (also available for projects) | -| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) | -| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) | -| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) | -| [Resource label events](resource_label_events.md) | `/groups/:id/epics/.../resource_label_events` (also available for projects) | -| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) | - -### Standalone resources - -The following API resources are available outside of project and group contexts (including `/users`): - -| Resource | Available endpoints | -|:--------------------------------------------------|:------------------------------------------------------------------------| -| [Applications](applications.md) | `/applications` | -| [Avatar](avatar.md) | `/avatar` | -| [Broadcast messages](broadcast_messages.md) | `/broadcast_messages` | -| [Code snippets](snippets.md) | `/snippets` | -| [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) | -| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) | -| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) | -| [Feature flags](features.md) | `/features` | -| [Geo Nodes](geo_nodes.md) **(PREMIUM ONLY)** | `/geo_nodes` | -| [Import repository from GitHub](import.md) | `/import/github` | -| [Issues](issues.md) | `/issues` (also available for groups and projects) | -| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) | -| [Keys](keys.md) | `/keys` | -| [License](license.md) **(CORE ONLY)** | `/license` | -| [Markdown](markdown.md) | `/markdown` | -| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) | -| [Namespaces](namespaces.md) | `/namespaces` | -| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) | -| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) | -| [Projects](projects.md) | `/users/:id/projects` (also available for projects) | -| [Runners](runners.md) | `/runners` (also available for projects) | -| [Search](search.md) | `/search` (also available for groups and projects) | -| [Settings](settings.md) | `/application/settings` | -| [Sidekiq metrics](sidekiq_metrics.md) | `/sidekiq` | -| [Suggestions](suggestions.md) | `/suggestions` | -| [System hooks](system_hooks.md) | `/hooks` | -| [Todos](todos.md) | `/todos` | -| [Users](users.md) | `/users` | -| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` | -| [Version](version.md) | `/version` | - -### Templates API resources - -Endpoints are available for: - -- [Dockerfile templates](templates/dockerfiles.md). -- [`.gitignore` templates](templates/gitignores.md). -- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md). -- [Open source license templates](templates/licenses.md). +## Available API resources + +For a list of the available resources and their endpoints, see +[API resources](api_resources.md). ## SCIM **(SILVER ONLY)** diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md new file mode 100644 index 00000000000..b32f11464ef --- /dev/null +++ b/doc/api/api_resources.md @@ -0,0 +1,144 @@ +# API resources + +Available resources for the [GitLab API](README.md) can be grouped in the following contexts: + +- [Projects](#project-resources). +- [Groups](#group-resources). +- [Standalone](#standalone-resources). + +See also: + +- [V3 to V4](v3_to_v4.md). +- Adding [deploy keys for multiple projects](deploy_key_multiple_projects.md). +- [API Resources for various templates](#templates-api-resources). + +## Project resources + +The following API resources are available in the project context: + +| Resource | Available endpoints | +|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) | +| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` | +| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` | +| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` | +| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` | +| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) | +| [Dependencies](dependencies.md) **(ULTIMATE)** | `/projects/:id/dependencies` +| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) | +| [Deployments](deployments.md) | `/projects/:id/deployments` | +| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) | +| [Environments](environments.md) | `/projects/:id/environments` | +| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) | +| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) | +| [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) | +| [Issue boards](boards.md) | `/projects/:id/boards` | +| [Issue links](issue_links.md) **(STARTER)** | `/projects/:id/issues/.../links` | +| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` | +| [Labels](labels.md) | `/projects/:id/labels` | +| [Managed licenses](managed_licenses.md) **(ULTIMATE)** | `/projects/:id/managed_licenses` | +| [Members](members.md) | `/projects/:id/members` (also available for groups) | +| [Merge request approvals](merge_request_approvals.md) **(STARTER)** | `/projects/:id/approvals`, `/projects/:id/merge_requests/.../approvals` | +| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) | +| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` (also available for groups) | +| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) | +| [Packages](packages.md) **(PREMIUM)** | `/projects/:id/packages` | +| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) | +| [Pipelines](pipelines.md) | `/projects/:id/pipelines` | +| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` | +| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` | +| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) | +| [Project badges](project_badges.md) | `/projects/:id/badges` | +| [Project clusters](project_clusters.md) | `/projects/:id/clusters` | +| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` | +| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` | +| [Project milestones](milestones.md) | `/projects/:id/milestones` | +| [Project snippets](project_snippets.md) | `/projects/:id/snippets` | +| [Project templates](project_templates.md) | `/projects/:id/templates` | +| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` | +| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` | +| [Releases](releases/index.md) | `/projects/:id/releases` | +| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` | +| [Repositories](repositories.md) | `/projects/:id/repository` | +| [Repository files](repository_files.md) | `/projects/:id/repository/files` | +| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` | +| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) | +| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) | +| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) | +| [Services](services.md) | `/projects/:id/services` | +| [Tags](tags.md) | `/projects/:id/repository/tags` | +| [Vulnerabilities](vulnerabilities.md) **(ULTIMATE)** | `/projects/:id/vulnerabilities` (also available for groups) | +| [Wikis](wikis.md) | `/projects/:id/wikis` | + +## Group resources + +The following API resources are available in the group context: + +| Resource | Available endpoints | +|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------| +| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) | +| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) | +| [Discussions](discussions.md) (threaded comments) **(ULTIMATE)** | `/groups/:id/epics/.../discussions` (also available for projects) | +| [Epic issues](epic_issues.md) **(ULTIMATE)** | `/groups/:id/epics/.../issues` | +| [Epic links](epic_links.md) **(ULTIMATE)** | `/groups/:id/epics/.../epics` | +| [Epics](epics.md) **(ULTIMATE)** | `/groups/:id/epics` | +| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` | +| [Group badges](group_badges.md) | `/groups/:id/badges` | +| [Group issue boards](group_boards.md) | `/groups/:id/boards` | +| [Group labels](group_labels.md) | `/groups/:id/labels` | +| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` | +| [Group milestones](group_milestones.md) | `/groups/:id/milestones` | +| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) | +| [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) | +| [Members](members.md) | `/groups/:id/members` (also available for projects) | +| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) | +| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) | +| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) | +| [Resource label events](resource_label_events.md) | `/groups/:id/epics/.../resource_label_events` (also available for projects) | +| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) | + +## Standalone resources + +The following API resources are available outside of project and group contexts (including `/users`): + +| Resource | Available endpoints | +|:--------------------------------------------------|:------------------------------------------------------------------------| +| [Applications](applications.md) | `/applications` | +| [Avatar](avatar.md) | `/avatar` | +| [Broadcast messages](broadcast_messages.md) | `/broadcast_messages` | +| [Code snippets](snippets.md) | `/snippets` | +| [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) | +| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) | +| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) | +| [Feature flags](features.md) | `/features` | +| [Geo Nodes](geo_nodes.md) **(PREMIUM ONLY)** | `/geo_nodes` | +| [Import repository from GitHub](import.md) | `/import/github` | +| [Issues](issues.md) | `/issues` (also available for groups and projects) | +| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) | +| [Keys](keys.md) | `/keys` | +| [License](license.md) **(CORE ONLY)** | `/license` | +| [Markdown](markdown.md) | `/markdown` | +| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) | +| [Namespaces](namespaces.md) | `/namespaces` | +| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) | +| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) | +| [Projects](projects.md) | `/users/:id/projects` (also available for projects) | +| [Runners](runners.md) | `/runners` (also available for projects) | +| [Search](search.md) | `/search` (also available for groups and projects) | +| [Settings](settings.md) | `/application/settings` | +| [Sidekiq metrics](sidekiq_metrics.md) | `/sidekiq` | +| [Suggestions](suggestions.md) | `/suggestions` | +| [System hooks](system_hooks.md) | `/hooks` | +| [Todos](todos.md) | `/todos` | +| [Users](users.md) | `/users` | +| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` | +| [Version](version.md) | `/version` | + +## Templates API resources + +Endpoints are available for: + +- [Dockerfile templates](templates/dockerfiles.md). +- [`.gitignore` templates](templates/gitignores.md). +- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md). +- [Open source license templates](templates/licenses.md). diff --git a/doc/api/projects.md b/doc/api/projects.md index 8f68557edb3..70df44ec0fd 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1909,18 +1909,20 @@ GET /projects/:id/push_rule "author_email_regex": "", "file_name_regex": "", "max_file_size": 5, - "commit_committer_check": false + "commit_committer_check": false, + "reject_unsigned_commits": false } ``` Users on GitLab [Premium, Silver, or higher](https://about.gitlab.com/pricing/) will also see -the `commit_committer_check` parameter: +the `commit_committer_check` and `reject_unsigned_commits` parameters: ```json { "id": 1, "project_id": 3, - "commit_committer_check": false + "commit_committer_check": false, + "reject_unsigned_commits": false ... } ``` @@ -1946,6 +1948,7 @@ POST /projects/:id/push_rule | `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` | | `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) | | `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. | +| `reject_unsigned_commits` **(PREMIUM)** | boolean | no | Reject commit when it is not signed through GPG. | ### Edit project push rule @@ -1968,6 +1971,7 @@ PUT /projects/:id/push_rule | `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` | | `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) | | `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. | +| `reject_unsigned_commits` **(PREMIUM)** | boolean | no | Reject commits when they are not GPG signed. | ### Delete project push rule diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml index c963d6ed1c4..2afc99d0bf8 100644 --- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml @@ -25,6 +25,7 @@ container_scanning: # https://hub.docker.com/r/arminc/clair-local-scan/tags CLAIR_LOCAL_SCAN_VERSION: v2.0.8_0ed98e9ead65a51ba53f7cc53fa5e80c92169207 CLAIR_EXECUTABLE_VERSION: v12 + CLAIR_EXECUTABLE_SHA: 44f2a3fdd7b0d102c98510e7586f6956edc89ab72c6943980f92f4979f7f4081 ## Disable the proxy for clair-local-scan, otherwise Container Scanning will ## fail when a proxy is used. NO_PROXY: ${DOCKER_SERVICE},localhost @@ -44,6 +45,7 @@ container_scanning: - apk add -U wget ca-certificates - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} - wget https://github.com/arminc/clair-scanner/releases/download/${CLAIR_EXECUTABLE_VERSION}/clair-scanner_linux_amd64 + - echo "${CLAIR_EXECUTABLE_SHA} clair-scanner_linux_amd64" | sha256sum -c - mv clair-scanner_linux_amd64 clair-scanner - chmod +x clair-scanner - touch clair-whitelist.yml diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index b2f3345d33a..ff844645b11 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -5,7 +5,7 @@ module Gitlab class ConfigLoader DIRECTIVES = %w(base_uri child_src connect_src default_src font_src form_action frame_ancestors frame_src img_src manifest_src - media_src object_src script_src style_src worker_src).freeze + media_src object_src report_uri script_src style_src worker_src).freeze def self.default_settings_hash { diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js index c771984a137..a986bc49f28 100644 --- a/spec/frontend/lib/utils/url_utility_spec.js +++ b/spec/frontend/lib/utils/url_utility_spec.js @@ -34,6 +34,41 @@ describe('URL utility', () => { }); }); + describe('getParameterValues', () => { + beforeEach(() => { + setWindowLocation({ + href: 'https://gitlab.com?test=passing&multiple=1&multiple=2', + // make our fake location act like real window.location.toString + // URL() (used in getParameterValues) does this if passed an object + toString() { + return this.href; + }, + }); + }); + + it('returns empty array for no params', () => { + expect(urlUtils.getParameterValues()).toEqual([]); + }); + + it('returns empty array for non-matching params', () => { + expect(urlUtils.getParameterValues('notFound')).toEqual([]); + }); + + it('returns single match', () => { + expect(urlUtils.getParameterValues('test')).toEqual(['passing']); + }); + + it('returns multiple matches', () => { + expect(urlUtils.getParameterValues('multiple')).toEqual(['1', '2']); + }); + + it('accepts url as second arg', () => { + const url = 'https://gitlab.com?everything=works'; + expect(urlUtils.getParameterValues('everything', url)).toEqual(['works']); + expect(urlUtils.getParameterValues('test', url)).toEqual([]); + }); + }); + describe('mergeUrlParams', () => { it('adds w', () => { expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag'); diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index d3e10194d92..36f650d5933 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -307,7 +307,7 @@ describe('Dashboard', () => { }); spyOn(component.$store, 'dispatch').and.stub(); - const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff'); + const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough(); component.$store.commit( `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, @@ -319,7 +319,7 @@ describe('Dashboard', () => { Vue.nextTick() .then(() => { expect(component.$store.dispatch).toHaveBeenCalled(); - expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow); + expect(getTimeDiffSpy).toHaveBeenCalled(); done(); }) @@ -327,7 +327,17 @@ describe('Dashboard', () => { }); it('shows a specific time window selected from the url params', done => { - spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']); + const start = 1564439536; + const end = 1564441336; + spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({ + start, + end, + }); + spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => { + if (param === 'start') return [start]; + if (param === 'end') return [end]; + return []; + }); component = new DashboardComponent({ el: document.querySelector('.prometheus-graphs'), diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js index 677455275de..955a39e03a5 100644 --- a/spec/javascripts/monitoring/store/actions_spec.js +++ b/spec/javascripts/monitoring/store/actions_spec.js @@ -313,8 +313,8 @@ describe('Monitoring store actions', () => { it('commits prometheus query result', done => { const commit = jasmine.createSpy(); const params = { - start: '1557216349.469', - end: '1557218149.469', + start: '2019-08-06T12:40:02.184Z', + end: '2019-08-06T20:40:02.184Z', }; const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0]; const state = storeState(); diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js index 5570d57b8b2..e22e8cdc03d 100644 --- a/spec/javascripts/monitoring/utils_spec.js +++ b/spec/javascripts/monitoring/utils_spec.js @@ -3,28 +3,38 @@ import { timeWindows } from '~/monitoring/constants'; import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } 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(params.end - params.start).toEqual(28800); + expect(hoursBetween(params)).toEqual(8); }); it('accepts time window as an argument', () => { - const params = getTimeDiff(timeWindows.thirtyMinutes); + const params = getTimeDiff('thirtyMinutes'); - expect(params.end - params.start).not.toEqual(28800); + 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(window => { - const params = getTimeDiff(timeWindows[window]); - const diff = params.end - params.start; + nonDefaultWindows.forEach(timeWindow => { + const params = getTimeDiff(timeWindow); - // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs) - expect(diff).not.toEqual(28800); - expect(typeof diff).toEqual('number'); + // Ensure we're not returning the default + expect(hoursBetween(params)).not.toEqual(8); }); }); }); diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index e7670c9d523..1d404915617 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -13,7 +13,8 @@ describe Gitlab::ContentSecurityPolicy::ConfigLoader do child_src: "'self' https://child.example.com", default_src: "'self' https://other.example.com", script_src: "'self' https://script.exammple.com ", - worker_src: "data: https://worker.example.com" + worker_src: "data: https://worker.example.com", + report_uri: "http://example.com" } } end @@ -46,6 +47,7 @@ describe Gitlab::ContentSecurityPolicy::ConfigLoader do expect(policy.directives['default-src']).to eq(expected_config(:default_src)) expect(policy.directives['child-src']).to eq(expected_config(:child_src)) expect(policy.directives['worker-src']).to eq(expected_config(:worker_src)) + expect(policy.directives['report-uri']).to eq(expected_config(:report_uri)) end it 'ignores malformed policy statements' do |