Commit e8040624 authored by Laurent Wouters's avatar Laurent Wouters
Browse files

[fix] Implemented compliance warnings

parent 205bdaaab6ec
/*******************************************************************************
* Copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
*
* This file is part of CubicWeb.
*
* CubicWeb is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 2.1 of the License, or (at your option)
* any later version.
*
* CubicWeb is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React = require("react");
import {
ResourceData,
checkCompliance,
ComplianceWarning,
ComplianceWarningKind,
ComplianceWarningForSourceTopicMismatch,
ComplianceWarningForSourceFromRedirection,
ComplianceWarningForInconsistentAlternativeSources,
DocumentSource,
ComplianceWarningForBadNegotiation
} from "../data";
export interface CurrentTabComplianceProps {
displayBack: boolean;
data: ResourceData;
onClickBack: () => void;
}
export class CurrentTabCompliance extends React.Component<
CurrentTabComplianceProps,
{}
> {
constructor(props: CurrentTabComplianceProps) {
super(props);
}
renderWarning(warning: ComplianceWarning): React.ReactNode {
if (warning.kind == ComplianceWarningKind.SourceTopicMismatch) {
let w = warning as ComplianceWarningForSourceTopicMismatch;
return (
<div className="card border-warning mb-3">
<div className="card-header">Source/Topic Mispatch</div>
<div className="card-body">
A source of data is found at{" "}
<a href={w.source.url}>{w.source.url}</a>, but its location is not
one of the URI for the current resource.
</div>
</div>
);
} else if (warning.kind == ComplianceWarningKind.SourceFromRedirection) {
let w = warning as ComplianceWarningForSourceFromRedirection;
return (
<div className="card border-warning mb-3">
<div className="card-header">Source from redirection</div>
<div className="card-body">
The source of data at <a href={w.source.url}>{w.source.url}</a> has
been found through a rediction.
</div>
</div>
);
} else if (
warning.kind == ComplianceWarningKind.InconsistentAlternativeSources
) {
let w = warning as ComplianceWarningForInconsistentAlternativeSources;
return (
<div className="card border-warning mb-3">
<div className="card-header">Inconsistent alternative sources</div>
<div className="card-body">
The following sources are from alternative writings of an URI but do
not provide the same data:
{w.sources.map((source: DocumentSource, index: number) => {
return (
<span key={"source-" + index}>
{index > 0 ? ", " : ""}
<a href={source.url}>{source.url}</a>
</span>
);
})}
</div>
</div>
);
} else if (warning.kind == ComplianceWarningKind.BadNegotiation) {
let w = warning as ComplianceWarningForBadNegotiation;
return (
<div className="card border-warning mb-3">
<div className="card-header">Bad negotiation</div>
<div className="card-body">
The sources of data at <a href={w.document.url}>{w.document.url}</a>{" "}
have been detected from HTML headers because HTTP content
negotiation could not be performed.
</div>
</div>
);
}
return <span />;
}
render() {
let warnings = checkCompliance(this.props.data);
return (
<div className="container-fluid">
{this.props.displayBack ? (
<div className="row">
<div className="col-2">
<a
className="btn btn-outline-light"
onClick={this.props.onClickBack}
title="Back"
>
<span style={{ fontSize: "20pt", cursor: "pointer" }}>
<img width="24px" src="../icons/back.svg" />
</span>
</a>
</div>
<div className="col-2" />
<div className="col-2" />
<div className="col-6" />
</div>
) : (
<div />
)}
<div className="row">
<div className="col-12">
<span style={{ fontSize: "17pt" }}>Linked Data Compliance</span>
</div>
</div>
<hr />
{warnings.length == 0 ? (
<span style={{ color: "darkgreen" }}>All is good!</span>
) : (
warnings.map((warning: ComplianceWarning, index: number) => {
return (
<div
className="row"
key={"warning-" + index}
style={{ marginTop: "5pt" }}
>
<div className="col-12">{this.renderWarning(warning)}</div>
</div>
);
})
)}
</div>
);
}
}
......@@ -800,6 +800,136 @@ export class ResourceData {
supplements: application.Resource[];
}
/**
* The different kind of compliance warnings
*/
export enum ComplianceWarningKind {
/**
* A mismatch between the URI of a source of data and the URI of the current resource
* The source of data is from a different URI than the URI of the current resource
*/
SourceTopicMismatch,
/**
* The source has been obtained through a redirection from the URI of the current resource
* The source of data should be obtained at the same URI through content type negotiation
*/
SourceFromRedirection,
/**
* Multiple alternative writings of the same URI do not contain the same data
*/
InconsistentAlternativeSources,
/**
* When bad negotiation occured for an URI
* This is detected through the use of HTML links as the only source of data for an URI
*/
BadNegotiation
}
/**
* The data about a warning regarding the compliance of a the data known in a tab
*/
export interface ComplianceWarning {
/**
* The kind of compliance warning
*/
kind: ComplianceWarningKind;
}
/**
* A mismatch between the URI of a source of data and the URI of the current resource
*/
export class ComplianceWarningForSourceTopicMismatch
implements ComplianceWarning {
/**
* The kind of compliance warning
*/
kind: ComplianceWarningKind;
/**
* The source that triggered the warning
*/
source: DocumentSource;
/**
* Initializes this warning
* @param source The source that triggered the warning
*/
constructor(source: DocumentSource) {
this.kind = ComplianceWarningKind.SourceTopicMismatch;
this.source = source;
}
}
/**
* The source has been obtained through a redirection from the URI of the current resource
*/
export class ComplianceWarningForSourceFromRedirection
implements ComplianceWarning {
/**
* The kind of compliance warning
*/
kind: ComplianceWarningKind;
/**
* The source that triggered the warning
*/
source: DocumentSource;
/**
* Initializes this warning
* @param source The source that triggered the warning
*/
constructor(source: DocumentSource) {
this.kind = ComplianceWarningKind.SourceFromRedirection;
this.source = source;
}
}
/**
* Multiple alternative writings of the same URI do not contain the same data
*/
export class ComplianceWarningForInconsistentAlternativeSources
implements ComplianceWarning {
/**
* The kind of compliance warning
*/
kind: ComplianceWarningKind;
/**
* The sources that triggered the warning
*/
sources: DocumentSource[];
/**
* Initializes this warning
* @param source The sources that triggered the warning
*/
constructor(sources: DocumentSource[]) {
this.kind = ComplianceWarningKind.InconsistentAlternativeSources;
this.sources = sources;
}
}
/**
* When bad negotiation occured for an URI
*/
export class ComplianceWarningForBadNegotiation implements ComplianceWarning {
/**
* The kind of compliance warning
*/
kind: ComplianceWarningKind;
/**
* The document that triggered the warning
*/
document: DocumentObservations;
/**
* Initializes this warning
* @param source The document that triggered the warning
*/
constructor(document: DocumentObservations) {
this.kind = ComplianceWarningKind.BadNegotiation;
this.document = document;
}
}
/**
* Gets the sources of data for the specified resource
* @param data The current data about a resource
......@@ -835,6 +965,58 @@ export function selectPrimaryTopic(data: ResourceData): application.Resource {
}
}
/**
* Checks the data for compliance issues
* @param data The current data about a resource
*/
export function checkCompliance(data: ResourceData): ComplianceWarning[] {
let warnings: ComplianceWarning[] = [];
let loadedFromAlternatives: DocumentSource[] = [];
Object.keys(data.observations).forEach((uri: string) => {
let observation = data.observations[uri];
var allHtmlLink = true;
observation.sources.forEach((source: DocumentSource) => {
if (source == NO_DATA) return;
if (data.resource.uris.indexOf(source.url) < 0) {
warnings.push(new ComplianceWarningForSourceTopicMismatch(source));
}
if (source.origin.kind == ORIGIN_KIND_REDIRECTED) {
warnings.push(new ComplianceWarningForSourceFromRedirection(source));
}
allHtmlLink =
allHtmlLink && source.origin.kind == ORIGIN_KIND_LINKED_HTML;
if (
((source.origin.kind == ORIGIN_KIND_DIRECT ||
source.origin.kind == ORIGIN_KIND_NEGOTIATED ||
source.origin.kind == ORIGIN_KIND_REDIRECTED) &&
data.resource.uris.indexOf(source.origin.url) >= 0) ||
data.resource.uris.indexOf(source.url)
) {
loadedFromAlternatives.push(source);
}
});
if (allHtmlLink) {
warnings.push(new ComplianceWarningForBadNegotiation(observation));
}
});
let consistent = loadedFromAlternatives
.map(
(source: DocumentSource, index: number) =>
index == 0
? true
: source.loaded == loadedFromAlternatives[index - 1].loaded
)
.reduce((acc: boolean, value: boolean) => acc && value, true);
if (!consistent) {
warnings.push(
new ComplianceWarningForInconsistentAlternativeSources(
loadedFromAlternatives
)
);
}
return warnings;
}
/**
* Tries to negotiate data content at the specified URL
*/
......
......@@ -36,6 +36,7 @@ import { ConfigLoading } from "../common/config/ConfigLoading";
import { CurrentTabDataSources } from "../common/config/CurrentTabDataSources";
import { CurrentTabViews } from "../common/config/CurrentTabViews";
import { CurrentTabResources } from "../common/config/CurrentTabRessources";
import { CurrentTabCompliance } from "../common/config/CurrentTabCompliance";
/**
* Gets the identifier of the currently active tab
......@@ -190,6 +191,13 @@ class Sidebar extends React.Component<{}, State> {
onClickBack={() => {}}
/>
</div>
<div style={{ marginTop: "10pt" }}>
<CurrentTabCompliance
data={this.state.data}
displayBack={false}
onClickBack={() => {}}
/>
</div>
<div style={{ marginTop: "10pt" }}>
<CurrentTabResources
data={this.state.data}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment