From 800140b847739d02cd90b695d45ed4c5d38e3a60 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Mon, 16 Aug 2021 16:09:55 +0100 Subject: [PATCH 01/14] Add KBART generation infrastructure and draft generation logic --- thoth-app/src/models/work/mod.rs | 16 ++- thoth-export-server/src/csv/csv_thoth.rs | 3 +- thoth-export-server/src/csv/kbart_oclc.rs | 147 ++++++++++++++++++++++ thoth-export-server/src/csv/mod.rs | 5 +- thoth-export-server/src/data.rs | 61 +++++++++ thoth-export-server/src/record.rs | 29 ++++- 6 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 thoth-export-server/src/csv/kbart_oclc.rs diff --git a/thoth-app/src/models/work/mod.rs b/thoth-app/src/models/work/mod.rs index efc9f420..6fd7e7e0 100644 --- a/thoth-app/src/models/work/mod.rs +++ b/thoth-app/src/models/work/mod.rs @@ -119,6 +119,7 @@ pub trait DisplayWork { fn onix_projectmuse_endpoint(&self) -> String; fn onix_oapen_endpoint(&self) -> String; fn csv_endpoint(&self) -> String; + fn kbart_endpoint(&self) -> String; fn cover_alt_text(&self) -> String; fn license_icons(&self) -> Html; fn status_tag(&self) -> Html; @@ -147,6 +148,13 @@ impl DisplayWork for WorkWithRelations { ) } + fn kbart_endpoint(&self) -> String { + format!( + "{}/specifications/kbart::oclc/work/{}", + THOTH_EXPORT_API, &self.work_id + ) + } + fn cover_alt_text(&self) -> String { format!("{} - Cover Image", &self.title) } @@ -340,7 +348,7 @@ impl DisplayWork for WorkWithRelations { href={self.onix_projectmuse_endpoint()} class="dropdown-item" > - {"ONIX (Project Muse)"} + {"ONIX (Project MUSE)"} {"CSV"} + + {"KBART"} + diff --git a/thoth-export-server/src/csv/csv_thoth.rs b/thoth-export-server/src/csv/csv_thoth.rs index 29a5ff49..5ff70125 100644 --- a/thoth-export-server/src/csv/csv_thoth.rs +++ b/thoth-export-server/src/csv/csv_thoth.rs @@ -304,6 +304,7 @@ impl CsvCell for WorkFundings { #[cfg(test)] mod tests { use super::*; + use crate::record::DELIMITER_COMMA; use lazy_static::lazy_static; use std::str::FromStr; use thoth_client::{ @@ -495,7 +496,7 @@ mod tests { #[test] fn test_csv_thoth() { - let to_test = CsvThoth.generate(&[TEST_WORK.clone()]); + let to_test = CsvThoth.generate(&[TEST_WORK.clone()], DELIMITER_COMMA); assert_eq!(to_test, Ok(TEST_RESULT.to_string())) } diff --git a/thoth-export-server/src/csv/kbart_oclc.rs b/thoth-export-server/src/csv/kbart_oclc.rs new file mode 100644 index 00000000..e6dcafda --- /dev/null +++ b/thoth-export-server/src/csv/kbart_oclc.rs @@ -0,0 +1,147 @@ +use csv::Writer; +use serde::Serialize; +use std::convert::TryFrom; +use std::io::Write; +use thoth_client::{ContributionType, PublicationType, Work, WorkType}; +use thoth_errors::{ThothError, ThothResult}; + +use super::{CsvRow, CsvSpecification}; + +pub(crate) struct KbartOclc; + +#[derive(Debug, Serialize)] +struct KbartOclcRow { + publication_title: String, + print_identifier: Option, + online_identifier: Option, + date_first_issue_online: Option, + num_first_vol_online: Option, + num_first_issue_online: Option, + date_last_issue_online: Option, + num_last_vol_online: Option, + num_last_issue_online: Option, + title_url: String, + first_author: Option, + title_id: Option, + embargo_info: Option, + coverage_depth: String, + notes: Option, + publisher_name: Option, + publication_type: Option, + date_monograph_published_print: Option, + date_monograph_published_online: Option, + monograph_volume: Option, + monograph_edition: Option, + first_editor: Option, + parent_publication_title_id: Option, + preceding_publication_title_id: Option, + access_type: Option, +} + +impl CsvSpecification for KbartOclc { + fn handle_event(w: &mut Writer, works: &[Work]) -> ThothResult<()> { + for work in works.iter() { + CsvRow::::csv_row(work, w)?; + } + Ok(()) + } +} + +impl CsvRow for Work { + fn csv_row(&self, w: &mut Writer) -> ThothResult<()> { + w.serialize(KbartOclcRow::try_from(self.clone())?) + .map_err(|e| e.into()) + } +} + +impl TryFrom for KbartOclcRow { + type Error = ThothError; + + fn try_from(work: Work) -> ThothResult { + // title_url is mandatory in KBART but optional in Thoth + if let Some(title_url) = work.landing_page { + let publication_title = match work.subtitle { + Some(subtitle) => format!("{}: {}", work.title, subtitle), + None => work.full_title, + }; + let mut print_identifier = None; + let mut online_identifier = None; + for publication in work.publications { + if publication.publication_type == PublicationType::PAPERBACK { + print_identifier = publication.isbn.clone(); + } + if publication.publication_type == PublicationType::PDF { + online_identifier = publication.isbn.clone(); + } + } + let mut main_authors = vec![]; + let mut main_editors = vec![]; + let mut contributions = work.contributions; + contributions.sort_by(|a, b| a.contribution_ordinal.cmp(&b.contribution_ordinal)); + for contribution in contributions { + if contribution.main_contribution { + if work.work_type == WorkType::EDITED_BOOK { + if contribution.contribution_type == ContributionType::EDITOR { + main_editors.push(contribution.last_name); + } + } else if contribution.contribution_type == ContributionType::AUTHOR { + main_authors.push(contribution.last_name); + } + } + } + let first_author = match main_authors.is_empty() { + true => None, + false => Some(main_authors.join("; ")), + }; + let first_editor = match main_editors.is_empty() { + true => None, + false => Some(main_editors.join("; ")), + }; + let publication_type = match work.work_type { + WorkType::BOOK_SET => Some("serial".to_string()), + _ => Some("monograph".to_string()), + }; + let publication_year = work + .publication_date + .map(|date| chrono::Datelike::year(&date).into()); + let mut monograph_volume = None; + let mut parent_publication_title_id = None; + if !work.issues.is_empty() { + monograph_volume = Some(work.issues[0].issue_ordinal); + parent_publication_title_id = Some(work.issues[0].series.series_name.clone()); + } + Ok(KbartOclcRow { + publication_title, + print_identifier, + online_identifier, + date_first_issue_online: None, + num_first_vol_online: None, + num_first_issue_online: None, + date_last_issue_online: None, + num_last_vol_online: None, + num_last_issue_online: None, + title_url, + first_author, + title_id: work.doi, + embargo_info: None, + coverage_depth: "fulltext".to_string(), + notes: None, + publisher_name: Some(work.imprint.publisher.publisher_name), + publication_type, + date_monograph_published_print: publication_year, + date_monograph_published_online: publication_year, + monograph_volume, + monograph_edition: Some(work.edition), + first_editor, + parent_publication_title_id, + preceding_publication_title_id: None, + access_type: Some("F".to_string()), + }) + } else { + Err(ThothError::IncompleteMetadataRecord( + "kbart::oclc".to_string(), + "Missing Title URL".to_string(), + )) + } + } +} diff --git a/thoth-export-server/src/csv/mod.rs b/thoth-export-server/src/csv/mod.rs index c6115c2a..ec268e9e 100644 --- a/thoth-export-server/src/csv/mod.rs +++ b/thoth-export-server/src/csv/mod.rs @@ -6,9 +6,10 @@ use thoth_errors::{ThothError, ThothResult}; pub(crate) trait CsvSpecification { const QUOTE_STYLE: QuoteStyle = QuoteStyle::Always; - fn generate(&self, works: &[Work]) -> ThothResult { + fn generate(&self, works: &[Work], delimiter: u8) -> ThothResult { let mut writer = WriterBuilder::new() .quote_style(Self::QUOTE_STYLE) + .delimiter(delimiter) .from_writer(Vec::new()); Self::handle_event(&mut writer, works) .map(|_| writer.into_inner().map_err(|e| e.error().into())) @@ -32,3 +33,5 @@ pub(crate) trait CsvCell { mod csv_thoth; pub(crate) use csv_thoth::CsvThoth; +mod kbart_oclc; +pub(crate) use kbart_oclc::KbartOclc; diff --git a/thoth-export-server/src/data.rs b/thoth-export-server/src/data.rs index e8e591d7..2d544d7d 100644 --- a/thoth-export-server/src/data.rs +++ b/thoth-export-server/src/data.rs @@ -25,6 +25,18 @@ lazy_static! { format: concat!(env!("THOTH_EXPORT_API"), "/formats/csv"), accepted_by: vec![concat!(env!("THOTH_EXPORT_API"), "/platforms/thoth"),], }, + Specification { + id: "kbart::oclc", + name: "OCLC KBART", + format: concat!(env!("THOTH_EXPORT_API"), "/formats/kbart"), + accepted_by: vec![ + concat!(env!("THOTH_EXPORT_API"), "/platforms/oclc_kb"), + concat!(env!("THOTH_EXPORT_API"), "/platforms/proquest_kb"), + concat!(env!("THOTH_EXPORT_API"), "/platforms/proquest_exlibris"), + concat!(env!("THOTH_EXPORT_API"), "/platforms/ebsco_kb"), + concat!(env!("THOTH_EXPORT_API"), "/platforms/jisc_kb"), + ], + }, ]; pub(crate) static ref ALL_PLATFORMS: Vec> = vec![ Platform { @@ -51,6 +63,46 @@ lazy_static! { "/specifications/onix_3.0::oapen" ),], }, + Platform { + id: "oclc_kb", + name: "OCLC KB", + accepts: vec![concat!( + env!("THOTH_EXPORT_API"), + "/specifications/kbart::oclc" + ),], + }, + Platform { + id: "proquest_kb", + name: "ProQuest KB", + accepts: vec![concat!( + env!("THOTH_EXPORT_API"), + "/specifications/kbart::oclc" + ),], + }, + Platform { + id: "proquest_exlibris", + name: "ProQuest ExLibris", + accepts: vec![concat!( + env!("THOTH_EXPORT_API"), + "/specifications/kbart::oclc" + ),], + }, + Platform { + id: "ebsco_kb", + name: "EBSCO KB", + accepts: vec![concat!( + env!("THOTH_EXPORT_API"), + "/specifications/kbart::oclc" + ),], + }, + Platform { + id: "jisc_kb", + name: "JISC KB", + accepts: vec![concat!( + env!("THOTH_EXPORT_API"), + "/specifications/kbart::oclc" + ),], + }, ]; pub(crate) static ref ALL_FORMATS: Vec> = vec![ Format { @@ -74,6 +126,15 @@ lazy_static! { "/specifications/csv::thoth" ),], }, + Format { + id: "kbart", + name: "KBART", + version: None, + specifications: vec![concat!( + env!("THOTH_EXPORT_API"), + "/specifications/kbart::oclc" + ),], + }, ]; } diff --git a/thoth-export-server/src/record.rs b/thoth-export-server/src/record.rs index 917a80a8..acdfdc3f 100644 --- a/thoth-export-server/src/record.rs +++ b/thoth-export-server/src/record.rs @@ -8,16 +8,20 @@ use std::str::FromStr; use thoth_client::Work; use thoth_errors::{ThothError, ThothResult}; -use crate::csv::{CsvSpecification, CsvThoth}; +use crate::csv::{CsvSpecification, CsvThoth, KbartOclc}; use crate::xml::{Onix3Oapen, Onix3ProjectMuse, XmlSpecification}; pub(crate) trait AsRecord {} impl AsRecord for Vec {} +pub const DELIMITER_COMMA: u8 = b','; +pub const DELIMITER_TAB: u8 = b'\t'; + pub(crate) enum MetadataSpecification { Onix3ProjectMuse(Onix3ProjectMuse), Onix3Oapen(Onix3Oapen), CsvThoth(CsvThoth), + KbartOclc(KbartOclc), } pub(crate) struct MetadataRecord { @@ -32,8 +36,10 @@ where { const XML_MIME_TYPE: &'static str = "text/xml; charset=utf-8"; const CSV_MIME_TYPE: &'static str = "text/csv; charset=utf-8"; + const TXT_MIME_TYPE: &'static str = "text/plain; charset=utf-8"; const XML_EXTENSION: &'static str = ".xml"; const CSV_EXTENSION: &'static str = ".csv"; + const TXT_EXTENSION: &'static str = ".txt"; pub(crate) fn new(id: String, specification: MetadataSpecification, data: T) -> Self { MetadataRecord { @@ -48,6 +54,7 @@ where MetadataSpecification::Onix3ProjectMuse(_) => Self::XML_MIME_TYPE, MetadataSpecification::Onix3Oapen(_) => Self::XML_MIME_TYPE, MetadataSpecification::CsvThoth(_) => Self::CSV_MIME_TYPE, + MetadataSpecification::KbartOclc(_) => Self::TXT_MIME_TYPE, } } @@ -56,6 +63,7 @@ where MetadataSpecification::Onix3ProjectMuse(_) => self.xml_file_name(), MetadataSpecification::Onix3Oapen(_) => self.xml_file_name(), MetadataSpecification::CsvThoth(_) => self.csv_file_name(), + MetadataSpecification::KbartOclc(_) => self.txt_file_name(), } } @@ -67,6 +75,10 @@ where self.format_file_name(Self::CSV_EXTENSION) } + fn txt_file_name(&self) -> String { + self.format_file_name(Self::TXT_EXTENSION) + } + fn format_file_name(&self, extension: &'static str) -> String { format!( "{}__{}{}", @@ -88,7 +100,12 @@ impl MetadataRecord> { onix3_project_muse.generate(&self.data) } MetadataSpecification::Onix3Oapen(onix3_oapen) => onix3_oapen.generate(&self.data), - MetadataSpecification::CsvThoth(csv_thoth) => csv_thoth.generate(&self.data), + MetadataSpecification::CsvThoth(csv_thoth) => { + csv_thoth.generate(&self.data, DELIMITER_COMMA) + } + MetadataSpecification::KbartOclc(kbart_oclc) => { + kbart_oclc.generate(&self.data, DELIMITER_TAB) + } } } } @@ -140,6 +157,7 @@ impl FromStr for MetadataSpecification { } "onix_3.0::oapen" => Ok(MetadataSpecification::Onix3Oapen(Onix3Oapen {})), "csv::thoth" => Ok(MetadataSpecification::CsvThoth(CsvThoth {})), + "kbart::oclc" => Ok(MetadataSpecification::KbartOclc(KbartOclc {})), _ => Err(ThothError::InvalidMetadataSpecification(input.to_string())), } } @@ -151,6 +169,7 @@ impl ToString for MetadataSpecification { MetadataSpecification::Onix3ProjectMuse(_) => "onix_3.0::project_muse".to_string(), MetadataSpecification::Onix3Oapen(_) => "onix_3.0::oapen".to_string(), MetadataSpecification::CsvThoth(_) => "csv::thoth".to_string(), + MetadataSpecification::KbartOclc(_) => "kbart::oclc".to_string(), } } } @@ -197,5 +216,11 @@ mod tests { to_test.file_name(), "onix_3.0__oapen__some_id.xml".to_string() ); + let to_test = MetadataRecord::new( + "some_id".to_string(), + MetadataSpecification::KbartOclc(KbartOclc {}), + vec![], + ); + assert_eq!(to_test.file_name(), "kbart__oclc__some_id.txt".to_string()); } } From a5f9aa2a1b5e7127c89389bb0643fa60a81c73c3 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Thu, 26 Aug 2021 11:59:11 +0100 Subject: [PATCH 02/14] Jisc feedback: publication date mandatory, only one first author/editor, use internal ID for series, publication type title case --- thoth-client/assets/queries.graphql | 1 + thoth-export-server/src/csv/kbart_oclc.rs | 77 ++++++++++++++-------- thoth-export-server/src/xml/onix3_oapen.rs | 1 + 3 files changed, 51 insertions(+), 28 deletions(-) diff --git a/thoth-client/assets/queries.graphql b/thoth-client/assets/queries.graphql index a103c1fd..65ad8bf7 100644 --- a/thoth-client/assets/queries.graphql +++ b/thoth-client/assets/queries.graphql @@ -37,6 +37,7 @@ fragment Work on Work { issues { issueOrdinal series { + seriesId seriesType seriesName issnPrint diff --git a/thoth-export-server/src/csv/kbart_oclc.rs b/thoth-export-server/src/csv/kbart_oclc.rs index e6dcafda..9f15aed4 100644 --- a/thoth-export-server/src/csv/kbart_oclc.rs +++ b/thoth-export-server/src/csv/kbart_oclc.rs @@ -29,7 +29,7 @@ struct KbartOclcRow { publisher_name: Option, publication_type: Option, date_monograph_published_print: Option, - date_monograph_published_online: Option, + date_monograph_published_online: i64, monograph_volume: Option, monograph_edition: Option, first_editor: Option, @@ -59,56 +59,82 @@ impl TryFrom for KbartOclcRow { fn try_from(work: Work) -> ThothResult { // title_url is mandatory in KBART but optional in Thoth - if let Some(title_url) = work.landing_page { + if work.landing_page.is_none() { + Err(ThothError::IncompleteMetadataRecord( + "kbart::oclc".to_string(), + "Missing Title URL".to_string(), + )) + // Don't output works with no publication date (mandatory in KBART) + } else if work.publication_date.is_none() { + Err(ThothError::IncompleteMetadataRecord( + "kbart::oclc".to_string(), + "Missing Publication Date".to_string(), + )) + } else { let publication_title = match work.subtitle { Some(subtitle) => format!("{}: {}", work.title, subtitle), None => work.full_title, }; let mut print_identifier = None; let mut online_identifier = None; + let mut print_edition_exists = false; for publication in work.publications { + if publication.publication_type == PublicationType::PDF + && publication.isbn.is_some() + { + online_identifier = publication.isbn.clone(); + } if publication.publication_type == PublicationType::PAPERBACK { - print_identifier = publication.isbn.clone(); + print_edition_exists = true; + if publication.isbn.is_some() { + print_identifier = publication.isbn.clone(); + } } - if publication.publication_type == PublicationType::PDF { - online_identifier = publication.isbn.clone(); + if publication.publication_type == PublicationType::HARDBACK { + print_edition_exists = true; } } - let mut main_authors = vec![]; - let mut main_editors = vec![]; + let mut first_author = None; + let mut first_editor = None; let mut contributions = work.contributions; + // The first author/editor will usually be the contributor with contribution_ordinal 1, + // but this is not guaranteed, so we select the highest-ranked contributor of the + // appropriate contribution type who is listed as a "main" contributor. contributions.sort_by(|a, b| a.contribution_ordinal.cmp(&b.contribution_ordinal)); for contribution in contributions { if contribution.main_contribution { if work.work_type == WorkType::EDITED_BOOK { if contribution.contribution_type == ContributionType::EDITOR { - main_editors.push(contribution.last_name); + first_editor = Some(contribution.last_name); + break; } } else if contribution.contribution_type == ContributionType::AUTHOR { - main_authors.push(contribution.last_name); + first_author = Some(contribution.last_name); + break; } } } - let first_author = match main_authors.is_empty() { - true => None, - false => Some(main_authors.join("; ")), - }; - let first_editor = match main_editors.is_empty() { - true => None, - false => Some(main_editors.join("; ")), - }; let publication_type = match work.work_type { - WorkType::BOOK_SET => Some("serial".to_string()), - _ => Some("monograph".to_string()), + WorkType::BOOK_SET => Some("Serial".to_string()), + _ => Some("Monograph".to_string()), }; let publication_year = work .publication_date - .map(|date| chrono::Datelike::year(&date).into()); + .map(|date| chrono::Datelike::year(&date).into()) + .unwrap(); + let date_monograph_published_print = match print_edition_exists { + true => Some(publication_year), + false => None, + }; let mut monograph_volume = None; let mut parent_publication_title_id = None; if !work.issues.is_empty() { + // Note that it is possible for a work to belong to more than one series. + // Only one series can be listed in KBART, so we select the first one found. monograph_volume = Some(work.issues[0].issue_ordinal); - parent_publication_title_id = Some(work.issues[0].series.series_name.clone()); + // This should match the series' `title_id` if also provided in the KBART. + // Ideally it should be a DOI, otherwise an internal ID. + parent_publication_title_id = Some(work.issues[0].series.series_id.to_string()); } Ok(KbartOclcRow { publication_title, @@ -120,7 +146,7 @@ impl TryFrom for KbartOclcRow { date_last_issue_online: None, num_last_vol_online: None, num_last_issue_online: None, - title_url, + title_url: work.landing_page.unwrap(), first_author, title_id: work.doi, embargo_info: None, @@ -128,7 +154,7 @@ impl TryFrom for KbartOclcRow { notes: None, publisher_name: Some(work.imprint.publisher.publisher_name), publication_type, - date_monograph_published_print: publication_year, + date_monograph_published_print, date_monograph_published_online: publication_year, monograph_volume, monograph_edition: Some(work.edition), @@ -137,11 +163,6 @@ impl TryFrom for KbartOclcRow { preceding_publication_title_id: None, access_type: Some("F".to_string()), }) - } else { - Err(ThothError::IncompleteMetadataRecord( - "kbart::oclc".to_string(), - "Missing Title URL".to_string(), - )) } } } diff --git a/thoth-export-server/src/xml/onix3_oapen.rs b/thoth-export-server/src/xml/onix3_oapen.rs index 8b173f6e..6b38ddb0 100644 --- a/thoth-export-server/src/xml/onix3_oapen.rs +++ b/thoth-export-server/src/xml/onix3_oapen.rs @@ -769,6 +769,7 @@ mod tests { let mut test_issue = WorkIssues { issue_ordinal: 1, series: WorkIssuesSeries { + series_id: Uuid::from_str("00000000-0000-0000-AAAA-000000000002").unwrap(), series_type: thoth_client::SeriesType::JOURNAL, series_name: "Name of series".to_string(), issn_print: "1234-5678".to_string(), From 4684ac0709bd815e7e66c0f99d575ee94589df85 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:01:57 +0100 Subject: [PATCH 03/14] Fix bug: skip records with missing data in publisher export instead of failing when one is hit; minor improvements --- thoth-export-server/src/csv/kbart_oclc.rs | 68 ++++++++++++----------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/thoth-export-server/src/csv/kbart_oclc.rs b/thoth-export-server/src/csv/kbart_oclc.rs index 9f15aed4..d4188c4d 100644 --- a/thoth-export-server/src/csv/kbart_oclc.rs +++ b/thoth-export-server/src/csv/kbart_oclc.rs @@ -27,7 +27,7 @@ struct KbartOclcRow { coverage_depth: String, notes: Option, publisher_name: Option, - publication_type: Option, + publication_type: String, date_monograph_published_print: Option, date_monograph_published_online: i64, monograph_volume: Option, @@ -35,15 +35,24 @@ struct KbartOclcRow { first_editor: Option, parent_publication_title_id: Option, preceding_publication_title_id: Option, - access_type: Option, + access_type: String, } impl CsvSpecification for KbartOclc { fn handle_event(w: &mut Writer, works: &[Work]) -> ThothResult<()> { - for work in works.iter() { - CsvRow::::csv_row(work, w)?; + match works.len() { + 0 => Err(ThothError::IncompleteMetadataRecord( + "onix_3.0::project_muse".to_string(), + "Not enough data".to_string(), + )), + 1 => CsvRow::::csv_row(works.first().unwrap(), w), + _ => { + for work in works.iter() { + CsvRow::::csv_row(work, w).ok(); + } + Ok(()) + } } - Ok(()) } } @@ -62,7 +71,7 @@ impl TryFrom for KbartOclcRow { if work.landing_page.is_none() { Err(ThothError::IncompleteMetadataRecord( "kbart::oclc".to_string(), - "Missing Title URL".to_string(), + "Missing Landing Page".to_string(), )) // Don't output works with no publication date (mandatory in KBART) } else if work.publication_date.is_none() { @@ -71,10 +80,6 @@ impl TryFrom for KbartOclcRow { "Missing Publication Date".to_string(), )) } else { - let publication_title = match work.subtitle { - Some(subtitle) => format!("{}: {}", work.title, subtitle), - None => work.full_title, - }; let mut print_identifier = None; let mut online_identifier = None; let mut print_edition_exists = false; @@ -114,30 +119,19 @@ impl TryFrom for KbartOclcRow { } } } - let publication_type = match work.work_type { - WorkType::BOOK_SET => Some("Serial".to_string()), - _ => Some("Monograph".to_string()), - }; - let publication_year = work + let date_monograph_published_online = work .publication_date .map(|date| chrono::Datelike::year(&date).into()) .unwrap(); let date_monograph_published_print = match print_edition_exists { - true => Some(publication_year), + true => Some(date_monograph_published_online), false => None, }; - let mut monograph_volume = None; - let mut parent_publication_title_id = None; - if !work.issues.is_empty() { - // Note that it is possible for a work to belong to more than one series. - // Only one series can be listed in KBART, so we select the first one found. - monograph_volume = Some(work.issues[0].issue_ordinal); - // This should match the series' `title_id` if also provided in the KBART. - // Ideally it should be a DOI, otherwise an internal ID. - parent_publication_title_id = Some(work.issues[0].series.series_id.to_string()); - } Ok(KbartOclcRow { - publication_title, + publication_title: match work.subtitle { + Some(subtitle) => format!("{}: {}", work.title, subtitle), + None => work.full_title, + }, print_identifier, online_identifier, date_first_issue_online: None, @@ -153,15 +147,25 @@ impl TryFrom for KbartOclcRow { coverage_depth: "fulltext".to_string(), notes: None, publisher_name: Some(work.imprint.publisher.publisher_name), - publication_type, + publication_type: match work.work_type { + WorkType::BOOK_SET => "Serial".to_string(), + _ => "Monograph".to_string(), + }, date_monograph_published_print, - date_monograph_published_online: publication_year, - monograph_volume, + date_monograph_published_online, + // Note that it is possible for a work to belong to more than one series. + // Only one series can be listed in KBART, so we select the first one found (if any). + monograph_volume: work.issues.first().map(|i| i.issue_ordinal), monograph_edition: Some(work.edition), first_editor, - parent_publication_title_id, + // This should match the series' `title_id` if also provided in the KBART. + // Ideally it should be a DOI, otherwise an internal ID. + parent_publication_title_id: work + .issues + .first() + .map(|i| i.series.series_id.to_string()), preceding_publication_title_id: None, - access_type: Some("F".to_string()), + access_type: "F".to_string(), }) } } From 1223b28143428c93c61ad0957703ce322a8ebcdf Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Fri, 27 Aug 2021 10:02:38 +0100 Subject: [PATCH 04/14] OCLC feedback: can use eISSN for series ID --- thoth-client/assets/queries.graphql | 1 - thoth-export-server/src/csv/kbart_oclc.rs | 3 +-- thoth-export-server/src/xml/onix3_oapen.rs | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/thoth-client/assets/queries.graphql b/thoth-client/assets/queries.graphql index 65ad8bf7..a103c1fd 100644 --- a/thoth-client/assets/queries.graphql +++ b/thoth-client/assets/queries.graphql @@ -37,7 +37,6 @@ fragment Work on Work { issues { issueOrdinal series { - seriesId seriesType seriesName issnPrint diff --git a/thoth-export-server/src/csv/kbart_oclc.rs b/thoth-export-server/src/csv/kbart_oclc.rs index d4188c4d..1d5f7724 100644 --- a/thoth-export-server/src/csv/kbart_oclc.rs +++ b/thoth-export-server/src/csv/kbart_oclc.rs @@ -159,11 +159,10 @@ impl TryFrom for KbartOclcRow { monograph_edition: Some(work.edition), first_editor, // This should match the series' `title_id` if also provided in the KBART. - // Ideally it should be a DOI, otherwise an internal ID. parent_publication_title_id: work .issues .first() - .map(|i| i.series.series_id.to_string()), + .map(|i| i.series.issn_digital.to_string()), preceding_publication_title_id: None, access_type: "F".to_string(), }) diff --git a/thoth-export-server/src/xml/onix3_oapen.rs b/thoth-export-server/src/xml/onix3_oapen.rs index 6b38ddb0..8b173f6e 100644 --- a/thoth-export-server/src/xml/onix3_oapen.rs +++ b/thoth-export-server/src/xml/onix3_oapen.rs @@ -769,7 +769,6 @@ mod tests { let mut test_issue = WorkIssues { issue_ordinal: 1, series: WorkIssuesSeries { - series_id: Uuid::from_str("00000000-0000-0000-AAAA-000000000002").unwrap(), series_type: thoth_client::SeriesType::JOURNAL, series_name: "Name of series".to_string(), issn_print: "1234-5678".to_string(), From f0bbe3e4cd9bf626b3053415e177d5f24a2cf943 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Fri, 27 Aug 2021 10:57:08 +0100 Subject: [PATCH 05/14] Add a basic test for KBART (based on CSV test) --- thoth-export-server/src/csv/kbart_oclc.rs | 153 ++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/thoth-export-server/src/csv/kbart_oclc.rs b/thoth-export-server/src/csv/kbart_oclc.rs index 1d5f7724..87685577 100644 --- a/thoth-export-server/src/csv/kbart_oclc.rs +++ b/thoth-export-server/src/csv/kbart_oclc.rs @@ -169,3 +169,156 @@ impl TryFrom for KbartOclcRow { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::record::DELIMITER_TAB; + use lazy_static::lazy_static; + use std::str::FromStr; + use thoth_client::{ + ContributionType, PublicationType, WorkContributions, WorkContributionsContributor, + WorkImprint, WorkImprintPublisher, WorkIssues, WorkIssuesSeries, WorkPublications, + WorkStatus, WorkType, + }; + use uuid::Uuid; + + lazy_static! { + static ref TEST_WORK: Work = Work { + work_id: Uuid::from_str("00000000-0000-0000-AAAA-000000000001").unwrap(), + work_status: WorkStatus::ACTIVE, + full_title: "Book Title: Book Subtitle".to_string(), + title: "Book Title".to_string(), + subtitle: Some("Separate Subtitle".to_string()), + work_type: WorkType::MONOGRAPH, + edition: 1, + doi: Some("https://doi.org/10.00001/BOOK.0001".to_string()), + publication_date: Some(chrono::NaiveDate::from_ymd(1999, 12, 31)), + license: Some("http://creativecommons.org/licenses/by/4.0/".to_string()), + copyright_holder: "Author 1; Author 2".to_string(), + short_abstract: Some("Lorem ipsum dolor sit amet".to_string()), + long_abstract: Some( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit".to_string() + ), + general_note: None, + place: Some("León, Spain".to_string()), + width: Some(156.0), + height: Some(234.0), + page_count: Some(334), + page_breakdown: Some("x+334".to_string()), + image_count: Some(15), + table_count: None, + audio_count: None, + video_count: None, + landing_page: Some("https://www.book.com".to_string()), + toc: None, + lccn: None, + oclc: None, + cover_url: Some("https://www.book.com/cover".to_string()), + cover_caption: None, + imprint: WorkImprint { + imprint_name: "OA Editions Imprint".to_string(), + publisher: WorkImprintPublisher { + publisher_name: "OA Editions".to_string(), + }, + }, + issues: vec![ + WorkIssues { + issue_ordinal: 20, + series: WorkIssuesSeries { + series_type: thoth_client::SeriesType::BOOK_SERIES, + series_name: "Name of series".to_string(), + issn_print: "1234-5678".to_string(), + issn_digital: "8765-4321".to_string(), + series_url: None, + }, + }, + WorkIssues { + issue_ordinal: 50, + series: WorkIssuesSeries { + series_type: thoth_client::SeriesType::BOOK_SERIES, + series_name: "Name of second series".to_string(), + issn_print: "1111-2222".to_string(), + issn_digital: "3333-4444".to_string(), + series_url: None, + }, + } + ], + contributions: vec![ + WorkContributions { + contribution_type: ContributionType::AUTHOR, + first_name: Some("Author".to_string()), + last_name: "First".to_string(), + full_name: "Author First".to_string(), + main_contribution: true, + biography: None, + institution: None, + contribution_ordinal: 1, + contributor: WorkContributionsContributor { + orcid: Some("https://orcid.org/0000-0000-0000-0001".to_string()), + }, + }, + WorkContributions { + contribution_type: ContributionType::AUTHOR, + first_name: Some("Author".to_string()), + last_name: "Second".to_string(), + full_name: "Author Second".to_string(), + main_contribution: true, + biography: None, + institution: None, + contribution_ordinal: 2, + contributor: WorkContributionsContributor { orcid: None }, + }, + ], + languages: vec![], + publications: vec![ + WorkPublications { + publication_id: Uuid::from_str("00000000-0000-0000-BBBB-000000000002").unwrap(), + publication_type: PublicationType::PAPERBACK, + publication_url: Some("https://www.book.com/paperback".to_string()), + isbn: Some("978-1-00000-000-0".to_string()), + prices: vec![], + }, + WorkPublications { + publication_id: Uuid::from_str("00000000-0000-0000-CCCC-000000000003").unwrap(), + publication_type: PublicationType::HARDBACK, + publication_url: Some("https://www.book.com/hardback".to_string()), + isbn: Some("978-1-00000-000-1".to_string()), + prices: vec![], + }, + WorkPublications { + publication_id: Uuid::from_str("00000000-0000-0000-DDDD-000000000004").unwrap(), + publication_type: PublicationType::PDF, + publication_url: Some("https://www.book.com/pdf".to_string()), + isbn: Some("978-1-00000-000-2".to_string()), + prices: vec![], + }, + WorkPublications { + publication_id: Uuid::from_str("00000000-0000-0000-EEEE-000000000005").unwrap(), + publication_type: PublicationType::HTML, + publication_url: Some("https://www.book.com/html".to_string()), + isbn: None, + prices: vec![], + }, + WorkPublications { + publication_id: Uuid::from_str("00000000-0000-0000-FFFF-000000000006").unwrap(), + publication_type: PublicationType::XML, + publication_url: Some("https://www.book.com/xml".to_string()), + isbn: Some("978-1-00000-000-3".to_string()), + prices: vec![], + }, + ], + subjects: vec![], + fundings: vec![], + }; + } + + const TEST_RESULT: &str = "\"publication_title\"\t\"print_identifier\"\t\"online_identifier\"\t\"date_first_issue_online\"\t\"num_first_vol_online\"\t\"num_first_issue_online\"\t\"date_last_issue_online\"\t\"num_last_vol_online\"\t\"num_last_issue_online\"\t\"title_url\"\t\"first_author\"\t\"title_id\"\t\"embargo_info\"\t\"coverage_depth\"\t\"notes\"\t\"publisher_name\"\t\"publication_type\"\t\"date_monograph_published_print\"\t\"date_monograph_published_online\"\t\"monograph_volume\"\t\"monograph_edition\"\t\"first_editor\"\t\"parent_publication_title_id\"\t\"preceding_publication_title_id\"\t\"access_type\"\n\"Book Title: Separate Subtitle\"\t\"978-1-00000-000-0\"\t\"978-1-00000-000-2\"\t\"\"\t\"\"\t\"\"\t\"\"\t\"\"\t\"\"\t\"https://www.book.com\"\t\"First\"\t\"https://doi.org/10.00001/BOOK.0001\"\t\"\"\t\"fulltext\"\t\"\"\t\"OA Editions\"\t\"Monograph\"\t\"1999\"\t\"1999\"\t\"20\"\t\"1\"\t\"\"\t\"8765-4321\"\t\"\"\t\"F\"\n"; + + #[test] + fn test_kbart_oclc() { + let to_test = KbartOclc.generate(&[TEST_WORK.clone()], DELIMITER_TAB); + + assert_eq!(to_test, Ok(TEST_RESULT.to_string())) + } +} From 96fc4670867f339e6cfe38db189fd15be613c079 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Fri, 27 Aug 2021 13:15:54 +0100 Subject: [PATCH 06/14] Minor correction to changelog (for issue #259) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 718fe66f..188b0e3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - [#259](https://github.com/thoth-pub/thoth/issues/259) - Units selection dropdown to Work and NewWork pages, which updates the Width/Height display on change - [#259](https://github.com/thoth-pub/thoth/issues/259) - Local storage key to retain user's choice of units across all Work/NewWork pages - - [#259](https://github.com/thoth-pub/thoth/issues/259) - Backend function to convert to/from database units (mm): uses 1inch = 25.4mm as conversion factor, rounds mm values to nearest mm, rounds cm values to 1 decimal place, rounds inch values to 2 decimal places and then to nearest sixteenth of an inch + - [#259](https://github.com/thoth-pub/thoth/issues/259) - Backend function to convert to/from database units (mm): uses 1inch = 25.4mm as conversion factor, rounds mm values to nearest mm, rounds cm values to 1 decimal place, rounds inch values to 2 decimal places - [#259](https://github.com/thoth-pub/thoth/issues/259) - Constraints on Width/Height fields depending on unit selection: user may only enter whole numbers when in mm, numbers with up to 1 decimal place when in cm, numbers with up to 2 decimal places when in inches ### Changed From 1516f9ae96cabeeb131dd162fa63428d5e646f08 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Fri, 27 Aug 2021 16:21:02 +0100 Subject: [PATCH 07/14] Add delete confirmation modal dialogue to Publication delete button --- thoth-app/src/component/publication.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/thoth-app/src/component/publication.rs b/thoth-app/src/component/publication.rs index 7c5649c4..12af937e 100644 --- a/thoth-app/src/component/publication.rs +++ b/thoth-app/src/component/publication.rs @@ -18,6 +18,7 @@ use crate::agent::notification_bus::NotificationBus; use crate::agent::notification_bus::NotificationDispatcher; use crate::agent::notification_bus::NotificationStatus; use crate::agent::notification_bus::Request; +use crate::component::delete_dialogue::ConfirmDeleteComponent; use crate::component::prices_form::PricesFormComponent; use crate::component::utils::Loader; use crate::models::publication::delete_publication_mutation::DeletePublicationRequest; @@ -32,7 +33,6 @@ use crate::models::publication::publication_query::PublicationRequestBody; use crate::models::publication::publication_query::Variables; use crate::route::AdminRoute; use crate::route::AppRoute; -use crate::string::DELETE_BUTTON; pub struct PublicationComponent { publication: PublicationWithRelations, @@ -219,9 +219,15 @@ impl Component for PublicationComponent {

- +

From 370c42a0b7d0e8c8eabcf991570235c297a8bd6c Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Fri, 27 Aug 2021 16:33:32 +0100 Subject: [PATCH 08/14] Make subjectCount query filter parameter optional (by supplying empty string as default) --- thoth-api/src/graphql/model.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/thoth-api/src/graphql/model.rs b/thoth-api/src/graphql/model.rs index c3a9436d..9bb47ea1 100644 --- a/thoth-api/src/graphql/model.rs +++ b/thoth-api/src/graphql/model.rs @@ -815,7 +815,16 @@ impl QueryRoot { Subject::from_id(&context.db, &subject_id).map_err(|e| e.into()) } - #[graphql(description = "Get the total number of subjects associated to works")] + #[graphql( + description = "Get the total number of subjects associated to works", + arguments( + filter( + default = "".to_string(), + description = "A query string to search. This argument is a test, do not rely on it. At present it simply searches for case insensitive literals on subject_code", + ), + subject_type(description = "A specific type to filter by"), + ) + )] fn subject_count( context: &Context, filter: String, From 988481a5e32ff53392a39b87e81434b6d6bbf678 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Fri, 27 Aug 2021 16:46:54 +0100 Subject: [PATCH 09/14] Add lastName to contributor query filter fields --- thoth-api/src/contributor/crud.rs | 2 ++ thoth-api/src/graphql/model.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/thoth-api/src/contributor/crud.rs b/thoth-api/src/contributor/crud.rs index 35444747..3737b6e6 100644 --- a/thoth-api/src/contributor/crud.rs +++ b/thoth-api/src/contributor/crud.rs @@ -77,6 +77,7 @@ impl Crud for Contributor { query = query.filter( full_name .ilike(format!("%{}%", filter)) + .or(last_name.ilike(format!("%{}%", filter))) .or(orcid.ilike(format!("%{}%", filter))), ); } @@ -104,6 +105,7 @@ impl Crud for Contributor { query = query.filter( full_name .ilike(format!("%{}%", filter)) + .or(last_name.ilike(format!("%{}%", filter))) .or(orcid.ilike(format!("%{}%", filter))), ); } diff --git a/thoth-api/src/graphql/model.rs b/thoth-api/src/graphql/model.rs index 9bb47ea1..a6e3e6d6 100644 --- a/thoth-api/src/graphql/model.rs +++ b/thoth-api/src/graphql/model.rs @@ -415,7 +415,7 @@ impl QueryRoot { offset(default = 0, description = "The number of items to skip"), filter( default = "".to_string(), - description = "A query string to search. This argument is a test, do not rely on it. At present it simply searches for case insensitive literals on full_name and orcid" + description = "A query string to search. This argument is a test, do not rely on it. At present it simply searches for case insensitive literals on full_name, last_name and orcid" ), order( default = ContributorOrderBy::default(), @@ -455,7 +455,7 @@ impl QueryRoot { arguments( filter( default = "".to_string(), - description = "A query string to search. This argument is a test, do not rely on it. At present it simply searches for case insensitive literals on full_name and orcid", + description = "A query string to search. This argument is a test, do not rely on it. At present it simply searches for case insensitive literals on full_name, last_name and orcid", ), ) )] From d28fef68872b6734c1bcdece7344280164656005 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Tue, 31 Aug 2021 13:33:17 +0100 Subject: [PATCH 10/14] Filter on each word of contributor query string [behaviour is poor: too many results] --- thoth-api/src/contributor/crud.rs | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/thoth-api/src/contributor/crud.rs b/thoth-api/src/contributor/crud.rs index 3737b6e6..b1fcbd46 100644 --- a/thoth-api/src/contributor/crud.rs +++ b/thoth-api/src/contributor/crud.rs @@ -6,9 +6,7 @@ use crate::graphql::utils::Direction; use crate::model::{Crud, DbInsert, HistoryEntry}; use crate::schema::{contributor, contributor_history}; use crate::{crud_methods, db_insert}; -use diesel::{ - BoolExpressionMethods, ExpressionMethods, PgTextExpressionMethods, QueryDsl, RunQueryDsl, -}; +use diesel::{ExpressionMethods, PgTextExpressionMethods, QueryDsl, RunQueryDsl}; use thoth_errors::{ThothError, ThothResult}; use uuid::Uuid; @@ -74,12 +72,12 @@ impl Crud for Contributor { }, } if let Some(filter) = filter { - query = query.filter( - full_name - .ilike(format!("%{}%", filter)) - .or(last_name.ilike(format!("%{}%", filter))) - .or(orcid.ilike(format!("%{}%", filter))), - ); + for substring in filter.split_whitespace() { + query = query + .or_filter(full_name.ilike(format!("%{}%", substring))) + .or_filter(last_name.ilike(format!("%{}%", substring))) + .or_filter(orcid.ilike(format!("%{}%", substring))); + } } match query .limit(limit.into()) @@ -102,12 +100,12 @@ impl Crud for Contributor { let connection = db.get().unwrap(); let mut query = contributor.into_boxed(); if let Some(filter) = filter { - query = query.filter( - full_name - .ilike(format!("%{}%", filter)) - .or(last_name.ilike(format!("%{}%", filter))) - .or(orcid.ilike(format!("%{}%", filter))), - ); + for substring in filter.split_whitespace() { + query = query + .or_filter(full_name.ilike(format!("%{}%", substring))) + .or_filter(last_name.ilike(format!("%{}%", substring))) + .or_filter(orcid.ilike(format!("%{}%", substring))); + } } // `SELECT COUNT(*)` in postgres returns a BIGINT, which diesel parses as i64. Juniper does From 578c560fad28df1971629dc25b23f45f60ab7d71 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Tue, 31 Aug 2021 13:33:53 +0100 Subject: [PATCH 11/14] Revert "Filter on each word of contributor query string [behaviour is poor: too many results]" This reverts commit d28fef68872b6734c1bcdece7344280164656005. --- thoth-api/src/contributor/crud.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/thoth-api/src/contributor/crud.rs b/thoth-api/src/contributor/crud.rs index b1fcbd46..3737b6e6 100644 --- a/thoth-api/src/contributor/crud.rs +++ b/thoth-api/src/contributor/crud.rs @@ -6,7 +6,9 @@ use crate::graphql::utils::Direction; use crate::model::{Crud, DbInsert, HistoryEntry}; use crate::schema::{contributor, contributor_history}; use crate::{crud_methods, db_insert}; -use diesel::{ExpressionMethods, PgTextExpressionMethods, QueryDsl, RunQueryDsl}; +use diesel::{ + BoolExpressionMethods, ExpressionMethods, PgTextExpressionMethods, QueryDsl, RunQueryDsl, +}; use thoth_errors::{ThothError, ThothResult}; use uuid::Uuid; @@ -72,12 +74,12 @@ impl Crud for Contributor { }, } if let Some(filter) = filter { - for substring in filter.split_whitespace() { - query = query - .or_filter(full_name.ilike(format!("%{}%", substring))) - .or_filter(last_name.ilike(format!("%{}%", substring))) - .or_filter(orcid.ilike(format!("%{}%", substring))); - } + query = query.filter( + full_name + .ilike(format!("%{}%", filter)) + .or(last_name.ilike(format!("%{}%", filter))) + .or(orcid.ilike(format!("%{}%", filter))), + ); } match query .limit(limit.into()) @@ -100,12 +102,12 @@ impl Crud for Contributor { let connection = db.get().unwrap(); let mut query = contributor.into_boxed(); if let Some(filter) = filter { - for substring in filter.split_whitespace() { - query = query - .or_filter(full_name.ilike(format!("%{}%", substring))) - .or_filter(last_name.ilike(format!("%{}%", substring))) - .or_filter(orcid.ilike(format!("%{}%", substring))); - } + query = query.filter( + full_name + .ilike(format!("%{}%", filter)) + .or(last_name.ilike(format!("%{}%", filter))) + .or(orcid.ilike(format!("%{}%", filter))), + ); } // `SELECT COUNT(*)` in postgres returns a BIGINT, which diesel parses as i64. Juniper does From a4d92242b1b609a668c41456e55df633e82d2a08 Mon Sep 17 00:00:00 2001 From: rhigman <73792779+rhigman@users.noreply.github.com> Date: Thu, 2 Sep 2021 11:12:21 +0100 Subject: [PATCH 12/14] KBART Standing Committee feedback: remove quotation marks around data cells --- thoth-export-server/src/csv/csv_thoth.rs | 3 ++- thoth-export-server/src/csv/kbart_oclc.rs | 6 ++++-- thoth-export-server/src/csv/mod.rs | 11 +++++++---- thoth-export-server/src/record.rs | 5 +++-- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/thoth-export-server/src/csv/csv_thoth.rs b/thoth-export-server/src/csv/csv_thoth.rs index 5ff70125..3c00213f 100644 --- a/thoth-export-server/src/csv/csv_thoth.rs +++ b/thoth-export-server/src/csv/csv_thoth.rs @@ -305,6 +305,7 @@ impl CsvCell for WorkFundings { mod tests { use super::*; use crate::record::DELIMITER_COMMA; + use csv::QuoteStyle; use lazy_static::lazy_static; use std::str::FromStr; use thoth_client::{ @@ -496,7 +497,7 @@ mod tests { #[test] fn test_csv_thoth() { - let to_test = CsvThoth.generate(&[TEST_WORK.clone()], DELIMITER_COMMA); + let to_test = CsvThoth.generate(&[TEST_WORK.clone()], QuoteStyle::Always, DELIMITER_COMMA); assert_eq!(to_test, Ok(TEST_RESULT.to_string())) } diff --git a/thoth-export-server/src/csv/kbart_oclc.rs b/thoth-export-server/src/csv/kbart_oclc.rs index 87685577..396a6a02 100644 --- a/thoth-export-server/src/csv/kbart_oclc.rs +++ b/thoth-export-server/src/csv/kbart_oclc.rs @@ -174,6 +174,7 @@ impl TryFrom for KbartOclcRow { mod tests { use super::*; use crate::record::DELIMITER_TAB; + use csv::QuoteStyle; use lazy_static::lazy_static; use std::str::FromStr; use thoth_client::{ @@ -313,11 +314,12 @@ mod tests { }; } - const TEST_RESULT: &str = "\"publication_title\"\t\"print_identifier\"\t\"online_identifier\"\t\"date_first_issue_online\"\t\"num_first_vol_online\"\t\"num_first_issue_online\"\t\"date_last_issue_online\"\t\"num_last_vol_online\"\t\"num_last_issue_online\"\t\"title_url\"\t\"first_author\"\t\"title_id\"\t\"embargo_info\"\t\"coverage_depth\"\t\"notes\"\t\"publisher_name\"\t\"publication_type\"\t\"date_monograph_published_print\"\t\"date_monograph_published_online\"\t\"monograph_volume\"\t\"monograph_edition\"\t\"first_editor\"\t\"parent_publication_title_id\"\t\"preceding_publication_title_id\"\t\"access_type\"\n\"Book Title: Separate Subtitle\"\t\"978-1-00000-000-0\"\t\"978-1-00000-000-2\"\t\"\"\t\"\"\t\"\"\t\"\"\t\"\"\t\"\"\t\"https://www.book.com\"\t\"First\"\t\"https://doi.org/10.00001/BOOK.0001\"\t\"\"\t\"fulltext\"\t\"\"\t\"OA Editions\"\t\"Monograph\"\t\"1999\"\t\"1999\"\t\"20\"\t\"1\"\t\"\"\t\"8765-4321\"\t\"\"\t\"F\"\n"; + const TEST_RESULT: &str = "publication_title\tprint_identifier\tonline_identifier\tdate_first_issue_online\tnum_first_vol_online\tnum_first_issue_online\tdate_last_issue_online\tnum_last_vol_online\tnum_last_issue_online\ttitle_url\tfirst_author\ttitle_id\tembargo_info\tcoverage_depth\tnotes\tpublisher_name\tpublication_type\tdate_monograph_published_print\tdate_monograph_published_online\tmonograph_volume\tmonograph_edition\tfirst_editor\tparent_publication_title_id\tpreceding_publication_title_id\taccess_type\nBook Title: Separate Subtitle\t978-1-00000-000-0\t978-1-00000-000-2\t\t\t\t\t\t\thttps://www.book.com\tFirst\thttps://doi.org/10.00001/BOOK.0001\t\tfulltext\t\tOA Editions\tMonograph\t1999\t1999\t20\t1\t\t8765-4321\t\tF\n"; #[test] fn test_kbart_oclc() { - let to_test = KbartOclc.generate(&[TEST_WORK.clone()], DELIMITER_TAB); + let to_test = + KbartOclc.generate(&[TEST_WORK.clone()], QuoteStyle::Necessary, DELIMITER_TAB); assert_eq!(to_test, Ok(TEST_RESULT.to_string())) } diff --git a/thoth-export-server/src/csv/mod.rs b/thoth-export-server/src/csv/mod.rs index ec268e9e..7b7b5cad 100644 --- a/thoth-export-server/src/csv/mod.rs +++ b/thoth-export-server/src/csv/mod.rs @@ -4,11 +4,14 @@ use thoth_client::Work; use thoth_errors::{ThothError, ThothResult}; pub(crate) trait CsvSpecification { - const QUOTE_STYLE: QuoteStyle = QuoteStyle::Always; - - fn generate(&self, works: &[Work], delimiter: u8) -> ThothResult { + fn generate( + &self, + works: &[Work], + quote_style: QuoteStyle, + delimiter: u8, + ) -> ThothResult { let mut writer = WriterBuilder::new() - .quote_style(Self::QUOTE_STYLE) + .quote_style(quote_style) .delimiter(delimiter) .from_writer(Vec::new()); Self::handle_event(&mut writer, works) diff --git a/thoth-export-server/src/record.rs b/thoth-export-server/src/record.rs index acdfdc3f..95f3ea91 100644 --- a/thoth-export-server/src/record.rs +++ b/thoth-export-server/src/record.rs @@ -1,4 +1,5 @@ use actix_web::{http::StatusCode, HttpRequest, Responder}; +use csv::QuoteStyle; use paperclip::actix::web::HttpResponse; use paperclip::actix::OperationModifier; use paperclip::util::{ready, Ready}; @@ -101,10 +102,10 @@ impl MetadataRecord> { } MetadataSpecification::Onix3Oapen(onix3_oapen) => onix3_oapen.generate(&self.data), MetadataSpecification::CsvThoth(csv_thoth) => { - csv_thoth.generate(&self.data, DELIMITER_COMMA) + csv_thoth.generate(&self.data, QuoteStyle::Always, DELIMITER_COMMA) } MetadataSpecification::KbartOclc(kbart_oclc) => { - kbart_oclc.generate(&self.data, DELIMITER_TAB) + kbart_oclc.generate(&self.data, QuoteStyle::Necessary, DELIMITER_TAB) } } } From 5d8c04fc5a155c6e3e11f618bdec9c6c52fcd0ea Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Thu, 2 Sep 2021 13:13:51 +0100 Subject: [PATCH 13/14] Bump version v0.4.6 --- Cargo.lock | 16 ++++++++-------- Cargo.toml | 12 ++++++------ thoth-api-server/Cargo.toml | 6 +++--- thoth-api/Cargo.toml | 4 ++-- thoth-app-server/Cargo.toml | 2 +- thoth-app/Cargo.toml | 6 +++--- thoth-app/manifest.json | 2 +- thoth-client/Cargo.toml | 6 +++--- thoth-errors/Cargo.toml | 2 +- thoth-export-server/Cargo.toml | 8 ++++---- 10 files changed, 32 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4436396..56ca9929 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3936,7 +3936,7 @@ dependencies = [ [[package]] name = "thoth" -version = "0.4.5" +version = "0.4.6" dependencies = [ "cargo-husky", "clap", @@ -3951,7 +3951,7 @@ dependencies = [ [[package]] name = "thoth-api" -version = "0.4.5" +version = "0.4.6" dependencies = [ "actix-web", "argon2rs", @@ -3980,7 +3980,7 @@ dependencies = [ [[package]] name = "thoth-api-server" -version = "0.4.5" +version = "0.4.6" dependencies = [ "actix-cors", "actix-identity", @@ -3995,7 +3995,7 @@ dependencies = [ [[package]] name = "thoth-app" -version = "0.4.5" +version = "0.4.6" dependencies = [ "anyhow", "chrono", @@ -4018,7 +4018,7 @@ dependencies = [ [[package]] name = "thoth-app-server" -version = "0.4.5" +version = "0.4.6" dependencies = [ "actix-cors", "actix-web", @@ -4027,7 +4027,7 @@ dependencies = [ [[package]] name = "thoth-client" -version = "0.4.5" +version = "0.4.6" dependencies = [ "chrono", "graphql_client", @@ -4041,7 +4041,7 @@ dependencies = [ [[package]] name = "thoth-errors" -version = "0.4.5" +version = "0.4.6" dependencies = [ "actix-web", "csv", @@ -4056,7 +4056,7 @@ dependencies = [ [[package]] name = "thoth-export-server" -version = "0.4.5" +version = "0.4.6" dependencies = [ "actix-cors", "actix-web", diff --git a/Cargo.toml b/Cargo.toml index db39823a..230da6ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth" -version = "0.4.5" +version = "0.4.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -16,11 +16,11 @@ maintenance = { status = "actively-developed" } members = ["thoth-api", "thoth-api-server", "thoth-app", "thoth-app-server", "thoth-client", "thoth-errors", "thoth-export-server"] [dependencies] -thoth-api = { version = "0.4.5", path = "thoth-api", features = ["backend"] } -thoth-api-server = { version = "0.4.5", path = "thoth-api-server" } -thoth-app-server = { version = "0.4.5", path = "thoth-app-server" } -thoth-errors = { version = "0.4.5", path = "thoth-errors" } -thoth-export-server = { version = "0.4.5", path = "thoth-export-server" } +thoth-api = { version = "0.4.6", path = "thoth-api", features = ["backend"] } +thoth-api-server = { version = "0.4.6", path = "thoth-api-server" } +thoth-app-server = { version = "0.4.6", path = "thoth-app-server" } +thoth-errors = { version = "0.4.6", path = "thoth-errors" } +thoth-export-server = { version = "0.4.6", path = "thoth-export-server" } clap = "2.33.3" dialoguer = "0.7.1" dotenv = "0.9.0" diff --git a/thoth-api-server/Cargo.toml b/thoth-api-server/Cargo.toml index 7dea4a3b..ef597e7c 100644 --- a/thoth-api-server/Cargo.toml +++ b/thoth-api-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-api-server" -version = "0.4.5" +version = "0.4.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -9,8 +9,8 @@ repository = "https://github.com/thoth-pub/thoth" readme = "README.md" [dependencies] -thoth-api = { version = "0.4.5", path = "../thoth-api", features = ["backend"] } -thoth-errors = { version = "0.4.5", path = "../thoth-errors" } +thoth-api = { version = "0.4.6", path = "../thoth-api", features = ["backend"] } +thoth-errors = { version = "0.4.6", path = "../thoth-errors" } actix-web = "3.3.2" actix-cors = "0.5.4" actix-identity = "0.3.1" diff --git a/thoth-api/Cargo.toml b/thoth-api/Cargo.toml index 238114ad..028172f3 100644 --- a/thoth-api/Cargo.toml +++ b/thoth-api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-api" -version = "0.4.5" +version = "0.4.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -16,7 +16,7 @@ maintenance = { status = "actively-developed" } backend = ["diesel", "diesel-derive-enum", "diesel_migrations", "futures", "actix-web"] [dependencies] -thoth-errors = { version = "0.4.5", path = "../thoth-errors" } +thoth-errors = { version = "0.4.6", path = "../thoth-errors" } actix-web = { version = "3.3.2", optional = true } argon2rs = "0.2.5" isbn2 = "0.4.0" diff --git a/thoth-app-server/Cargo.toml b/thoth-app-server/Cargo.toml index c10c63ba..70a641d1 100644 --- a/thoth-app-server/Cargo.toml +++ b/thoth-app-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-app-server" -version = "0.4.5" +version = "0.4.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" diff --git a/thoth-app/Cargo.toml b/thoth-app/Cargo.toml index 30038a70..c5d63e9a 100644 --- a/thoth-app/Cargo.toml +++ b/thoth-app/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-app" -version = "0.4.5" +version = "0.4.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -33,5 +33,5 @@ serde = { version = "1.0.115", features = ["derive"] } serde_json = "1.0" url = "2.1.1" uuid = { version = "0.7", features = ["serde", "v4"] } -thoth-api = { version = "0.4.5", path = "../thoth-api" } -thoth-errors = { version = "0.4.5", path = "../thoth-errors" } +thoth-api = { version = "0.4.6", path = "../thoth-api" } +thoth-errors = { version = "0.4.6", path = "../thoth-errors" } diff --git a/thoth-app/manifest.json b/thoth-app/manifest.json index e5029b3a..21caea6b 100644 --- a/thoth-app/manifest.json +++ b/thoth-app/manifest.json @@ -9,7 +9,7 @@ "start_url": "/?homescreen=1", "background_color": "#ffffff", "theme_color": "#ffdd57", - "version": "0.4.5", + "version": "0.4.6", "icons": [ { "src": "\/android-icon-36x36.png", diff --git a/thoth-client/Cargo.toml b/thoth-client/Cargo.toml index 430e6df5..2e9d3819 100644 --- a/thoth-client/Cargo.toml +++ b/thoth-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-client" -version = "0.4.5" +version = "0.4.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -9,8 +9,8 @@ repository = "https://github.com/thoth-pub/thoth" readme = "README.md" [dependencies] -thoth-api = {version = "0.4.5", path = "../thoth-api" } -thoth-errors = {version = "0.4.5", path = "../thoth-errors" } +thoth-api = {version = "0.4.6", path = "../thoth-api" } +thoth-errors = {version = "0.4.6", path = "../thoth-errors" } graphql_client = "0.9.0" chrono = { version = "0.4", features = ["serde"] } reqwest = { version = "0.10", features = ["json"] } diff --git a/thoth-errors/Cargo.toml b/thoth-errors/Cargo.toml index 60eeab31..180c30c0 100644 --- a/thoth-errors/Cargo.toml +++ b/thoth-errors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-errors" -version = "0.4.5" +version = "0.4.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" diff --git a/thoth-export-server/Cargo.toml b/thoth-export-server/Cargo.toml index f9655b1c..71f9ed8d 100644 --- a/thoth-export-server/Cargo.toml +++ b/thoth-export-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "thoth-export-server" -version = "0.4.5" +version = "0.4.6" authors = ["Javier Arias ", "Ross Higman "] edition = "2018" license = "Apache-2.0" @@ -9,9 +9,9 @@ repository = "https://github.com/thoth-pub/thoth" readme = "README.md" [dependencies] -thoth-api = { version = "0.4.5", path = "../thoth-api" } -thoth-errors = { version = "0.4.5", path = "../thoth-errors" } -thoth-client = { version = "0.4.5", path = "../thoth-client" } +thoth-api = { version = "0.4.6", path = "../thoth-api" } +thoth-errors = { version = "0.4.6", path = "../thoth-errors" } +thoth-client = { version = "0.4.6", path = "../thoth-client" } actix-web = "3.3.2" actix-cors = "0.5.4" chrono = { version = "0.4", features = ["serde"] } From 8fd693bb7fd36d4a4bff585580d61dbea9d62c88 Mon Sep 17 00:00:00 2001 From: Javier Arias Date: Thu, 2 Sep 2021 13:18:03 +0100 Subject: [PATCH 14/14] Update Changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 188b0e3b..a1dc8ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to thoth will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.4.6]](https://github.com/thoth-pub/thoth/releases/tag/v0.4.6) - 2021-09-02 +### Added + - [#88](https://github.com/thoth-pub/thoth/issues/88) - Implement KBART specification + - [#266](https://github.com/thoth-pub/thoth/issues/266) - Delete confirmation to publications + +### Changed + - [#272](https://github.com/thoth-pub/thoth/issues/272) - Use more fields in `contributors` filtering + +### Fixed + - [#271](https://github.com/thoth-pub/thoth/issues/271) - Make filter parameter optional in `subjectCount` + + ## [[0.4.5]](https://github.com/thoth-pub/thoth/releases/tag/v0.4.5) - 2021-08-12 ### Added - [#259](https://github.com/thoth-pub/thoth/issues/259) - Units selection dropdown to Work and NewWork pages, which updates the Width/Height display on change