blob: c693c77fc25ea3ff8dfce440f6210f00773b51ff [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import {
Component,
EventEmitter,
Input,
OnChanges,
Output,
SimpleChanges,
ViewChild,
} from '@angular/core';
import { RestService } from '../../../../services/rest.service';
import { ITreeOptions, TreeComponent } from '@ali-hm/angular-tree-component';
import { UUID } from 'angular2-uuid';
import { DataTypesService } from '../../../../services/data-type.service';
import {
AdapterDescription,
CorrectionValueTransformationRuleDescription,
EventPropertyNested,
EventPropertyPrimitive,
EventPropertyUnion,
EventSchema,
FieldStatusInfo,
GuessSchema,
SpLogMessage,
} from '@streampipes/platform-services';
import { MatStepper } from '@angular/material/stepper';
import { UserErrorMessage } from '../../../../../core-model/base/UserErrorMessage';
import { TransformationRuleService } from '../../../../services/transformation-rule.service';
import { StaticValueTransformService } from '../../../../services/static-value-transform.service';
import { EventPropertyUtilsService } from '../../../../services/event-property-utils.service';
@Component({
selector: 'sp-event-schema',
templateUrl: './event-schema.component.html',
styleUrls: ['./event-schema.component.scss'],
})
export class EventSchemaComponent implements OnChanges {
constructor(
private restService: RestService,
private dataTypesService: DataTypesService,
private transformationRuleService: TransformationRuleService,
private staticValueTransformService: StaticValueTransformService,
private epUtilsService: EventPropertyUtilsService,
) {}
@Input()
adapterDescription: AdapterDescription;
isEditable = true;
originalSchema: EventSchema;
targetSchema: EventSchema = new EventSchema();
timestampPresent = false;
@Input()
isEditMode;
refreshedEventSchema = false;
@Output()
isEditableChange = new EventEmitter<boolean>();
@Output()
goBackEmitter: EventEmitter<MatStepper> = new EventEmitter();
/**
* Cancels the adapter configuration process
*/
@Output()
removeSelectionEmitter: EventEmitter<boolean> = new EventEmitter();
/**
* Go to next configuration step when this is complete
*/
@Output()
clickNextEmitter: EventEmitter<MatStepper> = new EventEmitter();
_tree: TreeComponent;
schemaGuess: GuessSchema = new GuessSchema();
countSelected = 0;
isLoading = false;
isError = false;
isPreviewEnabled = false;
errorMessage: SpLogMessage;
nodes: EventPropertyUnion[] = new Array<EventPropertyUnion>();
validEventSchema = false;
schemaErrorHints: UserErrorMessage[] = [];
eventPreview: string[];
desiredPreview: Record<string, any>;
fieldStatusInfo: Record<string, FieldStatusInfo>;
options: ITreeOptions = {
childrenField: 'eventProperties',
allowDrag: () => {
return this.isEditable;
},
allowDrop: (node, { parent }) => {
return (
parent.data instanceof EventPropertyNested ||
parent.data.virtual
);
},
displayField: 'runTimeName',
};
public onUpdateData(treeComponent: TreeComponent): void {
treeComponent.treeModel.expandAll();
}
public setEventSchemaEditWarning() {
this.schemaErrorHints.push(
new UserErrorMessage(
'Edit mode',
'Changes in the adapter might require you to refresh the event schema.',
'info',
),
);
}
public guessSchema(): void {
this.isLoading = true;
this.isError = false;
this.restService.getGuessSchema(this.adapterDescription).subscribe(
guessSchema => {
this.eventPreview = guessSchema.eventPreview;
this.fieldStatusInfo = guessSchema.fieldStatusInfo;
this.targetSchema = guessSchema.targetSchema;
this.targetSchema.eventProperties.sort((a, b) => {
return a.runtimeName < b.runtimeName ? -1 : 1;
});
this.schemaGuess = guessSchema;
this.originalSchema = guessSchema.eventSchema;
this.validEventSchema = this.checkIfValid(this.targetSchema);
this.isEditable = true;
this.isEditableChange.emit(true);
this.isLoading = false;
this.refreshedEventSchema = true;
this.refreshTree();
if (
guessSchema.eventPreview &&
guessSchema.eventPreview.length > 0
) {
this.updatePreview();
}
},
errorMessage => {
this.errorMessage = errorMessage.error;
this.isError = true;
this.isLoading = false;
this.targetSchema = new EventSchema();
},
);
}
public refreshTree(refreshPreview = true): void {
if (this.targetSchema && this.targetSchema.eventProperties) {
this.nodes = new Array<EventPropertyUnion>();
this.nodes.push(...this.targetSchema.eventProperties);
this.validEventSchema = this.checkIfValid(this.targetSchema);
if (refreshPreview) {
this.updatePreview();
}
setTimeout(() => {
if (this._tree) {
this._tree.treeModel.expandAll();
}
});
}
}
public addNestedProperty(eventProperty?: EventPropertyNested): void {
const uuid: string = UUID.UUID();
const nested: EventPropertyNested = new EventPropertyNested();
nested['@class'] =
'org.apache.streampipes.model.schema.EventPropertyNested';
nested.elementId = uuid;
nested.eventProperties = [];
nested.domainProperties = [];
nested.runtimeName = 'nested';
nested.additionalMetadata = {};
if (!eventProperty) {
this.targetSchema.eventProperties.push(nested);
} else {
eventProperty.eventProperties.push(nested);
}
this.refreshTree();
}
public removeSelectedProperties(eventProperties?: any): void {
eventProperties = eventProperties || this.targetSchema.eventProperties;
for (let i = eventProperties.length - 1; i >= 0; --i) {
if (eventProperties[i].eventProperties) {
this.removeSelectedProperties(
eventProperties[i].eventProperties,
);
}
if (eventProperties[i].selected) {
eventProperties.splice(i, 1);
}
}
this.countSelected = 0;
this.refreshTree();
}
public addStaticValueProperty(runtimeName: string): void {
const eventProperty = new EventPropertyPrimitive();
eventProperty['@class'] =
'org.apache.streampipes.model.schema.EventPropertyPrimitive';
eventProperty.elementId =
this.staticValueTransformService.makeDefaultElementId();
eventProperty.runtimeName = runtimeName;
eventProperty.runtimeType = this.dataTypesService.getStringTypeUrl();
eventProperty.domainProperties = [];
eventProperty.propertyScope = 'DIMENSION_PROPERTY';
eventProperty.additionalMetadata = {};
this.targetSchema.eventProperties.push(eventProperty);
this.refreshTree();
}
public addTimestampProperty(): void {
const eventProperty = new EventPropertyPrimitive();
eventProperty['@class'] =
'org.apache.streampipes.model.schema.EventPropertyPrimitive';
eventProperty.elementId =
'http://eventProperty.de/timestamp/' + UUID.UUID();
eventProperty.runtimeName = 'timestamp';
eventProperty.label = 'Timestamp';
eventProperty.description = 'The current timestamp value';
eventProperty.domainProperties = ['http://schema.org/DateTime'];
eventProperty.propertyScope = 'HEADER_PROPERTY';
eventProperty.runtimeType = 'http://www.w3.org/2001/XMLSchema#long';
eventProperty.additionalMetadata = {};
this.targetSchema.eventProperties.push(eventProperty);
this.refreshTree();
}
public updatePreview(): void {
this.isPreviewEnabled = false;
const ruleDescriptions =
this.transformationRuleService.getTransformationRuleDescriptions(
this.originalSchema,
this.targetSchema,
);
if (this.eventPreview && this.eventPreview.length > 0) {
this.restService
.getAdapterEventPreview({
rules: ruleDescriptions,
inputData: this.eventPreview[0],
})
.subscribe(preview => {
this.desiredPreview = preview;
this.isPreviewEnabled = true;
});
}
}
ngOnChanges(changes: SimpleChanges) {
setTimeout(() => {
this.refreshTree();
}, 200);
}
public removeSelection() {
this.removeSelectionEmitter.emit();
}
public clickNext() {
this.clickNextEmitter.emit();
}
public goBack() {
this.goBackEmitter.emit();
}
private checkIfValid(eventSchema: EventSchema): boolean {
this.timestampPresent = false;
eventSchema.eventProperties.forEach(p => {
if (p.domainProperties.indexOf('http://schema.org/DateTime') > -1) {
this.timestampPresent = true;
}
});
this.schemaErrorHints = [];
if (this.isEditMode && !this.refreshedEventSchema) {
this.setEventSchemaEditWarning();
}
if (!this.timestampPresent) {
this.schemaErrorHints.push(
new UserErrorMessage(
'Missing Timestamp',
'The timestamp must be a UNIX timestamp in milliseconds. Edit the timestamp field or add an ingestion timestamp.',
),
);
}
if (this.fieldStatusInfo) {
const badFields = eventSchema.eventProperties
.filter(
ep => this.fieldStatusInfo[ep.runtimeName] !== undefined,
)
.map(ep => this.fieldStatusInfo[ep.runtimeName])
.find(field => field.fieldStatus !== 'GOOD');
if (badFields !== undefined) {
this.schemaErrorHints.push(
new UserErrorMessage(
'Bad reading',
'At least one field could not be properly read. If this is a permanent problem, consider removing it - keeping this field might cause the adapter to fail or to omit sending events.',
'warning',
),
);
}
}
return this.timestampPresent;
}
getOriginalSchema(): EventSchema {
return this.originalSchema;
}
getTargetSchema(): EventSchema {
this.targetSchema.eventProperties = this.nodes;
return this.targetSchema;
}
onNodeMove(event: any) {
this.targetSchema.eventProperties = this.nodes;
this.updatePreview();
}
@ViewChild('tree')
set tree(treeComponent: TreeComponent) {
this._tree = treeComponent;
}
get tree(): TreeComponent {
return this._tree;
}
}