Skip to content

Commit

Permalink
Merge branch 'main' into issue-30733-ftm-new-uve-toolbar-implement-ap…
Browse files Browse the repository at this point in the history
…i-button
  • Loading branch information
rjvelazco committed Dec 3, 2024
2 parents 5fd8f3a + 83de9e0 commit 9e1637d
Show file tree
Hide file tree
Showing 14 changed files with 998 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@
</div>

<div class="p-toolbar-group-end">
<span data-testId="uve-toolbar-running-experiment">Experiments</span>
@if ($toolbar().runningExperiment; as runningExperiment) {
<dot-ema-running-experiment
[runningExperiment]="runningExperiment"
data-testId="uve-toolbar-running-experiment" />
}

<span data-testId="uve-toolbar-language-selector">Language</span>
<span data-testId="uve-toolbar-persona-selector">Persona</span>
<span data-testId="uve-toolbar-workflow-actions">Workflows</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import { LoginService } from '@dotcms/dotcms-js';
import {
DotExperimentsServiceMock,
DotLanguagesServiceMock,
DotLicenseServiceMock
DotLicenseServiceMock,
getRunningExperimentMock
} from '@dotcms/utils-testing';

import { DotUveToolbarComponent } from './dot-uve-toolbar.component';
Expand All @@ -34,6 +35,7 @@ import {
sanitizeURL
} from '../../../utils';
import { DotEmaBookmarksComponent } from '../dot-ema-bookmarks/dot-ema-bookmarks.component';
import { DotEmaRunningExperimentComponent } from '../dot-ema-running-experiment/dot-ema-running-experiment.component';

const $apiURL = '/api/v1/page/json/123-xyz-567-xxl?host_id=123-xyz-567-xxl&language_id=1';

Expand All @@ -43,7 +45,11 @@ describe('DotUveToolbarComponent', () => {

const createComponent = createComponentFactory({
component: DotUveToolbarComponent,
imports: [HttpClientTestingModule, MockComponent(DotEmaBookmarksComponent)],
imports: [
HttpClientTestingModule,
MockComponent(DotEmaBookmarksComponent),
MockComponent(DotEmaRunningExperimentComponent)
],
providers: [
UVEStore,
provideHttpClientTesting(),
Expand Down Expand Up @@ -96,39 +102,40 @@ describe('DotUveToolbarComponent', () => {
siteId: pageAPIResponse?.site.identifier
});

const baseUVEToolbarState = {
editor: {
bookmarksUrl,
copyUrl: createFullURL(params, pageAPIResponse?.site.identifier),
apiUrl: `${'http://localhost'}${pageAPI}`
},
preview: null,
currentLanguage: pageAPIResponse?.viewAs.language,
urlContentMap: null,
runningExperiment: null,
workflowActionsInode: pageAPIResponse?.page.inode,
unlockButton: null,
showInfoDisplay: shouldShowInfoDisplay,
personaSelector: {
pageId: pageAPIResponse?.page.identifier,
value: pageAPIResponse?.viewAs.persona ?? DEFAULT_PERSONA
}
};

const baseUVEState = {
$uveToolbar: signal(baseUVEToolbarState),
setDevice: jest.fn(),
setSocialMedia: jest.fn(),
pageParams: signal(params),
pageAPIResponse: signal(MOCK_RESPONSE_VTL),
$apiURL: signal($apiURL),
reloadCurrentPage: jest.fn(),
loadPageAsset: jest.fn()
};

describe('base state', () => {
beforeEach(() => {
spectator = createComponent({
providers: [
mockProvider(UVEStore, {
$uveToolbar: signal({
editor: {
bookmarksUrl,
copyUrl: createFullURL(params, pageAPIResponse?.site.identifier),
apiUrl: `${'http://localhost'}${pageAPI}`
},
preview: null,

currentLanguage: pageAPIResponse?.viewAs.language,
urlContentMap: null,
runningExperiment: null,
workflowActionsInode: pageAPIResponse?.page.inode,
unlockButton: null,
showInfoDisplay: shouldShowInfoDisplay,
personaSelector: {
pageId: pageAPIResponse?.page.identifier,
value: pageAPIResponse?.viewAs.persona ?? DEFAULT_PERSONA
}
}),
$apiURL: signal($apiURL),
setDevice: jest.fn(),
setSocialMedia: jest.fn(),
pageParams: signal(params),
pageAPIResponse: signal(MOCK_RESPONSE_VTL),
reloadCurrentPage: jest.fn(),
loadPageAsset: jest.fn()
})
]
providers: [mockProvider(UVEStore, { ...baseUVEState })]
});

messageService = spectator.inject(MessageService);
Expand All @@ -142,6 +149,12 @@ describe('DotUveToolbarComponent', () => {
});
});

describe('dot-ema-running-experiment', () => {
it('should be null', () => {
expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeNull();
});
});

it('should have preview button', () => {
expect(spectator.query(byTestId('uve-toolbar-preview'))).toBeTruthy();
});
Expand Down Expand Up @@ -204,4 +217,26 @@ describe('DotUveToolbarComponent', () => {
});
});
});

describe('State changes', () => {
describe('Experiment is running', () => {
beforeEach(() => {
const state = {
...baseUVEState,
$uveToolbar: signal({
...baseUVEToolbarState,
runningExperiment: getRunningExperimentMock()
})
};

spectator = createComponent({
providers: [mockProvider(UVEStore, { ...state })]
});
});

it('should have experiment running component', () => {
expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeTruthy();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DotMessageService } from '@dotcms/data-access';
import { UVEStore } from '../../../store/dot-uve.store';
import { DotEmaBookmarksComponent } from '../dot-ema-bookmarks/dot-ema-bookmarks.component';
import { DotEmaInfoDisplayComponent } from '../dot-ema-info-display/dot-ema-info-display.component';
import { DotEmaRunningExperimentComponent } from '../dot-ema-running-experiment/dot-ema-running-experiment.component';

@Component({
selector: 'dot-uve-toolbar',
Expand All @@ -19,6 +20,7 @@ import { DotEmaInfoDisplayComponent } from '../dot-ema-info-display/dot-ema-info
ToolbarModule,
DotEmaBookmarksComponent,
DotEmaInfoDisplayComponent,
DotEmaRunningExperimentComponent,
ClipboardModule
],
templateUrl: './dot-uve-toolbar.component.html',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export interface DotPageApiResponse {
containers: DotPageContainerStructure;
urlContentMap?: DotCMSContentlet;
vanityUrl?: VanityUrl;
runningExperimentId?: string;
}

export interface DotPageApiParams {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,13 @@ export function withLoad() {
});
}
}),
switchMap(({ pageAsset, isEnterprise, currentUser }) =>
forkJoin({
switchMap(({ pageAsset, isEnterprise, currentUser }) => {
const experimentId =
pageParams?.experimentId ?? pageAsset?.runningExperimentId;

return forkJoin({
experiment: dotExperimentsService.getById(
pageParams?.experimentId || DEFAULT_VARIANT_ID
experimentId ?? DEFAULT_VARIANT_ID
),
languages: dotLanguagesService.getLanguagesUsedPage(
pageAsset.page.identifier
Expand Down Expand Up @@ -145,8 +148,8 @@ export function withLoad() {
});
}
})
)
)
);
})
);
})
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.dotmarketing.exception.DotSecurityException;
import com.dotmarketing.portlets.contentlet.action.ImportAuditUtil;
import com.dotmarketing.portlets.languagesmanager.model.Language;
import com.dotmarketing.portlets.workflows.model.WorkflowAction;
import com.dotmarketing.util.AdminLogger;
import com.dotmarketing.util.ImportUtil;
import com.dotmarketing.util.Logger;
Expand All @@ -38,12 +39,7 @@
import java.io.Reader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.LongConsumer;

Expand Down Expand Up @@ -198,6 +194,13 @@ && getWorkflowActionId(parameters).isEmpty()) {
// Make sure the content type exist (will throw an exception if it doesn't)
final var contentTypeFound = findContentType(parameters);

// Make sure the workflow action exist (will throw an exception if it doesn't)
findWorkflowAction(parameters);

// Make sure the fields exist in the content type (will throw an exception if it doesn't)
validateFields(parameters, contentTypeFound);


// Security measure to prevent invalid attempts to import a host.
final ContentType hostContentType = APILocator.getContentTypeAPI(
APILocator.systemUser()).find(Host.HOST_VELOCITY_VAR_NAME);
Expand All @@ -212,6 +215,30 @@ && getWorkflowActionId(parameters).isEmpty()) {
}
}

