Skip to content

Commit

Permalink
fix: local and remote annotation updated
Browse files Browse the repository at this point in the history
  • Loading branch information
broksy committed Feb 13, 2025
1 parent 0464e15 commit e26056e
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 151 deletions.
26 changes: 0 additions & 26 deletions packages/odata-service-writer/src/data/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,32 +153,6 @@ export async function removeAnnotationsFromCDSFiles(
}
}

/**
* Removes local copies of metadata.xml and local annotations.
*
* @param {Editor} fs - the memfs editor instance
* @param {string} basePath - the root path of an existing UI5 application
* @param {string} webappPath - the webapp path of an existing UI5 application
* @param {OdataService} service - the OData service instance with EDMX type
*/
export async function removeLocalServiceAnnotationFiles(
fs: Editor,
basePath: string,
webappPath: string,
service: EdmxOdataService
): Promise<void> {
const localMetadaPath = join(webappPath, 'localService', service.name ?? 'mainService', 'metadata.xml');
if (fs.exists(localMetadaPath)) {
fs.delete(localMetadaPath);
}
if (service.localAnnotationsName) {
const localAnnotationsPath = join(basePath, 'webapp', 'annotations', `${service.localAnnotationsName}.xml`);
if (fs.exists(localAnnotationsPath)) {
fs.delete(localAnnotationsPath);
}
}
}

/**
* Writes local copies of metadata.xml and local annotations.
*
Expand Down
190 changes: 122 additions & 68 deletions packages/odata-service-writer/src/data/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,22 @@ function enhanceManifestDatasources(
serviceLocalAnnotations
} = dataSourceUpdateSettings;
const dataSources = manifest?.['sap.app'].dataSources ?? {};
const { annotations, annotationDataSources } = getDataSourceAnnotations(
const createdRemoteAnnotationDataSources = addRemoteAnnotationDataSources(
fs,
webappPath,
dataSources,
serviceName,
serviceRemoteAnnotations,
serviceLocalAnnotations
forceServiceUpdate
);
const createdLocalAnnotationDataSources = addLocalAnnotationDataSources(
dataSources,
serviceName,
serviceLocalAnnotations,
forceServiceUpdate
);
// Annotation dataSources used by service - remote and local annotations are handled differently
let previousAnnotationNames: string[] = [];
if (dataSources[serviceName]?.settings?.annotations && forceServiceUpdate) {
previousAnnotationNames = dataSources[serviceName].settings?.annotations;
}
const settings = {
annotations
annotations: [...createdRemoteAnnotationDataSources, ...createdLocalAnnotationDataSources]
};
if (serviceMetadata) {
settings['localUri'] = `localService/${serviceName}/metadata.xml`;
Expand All @@ -67,22 +71,6 @@ function enhanceManifestDatasources(
type: 'OData',
settings
};
// Create or update service annotation dataSources in manifest.json for service
for (const name in annotationDataSources) {
const annotationDataSource = annotationDataSources[name];
dataSources[name] = annotationDataSource;
}
// Clean old annotations in case of an update
previousAnnotationNames.forEach((name) => {
if (dataSources[name] && !annotations.includes(name)) {
const oldAnnotationPath = dataSources[name].settings?.localUri;
// Delete old annotation files
if (oldAnnotationPath && fs.exists(join(webappPath, oldAnnotationPath))) {
fs.delete(join(webappPath, oldAnnotationPath));
}
delete dataSources[name];
}
});
// Update manifest.json dataSources
manifest['sap.app'].dataSources = dataSources;
}
Expand Down Expand Up @@ -157,30 +145,116 @@ function getModelSettings(minUI5Version: string | undefined): { includeSynchroni
}

/**
* Populates annotations as manifest dataSources.
* Adds local annotations to manifest dataSources.
*
* @param {string} serviceName - name of the OData service instance
* @param {EdmxAnnotationsInfo | EdmxAnnotationsInfo[]} serviceRemoteAnnotations - remote annotations of the OData service instance
* @param dataSources - dataSources from manifest.json
* @param serviceName - name of the OData service instance
* @param {string | string[]} serviceLocalAnnotations - local annotations of the OData service instance
* @returns annotations list and annotation dataSources.
* @param {boolean} useOldAnnotations - if true, uses already existing local annotations for service
* @returns created annotation dataSources list for service.
*/
function getDataSourceAnnotations(
function addLocalAnnotationDataSources(
dataSources: { [k: string]: ManifestNamespace.DataSource },
serviceName: string,
serviceLocalAnnotations?: string | string[],
useOldAnnotations?: boolean
): string[] {
const createdAnnotations: string[] = [];
if (useOldAnnotations) {
// Update old annotations
const serviceAnnotations = dataSources?.[serviceName]?.settings?.annotations ?? [];
serviceAnnotations.forEach((name) => {
const dataSource = dataSources[name];
// make sure we only work with local annotations for current service
if (dataSource?.type === 'ODataAnnotation' && dataSource.uri === dataSource.settings?.localUri) {
createdAnnotations.push(name);
}
});
} else if (Array.isArray(serviceLocalAnnotations)) {
serviceLocalAnnotations.forEach((localAnnotation: string) => {
dataSources[localAnnotation] = {
type: 'ODataAnnotation',
uri: `annotations/${localAnnotation}.xml`,
settings: {
localUri: `annotations/${localAnnotation}.xml`
}
};
createdAnnotations.push(localAnnotation);
});
} else if (serviceLocalAnnotations) {
dataSources[serviceLocalAnnotations] = {
type: 'ODataAnnotation',
uri: `annotations/${serviceLocalAnnotations}.xml`,
settings: {
localUri: `annotations/${serviceLocalAnnotations}.xml`
}
};
createdAnnotations.push(serviceLocalAnnotations);
}
return createdAnnotations;
}

/**
* Removes unused service annotations from manifest dataSources and unused annotation files.
*
* @param {Editor} fs - the memfs editor instance
* @param {string} webappPath - the webapp path of an existing UI5 application
* @param dataSources - dataSources from manifest.json
* @param serviceName - name of the OData service instance
* @param createdAnnotations - name of the OData service instance
*/
function removeUnusedAnnotations(
fs: Editor,
webappPath: string,
dataSources: { [k: string]: ManifestNamespace.DataSource },
serviceName: string,
createdAnnotations: string[]
): void {
// Clean unused annotations
const serviceAnnotations = dataSources?.[serviceName].settings?.annotations ?? [];
serviceAnnotations.forEach((name) => {
const dataSource = dataSources[name];
// make sure we only work with remote annotations for current service
if (
dataSource?.type === 'ODataAnnotation' &&
dataSource.uri !== dataSource.settings?.localUri &&
!createdAnnotations.includes(name)
) {
const oldAnnotationPath = dataSource.settings?.localUri;
// Delete old annotation files
if (oldAnnotationPath && fs.exists(join(webappPath, oldAnnotationPath))) {
fs.delete(join(webappPath, oldAnnotationPath));
}
// Delete old annotation dataSource netry
delete dataSources[name];
}
});
}

/**
* Adds remote annotations to manifest dataSources and removes unused annotations by the service.
*
* @param {Editor} fs - the memfs editor instance
* @param {string} webappPath - the webapp path of an existing UI5 application
* @param dataSources - dataSources from manifest.json
* @param serviceName - name of the OData service instance
* @param {EdmxAnnotationsInfo | EdmxAnnotationsInfo[]} serviceRemoteAnnotations - remote annotations of the OData service instance
* @param {boolean} cleanOldAnnotations - if true, checks and updates service annotations
* @returns created annotation dataSources list for service.
*/
function addRemoteAnnotationDataSources(
fs: Editor,
webappPath: string,
dataSources: { [k: string]: ManifestNamespace.DataSource },
serviceName: string,
serviceRemoteAnnotations?: EdmxAnnotationsInfo | EdmxAnnotationsInfo[],
serviceLocalAnnotations?: string | string[]
): {
annotations: string[];
annotationDataSources: { [k: string]: ManifestNamespace.DataSource };
} {
const annotations: string[] = [];
// Service annotation names to be stored in service settings of dataSource
const annotationDataSources: { [k: string]: ManifestNamespace.DataSource } = {};
// Handle remote annotations used by service
cleanOldAnnotations?: boolean
): string[] {
const createdAnnotations: string[] = [];
if (Array.isArray(serviceRemoteAnnotations)) {
serviceRemoteAnnotations.forEach((remoteAnnotation) => {
if (remoteAnnotation.name) {
annotations.push(remoteAnnotation.name);
annotationDataSources[remoteAnnotation.name] = {
dataSources[remoteAnnotation.name] = {
uri: `/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Annotations(TechnicalName='${encodeURIComponent(
remoteAnnotation.technicalName
)}',Version='0001')/$value/`,
Expand All @@ -189,11 +263,11 @@ function getDataSourceAnnotations(
localUri: `localService/${serviceName}/${remoteAnnotation.technicalName}.xml`
}
};
createdAnnotations.push(remoteAnnotation.name);
}
});
} else if (serviceRemoteAnnotations?.name) {
annotations.push(serviceRemoteAnnotations.name);
annotationDataSources[serviceRemoteAnnotations.name] = {
dataSources[serviceRemoteAnnotations.name] = {
uri: `/sap/opu/odata/IWFND/CATALOGSERVICE;v=2/Annotations(TechnicalName='${encodeURIComponent(
serviceRemoteAnnotations.technicalName
)}',Version='0001')/$value/`,
Expand All @@ -202,33 +276,13 @@ function getDataSourceAnnotations(
localUri: `localService/${serviceName}/${serviceRemoteAnnotations.technicalName}.xml`
}
};
createdAnnotations.push(serviceRemoteAnnotations.name);
}
// Handle local annotations used by service
if (Array.isArray(serviceLocalAnnotations)) {
serviceLocalAnnotations.forEach((localAnnotation: string) => {
annotations.push(localAnnotation);
annotationDataSources[localAnnotation] = {
type: 'ODataAnnotation',
uri: `annotations/${localAnnotation}.xml`,
settings: {
localUri: `annotations/${localAnnotation}.xml`
}
};
});
} else if (serviceLocalAnnotations) {
annotations.push(serviceLocalAnnotations);
annotationDataSources[serviceLocalAnnotations] = {
type: 'ODataAnnotation',
uri: `annotations/${serviceLocalAnnotations}.xml`,
settings: {
localUri: `annotations/${serviceLocalAnnotations}.xml`
}
};
if (cleanOldAnnotations) {
// Clean unused annotations
removeUnusedAnnotations(fs, webappPath, dataSources, serviceName, createdAnnotations);
}
return {
annotations,
annotationDataSources
};
return createdAnnotations;
}

/**
Expand Down Expand Up @@ -360,7 +414,7 @@ function removeFileForDataSource(fs: Editor, manifestPath: string, dataSource: M
* @param fs - the memfs editor instance
* @param manifestPath - the root path of an existing UI5 application
* @param annotations - annotations list
* @param dataSources - list of dataSources from manifest.json
* @param dataSources - dataSources from manifest.json
*/
function removeAnnotations(
fs: Editor,
Expand Down
4 changes: 1 addition & 3 deletions packages/odata-service-writer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,10 @@ async function update(basePath: string, service: OdataService, fs?: Editor): Pro
await enhanceData(basePath, service, fs);
// Set isServiceTypeEdmx true if service is EDMX
const isServiceTypeEdmx = service.type === ServiceType.EDMX;
// Prepare template folder for manifest and xml updates
const templateRoot = join(__dirname, '../templates');
await updateManifest(basePath, service, fs, true);
// Dont extend/update backend and mockserver middlewares if service type is CDS
if (isServiceTypeEdmx) {
await updateServicesData(basePath, paths, templateRoot, service as EdmxOdataService, fs);
await updateServicesData(basePath, paths, service as EdmxOdataService, fs);
}
return fs;
}
Expand Down
57 changes: 28 additions & 29 deletions packages/odata-service-writer/src/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import { FileName, getWebappPath } from '@sap-ux/project-access';
import type { CustomMiddleware, FioriToolsProxyConfigBackend as ProxyBackend } from '@sap-ux/ui5-config';
import { UI5Config, YAMLError, yamlErrorCode } from '@sap-ux/ui5-config';
import { generateMockserverConfig } from '@sap-ux/mockserver-config-writer';
import {
removeLocalServiceAnnotationFiles,
writeLocalServiceAnnotationXMLFiles,
writeRemoteServiceAnnotationXmlFiles
} from './data/annotations';
import { writeLocalServiceAnnotationXMLFiles, writeRemoteServiceAnnotationXmlFiles } from './data/annotations';
import { updatePackageJson } from './data/package';

/**
Expand Down Expand Up @@ -135,14 +131,12 @@ export async function addServicesData(
*
* @param {string} basePath - the root path of an existing UI5 application
* @param {ProjectPaths} paths - paths to the project files (package.json, ui5.yaml, ui5-local.yaml and ui5-mock.yaml)
* @param {string} templateRoot - path to the file templates
* @param {EdmxOdataService} service - the OData service instance
* @param {Editor} fs - the memfs editor instance
*/
export async function updateServicesData(
basePath: string,
paths: ProjectPaths,
templateRoot: string,
service: EdmxOdataService,
fs: Editor
): Promise<void> {
Expand All @@ -155,32 +149,37 @@ export async function updateServicesData(
ui5LocalConfig = await UI5Config.newInstance(fs.read(paths.ui5LocalYaml));
}
// For update, updatable files should already exist
if (service.metadata && paths.ui5MockYaml) {
if (service.metadata) {
const webappPath = await getWebappPath(basePath, fs);
if (paths.ui5Yaml && ui5Config) {
const config = {
webappPath: webappPath,
// Since ui5-mock.yaml already exists, set 'skip' to skip package.json file updates
packageJsonConfig: {
skip: true
},
// Set 'overwrite' to true to overwrite services data in YAML files
ui5MockYamlConfig: {
overwrite: true
// Generate mockserver only when ui5-mock.yaml already exists
if (paths.ui5MockYaml) {
if (paths.ui5Yaml && ui5Config) {
const config = {
webappPath: webappPath,
// Since ui5-mock.yaml already exists, set 'skip' to skip package.json file updates
packageJsonConfig: {
skip: true
},
// Set 'overwrite' to true to overwrite services data in YAML files
ui5MockYamlConfig: {
overwrite: true
}
};
// Regenerate mockserver middleware for ui5-mock.yaml by overwriting
await generateMockserverConfig(basePath, config, fs);
// Update ui5-local.yaml with mockserver middleware from updated ui5-mock.yaml
await generateMockserverMiddlewareBasedOnUi5MockYaml(
fs,
paths.ui5Yaml,
paths.ui5LocalYaml,
ui5LocalConfig
);
if (paths.ui5LocalYaml && ui5LocalConfig) {
// write ui5 local yaml if service type is not CDS
fs.write(paths.ui5LocalYaml, ui5LocalConfig.toString());
}
};
// Regenerate mockserver middleware for ui5-mock.yaml by overwriting
await generateMockserverConfig(basePath, config, fs);
// Update ui5-local.yaml with mockserver middleware from updated ui5-mock.yaml
await generateMockserverMiddlewareBasedOnUi5MockYaml(fs, paths.ui5Yaml, paths.ui5LocalYaml, ui5LocalConfig);
if (paths.ui5LocalYaml && ui5LocalConfig) {
// write ui5 local yaml if service type is not CDS
fs.write(paths.ui5LocalYaml, ui5LocalConfig.toString());
}
}
// Just in case annotations have changed, remove and write new ones
await removeLocalServiceAnnotationFiles(fs, basePath, webappPath, service);
await writeLocalServiceAnnotationXMLFiles(fs, basePath, webappPath, templateRoot, service);
}
// Write new annotations files
writeRemoteServiceAnnotationXmlFiles(fs, basePath, service.name ?? 'mainService', service.annotations);
Expand Down
Loading

0 comments on commit e26056e

Please sign in to comment.