Skip to content

Commit

Permalink
Merge branch 'main' into issue-28510-remove-bundle-name-column
Browse files Browse the repository at this point in the history
  • Loading branch information
erickgonzalez authored Oct 23, 2024
2 parents 12d1cc8 + 9af96d6 commit c29be9b
Show file tree
Hide file tree
Showing 20 changed files with 520 additions and 36 deletions.
7 changes: 7 additions & 0 deletions core-web/apps/dotcms-ui/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DotLoginPageComponent } from '@components/login/main/dot-login-page.com
import { MainCoreLegacyComponent } from '@components/main-core-legacy/main-core-legacy-component';
import { MainComponentLegacyComponent } from '@components/main-legacy/main-legacy.component';
import { EmaAppConfigurationService } from '@dotcms/data-access';
import { DotEnterpriseLicenseResolver } from '@dotcms/ui';
import { DotCustomReuseStrategyService } from '@shared/dot-custom-reuse-strategy/dot-custom-reuse-strategy.service';

import { AuthGuardService } from './api/services/guards/auth-guard.service';
Expand Down Expand Up @@ -72,6 +73,12 @@ const PORTLETS_ANGULAR: Route[] = [
},
{
path: 'analytics-search',
canActivate: [MenuGuardService],
canActivateChild: [MenuGuardService],
providers: [DotEnterpriseLicenseResolver],
resolve: {
isEnterprise: DotEnterpriseLicenseResolver
},
data: {
reuseRoute: false
},
Expand Down
1 change: 1 addition & 0 deletions core-web/libs/data-access/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,4 @@ export * from './lib/dot-page-state/dot-page-state.service';
export * from './lib/dot-edit-page-resolver/dot-edit-page-resolver.service';
export * from './lib/dot-resource-links/dot-resource-links.service';
export * from './lib/dot-ai/dot-ai.service';
export * from './lib/dot-analytics-search/dot-analytics-search.service';
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/jest';

import { DotAnalyticsSearchService } from '@dotcms/data-access';
import { AnalyticsQueryType } from '@dotcms/dotcms-models';

describe('DotAnalyticsSearchService', () => {
let spectator: SpectatorHttp<DotAnalyticsSearchService>;
const createHttp = createHttpFactory(DotAnalyticsSearchService);
const mockResponse = { entity: [{ id: '1', name: 'result1' }] };
const query = { measures: ['request.count'], orders: 'request.count DESC' };

beforeEach(() => (spectator = createHttp()));

it('should perform a POST request to the base URL and return results', (done) => {
spectator.service.get(query).subscribe((results) => {
expect(results).toEqual(mockResponse.entity);
done();
});

const req = spectator.expectOne('/api/v1/analytics/content/_query', HttpMethod.POST);
req.flush(mockResponse);

expect(req.request.body).toEqual({ ...query });
});

it('should perform a POST request to the cube URL and return results', (done) => {
spectator.service.get(query, AnalyticsQueryType.CUBE).subscribe((results) => {
expect(results).toEqual(mockResponse.entity);
done();
});

const req = spectator.expectOne('/api/v1/analytics/content/_query/cube', HttpMethod.POST);
req.flush(mockResponse);

expect(req.request.body).toEqual({ ...query });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { JsonObject } from '@angular-devkit/core';
import { Observable } from 'rxjs';

import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';

import { pluck } from 'rxjs/operators';

import { AnalyticsQueryType } from '@dotcms/dotcms-models';

/**
* Service for performing analytics search operations.
*/
@Injectable()
export class DotAnalyticsSearchService {
/**
* URL for analytics content search with DotCMS standards.
* @private
*/
readonly #BASE_URL = '/api/v1/analytics/content/_query';
/**
* URL for analytics content search with the cube query standards.
* @private
*/
readonly #CUBE_URL = '/api/v1/analytics/content/_query/cube';

readonly #http = inject(HttpClient);

/**
* Performs a POST request to the base URL with the provided query.
* @param {Record<string, unknown>} query - The query object to be sent in the request body.
* @param {AnalyticsQueryType} [type=AnalyticsQueryType.DEFAULT] - The type of analytics query to be performed.
* @returns {Observable<JsonObject[]>} - An observable containing the response from the server.
*/
get(
query: Record<string, unknown>,
type: AnalyticsQueryType = AnalyticsQueryType.DEFAULT
): Observable<JsonObject[]> {
return this.#http
.post(type == AnalyticsQueryType.DEFAULT ? this.#BASE_URL : this.#CUBE_URL, query)
.pipe(pluck('entity'));
}
}
1 change: 1 addition & 0 deletions core-web/libs/dotcms-models/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,4 @@ export * from './lib/dot-action-menu-item.model';
export * from './lib/dot-vanity-url.model';
export * from './lib/dot-categories.model';
export * from './lib/dot-ai.model';
export * from './lib/dot-content-analytics.model';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum AnalyticsQueryType {
DEFAULT = 'DEFAULT',
CUBE = 'CUBE'
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
/* eslint-disable */
export default {
displayName: 'dot-analytics-search',
displayName: 'portlets-dot-analytics-search',
preset: '../../../../jest.preset.js',
setupFilesAfterEnv: ['<rootDir>/src/test-setup.ts'],
coverageDirectory: '../../../../coverage/libs/portlets/dot-analytics-search/portlet',
globals: {},
transform: {
'^.+\\.(ts|mjs|js|html)$': [
'jest-preset-angular',
Expand All @@ -18,5 +18,6 @@ export default {
'jest-preset-angular/build/serializers/no-ng-attributes',
'jest-preset-angular/build/serializers/ng-snapshot',
'jest-preset-angular/build/serializers/html-comment'
]
],
testEnvironment: '@happy-dom/jest-environment'
};
12 changes: 9 additions & 3 deletions core-web/libs/portlets/dot-analytics-search/portlet/project.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"name": "dot-analytics-search",
"name": "portlets-dot-analytics-search",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/portlets/dot-analytics-search/portlet/src",
"prefix": "lib",
"prefix": "dot",
"projectType": "library",
"tags": ["type:feature", "scope:dotcms-ui", "portlet:analytics-search"],
"targets": {
Expand All @@ -11,10 +11,16 @@
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/portlets/dot-analytics-search/portlet/jest.config.ts"
},
"configurations": {
"ci": {
"verbose": false
}
}
},
"lint": {
"executor": "@nx/eslint:lint"
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
}
}
}
Original file line number Diff line number Diff line change
@@ -1 +1,27 @@
<p>dot-analytics-search works!</p>
<section class="content-analytics__query">
<div class="content-analytics__header">
<h4>{{ 'analytics.search.query' | dm }}</h4>
<div class="content-analytics__actions">
<button
pButton
(click)="handleRequest()"
[label]="'analytics.search.run.query' | dm"
icon="pi pi-arrow-right"
iconPos="right"
data-testId="run-query"></button>
</div>
</div>
<ngx-monaco-editor
[(ngModel)]="queryEditor"
[options]="ANALYTICS_MONACO_EDITOR_OPTIONS"
data-testId="query-editor"></ngx-monaco-editor>
</section>
<section class="content-analytics__results">
<div class="content-analytics__header">
<h4>{{ 'analytics.search.results' | dm }}</h4>
</div>
<ngx-monaco-editor
[ngModel]="results$()"
[options]="ANALYTICS__RESULTS_MONACO_EDITOR_OPTIONS"
data-testId="results-editor"></ngx-monaco-editor>
</section>
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,49 @@
overflow: auto;
background: $white;
display: flex;
flex-direction: column;
flex-direction: row;
padding: $spacing-3 $spacing-4;
gap: $spacing-3;

ngx-monaco-editor {
height: 300px;
width: 100%;
border: $field-border-size solid $color-palette-gray-400;
border-radius: $border-radius-md;
display: flex;
flex-grow: 1;
}

.content-analytics__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: $spacing-3;

h4 {
font-size: $font-size-lmd;
margin: $spacing-3 0;
}
}

.content-analytics__actions {
display: flex;
gap: $spacing-3;
}

section {
flex: 1;
display: flex;
flex-direction: column;
}

.content-analytics__results {
flex: 2;
}

::ng-deep {
.editor-container {
border-radius: $border-radius-md;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,73 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { byTestId, createComponentFactory, mockProvider, Spectator } from '@ngneat/spectator/jest';
import { MockModule } from 'ng-mocks';

import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { ActivatedRoute } from '@angular/router';

import { DotAnalyticsSearchService, DotHttpErrorManagerService } from '@dotcms/data-access';

import { DotAnalyticsSearchComponent } from './dot-analytics-search.component';

import { DotAnalyticsSearchStore } from '../store/dot-analytics-search.store';

describe('DotAnalyticsSearchComponent', () => {
let component: DotAnalyticsSearchComponent;
let fixture: ComponentFixture<DotAnalyticsSearchComponent>;
let spectator: Spectator<DotAnalyticsSearchComponent>;
let store: InstanceType<typeof DotAnalyticsSearchStore>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [DotAnalyticsSearchComponent]
}).compileComponents();
const createComponent = createComponentFactory({
component: DotAnalyticsSearchComponent,
imports: [MockModule(MonacoEditorModule)],
componentProviders: [DotAnalyticsSearchStore, DotAnalyticsSearchService],
declarations: [],
mocks: [],
providers: [
provideHttpClient(),
provideHttpClientTesting(),
{
provide: ActivatedRoute,
useValue: {
snapshot: {
data: {
isEnterprise: true
}
}
}
},
mockProvider(DotHttpErrorManagerService)
]
});

fixture = TestBed.createComponent(DotAnalyticsSearchComponent);
component = fixture.componentInstance;
fixture.detectChanges();
beforeEach(() => {
spectator = createComponent();
store = spectator.inject(DotAnalyticsSearchStore, true);
});

it('should create', () => {
expect(component).toBeTruthy();
it('should initialize store with enterprise state on init', () => {
const initLoadSpy = jest.spyOn(store, 'initLoad');
spectator.component.ngOnInit();

expect(initLoadSpy).toHaveBeenCalledWith(true);
});

it('should call getResults with valid JSON', () => {
const getResultsSpy = jest.spyOn(store, 'getResults');
spectator.component.queryEditor = '{"measures": ["request.count"]}';
spectator.detectChanges();
const button = spectator.query(byTestId('run-query')) as HTMLButtonElement;
spectator.click(button);

expect(getResultsSpy).toHaveBeenCalledWith({ measures: ['request.count'] });
});

it('should not call getResults with invalid JSON', () => {
const getResultsSpy = jest.spyOn(store, 'getResults');
spectator.component.queryEditor = 'invalid json';
spectator.detectChanges();
const button = spectator.query(byTestId('run-query')) as HTMLButtonElement;
spectator.click(button);

expect(getResultsSpy).not.toHaveBeenCalled();
});
});
Loading

0 comments on commit c29be9b

Please sign in to comment.