/**
* Validates that the fields specified in the job parameters exist in the given content type.
*
* <p>This method checks each field specified in the job parameters against the fields defined
* in the provided content type. If any field is not found in the content type, a
* {@link JobValidationException} is thrown.</p>
*
* @param parameters The job parameters containing the fields to validate
* @param contentTypeFound The content type to validate the fields against
* @throws JobValidationException if any field specified in the parameters is not found in the content type
*/
private void validateFields(final Map<String, Object> parameters, final ContentType contentTypeFound) {
var fields = contentTypeFound.fields();
for (String field : getFields(parameters)) {
if (fields.stream().noneMatch(f -> Objects.equals(f.variable(), field))) {
final var errorMessage = String.format(
"Field [%s] not found in Content Type [%s].", field, contentTypeFound.variable()
);
Logger.error(this, errorMessage);
throw new JobValidationException(errorMessage);
}
}
}

/**
* Handles cancellation requests for the import operation. When called, it marks the operation
* for cancellation.
Expand Down Expand Up @@ -618,6 +645,56 @@ private ContentType findContentType(final Map<String, Object> parameters)
}
}


/**
* Finds and returns a workflow action based on the provided parameters.
*
* <p>This method retrieves the workflow action ID from the given parameters and attempts to
* find the corresponding workflow action using the Workflow API.
*
*
* @param parameters a map containing parameters required for finding the workflow action,
* including the workflow action ID and user details.
*
* @return the {@link WorkflowAction} corresponding to the workflow action ID.
*
* @throws JobValidationException if the workflow action cannot be found.
* @throws JobProcessingException if an error occurs during user retrieval or
* workflow action lookup.
*/
private WorkflowAction findWorkflowAction(final Map<String, Object> parameters) {

final var workflowActionId = getWorkflowActionId(parameters);
final User user;

try {
user = getUser(parameters);
} catch (DotDataException | DotSecurityException e) {
final var errorMessage = "Error retrieving user.";
Logger.error(this.getClass(), errorMessage);
throw new JobProcessingException(errorMessage, e);
}

try {
var workflowAction = APILocator.getWorkflowAPI()
.findAction(workflowActionId,user);
if(Objects.isNull(workflowAction)){
final var errorMessage = String.format(
"Workflow Action [%s] not found.", workflowActionId
);
Logger.error(this.getClass(), errorMessage);
throw new JobValidationException(errorMessage);
}
return workflowAction;
} catch (DotDataException | DotSecurityException e) {
final var errorMessage = String.format(
"Error finding Workflow Action [%s].", workflowActionId
);
Logger.error(this.getClass(), errorMessage);
throw new JobProcessingException(errorMessage, e);
}
}

/**
* Retrieves the existing language based on an id or ISO code.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.dotcms.rest;

import com.dotcms.jobs.business.job.Job;

public class ResponseEntityJobView extends ResponseEntityView<Job> {
public ResponseEntityJobView(Job entity) {
super(entity);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dotcms.rest.api.v1.content.dotimport;

import com.dotcms.jobs.business.api.JobQueueManagerAPI;
import com.dotcms.jobs.business.job.Job;
import com.dotcms.rest.api.v1.JobQueueManagerHelper;
import com.dotcms.rest.api.v1.temp.DotTempFile;
import com.dotmarketing.business.APILocator;
Expand Down Expand Up @@ -95,6 +96,16 @@ public String createJob(
return jobQueueManagerAPI.createJob(queueName, jobParameters);
}

/**
* gets a job
* @param jobId The ID of the job
* @return Job
* @throws DotDataException if there's an error fetching the job
*/
Job getJob(final String jobId) throws DotDataException {
return jobQueueManagerAPI.getJob(jobId);
}

/**
* Constructs a map of job parameters based on the provided inputs.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,12 @@ public void checkValid() {
if (contentDisposition == null || contentDisposition.getFileName() == null) {
throw new ValidationException("The file must have a valid file name.");
}
if (!isCsvFile(contentDisposition.getFileName())) {
throw new ValidationException("The file must be a CSV file.");
}
}

private boolean isCsvFile(final String fileName) {
return fileName != null && fileName.toLowerCase().endsWith(".csv");
}
}
Loading

0 comments on commit 9e1637d

Please sign in to comment.