Commit 3c01a449 authored by Christophe Benz's avatar Christophe Benz

Enhance UX

parent 2bf9a556
{
"sections": [
{
"code": "scdl",
"title": "Validateurs SCDL",
"name": "scdl",
"title": "Socle Commun des Données Locales",
"catalog": "https://git.opendatafrance.net/scdl/catalog/raw/master/catalog.json"
},
{
"code": "external",
"name": "external",
"title": "Validateurs externes",
"catalog": [
{
......
......@@ -36,11 +36,11 @@ if config.HOMEPAGE_CONFIG:
caching_session = cachecontrol.CacheControl(requests.Session())
schema_from_url = generate_schema_from_url_func(caching_session)
for section in config.HOMEPAGE_CONFIG['sections']:
code = section['code']
log.info('Initializing homepage section "{}"...'.format(code))
name = section['name']
log.info('Initializing homepage section "{}"...'.format(name))
if isinstance(section['catalog'], str) and section['catalog'].startswith('http'):
url = section['catalog']
schema_catalog_map[code] = opendataschema.SchemaCatalog(url, session=caching_session)
schema_catalog_map[name] = opendataschema.SchemaCatalog(url, session=caching_session)
log.info("...done")
# Flask things
......
......@@ -10,7 +10,7 @@
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<title>Validata - {% block title %}{% endblock %}</title>
<title>Validata {% block title %}{% endblock %}</title>
<style>
body {
......@@ -28,7 +28,7 @@
<body>
<!-- horizontal navigation bar -->
<nav class="nav px-3 py-2">
<nav class="nav px-3 py-2 border-bottom">
<a class="navbar-brand" href="{{ url_for('home') }}">
<img src="{{ url_for('static', filename='img/logo-horizontal.png') }}" height="15" alt="Validata" />
</a>
......@@ -41,14 +41,21 @@
</nav>
<!-- Breadcrumbs -->
{% if breadcrumbs %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
{% for bc in breadcrumbs %}
<li class="breadcrumb-item"><a href="{{ bc.url }}">{{ bc.title|e }}</a></li>
<li class="breadcrumb-item">
{% if bc.url %}
<a href="{{ bc.url }}">{{ bc.title|e }}</a>
{% else %}
<span>{{ bc.title|e }}</span>
{% endif %}
</li>
{% endfor %}
<li class="breadcrumb-item active" aria-current="page">{{ title|e }}</li>
</ol>
</nav>
{% endif %}
<!-- Flashing messages -->
{% with messages = get_flashed_messages(with_categories=true) %}
......@@ -62,7 +69,7 @@
{% endwith %}
<!-- main content -->
<div id="content" class="container-fluid">
<div id="content" class="container">
{% block content %}{% endblock %}
</div>
......
{% extends "base_template.html" %} {% block title %}{{ title }}{% endblock %} {%
block head %}
{{ super() }}
{% endblock %} {% block content %}
<h1 class="my-4">Validez vos jeux de données</h1>
{% extends "base_template.html" %}
{% block title %}Accueil{% endblock %}
{% block content %}
{% for section in config.sections %}
<h2>{{section.title}}</h2>
<div class="row my-4">
{% for val in section.catalog %}
{% if val.website %}
<div class="col-sm-4 col-md-3 mb-4">
<div class="card text-center h-100">
<div class="card-body d-flex flex-column">
<h4 class="card-title">{{ val.title }}</h4>
<p class="card-text">{{ val.description }}</p>
<a
href="{{ val.website }}" target="_blank"
class="btn btn-primary mt-auto"
>Voir
<i class="fas fa-external-link-alt ml-1"></i>
</a
>
</div>
</div>
</div>
{% else %}
<div class="col-sm-4 col-md-3 mb-4">
<div class="card text-center h-100">
<div class="card-body d-flex flex-column">
<h4 class="card-title">{{ val.title }}</h4>
<p class="card-text">{{ val.description }}</p>
<a
href="{{ url_for('custom_validator') }}?schema_name={{val.name}}&schema_ref=master"
class="btn btn-primary mt-auto"
>Choisir</a
>
{% if section.name != "external" %}
<div class="mb-5">
<h2 class="my-4">{{section.title}}</h2>
<form action="{{ url_for('custom_validator') }}" method="GET">
<div class="form-row">
<div class="form-group col-lg-9">
<select required name="schema_name" class="form-control">
<option disabled selected value="">Choisissez un schéma</option>
{% for val in section.catalog | sort(attribute='title') %}
<option value="{{ val.name }}">{{ val.title }}</option>
{% endfor %}
</select>
</div>
<div class="form-group col-lg-3">
<button type="submit" class="btn btn-primary">Valider un fichier</button>
</div>
</div>
{% endif %}
{% endfor %}
<input type="hidden" name="schema_ref" value="master" />
</form>
</div>
{% endif %}
{% endfor %}
<h2>Validation à la carte</h2>
<p>
<div class="my-5">
<h2 class="my-4">Autre schéma</h2>
<form action="{{ url_for('custom_validator') }}" method="GET">
<div class="form-group">
<label for="schema_url">Indiquez ici l'URL du schéma de validation à utiliser</label>
<div class="form-row">
<div class="form-group col-lg-9">
<input
name="schema_url"
type="url"
class="form-control"
id="schema_url"
aria-describedby="urlHelp"
placeholder="https://..."
aria-describedby="urlHelpBlock"
placeholder="https://example.com/schema.json"
required
/>
<small id="urlHelpBlock" class="form-text text-muted">
Le schéma doit être au format
<a href="https://frictionlessdata.io/docs/table-schema/" target="_blank">Table Schema</a>.
</small>
</div>
<div class="form-group col-lg-3">
<button type="submit" class="btn btn-primary">Valider un fichier</button>
</div>
</div>
<input type="submit" class="btn btn-primary" value="Continuer" />
</form>
</p>
</div>
{% for section in config.sections %}
{% if section.name == "external" %}
<div class="my-5">
<h2 class="my-4">{{ section.title }}</h2>
<div class="row my-4">
{% for val in section.catalog %}
<div class="col-sm-4 mb-4">
<div class="card h-100">
<div class="card-body d-flex flex-column">
<h4 class="card-title">{{ val.title }}</h4>
<p class="card-text">{{ val.description }}</p>
<a
href="{{ val.website }}" target="_blank"
class="btn btn-outline-secondary mt-auto"
>Utiliser
<i class="fas fa-external-link-alt ml-1"></i>
</a
>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{% endfor %}
{% endblock %}
{% macro html(schema_info, schema_versions, schema_current_version, doc_url) %}
<h5 class="card-title">
Schéma
<h2 class="card-title">
{% if schema_info.title %}
« {{ schema_info.title }} »
{{ schema_info.title }}
{% endif %}
{% if schema_info.version %}
<span class="badge badge-primary">{{ schema_info.version }}</span>
{% endif %}
</h5>
</h2>
{% if schema_versions %}
<form class="form-inline my-2">
<div class="form-group">
<label for="git_ref" class="mr-2">Version</label>
<select class="form-control" id="version_select" name="git_ref">
{% for sv in schema_versions %}
<option{% if sv.name == schema_current_version %} selected="selected"{% endif %}>{{ sv.name }}</option>
{% endfor %}
</select>
</div>
</form>
{% endif %}
{% if schema_info.description %}
<h6 class="card-subtitle mb-2 text-muted">{{ schema_info.description }}</h6>
<p>{{ schema_info.description }}</p>
{% endif %}
{% if schema_info.contributors %}
<p>
<h6>Contributeur{% if schema_info.contributors|length > 1 %}s{% endif %} :</h6>
<ul>
<p>Contributeurs :</p>
<ul>
{% for contributor in schema_info.contributors %}
<li>
{{ contributor.title }}{% if contributor.title and contributor.organisation %}, {% endif %}
......@@ -25,35 +38,17 @@
{% if contributor.role == 'contributor' %}(contributeur){% endif %}
</li>
{% endfor %}
</ul>
</p>
</ul>
{% endif %}
{% if schema_info.homepage or schema_info.url %}
<p>
{% if schema_info.homepage %}
Site : <a href="{{ schema_info.homepage }}" target="_blank" title="{{ schema_info.homepage }}" class="card-link">{{ schema_info.homepage | truncate(40) }}</a>
{% endif %}
{#
{% if schema_info.homepage and schema_info.url %}<br />{% endif %}
{% if schema_info.url %}
Source : <a href="{{ schema_info.url }}" target="_blank" title="{{ schema_info.url }}" class="card-link">{{ schema_info.url | truncate(40) }}</a>
{% endif %}
#}
</p>
{% if schema_info.homepage %}
<a href="{{ schema_info.homepage }}" target="_blank" class="card-link d-block ml-0">
Page d'accueil
</a>
{% endif %}
{% if schema_versions %}
<p>
Changer de version Git :
<select id="version_select">
{% for sv in schema_versions %}
<option{% if sv.name == schema_current_version %} selected="selected"{% endif %}>{{ sv.name }}</option>
{% endfor %}
</select>
{% if schema_current_version == 'master' %}
<br />
<a href="{{ doc_url }}" target="_blank">Documentation</a>
{% endif %}
</p>
{% if schema_current_version == 'master' %}
<a href="{{ doc_url }}" target="_blank" class="card-link d-block ml-0">Documentation</a>
{% endif %}
{% endmacro %}
......
{% extends "base_template.html" %}
{% import 'schema_info_part.html' as schema_info_part %}
{% import 'schema_info_macros.html' as schema_info_macros %}
{% block title %}{{ title }}{% endblock %}
{% block title %}{{ section_title }} – {{ schema_info.title }}{% endblock %}
{% block content %}
<h1 class="my-4">{{ title }}</h1>
{% set cols_my_classes = 'my-md-0 my-4' %}
<div class="row">
<div class="col-md-4 {{ cols_my_classes }}">
<div class="card bg-faded">
<div class="row my-4">
<div class="col-lg-6 mb-4">
<div class="card">
<div class="card-body">
{{ schema_info_part.html(schema_info, schema_versions, schema_current_version, doc_url) }}
{{ schema_info_macros.html(schema_info, schema_versions, schema_current_version, doc_url) }}
</div>
</div>
</div>
<div class="col-md-8 {{ cols_my_classes }}">
<h2>Outil de validation</h2>
<!-- Tab validator -->
<p class="text">Validez ici le fichier de votre choix</p>
<div class="col-lg-6">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="file-tab" data-toggle="tab" href="#file" role="tab" aria-controls="file"
......@@ -38,51 +32,52 @@
</ul>
{% set padding_class = 'p-3' %}
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active {{ padding_class }}" id="file" role="tabpanel" aria-labelledby="file-tab">
<div class="tab-pane show active {{ padding_class }}" id="file" role="tabpanel" aria-labelledby="file-tab">
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="input" value="file" />
{% for key, value in schema_params.items() %}
<input type="hidden" name="{{ key }}" value="{{ value }}" />
{% endfor %}
<div class="form-group">
<label for="file">Choisissez un fichier tabulaire à valider (.xlsx, .xls, .ods, .csv, .tsv,
etc.)</label>
<input type="file" class="form-control-file" name="file" id="file" accept=".csv, .xls, .xlsx, .ods" />
<input required type="file" class="form-control-file" name="file" id="file" accept=".csv, .xls, .xlsx, .ods" aria-describedby="fileHelpBlock" />
<small id="fileHelpBlock" class="form-text text-muted">
Exemple : .xlsx, .xls, .ods, .csv, .tsv, etc.
</small>
</div>
<button type="submit" class="btn btn-primary">Valider</button>
<button type="submit" class="btn btn-primary">Valider le fichier</button>
</form>
</div>
<div class="tab-pane fade {{ padding_class }}" id="url" role="tabpanel" aria-labelledby="url-tab">
<div class="tab-pane {{ padding_class }}" id="url" role="tabpanel" aria-labelledby="url-tab">
<form method="GET">
<input type="hidden" name="input" value="url" />
{% for key, value in schema_params.items() %}
<input type="hidden" name="{{ key }}" value="{{ value }}" />
{% endfor %}
<div class="form-group">
<label for="url">Indiquez l'URL de la table à valider</label>
<input name="url" type="url" class="form-control" id="url" aria-describedby="urlHelp"
placeholder="https://..." />
<input required name="url" type="url" class="form-control" id="url" aria-describedby="urlHelpBlock" placeholder="https://example.com/file.csv" />
<small id="urlHelpBlock" class="form-text text-muted">
Exemple : .xlsx, .xls, .ods, .csv, .tsv, etc.
</small>
</div>
<button type="submit" class="btn btn-primary">Valider</button>
<button type="submit" class="btn btn-primary">Valider le fichier</button>
</form>
</div>
{% if schema_info.resources %}
<div class="tab-pane fade {{ padding_class }}" id="example" role="tabpanel" aria-labelledby="examples-tab">
<div class="tab-pane {{ padding_class }}" id="example" role="tabpanel" aria-labelledby="examples-tab">
<form method="GET">
<input name="input" value="example" type="hidden">
{% for key, value in schema_params.items() %}
<input type="hidden" name="{{ key }}" value="{{ value }}" />
{% endfor %}
<div class="form-group">
<label for="url">Choisissez l'exemple à valider</label>
<select name="url" id="url" class="form-control">
<option value="">...</option>
<select required name="url" id="url" class="form-control">
<option selected disabled value="">Choisissez un fichier tabulaire d'exemple</option>
{% for res in schema_info.resources %}
<option value="{{ res.path }}">{{ res.title }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Valider</button>
<button type="submit" class="btn btn-primary">Valider le fichier</button>
</form>
</div>
{% endif %}
......@@ -93,6 +88,6 @@
{% block page_scripts %}
{% if schema_versions %}
{{ schema_info_part.script() }}
{{ schema_info_macros.script() }}
{% endif %}
{% endblock %}
\ No newline at end of file
{% macro preview(source_data) %}
<p class="text">
Affichage de {{ source_data.preview_rows_nb }} lignes sur {{ source_data.rows_nb }} au total
<p>
Prévisualisation de {{ source_data.preview_rows_nb }} ligne{% if source_data.preview_rows_nb > 1 %}s{% endif %} sur {{ source_data.rows_nb }} au total :
</p>
<div class="table-responsive">
<table class="table table-striped table-bordered table-sm table-hover">
......@@ -114,41 +114,26 @@
{% set structure_errors = report.table['error-stats']['structure-errors'] %}
{% if structure_errors['count'] != 0 %}
Erreurs de structure ({{ structure_errors['count'] }}) :
<p>Erreur de structure ({{ structure_errors['count'] }}) :</p>
<ul>
{% for elt in structure_errors['count-by-code'] %}
<li>{{ elt[0] }} : {{ elt[1] }}</li>
{% for item in structure_errors['count-by-code'] %}
<li>{{ item[0] }} ({{ item[1] }})</li>
{% endfor %}
</ul>
{% else %}
Aucune erreur de structure.<br/>
<p>Aucune erreur de structure.</p>
{% endif %}
{% if report.table.do_display_body_errors %}
{% set value_errors = report.table['error-stats']['value-errors'] %}
{% if value_errors['count'] != 0 %}
Erreurs de contenu ({{ value_errors['count'] }}) :
{% if value_errors['count'] > 0 %}
<p>Erreur de contenu ({{ value_errors['count'] }} sur {{ value_errors['rows-count'] }} ligne{% if value_errors['rows-count'] > 1 %}s{% endif %}) :</p>
<ul>
{% for elt in value_errors['count-by-code'] %}
<li>{{ elt[0] }} : {{ elt[1] }}</li>
{% for item in value_errors['count-by-code'] %}
<li>{{ item[0] }} ({{ item[1] }})</li>
{% endfor %}
</ul>
<p>
{% if value_errors['rows-count'] == 1 %}
1 ligne en erreur
{% else %}
{{ value_errors['rows-count'] }} lignes en erreur
{% endif %}
sur
{% if report.table['row-count'] == 1 %}
1 ligne
{% else %}
{{ report.table['row-count'] }} lignes
{% endif %}
au total
</p>
{% endif %}
{% endif %}
......
{% extends "base_template.html" %}
{% import 'schema_info_part.html' as schema_info_part %}
{% import 'schema_info_macros.html' as schema_info_macros %}
{% import 'validation_macros.html' as macros %}
{% block title %}{{ title }}{% endblock %}
{% block title %}{{ section_title }} – {{ schema_info.title }}{% endblock %}
{% block head %}
{{ super() }}
......@@ -25,82 +26,75 @@
{% endblock %}
{% block content %}
{% if print_mode %}
<div class="row">
<div class="col-md-5">
{% endif %}
<h1 class="my-4">{{ title }}</h1>
<p>Validation effectuée {{ validation_date }}</p>
{% if display_badge %}
<p><img src="{{ badge_url }}" alt="{{ badge_msg }}" title="{{ badge_msg }}"/></p>
{% endif %}
{% if print_mode %}
</div>
<div class="col-md-7">
<img src="{{ url_for('static', filename='img/logo-horizontal.png') }}" height="80" alt="Validata" />
</div>
</div>
{% endif %}
<p>Validation effectuée {{ validation_date }}</p>
{% if source.type == 'url' %}
<p class="hidden-print">
<a href="{{ pdf_report_url }}&url={{source.url|urlencode}}" target="_blank">
<a class="btn btn-outline-secondary" href="{{ pdf_report_url }}&url={{source.url|urlencode}}" target="_blank">
Télécharger en PDF
</a>
</p>
{% endif %}
{# Schema info #}
<div class="row">
<div class="col-md-{% if print_mode %}12{% else %}5{% endif %} my-4">
<div class="card bg-faded">
<div class="row my-4">
<div class="col-lg-6 mb-4">
<div class="card">
<div class="card-body">
{{ schema_info_part.html(schema_info, schema_versions, schema_current_version, doc_url) }}
<hr />
<div>
<h5 class="card-title">
Fichier traité
</h5>
{% if source.type == 'file' %}
{{ source.filename }}
{% endif %}
{% if source.type == 'url' %}
<a href="{{source.url}}" target="_blank">
{{source.url}}
</a>
{% endif %}
<br />
Dimensions : {{ report.table.col_count }} colonnes et {{ report.table.row_count }} lignes
</div>
</div>
{{ schema_info_macros.html(schema_info, None, schema_current_version, doc_url) }}
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card">
<div class="card-body">
<h2 class="card-title">
Fichier {% if report.error_count == 0 %}valide{% else %}invalide{% endif %}
</h2>
{% import 'validation_macros.html' as macros %}
{% if display_badge %}
<p><img src="{{ badge_url }}" alt="{{ badge_msg }}" /></p>
{% endif %}
<p>
<code>
{% if source.type == 'file' %}
{{ source.filename }}
{% elif source.type == 'url' %}
<a href="{{source.url}}" target="_blank">{{source.filename}}</a>
{% endif %}
</code>
({{ report.table.col_count }} colonnes × {{ report.table.row_count }} lignes)
</p>
{% if report.error_count == 0 %}
<h2>La table est valide</h2>
<p>Aucune erreur détectée</p>
{{ macros.preview(source_data) }}
{% else %}
<h2>La table est invalide</h2>
{{ macros.error_statistics(report) }}
<p>
{% set error_count = report.table['error-stats']['count'] %}
Total :
{% if error_count == 1 %}
1 erreur détectée.
1 erreur détectée
{% else %}
{{ error_count }} erreurs détectées.
{{ error_count }} erreurs détectées
{% endif %}
</p>
{% endif %}
</div>
</div>
</div>
</div>
{{ macros.error_statistics(report) }}
{% if report.error_count == 0 %}
{{ macros.preview(source_data) }}
{% else %}
{% if report.table.errors.structure %}
<h3>Erreurs de structure</h3>
<h3 class="my-4">Erreurs de structure</h3>
{# Non-column errors #}
{% for err in report.table.errors.structure %}
{% if not err.in_column_comp %}
......@@ -146,7 +140,7 @@
{# We do display body errors! #}
{% if report.table.do_display_body_errors %}
<h3>Erreurs de contenu</h3>
<h3 class="my-4">Erreurs de contenu</h3>
{# No errors - display preview #}
{% if not report.table.errors.body %}
......@@ -174,10 +168,6 @@
{% endblock %}
{% block page_scripts %}
{% if schema_versions %}
{{ schema_info_part.script() }}
{% endif %}
<script>
$(function() {
// Errors tooltip activate
......
""" Call validation code """
import logging
from abc import abstractmethod, ABC
from abc import ABC, abstractmethod
from io import BytesIO
from pathlib import Path
from urllib.parse import urlparse
log = logging.getLogger(__name__)
......@@ -27,6 +28,7 @@ class URLValidataResource(ValidataResource):
"""Built from URL"""
super().__init__('url')
self.url = url
self.filename = Path(urlparse(url).path).name
def build_tabulator_stream_args(self):
"""URL implementation"""
......
This diff is collapsed.
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