Skip to content

fix: update quick action on file change on workspace #2755

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/lemon-eels-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@sap-ux-private/control-property-editor-common': patch
'@sap-ux-private/preview-middleware-client': patch
'@sap-ux/control-property-editor': patch
'@sap-ux/preview-middleware': patch
---

fix: update quick action list on external changes
5 changes: 5 additions & 0 deletions .changeset/plenty-mails-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sap-ux/adp-tooling': patch
---

fix: remove unused api routes and implementation for annotation file creation
3 changes: 2 additions & 1 deletion packages/adp-tooling/src/base/change-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export function writeAnnotationChange(
TemplateFileName.Annotation
);
const { namespaces, serviceUrl } = annotation;
renderFile(annotationsTemplate, { namespaces, path: serviceUrl }, {}, (err, str) => {
const schemaNamespace = `local_${timestamp}`;
renderFile(annotationsTemplate, { namespaces, path: serviceUrl, schemaNamespace }, {}, (err, str) => {
if (err) {
throw new Error('Error rendering template: ' + err.message);
}
Expand Down
1 change: 0 additions & 1 deletion packages/adp-tooling/src/preview/adp-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,6 @@ export class AdpPreview {
router.post(ApiRoutes.CONTROLLER, this.routesHandler.handleWriteControllerExt as RequestHandler);

router.get(ApiRoutes.CODE_EXT, this.routesHandler.handleGetControllerExtensionData as RequestHandler);
router.post(ApiRoutes.ANNOTATION, this.routesHandler.handleCreateAnnotationFile as RequestHandler);
router.get(
ApiRoutes.ANNOTATION,
this.routesHandler.handleGetAllAnnotationFilesMappedByDataSource as RequestHandler
Expand Down
68 changes: 13 additions & 55 deletions packages/adp-tooling/src/preview/routes-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ import type { NextFunction, Request, Response } from 'express';

import { TemplateFileName, HttpStatusCodes } from '../types';
import { DirName, FileName } from '@sap-ux/project-access';
import { ChangeType, type CodeExtChange } from '../types';
import { generateChange } from '../writer/editors';
import { type CodeExtChange } from '../types';
import { ManifestService } from '../base/abap/manifest-service';
import type { DataSources } from '../base/abap/manifest-service';
import { getAdpConfig, getVariant } from '../base/helper';
import { getAnnotationNamespaces } from '@sap-ux/odata-service-writer';
import { createAbapServiceProvider } from '@sap-ux/system-access';

interface WriteControllerBody {
Expand All @@ -34,10 +32,13 @@ interface AnnotationFileDetails {
interface AnnotationDataSourceMap {
[key: string]: {
serviceUrl: string;
isRunningInBAS: boolean;
annotationDetails: AnnotationFileDetails;
};
}
export interface AnnotationDataSourceResponse {
isRunningInBAS: boolean;
annotationDataSourceMap: AnnotationDataSourceMap;
}

/**
* @description Handles API Routes
Expand Down Expand Up @@ -265,51 +266,6 @@ export default class RoutesHandler {
next(e);
}
};
/**
* Handler for writing an annotation file to the workspace.
*
* @param req Request
* @param res Response
* @param next Next Function
*/
public handleCreateAnnotationFile = async (req: Request, res: Response, next: NextFunction) => {
try {
const { dataSource, serviceUrl } = req.body as { dataSource: string; serviceUrl: string };

if (!dataSource) {
res.status(HttpStatusCodes.BAD_REQUEST).send('No datasource found in manifest!');
this.logger.debug('Bad request. Could not find a datasource in manifest!');
return;
}
const project = this.util.getProject();
const projectRoot = project.getRootPath();
const manifestService = await this.getManifestService();
const metadata = await manifestService.getDataSourceMetadata(dataSource);
const namespaces = getAnnotationNamespaces({ metadata });
const fsEditor = await generateChange<ChangeType.ADD_ANNOTATIONS_TO_ODATA>(
projectRoot,
ChangeType.ADD_ANNOTATIONS_TO_ODATA,
{
annotation: {
dataSource,
namespaces,
serviceUrl: serviceUrl
},
variant: getVariant(projectRoot),
isCommand: false
}
);
fsEditor.commit((err) => this.logger.error(err));

const message = 'Annotation file created!';
res.status(HttpStatusCodes.CREATED).send(message);
} catch (e) {
const sanitizedMsg = sanitize(e.message);
this.logger.error(sanitizedMsg);
res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).send(sanitizedMsg);
next(e);
}
};

/**
* Handler for mapping annotation files with datasoruce.
Expand All @@ -324,19 +280,21 @@ export default class RoutesHandler {

const manifestService = await this.getManifestService();
const dataSources = manifestService.getManifestDataSources();
const apiResponse: AnnotationDataSourceMap = {};
const apiResponse: AnnotationDataSourceResponse = {
isRunningInBAS,
annotationDataSourceMap: {}
};

for (const dataSourceId in dataSources) {
if (dataSources[dataSourceId].type === 'OData') {
apiResponse[dataSourceId] = {
apiResponse.annotationDataSourceMap[dataSourceId] = {
annotationDetails: {
annotationExistsInWS: false
},
serviceUrl: dataSources[dataSourceId].uri,
isRunningInBAS: isRunningInBAS
serviceUrl: dataSources[dataSourceId].uri
};
}
this.fillAnnotationDataSourceMap(dataSources, dataSourceId, apiResponse);
this.fillAnnotationDataSourceMap(dataSources, dataSourceId, apiResponse.annotationDataSourceMap);
}
this.sendFilesResponse(res, apiResponse);
} catch (e) {
Expand All @@ -358,7 +316,7 @@ export default class RoutesHandler {
): void {
const project = this.util.getProject();
const getPath = (projectPath: string, relativePath: string): string =>
path.join(projectPath, DirName.Changes, relativePath).split(path.sep).join(path.posix.sep);
path.join(projectPath, relativePath).split(path.sep).join(path.posix.sep);
const annotations = [...(dataSources[dataSourceId].settings?.annotations ?? [])].reverse();
for (const annotation of annotations) {
const annotationSetting = dataSources[annotation];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class AnnotationsWriter implements IWriter<AnnotationsData> {
annotationsInsertPosition: 'END',
dataSource: {
[annotationNameSpace]: {
uri: `../annotations/${fileName}`,
uri: `annotations/${fileName}`,
type: 'ODataAnnotation'
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/adp-tooling/templates/changes/annotation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<edmx:Include Namespace="<%- namespace.namespace %>"<% if (namespace.alias) { %> Alias="<%- namespace.alias %>"<% } %>/><% }); %>
</edmx:Reference>
<edmx:DataServices>
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="local">
<Schema xmlns="http://docs.oasis-open.org/odata/ns/edm" Namespace="<%- schemaNamespace %>">
</Schema>
</edmx:DataServices>
</edmx:Edmx>
3 changes: 2 additions & 1 deletion packages/adp-tooling/test/unit/base/change-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,8 @@ describe('Change Utils', () => {
alias: 'mockAlias'
}
],
path: '/path/to/odata'
path: '/path/to/odata',
schemaNamespace: `local_123456789`
}),
expect.objectContaining({}),
expect.any(Function)
Expand Down
16 changes: 1 addition & 15 deletions packages/adp-tooling/test/unit/preview/adp-preview.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -709,26 +709,12 @@ describe('AdaptationProject', () => {
expect(e.message).toEqual(errorMsg);
}
});
test('POST /adp/api/annotation - throws error when controller name is undefined', async () => {
const response = await server.post('/adp/api/annotation').send({}).expect(400);
const message = response.text;
expect(message).toBe('No datasource found in manifest!');
});
test('POST /adp/api/annotation', async () => {
const response = await server
.post('/adp/api/annotation')
.send({ dataSource: 'exampleDataSource', serviceUrl: 'sap/opu/data' })
.expect(201);

const message = response.text;
expect(message).toBe('Annotation file created!');
});
test('GET /adp/api/annotation', async () => {
const response = await server.get('/adp/api/annotation').send().expect(200);

const message = response.text;
expect(message).toMatchInlineSnapshot(
`"{\\"mainService\\":{\\"annotationDetails\\":{\\"fileName\\":\\"annotation0.xml\\",\\"annotationPath\\":\\"//adp.project/webapp/changes/annotation0.xml\\",\\"annotationPathFromRoot\\":\\"adp.project/changes/annotation0.xml\\"},\\"serviceUrl\\":\\"main/service/uri\\",\\"isRunningInBAS\\":false},\\"secondaryService\\":{\\"annotationDetails\\":{\\"annotationExistsInWS\\":false},\\"serviceUrl\\":\\"secondary/service/uri\\",\\"isRunningInBAS\\":false}}"`
`"{\\"isRunningInBAS\\":false,\\"annotationDataSourceMap\\":{\\"mainService\\":{\\"annotationDetails\\":{\\"fileName\\":\\"annotation0.xml\\",\\"annotationPath\\":\\"//adp.project/webapp/annotation0.xml\\",\\"annotationPathFromRoot\\":\\"adp.project/annotation0.xml\\"},\\"serviceUrl\\":\\"main/service/uri\\"},\\"secondaryService\\":{\\"annotationDetails\\":{\\"annotationExistsInWS\\":false},\\"serviceUrl\\":\\"secondary/service/uri\\"}}}"`
);
});
});
Expand Down
4 changes: 3 additions & 1 deletion packages/control-property-editor-common/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ export const quickActionListChanged = createExternalAction<QuickActionGroup[]>('
export const updateQuickAction = createExternalAction<QuickAction>('update-quick-action');
export const executeQuickAction = createExternalAction<QuickActionExecutionPayload>('execute-quick-action');
export const setApplicationRequiresReload = createExternalAction<boolean>('set-application-requires-reload');
export const externalFileChange = createExternalAction<string>('external-file-change');

export type ExternalAction =
| ReturnType<typeof iconsLoaded>
Expand All @@ -403,4 +404,5 @@ export type ExternalAction =
| ReturnType<typeof quickActionListChanged>
| ReturnType<typeof setApplicationRequiresReload>
| ReturnType<typeof updateQuickAction>
| ReturnType<typeof executeQuickAction>;
| ReturnType<typeof executeQuickAction>
| ReturnType<typeof externalFileChange>;
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
changeStackModified,
controlSelected,
deletePropertyChanges,
externalFileChange,
iconsLoaded,
outlineChanged,
propertyChangeFailed,
Expand Down Expand Up @@ -168,4 +169,12 @@ describe('createExternalAction', () => {
expect(changedProp.type).toBe('[ext] add-extension-point');
expect(changedProp.payload).toStrictEqual(payload);
});

test('externalFileChange', () => {
const payload = 'filePath';

const externalFile = externalFileChange(payload);
expect(externalFile.type).toBe('[ext] external-file-change');
expect(externalFile.payload).toStrictEqual('filePath');
});
});
6 changes: 4 additions & 2 deletions packages/control-property-editor/src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
save,
setAppMode,
executeQuickAction,
appLoaded
appLoaded,
externalFileChange
} from '@sap-ux-private/control-property-editor-common';

import type reducer from './slice';
Expand Down Expand Up @@ -68,7 +69,8 @@ export const communicationMiddleware: Middleware<Dispatch<ExternalAction>, Retur
case redo.type:
case save.type:
case selectControl.type:
case addExtensionPoint.type: {
case addExtensionPoint.type:
case externalFileChange.type: {
sendAction(action);
break;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/control-property-editor/src/slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface SliceState {
changes: ChangesSlice;
dialogMessage: ShowMessage | undefined;
fileChanges?: string[];
lastExternalFileChangeTimestamp?: number;
appMode: 'navigation' | 'adaptation';
changeStack: {
canUndo: boolean;
Expand Down Expand Up @@ -365,6 +366,9 @@ const slice = createSlice<SliceState, SliceCaseReducers<SliceState>, string>({
}
return idx < 0;
});
if (newFileChanges.length) {
state.lastExternalFileChangeTimestamp = Date.now();
}
if (!state.fileChanges) {
state.fileChanges = newFileChanges;
} else {
Expand Down
13 changes: 11 additions & 2 deletions packages/control-property-editor/src/ws-middleware.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import type { Dispatch } from 'redux';
import type { AnyAction, Dispatch } from 'redux';
import type { Middleware, MiddlewareAPI } from '@reduxjs/toolkit';

import type { Action } from './actions';
import type reducer from './slice';
import { fileChanged, initializeLivereload } from './slice';
import { externalFileChange } from '@sap-ux-private/control-property-editor-common';

/**
* Communication between preview iframe and main application is realized through the communication middleware.
*
* @param store - redux store
* @returns Function
*/
export const webSocketMiddleware: Middleware<Dispatch<Action>> = (store: MiddlewareAPI) => {
export const webSocketMiddleware: Middleware<Dispatch<Action>> = (
store: MiddlewareAPI<Dispatch<AnyAction>, ReturnType<typeof reducer>>
) => {
return (next: Dispatch<Action>) =>
(action: Action): Action => {
if (initializeLivereload.match(action)) {
Expand All @@ -22,7 +26,12 @@ export const webSocketMiddleware: Middleware<Dispatch<Action>> = (store: Middlew
socket.addEventListener('message', (event) => {
const request = JSON.parse(event.data);
if (request.command === 'reload') {
const timestampBefore = store.getState().lastExternalFileChangeTimestamp;
store.dispatch(fileChanged([request.path]));
const timestampAfter = store.getState().lastExternalFileChangeTimestamp;
if (timestampAfter !== timestampBefore) {
store.dispatch(externalFileChange(request.path));
}
}
});
}
Expand Down
17 changes: 17 additions & 0 deletions packages/control-property-editor/test/unit/middleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,4 +233,21 @@ describe('communication middleware', () => {
`);
expect(sendActionfn).toHaveBeenCalledTimes(1);
});
test('externalFileChange - send action', () => {
const action = common.externalFileChange('file-path');
const next = jest.fn().mockReturnValue(action);
jest.mock('@sap-ux-private/control-property-editor-common', () => {
return {
externalFileChange: { type: '[ext] external-file-change' }
};
});
const result = middleWare(next)(action);
expect(result).toMatchInlineSnapshot(`
Object {
"payload": "file-path",
"type": "[ext] external-file-change",
}
`);
expect(sendActionfn).toHaveBeenCalledTimes(1);
});
});
10 changes: 6 additions & 4 deletions packages/control-property-editor/test/unit/slice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ import reducer, {
changeDeviceType,
setProjectScenario,
fileChanged,
initialState,
setFeatureToggles
} from '../../src/slice';
import { DeviceType } from '../../src/devices';
import { features } from 'process';

describe('main redux slice', () => {
describe('property changed', () => {
Expand Down Expand Up @@ -534,6 +532,7 @@ describe('main redux slice', () => {
});

test('external changes (scenario 1)', () => {
jest.spyOn(Date, 'now').mockReturnValue(1736392383604);
expect(
reducer(
{
Expand All @@ -548,11 +547,13 @@ describe('main redux slice', () => {
)
).toStrictEqual({
'changes': { 'controls': [], 'pending': [], 'pendingChangeIds': ['testFile1'], 'saved': [] },
'fileChanges': ['testFile2']
'fileChanges': ['testFile2'],
'lastExternalFileChangeTimestamp': 1736392383604
});
});

test('external changes (scenario 2)', () => {
jest.spyOn(Date, 'now').mockReturnValue(12333434312);
expect(
reducer(
{
Expand All @@ -568,7 +569,8 @@ describe('main redux slice', () => {
)
).toStrictEqual({
'changes': { 'controls': [], 'pending': [], 'pendingChangeIds': ['testFile1'], 'saved': [] },
'fileChanges': ['testFile3', 'testFile2']
'fileChanges': ['testFile3', 'testFile2'],
'lastExternalFileChangeTimestamp': 12333434312
});
});
});
Expand Down
Loading
Loading