Skip to content

Commit

Permalink
feat(adaptation-project): Add Fragment enhancement (#1183)
Browse files Browse the repository at this point in the history
* initial crude implementation

* Empty-Commit

* typescript support for rta coding

* removed odd change

* removed hardcoded file hosting

* enhanced example to also use a submodule

* changing folder and export name

* feat: added fragment class to test the src-rta folder

* feat: added new api endpoint to get fragments from workspace

* enable ui5 types

* refactor: put code into new folder

* enable ui5 transpiling

* fix: script path issue

* lock file

* config again

* linting

* win issue

* chore: added --glob flag to rimraf dist

* fix tests

* added info to readme

* fix: typo

* feat: add ADP API;

* feat: enhance typings

* feat: added helper functions to utility class

* feat: separated code into classes

* feat: changed initialization of new context item

* linting errors

* fix: tests

* refactor: rollback to original method

* added enum example

* Linting auto fix commit

* feat: added an api handler to client requests

* fixed auto-completion in VSCode

* feat: change sap imports

* fix

* fix: tests again

* chore: added new package and script for copying files

* feat: added templates for fragment and controller

* feat: added enums for server code

* feat: split router endpoints into separate functions

* feat: added class for handling api requests

* feat: added class that handles requests from client

* refactor: sorted imports, cleanup

* chore: added packages for sanitization and rate limiting

* fix: rate limiting for routes

* fix: sanitize strings to prevent accessing alt sources

* chore: added node cache package

* feat: added manifest descr interface

* feat: add caching for new route

* feat: add caching and new route handler

* feat: added new route to client, fixed json bug

* feat: add api request handler

* removed wrong entry

* refactor: made templates into two folders, fixed paths

* enhanced the types module with SAPUI5 types and required enhancements

* moved sapui5 types and enhancements into central types module and use it here

* Linting auto fix commit

* feat: implement search for fragments with change file

* feat: improve typings

* refactor: hungarian notations, add typings

* refactor: remove and add missing types for any

* fix: build issues

* fix the build

* Linting auto fix commit

* fix tell jest to ignore ui5 files

* fix: lint errors, jsdocs

* fix: some major sonar issues

* fix: some sonar code smells

* type fixes

* merging type fixes

* Linting auto fix commit

* Linting auto fix commit

* fix: eslint parsing error for windows

* chore: removed root and parser from eslintrc

* chore: 🔥 removed express-rate-limit package since the server runs only in dev mode

* fix: some sonar major issues

* refactor: change templates paths, remove script

* fix: sonar issues, refactor fragment dialog code

* fix: some eslint/ts errors

* fix: regex backtracking issue

* fix: typings for promise returning function

* refactor: change manfiest descr route logic

* set browserlist to remove polyfills from generated ui5 code

* refactor: remove the class, export indiv functions

* refactor: reduce cognitive complexity of a function

* revert wrong comma

* revert wrong comma

* fix: some sonar code smells

* fix: congnitive complexity

* feat: change dialog init and refactor code

* refactor: change folder name for all dialogs

* fix: lint errors, sonar bug

* refactor: change status codes to enum

* fix: sanitize user provided input

* refactor: remove unused code for filtering fragments with change file

* enhanced the readme

* chore: added --glob flag to rimraf dist

* refactor: made templates into two folders, fixed paths

* feat: add folders for fragments and controllers

* refactor: remove node-cache module

* feat: add requested changes

* fix: controller loading

* suggestion
git push

* added test for new functionality

* setup for jest testing of client sources

* turn-off contradicting rule

* feat: add controller and change fragment structure

* refactor: change initializing of a dialog

* feat: add controller specific methods

* feat: add base interface for all dialogs

* feat: move code to controller and cleanup

* feat: add rta border class to xml fragment

* test: fixed existing tests

* refactor: reorder imports, add missing jsdocs

* refactor: doc strings

* refactor: change method name

* feat: start adding tests

* fix: mocking of fs

* real fix

* fix: post test mock

* more specific rule ignore

* feat: add some tests

* fix: add requested changes

* feat: add more test suites

* feat: add more tests

* refactor: comment out broken test

* feat: add api handler tests

* test: add example test for controller

* review feedback: supertest

* forgetful me

* mocked JSONModel

* fix: mock

* test: added controller tests

* fix: mock

* refactor: fragments map

* fix: last controller test

* test: add missing expect statements

* simplify enablement

* further cleanup

* fix: tests

* forgot the lockfile

* moved unit test

* last fixes after merge

* more concise mocking

* use more concise mocking

* global sap mocking

* use global sap

* fix: eslint errors

* refactor: change xml fragment

* refactor: remove some ts-ignores

* fix: ui bug in dialog

* fix: remove classes for add fragment xml file

* fixed merge errors

* not needed

* chore: added changeset

* fix: add requested changes

* refactor: remove bcp reference

* refactor: remove all doc references in code

* simplified tests

* refactor: remove unused code

* fix: tests after refactor

* refactor: remove unnecessary assertion

* feat: change fragment instantiation

* refactor: remove file that does not belong to this PR

* refactor: change initialization of controller and view

* fix: existing tests

* refactor: switch statement to nested ifs

* refactor: move name check block inside else statement

* replace any

* refactor: remove code that is not used anywhere

* refactor: change typing

* refactor: change typings for get source method

* removing any

* typos

* fix: linting error after type fix

* fix: sonar code smells

* test: add missing tests

* chore: fix changeset message typo

* test: improve tests

* refactor: remove unused code, fix validation bug

* test: add missing suite

---------

Co-authored-by: Tobias Queck <tobias.queck@sap.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 20, 2023
1 parent cab73dc commit ac0adb2
Show file tree
Hide file tree
Showing 46 changed files with 1,854 additions and 91 deletions.
7 changes: 7 additions & 0 deletions .changeset/loud-insects-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@sap-ux-private/preview-middleware-client': minor
'@sap-ux/preview-middleware': minor
'@sap-ux/adp-tooling': minor
---

Enhancing the preview-middleware with new functionality such as adding an XML Fragment (creating "addXML" change).
2 changes: 1 addition & 1 deletion packages/adp-tooling/.eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
test/fixtures
dist
dist
15 changes: 10 additions & 5 deletions packages/adp-tooling/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# `@sap-ux/adp-tooling`

Module containing different tooling modules helpful when working with SAP UI5 adaptation projects.
A module containing different tooling modules helpful when working with SAP UI5 adaptation projects.

## preview
## Submodules

### preview
The submodule preview contains functionality allowing to preview adaptation projects. It is not a standalone UI5 middleware but designed to be integrated into the `@sap-ux/preview-middleware.`.

## writer
### writer
The submodule writer contains functionality to generate the core project structure of an SAP UI5 adaptation project. It is not a standalone generator but designed to be integrated into `@sap-ux/create` or any kind of yeoman generator.

## base
The submodule contains functionality required in different scenarios, e.g. prompting for generation or when initializing the preview.
### base
The submodule contains functionality required in different scenarios, e.g. prompting for generation or when initializing the preview.

## Templates
The templates folder contains ejs templates to be used for the generation of new adaptation projects as well as to generate artifacts in existing adaptation projects.
2 changes: 1 addition & 1 deletion packages/adp-tooling/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
const config = require('../../jest.base');
module.exports = config;
module.exports = config;
1 change: 1 addition & 0 deletions packages/adp-tooling/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@sap-ux/logger": "workspace:*",
"@sap-ux/system-access": "workspace:*",
"@sap-ux/ui5-config": "workspace:*",
"sanitize-filename": "1.6.3",
"ejs": "3.1.9",
"mem-fs": "2.1.0",
"mem-fs-editor": "9.4.0",
Expand Down
41 changes: 36 additions & 5 deletions packages/adp-tooling/src/preview/adp-preview.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import type { ToolsLogger } from '@sap-ux/logger';
import express from 'express';
import { ZipFile } from 'yazl';
import type { AdpPreviewConfig, DescriptorVariant } from '../types';
import type { NextFunction, Request, Response } from 'express';
import type { MergedAppDescriptor } from '@sap-ux/axios-extension';
import type { ReaderCollection } from '@ui5/fs';
import type { MiddlewareUtils } from '@ui5/server';
import type { NextFunction, Request, Response, Router, RequestHandler } from 'express';

import type { ToolsLogger } from '@sap-ux/logger';
import type { UI5FlexLayer } from '@sap-ux/project-access';
import { createAbapServiceProvider } from '@sap-ux/system-access';
import type { MergedAppDescriptor } from '@sap-ux/axios-extension';

import RoutesHandler from './routes-handler';
import type { AdpPreviewConfig, DescriptorVariant } from '../types';

export const enum ApiRoutes {
FRAGMENT = '/adp/api/fragment',
CONTROLLER = '/adp/api/controller'
}

/**
* Create a buffer based on the given zip file object.
Expand Down Expand Up @@ -36,6 +46,10 @@ export class AdpPreview {
* Merged descriptor variant with reference app manifest
*/
private mergedDescriptor: MergedAppDescriptor;
/**
* Routes handler class to handle API requests
*/
private routesHandler: RoutesHandler;

/**
* @returns merged manifest.
Expand Down Expand Up @@ -72,13 +86,17 @@ export class AdpPreview {
*
* @param config adp config
* @param project reference to the root of the project
* @param util middleware utilities provided by the UI5 CLI
* @param logger logger instance
*/
constructor(
private readonly config: AdpPreviewConfig,
private readonly project: ReaderCollection,
private readonly util: MiddlewareUtils,
private readonly logger: ToolsLogger
) {}
) {
this.routesHandler = new RoutesHandler(project, util, logger);
}

/**
* Fetch all required configurations from the backend and initialize all configurations.
Expand Down Expand Up @@ -131,4 +149,17 @@ export class AdpPreview {
}
}
}

/**
* Add additional APIs to the router that are required for adaptation projects only.
*
* @param router router that is to be enhanced with the API
*/
addApis(router: Router): void {
/**
* FRAGMENT Routes
*/
router.get(ApiRoutes.FRAGMENT, this.routesHandler.handleReadAllFragments as RequestHandler);
router.post(ApiRoutes.FRAGMENT, express.json(), this.routesHandler.handleWriteFragment as RequestHandler);
}
}
115 changes: 115 additions & 0 deletions packages/adp-tooling/src/preview/routes-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import * as fs from 'fs';
import * as path from 'path';
import sanitize from 'sanitize-filename';
import type { ReaderCollection } from '@ui5/fs';
import type { ToolsLogger } from '@sap-ux/logger';
import type { MiddlewareUtils } from '@ui5/server';
import type { NextFunction, Request, Response } from 'express';

import { FolderNames, TemplateFileName, HttpStatusCodes } from '../types';

interface WriteFragmentBody {
fragmentName: string;
}

/**
* @description Handles API Routes
*/
export default class RoutesHandler {
/**
* Constructor taking project as input.
*
* @param project Reference to the root of the project
* @param util middleware utilities provided by the UI5 CLI
* @param logger Logger instance
*/
constructor(
private readonly project: ReaderCollection,
private readonly util: MiddlewareUtils,
private readonly logger: ToolsLogger
) {}

/**
* Handler for reading all fragment files from the workspace.
*
* @param _ Request
* @param res Response
* @param next Next Function
*/
public handleReadAllFragments = async (_: Request, res: Response, next: NextFunction) => {
try {
const files = await this.project.byGlob('/**/changes/**/*.fragment.xml');

if (!files || files.length === 0) {
res.status(HttpStatusCodes.OK)
.contentType('application/json')
.send({ fragments: [], message: `No fragments found in the project workspace.` });
return;
}

const fragments = files.map((f) => ({
fragmentName: f.getName()
}));

res.status(HttpStatusCodes.OK)
.contentType('application/json')
.send({
fragments,
message: `${fragments.length} fragments found in the project workspace.`
});
this.logger.debug(`Read fragments ${JSON.stringify(fragments)}`);
} catch (e) {
this.logger.error(e.message);
res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).send({ message: e.message });
next(e);
}
};

/**
* Handler for writing a fragment file to the workspace.
*
* @param req Request
* @param res Response
* @param next Next Function
*/
public handleWriteFragment = async (req: Request, res: Response, next: NextFunction) => {
try {
const data = req.body as WriteFragmentBody;

const fragmentName = sanitize(data.fragmentName);

const sourcePath = this.util.getProject().getSourcePath();

if (fragmentName) {
const fullPath = path.join(sourcePath, FolderNames.Changes, FolderNames.Fragments);
const filePath = path.join(fullPath, `${fragmentName}.fragment.xml`);

if (!fs.existsSync(fullPath)) {
fs.mkdirSync(fullPath);
}

if (fs.existsSync(filePath)) {
res.status(HttpStatusCodes.CONFLICT).send(`Fragment with name "${fragmentName}" already exists`);
this.logger.debug(`XML Fragment with name "${fragmentName}" was created`);
return;
}

// Copy the template XML Fragment to the project's workspace
const fragmentTemplatePath = path.join(__dirname, '../../templates/rta', TemplateFileName.Fragment);
fs.copyFileSync(fragmentTemplatePath, filePath);

const message = 'XML Fragment created';
res.status(HttpStatusCodes.CREATED).send(message);
this.logger.debug(`XML Fragment with name "${fragmentName}" was created`);
} else {
res.status(HttpStatusCodes.BAD_REQUEST).send('Fragment name was not provided!');
this.logger.debug('Bad request. Fragment name was not provided!');
}
} catch (e) {
const sanitizedMsg = sanitize(e.message);
this.logger.error(sanitizedMsg);
res.status(HttpStatusCodes.INTERNAL_SERVER_ERROR).send(sanitizedMsg);
next(e);
}
};
}
41 changes: 41 additions & 0 deletions packages/adp-tooling/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,44 @@ export interface AdpWriterConfig {
description?: string;
};
}

export interface ManifestAppdescr {
fileName: string;
layer: string;
fileType: string;
reference: string;
id: string;
namespace: string;
version: string;
content: Content[];
}

export interface Content {
changeType: string;
content: object;
texts?: object;
}

export const enum FolderNames {
Changes = 'changes',
Fragments = 'fragments'
}

export const enum TemplateFileName {
Fragment = 'fragment.xml'
}

export const enum HttpStatusCodes {
OK = 200,
CREATED = 201,
NO_CONTENT = 204,
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
CONFLICT = 409,
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMETED = 501,
SERVICE_UNAVAILABLE = 503
}
2 changes: 1 addition & 1 deletion packages/adp-tooling/src/writer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export async function generate(basePath: string, config: AdpWriterConfig, fs?: E
if (!fs) {
fs = create(createStorage());
}
const tmplPath = join(__dirname, '../../templates');
const tmplPath = join(__dirname, '../../templates/project');
const fullConfig = setDefaults(config);

fs.copyTpl(join(tmplPath, '**/*.*'), join(basePath), fullConfig, undefined, {
Expand Down
File renamed without changes.
File renamed without changes.
4 changes: 4 additions & 0 deletions packages/adp-tooling/templates/rta/fragment.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<!-- Use stable and unique IDs!-->
<core:FragmentDefinition xmlns:core='sap.ui.core' xmlns='sap.m'>
<!-- add your xml here -->
</core:FragmentDefinition>
Loading

0 comments on commit ac0adb2

Please sign in to comment.