Skip to content
Snippets Groups Projects
Verified Commit 4c5af150 authored by Jiaan Louw's avatar Jiaan Louw 2️⃣ Committed by GitLab
Browse files

Revert "Merge branch '444915-improve-schema-errors-with-custom-validation' into 'master'"

This reverts merge request !153152
parent a4ccffa6
No related branches found
No related tags found
2 merge requests!162233Draft: Script to update Topology Service Gem,!153832Revert improve analytics dashboards schema errors
Showing
with 31 additions and 217 deletions
......@@ -18979,10 +18979,10 @@ Represents a product analytics dashboard.
| <a id="customizabledashboardconfigurationproject"></a>`configurationProject` | [`Project`](#project) | Project which contains the dashboard definition. |
| <a id="customizabledashboarddescription"></a>`description` | [`String`](#string) | Description of the dashboard. |
| <a id="customizabledashboarderrors"></a>`errors` | [`[String!]`](#string) | Errors on yaml definition. |
| <a id="customizabledashboardpanels"></a>`panels` | [`CustomizableDashboardPanelConnection`](#customizabledashboardpanelconnection) | Panels shown on the dashboard. (see [Connections](#connections)) |
| <a id="customizabledashboardpanels"></a>`panels` | [`CustomizableDashboardPanelConnection!`](#customizabledashboardpanelconnection) | Panels shown on the dashboard. (see [Connections](#connections)) |
| <a id="customizabledashboardslug"></a>`slug` | [`String!`](#string) | Slug of the dashboard. |
| <a id="customizabledashboardstatus"></a>`status` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 17.0. **Status**: Experiment. Status of the dashboard. |
| <a id="customizabledashboardtitle"></a>`title` | [`String`](#string) | Title of the dashboard. |
| <a id="customizabledashboardtitle"></a>`title` | [`String!`](#string) | Title of the dashboard. |
| <a id="customizabledashboarduserdefined"></a>`userDefined` | [`Boolean!`](#boolean) | Indicates whether the dashboard is user-defined or provided by GitLab. |
 
### `CustomizableDashboardPanel`
......@@ -18995,8 +18995,8 @@ Represents a product analytics dashboard panel.
| ---- | ---- | ----------- |
| <a id="customizabledashboardpanelgridattributes"></a>`gridAttributes` | [`JSON`](#json) | Description of the position and size of the panel. |
| <a id="customizabledashboardpanelqueryoverrides"></a>`queryOverrides` | [`JSON`](#json) | Overrides for the visualization query object. |
| <a id="customizabledashboardpaneltitle"></a>`title` | [`String`](#string) | Title of the panel. |
| <a id="customizabledashboardpanelvisualization"></a>`visualization` | [`CustomizableDashboardVisualization`](#customizabledashboardvisualization) | Visualization of the panel. |
| <a id="customizabledashboardpaneltitle"></a>`title` | [`String!`](#string) | Title of the panel. |
| <a id="customizabledashboardpanelvisualization"></a>`visualization` | [`CustomizableDashboardVisualization!`](#customizabledashboardvisualization) | Visualization of the panel. |
 
### `CustomizableDashboardVisualization`
 
......@@ -313,10 +313,6 @@ If the error persists:
- Check that your configurations match the [dashboard JSON schema](#define-a-dashboard) defined in `ee/app/validators/json_schemas/analytics_dashboard.json`.
- For product analytics, make sure your [admin and project settings](../product_analytics/index.md#project-level-settings) are set up correctly.
### `Invalid dashboard configuration`
If the dashboard displays a global error message that the configuration is invalid, check that your configurations match the [dashboard JSON schema](#define-a-dashboard) defined in `ee/app/validators/json_schemas/analytics_dashboard.json`.
### `Invalid visualization configuration`
If a dashboard panel displays a message that the visualization configuration is invalid,
......
......@@ -276,13 +276,10 @@ After you have set up the project, set up the configuration file:
| `height` (subfield of `gridAttributes`) | Height of the panel |
```yaml
# version - The latest version of the analytics dashboard schema
version: '2'
# title - Change the title of the Value Streams Dashboard.
# title - Change the title of the Value Streams Dashboard. [optional]
title: 'Custom Dashboard title'
# description - Change the description of the Value Streams Dashboard.
# description - Change the description of the Value Streams Dashboard. [optional]
description: 'Custom description'
# panels - List of panels that contain panel settings.
......
<script>
import { GlEmptyState, GlSkeletonLoader, GlAlert } from '@gitlab/ui';
import { GlEmptyState, GlSkeletonLoader } from '@gitlab/ui';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { createAlert } from '~/alert';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_CREATED } from '~/lib/utils/http_status';
......@@ -55,7 +55,6 @@ export default {
ValueStreamFeedbackBanner,
GlEmptyState,
GlSkeletonLoader,
GlAlert,
},
mixins: [InternalEvents.mixin(), glFeatureFlagsMixin()],
inject: {
......@@ -114,7 +113,7 @@ export default {
backUrl: this.$router.resolve('/').href,
changesSaved: false,
alert: null,
hasDashboardLoadError: false,
hasDashboardError: false,
savedPanels: null,
};
},
......@@ -136,12 +135,6 @@ export default {
showDashboardFilters() {
return !HIDE_DASHBOARD_FILTERS.includes(this.currentDashboard?.slug);
},
invalidDashboardErrors() {
return this.currentDashboard?.errors ?? [];
},
hasDashboardError() {
return this.hasDashboardLoadError || this.invalidDashboardErrors.length > 0;
},
},
watch: {
initialDashboard(initialDashboard) {
......@@ -209,10 +202,12 @@ export default {
title: s__('Analytics|Failed to load dashboard'),
message,
messageLinks: {
link: this.$options.troubleshootingUrl,
link: helpPagePath('user/analytics/analytics_dashboards', {
anchor: '#troubleshooting',
}),
},
});
this.hasDashboardLoadError = true;
this.hasDashboardError = true;
},
},
availableVisualizations: {
......@@ -349,31 +344,12 @@ export default {
return `panel-${slug.replaceAll('_', '-')}`;
},
},
troubleshootingUrl: helpPagePath('user/analytics/analytics_dashboards', {
anchor: '#troubleshooting',
}),
};
</script>
<template>
<div>
<template v-if="currentDashboard">
<gl-alert
v-if="invalidDashboardErrors.length > 0"
data-testid="analytics-dashboard-invalid-config-errors"
class="gl-mt-4"
:title="s__('Analytics|Invalid dashboard configuration')"
:primary-button-text="__('Learn more')"
:primary-button-link="$options.troubleshootingUrl"
:dismissible="false"
variant="danger"
>
<ul class="gl-m-0">
<li v-for="errorMessage in invalidDashboardErrors" :key="errorMessage">
{{ errorMessage }}
</li>
</ul>
</gl-alert>
<value-stream-feedback-banner v-if="showValueStreamFeedbackBanner" />
<product-analytics-feedback-banner v-if="showProductAnalyticsFeedbackBanner" />
<customizable-dashboard
......
......@@ -28,9 +28,6 @@ export default {
showBetaBadge() {
return this.dashboard?.status === DASHBOARD_STATUS_BETA;
},
showErrorsBadge() {
return this.dashboard?.errors?.length > 0;
},
redirectHref() {
return joinPaths(window.location.pathname, this.dashboard.slug);
},
......@@ -89,16 +86,6 @@ export default {
>
{{ __('Beta') }}
</gl-badge>
<gl-badge
v-if="showErrorsBadge"
data-testid="dashboard-errors-badge"
class="gl-ml-2"
size="sm"
icon="error"
variant="danger"
>
{{ __('Contains errors') }}
</gl-badge>
</div>
<gl-truncate-text
class="gl-line-height-normal gl-text-gray-500"
......
......@@ -5,7 +5,6 @@ fragment CustomizableDashboardFragment on CustomizableDashboardConnection {
description
userDefined
status
errors
panels {
nodes {
title
......
......@@ -5,6 +5,5 @@ fragment CustomizableDashboardsFragment on CustomizableDashboardConnection {
description
userDefined
status
errors
}
}
......@@ -6,6 +6,8 @@ class VisualizationResolver < BaseResolver
type ::Types::ProductAnalytics::VisualizationType, null: true
def resolve
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Visualization does not exist' unless object.visualization
object.visualization
end
end
......
......@@ -9,7 +9,7 @@ class DashboardType < BaseObject
field :title,
type: GraphQL::Types::String,
null: true,
null: false,
description: 'Title of the dashboard.'
field :category,
......@@ -35,7 +35,7 @@ class DashboardType < BaseObject
field :panels,
type: Types::ProductAnalytics::PanelType.connection_type,
null: true,
null: false,
description: 'Panels shown on the dashboard.'
field :user_defined,
......
......@@ -9,7 +9,7 @@ class PanelType < BaseObject
field :title,
type: GraphQL::Types::String,
null: true,
null: false,
description: 'Title of the panel.'
field :grid_attributes,
......@@ -24,7 +24,7 @@ class PanelType < BaseObject
field :visualization,
type: Types::ProductAnalytics::VisualizationType,
null: true,
null: false,
description: 'Visualization of the panel.',
resolver: Resolvers::ProductAnalytics::VisualizationResolver
end
......
......@@ -7,7 +7,7 @@
"additionalProperties": false,
"properties": {
"version": {
"const": "2"
"const": "1"
},
"title": {
"type": "string"
......@@ -57,6 +57,7 @@
},
"required": [
"gridAttributes",
"queryOverrides",
"title",
"visualization"
],
......@@ -118,16 +119,10 @@
"properties": {
"timeDimensions": {
"$ref": "#/definitions/TimeDimensions"
},
"namespace": {
"type": "string"
},
"filters": {
"$ref": "#/definitions/Filters"
}
},
"required": [
"timeDimensions"
],
"title": "QueryOverrides"
},
......@@ -147,40 +142,6 @@
"dateRange"
],
"title": "TimeDimensions"
},
"Filters": {
"type": "object",
"additionalProperties": false,
"properties": {
"excludeMetrics": {
"type": "array",
"items": {
"type": "string"
}
},
"include": {
"type": "array",
"items": {
"type": "string"
}
},
"labels": {
"type": "array",
"items": {
"type": "string"
}
},
"projectTopics": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
],
"title": "Filters"
}
}
}
---
title: AI impact analytics
description: Visualize the relation between AI usage and SDLC trends.
version: "2"
status: beta
panels:
- title: 'Metric trends for %{namespaceType}: %{namespaceName}'
......
---
title: Audience
description: Understand your audience
version: "2"
panels:
- visualization: daily_active_users
title: Daily Active Users
......
---
title: Behavior
description: All insights on user activity
version: "2"
panels:
- visualization: page_views_over_time
title: Page views over time
......
---
title: Value Streams Dashboard
description: The Value Streams Dashboard allows all stakeholders from executives to individual contributors to identify trends, patterns, and opportunities for software development improvements.
version: "2"
panels:
- visualization: dora_chart
- id: 1
visualization: dora_chart
title: Metrics comparison for %{namespaceName} %{namespaceType}
gridAttributes:
yPos: 1
......@@ -11,7 +11,8 @@ panels:
width: 12
height: 6
options: {}
- visualization: usage_overview
- id: 2
visualization: usage_overview
title: Usage overview for %{namespaceName} %{namespaceType}
gridAttributes:
yPos: 0
......@@ -19,7 +20,8 @@ panels:
width: 12
height: 1
options: {}
- visualization: dora_performers_score
- id: 3
visualization: dora_performers_score
title: DORA performers score for %{namespaceName} %{namespaceType}
gridAttributes:
yPos: 7
......
---
version: '2'
version: '1'
title: Dashboard Example 1
description: North Star Metrics across all departments for the last 3 quarters.
panels:
......
......@@ -48,8 +48,6 @@ import {
TEST_VISUALIZATIONS_GRAPHQL_SUCCESS_RESPONSE,
createDashboardGraphqlSuccessResponse,
getGraphQLDashboard,
TEST_INVALID_CUSTOM_DASHBOARD_GRAPHQL_SUCCESS_RESPONSE,
mockInvalidDashboardErrors,
} from '../mock_data';
jest.mock('~/sentry/sentry_browser_wrapper');
......@@ -90,8 +88,6 @@ describe('AnalyticsDashboard', () => {
const findProductAnalyticsFeedbackBanner = () =>
wrapper.findComponent(ProductAnalyticsFeedbackBanner);
const findValueStreamFeedbackBanner = () => wrapper.findComponent(ValueStreamFeedbackBanner);
const findInvalidDashboardAlert = () =>
wrapper.findByTestId('analytics-dashboard-invalid-config-errors');
const mockSaveDashboardImplementation = async (responseCallback, dashboardToSave = dashboard) => {
saveCustomDashboard.mockImplementation(responseCallback);
......@@ -273,14 +269,6 @@ describe('AnalyticsDashboard', () => {
expect(findDashboard().exists()).toBe(true);
});
it('should not render invalid dashboard alert', async () => {
createWrapper();
await waitForPromises();
expect(findInvalidDashboardAlert().exists()).toBe(false);
});
it('should add unique panel ids to each panel', async () => {
createWrapper();
......@@ -414,37 +402,6 @@ describe('AnalyticsDashboard', () => {
});
});
describe("when the dashboard's configuration is invalid", () => {
beforeEach(() => {
mockDashboardResponse(TEST_INVALID_CUSTOM_DASHBOARD_GRAPHQL_SUCCESS_RESPONSE);
createWrapper();
return waitForPromises();
});
it('does not render the loader', () => {
expect(findLoader().exists()).toBe(false);
});
it('renders the dashboard', () => {
expect(findDashboard().exists()).toBe(true);
});
it('renders an alert with error messages', () => {
expect(findInvalidDashboardAlert().props()).toMatchObject({
title: 'Invalid dashboard configuration',
primaryButtonText: 'Learn more',
primaryButtonLink: '/help/user/analytics/analytics_dashboards#troubleshooting',
dismissible: false,
});
mockInvalidDashboardErrors.forEach((error) =>
expect(findInvalidDashboardAlert().text()).toContain(error),
);
});
});
describe('available visualizations', () => {
it('fetches the available visualizations when a custom dashboard is loaded', async () => {
await setupDashboard(TEST_CUSTOM_DASHBOARD_GRAPHQL_SUCCESS_RESPONSE);
......
......@@ -2,10 +2,7 @@ import { GlIcon, GlTruncateText } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
import DashboardListItem from 'ee/analytics/analytics_dashboards/components/list/dashboard_list_item.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import {
mockInvalidDashboardErrors,
TEST_ALL_DASHBOARDS_GRAPHQL_SUCCESS_RESPONSE,
} from '../../mock_data';
import { TEST_ALL_DASHBOARDS_GRAPHQL_SUCCESS_RESPONSE } from '../../mock_data';
jest.mock('ee/analytics/analytics_dashboards/api/dashboards_api');
......@@ -29,12 +26,6 @@ const BETA_DASHBOARD = {
slug: '/slug',
status: 'beta',
};
const INVALID_DASHBOARD = {
title: 'title',
description: 'description',
slug: '/slug',
errors: mockInvalidDashboardErrors,
};
describe('DashboardsListItem', () => {
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
......@@ -47,7 +38,6 @@ describe('DashboardsListItem', () => {
const findRouterLink = () => wrapper.findByTestId('dashboard-router-link');
const findDescriptionTruncate = () => wrapper.findComponent(GlTruncateText);
const findBetaBadge = () => wrapper.findByTestId('dashboard-beta-badge');
const findErrorsBadge = () => wrapper.findByTestId('dashboard-errors-badge');
const $router = {
push: jest.fn(),
......@@ -96,10 +86,6 @@ describe('DashboardsListItem', () => {
expect(findBetaBadge().exists()).toBe(false);
});
it('does not render errors badge', () => {
expect(findErrorsBadge().exists()).toBe(false);
});
it('routes to the dashboard when a list item is clicked', async () => {
await findListItem().trigger('click');
......@@ -142,18 +128,4 @@ describe('DashboardsListItem', () => {
expect(findBetaBadge().exists()).toBe(true);
});
});
describe('with an invalid dashboard', () => {
beforeEach(() => {
createWrapper(INVALID_DASHBOARD);
});
it('renders the errors badge', () => {
expect(findErrorsBadge().props()).toMatchObject({
icon: 'error',
variant: 'danger',
});
expect(findErrorsBadge().text()).toBe('Contains errors');
});
});
});
......@@ -65,7 +65,6 @@ export const getGraphQLDashboard = (options = {}, withPanels = true) => {
status: null,
description: 'Understand your audience',
__typename: 'CustomizableDashboard',
errors: [],
...options,
};
......@@ -289,36 +288,6 @@ export const TEST_ALL_DASHBOARDS_GRAPHQL_SUCCESS_RESPONSE = {
},
};
export const mockInvalidDashboardErrors = [
'root is missing required keys: version',
"property '/panels/0' is missing required keys: queryOverrides",
"property '/panels/0/id' is invalid: error_type=schema",
"property '/panels/1' is missing required keys: queryOverrides",
"property '/panels/1/id' is invalid: error_type=schema",
"property '/panels/2' is missing required keys: queryOverrides",
"property '/panels/2/id' is invalid: error_type=schema",
];
export const TEST_INVALID_CUSTOM_DASHBOARD_GRAPHQL_SUCCESS_RESPONSE = {
data: {
project: {
id: 'gid://gitlab/Project/1',
customizableDashboards: {
nodes: [
getGraphQLDashboard({
slug: 'custom_dashboard',
title: 'Custom Dashboard',
userDefined: true,
errors: mockInvalidDashboardErrors,
}),
],
__typename: 'CustomizableDashboardConnection',
},
__typename: 'Project',
},
},
};
export const createDashboardGraphqlSuccessResponse = (dashboardNodes) => ({
data: {
project: {
......
......@@ -30,8 +30,8 @@
end
end
it 'returns nil' do
expect(subject).to be_nil
it 'raises an error' do
expect(subject).to be_a(Gitlab::Graphql::Errors::ResourceNotAvailable)
end
end
end
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment