Accessing interface/class of a nested module in TypeScript - module

I've got a TS module that holds an inner module, such as:
module abc.customer.ratings {
module abc.customer.ratings.bo {
export interface RatingScale {
id: number;
name: string;
type: string;
}
}
var scale: ??? // how to name the inner interface here?
}
I've tried to use:
RatingScale, just the name - failed
bo.RatingScale - the inner module name (like a relative path) + just the name - failed
abc.customer.ratings.bo.RatingScale - full module path from the beginning of the world - worked
My question is - can I use this in any shorter way, because the one that works is really verbose.

In this code:
module abc.customer.ratings {
module abc.customer.ratings.bo {
export interface RatingScale {
id: number;
name: string;
type: string;
}
}
var scale: ??? // how to name the inner interface here?
}
The fully-qualified name of RatingScale is abc.customer.ratings.abc.customer.ratings.bo.RatingScale. What you probably wanted to write was:
module abc.customer.ratings {
module bo {
export interface RatingScale {
id: number;
name: string;
type: string;
}
}
var scale: bo.RatingScale;
}

Related

How to return the entity with its relations after saving it?

I am building a storage application, with GraphQL as the backend, using Typegraphql and TypeORM.
The categories need to be added separately and then when adding a product, you choose from a dropdown one of the available categories. This in turn passes the categoryId to the product in a one-to-many/many-to-one relationship.
Here is my Category entity:
import {
Entity,
PrimaryColumn,
Column,
BaseEntity,
Generated,
OneToMany
} from 'typeorm';
import Product from './Product';
#ObjectType()
#Entity('categories')
export default class Category extends BaseEntity {
#Field()
#PrimaryColumn()
#Generated('uuid')
categoryId: string;
#Field()
#Column()
categoryName: string;
#OneToMany(() => Product, (product: Product) => product.category)
products: Product[];
}
and here is my Product entity
import {
Entity,
PrimaryColumn,
Column,
BaseEntity,
Generated,
ManyToOne,
JoinColumn
} from 'typeorm';
import Category from './Category';
#ObjectType()
#Entity('products')
export default class Product extends BaseEntity {
#Field()
#PrimaryColumn()
#Generated('uuid')
productID: string;
#Field()
#Column()
productName: string;
#Field(() => Category)
#ManyToOne(() => Category, (category: Category) => category.products, {
cascade: true,
lazy: true
})
#JoinColumn()
category: Category;
#Field()
#Column()
productQuantity: number;
#Field()
#Column({ type: 'decimal', precision: 2 })
productPrice: number;
#Field()
#Column({ type: 'decimal', precision: 2 })
productPriceRA: number;
#Field()
#Column({ type: 'decimal', precision: 2 })
productPriceKK: number;
#Field()
#Column('varchar', { length: 255 })
productSupplier: string;
#Field()
#Column('varchar', { length: 255 })
productOrderLink: string;
#Field()
#Column('longtext')
productImage: string;
}
For the save mutation, I've created an Input type as well:
export default class ProductInput implements Partial<Product> {
#Field()
productName: string;
#Field(() => String)
category: Category;
#Field()
productQuantity: number;
#Field()
productPrice: number;
#Field()
productPriceRA: number;
#Field()
productPriceKK: number;
#Field()
productSupplier: string;
#Field()
productOrderLink: string;
#Field()
productImage: string;
}
The relations work, as I am able to query the products, along with their category data with the following query:
{
getProducts {
productID
productName
category {
categoryId
categoryName
}
}
}
However, when saving a product it always returns
"message": "Cannot return null for non-nullable field Category.categoryName."
This is the Mutation's code in the Resolver:
#Mutation(() => Product, { description: 'Add new product' })
async addProduct(
#Arg('product') productInput: ProductInput
): Promise<Product | any> {
try {
const product = await Product.create(productInput).save();
console.log('product: ', product);
return product;
} catch (error) {
return error;
}
}
I've been trying different things, however nothing seems to work and I am wondering if it's even possible to directly return the entity with its relations. If it's not, the other option I can think of is to return true/false based on the result and re-query all of the data. But this seems very inefficient and I am actively trying to avoid going this route.
Any help will be much appreciated.
After some more research and I decided to go with the following approach:
try {
const { productID } = await Product.create(productInput).save();
return await Product.findOne(productID);
} catch (error) {
return error;
}
This allows me to directly return the product, based on the productID after it's saved in the database and properly returns the object with it's relationship.
GraphQL uses an notation to recognize data. You can see it as __typename object property. Of course, this must be turned on in the GraphQL server configuration. If you see it, it's already clear. You can reach the correct result without refetching the relation changes in the cached data on the client side with a trick like this.
For example, let's say we have updated the Product with category. In the data to return from the update mutation, it is sufficient to return only the id of the relation.
For this to work, category and product must be cached separately on the client beforehand.
for example:
mutation UpdateProduct($product: UpdateProductInput!) {
updateProduct(product: $product) {
id
title
category {
id
}
}
}
You can also write in writeFragment, which is a separate method, which is the most stingy, but it can make your job difficult in nested data.
export class ProductFragmentService {
constructor(private apollo: Apollo) {}
updateProduct(product: Product): void {
const client = this.apollo.client;
client.writeFragment({
id: `Product:${product.id}`,
fragment: gql`
fragment UpdateProductCategoryFragment on Product {
__typename
id
title
category {
id
}
}
`,
data: {
__typename: 'Product',
...product,
},
});
}
}
If you want all the fields belonging to category, you need to send them to resolver and return as a response from there. Otherwise, yes, it gives a warning that I could not find the name property.
The more profitable way of doing it is to send this data to the resolver with the input, as I wrote above, and return to the client as a response from the server.
If you still have to make another SQL request, it is necessary to call the same id after registration.
#Authorized()
#Mutation(() => Product, { description: 'Add new product' })
async addProduct(
#Arg('product') productInput: ProductInput
): Promise<Product> {
await this.productRepo.save(productInput);
return await this.productRepo.findOne({ where: { id: productInfo.id } });
}
that's all :)

Why can't I access 'User story' as a type?

I have a (correctly working) workflow script starting with this guard function:
var entities = require('#jetbrains/youtrack-scripting-api/entities');
exports.rule = entities.Issue.action({
title: 'Create default subtasks',
command: 'tt-create-subtasks',
guard: function(ctx) {
return ctx.issue.fields.Type.name == 'User Story';
},
I thought I would replace that with something like
return ctx.issue.fields.Type == UserStory;
and therefore change the requirements from:
requirements: {
Type: {
type: entities.EnumField.fieldType,
Task: {},
}
}
to:
requirements: {
Type: {
type: entities.EnumField.fieldType,
Task: {},
UserStory: {
name: 'User Story'
}
}
}
Task is used elsewhere in a similar fashion and that works:
newIssue.fields.Type = ctx.Type.Task;
But the editor gives red errors on UserStory in the giard function. Am I doing something wrong in the requirements?
If you declare the requirements like you described
requirements: {
Type: {
type: entities.EnumField.fieldType,
Task: {},
UserStory: {
name: 'User Story'
}
}
}
you'll be able to check the value the following way: issue.fields.is(ctx.Type, ctx.Type.UserStory).

Why is code completion for subclasses not working in angular templates?

For example, when we have 2 array properties on our component:
array: an ordinary Array
anonymousArray a subclass of Array
export class AppComponent {
readonly array = new Array<{
text: string;
value: string;
}>();
readonly anonymousArray = new class extends Array<{
text: string;
value: string;
}> {
add(text: string, value: string) {
this.push({
text,
value
});
}
}();
constructor() {
this.array.push({
text: "text1",
value: "value1"
});
this.anonymousArray.add("text", "value");
}
}
Then code-completion in the template works for the ordinary Array:
but not for the sub-class:
Here's a full Stackblitz example
IntelliJ will even show errors:
I wonder how this is possible in the first place: i.e. since Array.isArray(this.anonymousArray) is true, how/why does the template even see a difference?
Is this maybe a bug in Ivy or the angular language service?
Works fine for me in the most recent IDEA version:
Edit: appears to be specific to libraries versions being used, tracked at WEB-49995

How do I annotate an endpoint in NestJS for OpenAPI that takes Multipart Form Data

My NestJS server has an endpoint that accepts files and also additional form data
For example I pass a file and a user_id of the file creator in the form.
NestJS Swagger needs to be told explicitly that body contains the file and that the endpoint consumes multipart/form-data this is not documented in the NestJS docs https://docs.nestjs.com/openapi/types-and-parameters#types-and-parameters.
Luckily some bugs led to discussion about how to handle this use case
looking at these two discussions
https://github.com/nestjs/swagger/issues/167
https://github.com/nestjs/swagger/issues/417
I was able to put together the following
I have added annotation using a DTO:
the two critical parts are:
in the DTO add
#ApiProperty({
type: 'file',
properties: {
file: {
type: 'string',
format: 'binary',
},
},
})
public readonly file: any;
#IsString()
public readonly user_id: string;
in the controller add
#ApiConsumes('multipart/form-data')
this gets me a working endpoint
and this OpenAPI Json
{
"/users/files":{
"post":{
"operationId":"UsersController_addPrivateFile",
"summary":"...",
"parameters":[
],
"requestBody":{
"required":true,
"content":{
"multipart/form-data":{
"schema":{
"$ref":"#/components/schemas/UploadFileDto"
}
}
}
}
}
}
}
...
{
"UploadFileDto":{
"type":"object",
"properties":{
"file":{
"type":"file",
"properties":{
"file":{
"type":"string",
"format":"binary"
}
},
"description":"...",
"example":"'file': <any-kind-of-binary-file>"
},
"user_id":{
"type":"string",
"description":"...",
"example":"cus_IPqRS333voIGbS"
}
},
"required":[
"file",
"user_id"
]
}
}
Here is what I find a cleaner Approach:
#Injectable()
class FileToBodyInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const ctx = context.switchToHttp();
const req = ctx.getRequest();
if(req.body && req.file?.fieldname) {
const { fieldname } = req.file;
if(!req.body[fieldname]) {
req.body[fieldname] = req.file;
}
}
return next
.handle();
}
}
const ApiFile = (options?: ApiPropertyOptions): PropertyDecorator => (
target: Object, propertyKey: string | symbol
) => {
ApiProperty({
type: 'file',
properties: {
[propertyKey]: {
type: 'string',
format: 'binary',
},
},
})(target, propertyKey);
};
class UserImageDTO {
#ApiFile()
file: Express.Multer.File; // you can name it something else like image or photo
#ApiProperty()
user_id: string;
}
#Controller('users')
export class UsersController {
#ApiBody({ type: UserImageDTO })
// #ApiResponse( { type: ... } ) // some dto to annotate the response
#Post('files')
#ApiConsumes('multipart/form-data')
#UseInterceptors(
FileInterceptor('file'), //this should match the file property name
FileToBodyInterceptor, // this is to inject the file into the body object
)
async addFile(#Body() userImage: UserImageDTO): Promise<void> { // if you return something to the client put it here
console.log({modelImage}); // all the fields and the file
console.log(userImage.file); // the file is here
// ... your logic
}
}
FileToBodyInterceptor and ApiFile are general, I wish they where in the NestJs
You probably need to install #types/multer to have to Express.Multer.File

Set programmatically jsonValidation for dynamic mapping

I am creating a new vscode extension, and I need to extend the standard usage of the jsonValidation system already present in vscode.
Note : I am talking about the system defined in package.json :
"contributes" : {
"languages": [
{
"id" : "yml",
"filenamePatterns": ["module.service"]
},
{
"id" : "json",
"filenamePatterns": ["module.*"]
}
],
"jsonValidation": [
{
"fileMatch": "module.test",
"url": "./resources/test.schema"
}
]
}
Now, I need to create a dynamic mapping, where the json fields filematch/url are defined from some internal rules (like version and other internal stuff). The standard usage is static : one fileMatch -> one schema.
I want for example to read the version from the json file to validate, and set the schema after that :
{
"version" : "1.1"
}
validation schema must be test-schema.1.1 instead of test-schema.1.0
note : The question is only about the modification of the configuration provided by package.json from the extensions.ts
Thanks for the support
** EDIT since the previous solution was not working in all cases
There is one solution to modify the package.json at the activating of the function.
export function activate(context: vscode.ExtensionContext) {
const myPlugin = vscode.extensions.getExtension("your.plugin.id");
if (!myPlugin)
{
throw new Error("Composer plugin is not found...")
}
// Get the current workspace path to found the schema later.
const folderPath = vscode.workspace.workspaceFolders;
if (!folderPath)
{
return;
}
const baseUri : vscode.Uri = folderPath[0].uri;
let packageJSON = myPlugin.packageJSON;
if (packageJSON && packageJSON.contributes && packageJSON.contributes.jsonValidation)
{
let jsonValidation = packageJSON.contributes.jsonValidation;
const schemaUri : vscode.Uri = vscode.Uri.joinPath(baseUri, "/schema/value-0.3.0.json-schema");
const schema = new JsonSchemaMatch("value.ospp", schemaUri)
jsonValidation.push(schema);
}
}
And the json schema class
class JsonSchemaMatch
{
fileMatch: string;
url : string;
constructor(fileMatch : string, url: vscode.Uri)
{
this.fileMatch = fileMatch;
this.url = url.path;
}
}
Another important information is the loading of the element of contributes is not reread after modification, for example
class Language
{
id: string;
filenamePatterns : string[];
constructor(id : string, filenamePatterns: string[])
{
this.id = id;
this.filenamePatterns = filenamePatterns;
}
}
if (packageJSON && packageJSON.contributes && packageJSON.contributes.languages)
{
let languages : Language[] = packageJSON.contributes.languages;
for (let language of languages) {
if (language.id == "json") {
language.filenamePatterns.push("test.my-json-type")
}
}
}
This change has no effect, since the loading of file association is already done (I have not dig for the reason, but I think this is the case)
In this case, creating a settings.json in the workspace directory can do the job:
settings.json
{
"files.associations": {
"target.snmp": "json",
"stack.cfg": "json"
}
}
Be aware that the settings.json can be created by the user with legitimate reason, so don't override it, just fill it.