diff --git a/.gitignore b/.gitignore index 5e73cfd20a..087f3ab5a4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,11 @@ src* build/* dist/* tmp/* + *.zip +!doajtest/unit/resources/*.zip +!doajtest/preservation_upload_test_package/*.zip + scratch.py .coverage htmlcov/* diff --git a/cms/assets/img/sponsors/Beijing.jpg b/cms/assets/img/sponsors/Beijing.jpg new file mode 100644 index 0000000000..30652d63e6 Binary files /dev/null and b/cms/assets/img/sponsors/Beijing.jpg differ diff --git a/cms/assets/img/sponsors/Degruyter.svg b/cms/assets/img/sponsors/Degruyter.svg new file mode 100644 index 0000000000..4fcff995f5 --- /dev/null +++ b/cms/assets/img/sponsors/Degruyter.svg @@ -0,0 +1,3 @@ + + + diff --git a/cms/assets/img/sponsors/Lasi.png b/cms/assets/img/sponsors/Lasi.png new file mode 100644 index 0000000000..f5c0f97ff6 Binary files /dev/null and b/cms/assets/img/sponsors/Lasi.png differ diff --git a/cms/data/nav.yml b/cms/data/nav.yml index ddd6b7cf7e..07e09ce2ab 100644 --- a/cms/data/nav.yml +++ b/cms/data/nav.yml @@ -27,7 +27,7 @@ entries: route: doaj.openurl # ~~->OpenURLDocumentation:WebRoute~~ - label: XML route: doaj.xml # ~~->XMLDocumentation:WebRoute~~ - - label: FAQs + - label: Metadata help route: doaj.faq # ~~->FAQ:WebRoute~~ - id: about label: About @@ -60,7 +60,7 @@ entries: route: doaj.support # ~~->Support:WebRoute~~ - label: Publishers route: doaj.publisher_supporters # ~~->PublisherSupporters:WebRoute~~ - - label: Supporters + - label: Institutional and library supporters route: doaj.supporters # ~~->Supporters:WebRoute~~ - id: apply label: Apply @@ -77,8 +77,8 @@ entries: route: doaj.seal # ~~->Seal:WebRoute~~ - label: Transparency & best practice route: doaj.transparency # ~~->Transparency:WebRoute~~ - - label: Why index your journal in DOAJ? - route: doaj.why_index # ~~->WhyIndex:WebRoute~~ + - label: Publisher information + route: doaj.publisher_responsibilities # ~~->PublisherResponsibilities:WebRoute~~ - label: Licensing & copyright route: doaj.copyright_and_licensing # ~~->CopyrightAndLicensing:WebRoute~~ # Uncomment once this page is ready to be published diff --git a/cms/data/notifications.yml b/cms/data/notifications.yml index 1e417e9b71..d4b9906822 100644 --- a/cms/data/notifications.yml +++ b/cms/data/notifications.yml @@ -1,7 +1,7 @@ # ~~Notifications:Data~~ application:assed:assigned:notify: long: | - An application, or update request for the journal **{journal_title}** has been assigned to you by the Editor of your group **{group_name}**. Please start work on this within 10 days. + An application for the journal **{journal_title}** has been assigned to you by the Editor of your group **{group_name}**. Please start work on this within 10 days. short: New application ({issns}) assigned to you @@ -146,4 +146,4 @@ journal:assed:discontinuing_soon:notify: long: | Journal "{title}" (id: {id}) will discontinue in {days} days. short: - Journal discontinuing \ No newline at end of file + Journal discontinuing diff --git a/cms/data/sponsors.yml b/cms/data/sponsors.yml index 379c51cf69..2077eeb66e 100644 --- a/cms/data/sponsors.yml +++ b/cms/data/sponsors.yml @@ -1,17 +1,14 @@ # List of sponsors separated by tiers (premier, sustaining, basic) # ~~Sponsors:Data~~ -gold: - name: Royal Society of Chemistry url: https://www.rsc.org/ logo: rsc.png - + - name: Georg Thieme Verlag KG url: https://www.thieme.com/ logo: thieme.svg - -silver: - name: AOSIS url: https://aosis.co.za/ logo: aosis.png @@ -19,7 +16,7 @@ silver: - name: Cappelen Damm Akademisk url: https://www.cappelendammundervisning.no/ logo: cda.jpg - + - name: Copernicus Publications url: https://publications.copernicus.org logo: copernicus.svg @@ -31,7 +28,7 @@ silver: - name: Frontiers url: https://www.frontiersin.org/ logo: frontiers.svg - + - name: Knowledge E url: https://knowledgee.com/ logo: knowledgee.png @@ -43,7 +40,7 @@ silver: - name: OA.Works url: https://oa.works/ logo: oaworks.png - + - name: SAGE Publishing url: https://www.sagepublications.com/ logo: sage.svg @@ -51,21 +48,19 @@ silver: - name: Taylor & Francis Group url: https://www.taylorandfrancisgroup.com/ logo: tf.svg - + - name: John Wiley and Sons LTD url: https://www.wiley.com/en-us logo: Wiley_Wordmark_black.png - -bronze: - name: American Chemical Society url: https://pubs.acs.org/ logo: acs.jpg - + - name: American Psychological Association url: https://www.apa.org/pubs logo: apa.png - + - name: Cambridge University Press url: https://www.cambridge.org/ logo: cambridge.svg @@ -74,6 +69,10 @@ bronze: url: https://www.digital-science.com/ logo: ds.svg +- name: De Gruyter + url: https://www.degruyter.com/ + logo: Degruyter.svg + - name: eLife Sciences Publications url: https://elifesciences.org/ logo: elife.svg @@ -81,7 +80,7 @@ bronze: - name: Elsevier url: https://www.elsevier.com/ logo: elsevier.svg - + - name: Emerald Publishing url: https://emeraldpublishing.com/ logo: emerald.svg @@ -89,15 +88,15 @@ bronze: - name: IEEE url: https://www.ieee.org/ logo: ieee.png - + - name: Institute of Physics url: https://www.iop.org/ logo: iop.jpg - + - name: International Union for Conservation of Nature url: https://iucn.org/ logo: IUCN.svg - + - name: JMIR Publications url: https://jmirpublications.com/ logo: jmir.svg @@ -129,12 +128,20 @@ bronze: - name: SciFree url: https://scifree.se/ logo: scifree.svg - + - name: The IET url: https://www.theiet.org/ logo: iet.svg - name: Ubiquity Press url: https://www.ubiquitypress.com/ - logo: ubiquity.svg - + logo: ubiquity.svg + +- name: Iași University of Life Sciences + url: https://iuls.ro/en/ + logo: Lasi.png + +- name: Beijing Lanbotu Technology Co + url: https://xueshu.libtools.com.cn/?tenant_id=1 + logo: Beijing.jpg + diff --git a/cms/data/volunteers.yml b/cms/data/volunteers.yml index 9e09159ad7..3c1aa00afe 100644 --- a/cms/data/volunteers.yml +++ b/cms/data/volunteers.yml @@ -4,23 +4,13 @@ ## All editors ed: - -- name: Alain Chaple - area: Medical Sciences - year_since: - city: La Habana - country: Cuba - language: Spanish, English - photo: "alainchaple.png" - -- name: Alicia Aparicio - area: Humanities, Social Sciences +- name: Christine Davidian + area: Technology year_since: - city: Buenos Aires - country: Argentina - language: Spanish - featured: true - + city: Glassboro, NJ + country: USA + language: English, French + - name: Fei Yu area: Librarianship, Scholarly Publishing, Data Management year_since: @@ -29,14 +19,6 @@ ed: language: Chinese, English featured: true -- name: Geoff Husic - area: Humanities, Social Sciences - year_since: - city: Lawrence, KS - country: USA - language: Russian, German, English - featured: true - - name: Ikhwan Arief area: Science, Technology year_since: @@ -58,6 +40,13 @@ ed: country: Türkiye language: Turkish, English featured: true + +- name: Muhamad Ratodi + area: Public Health + year_since: + city: Surabaya + country: Indonesia + language: Indonesian, English - name: Natalia Pamuła-Cieślak area: Humanities, Social Sciences @@ -110,7 +99,14 @@ ed: ## All associate editors ass_ed: - +- name: Alain Chaple + area: Medical Sciences + year_since: + city: La Habana + country: Cuba + language: Spanish, English + photo: "alainchaple.png" + - name: Aleksandra Zawadzka area: Librarianship year_since: @@ -134,13 +130,6 @@ ass_ed: language: Persian (Farsi), English photo: "Amir Reza Asnafi.jpg" -- name: Ana Fresco-Santalla - area: Humanities - year_since: - city: Granada - country: Spain - language: Galician, Portuguese, Spanish, English - - name: Andista Candra Yusro area: Social Sciences year_since: @@ -198,13 +187,6 @@ ass_ed: language: Indonesian, English photo: "azhar.jpg" -- name: Barbara Łuszczek - area: Librarianship - year_since: - city: Bydgoszcz - country: Poland - language: Polish, English - - name: Ben Catt area: Librarianship, Research Support year_since: @@ -219,14 +201,6 @@ ass_ed: country: Indonesia language: Indonesian, English -- name: Cara Forster - area: Health Sciences and Library Science - year_since: - city: Germantown, Maryland - country: USA - language: English, American Sign Language - photo: "cara.jpg" - - name: Carlos Alegre area: Social Sciences year_since: @@ -243,13 +217,6 @@ ass_ed: language: Polish, English photo: "CezaryBorkowicz.jpg" -- name: Christine Davidian - area: Technology - year_since: - city: Glassboro, NJ - country: USA - language: English, French - - name: Constancy Aleru area: Medical Sciences year_since: @@ -265,13 +232,6 @@ ass_ed: country: Indonesia language: Indonesian, English photo: - -- name: Dania Deroy Dominguez - area: Social Sciences - year_since: - city: La Habana - country: Cuba - language: English, French - name: Daria Chrześcijańska area: Computer Science, Social Sciences @@ -620,13 +580,6 @@ ass_ed: language: Spanish, English photo: "MiltonRamirez.jpg" -- name: Muhamad Ratodi - area: Public Health - year_since: - city: Surabaya - country: Indonesia - language: Indonesian, English - - name: Muhamad Taufik Hidayat area: Humanities, Social Sciences year_since: @@ -734,14 +687,6 @@ ass_ed: country: Indonesia language: Indonesian, English -- name: Ramiro Doyenart - area: Health Sciences - year_since: - city: Rio Grande do Sul - country: Brazil - language: Spanish, Portuguese, English - photo: "RamiroDoyenart.jpeg" - - name: Ran Dang area: Biology, Medical Sciences year_since: @@ -771,14 +716,6 @@ ass_ed: country: USA language: English -- name: Richard James - area: Medicine - year_since: - city: Philadelphia, PA - country: USA - language: English - photo: "rjames.png" - - name: Roberto Sarmiento area: Engineering year_since: @@ -860,13 +797,6 @@ ass_ed: country: Romania language: Romanian, English, French -- name: Umar Khasan - area: Social Sciences - year_since: - city: Bangkalang - country: Indonesia - language: Bahasa Indonesian, English - - name: Utiya Hikmah area: Material Physics year_since: @@ -883,14 +813,6 @@ ass_ed: language: Kannada, English photo: "Vasanth.jpg" -- name: Weldy St-Fleur - area: Social Sciences - year_since: - city: São Paulo - country: Brazil - language: Portuguese, English - photo: "weldy.jpeg" - - name: Yalçın Tükel area: Sports recreation year_since: diff --git a/cms/pages/about/at-20.md b/cms/pages/about/at-20.md index 1678adfe2a..918d1214b1 100644 --- a/cms/pages/about/at-20.md +++ b/cms/pages/about/at-20.md @@ -31,15 +31,16 @@ There is also an opportunity for you to [support DOAJ during its 20th year](/at- - Event Time: 13:00 UTC - Duration: 90 mins - {% include "includes/svg/at-20/theme_global.svg" %} - - **[Registration is open](https://us02web.zoom.us/webinar/register/WN_fu42oi59S7GZ366rjyAUGg#/registration)** + - **[Recording is available](https://www.youtube.com/watch?v=TRjtc-7tg8w)** - Name: _DOAJ at 20: Global_ - Date: _28th September 2023_ - - Event Time: 13:00 UTC ([Check the event time](https://www.timeanddate.com/worldclock/fixedtime.html?iso=20230928T13&ah=1&am=30) where you are.) + - Event Time: 13:00 UTC - Duration: 2 hours - {% include "includes/svg/at-20/theme_trusted.svg" %} + - **[Registration is open](https://us02web.zoom.us/webinar/register/WN_sePTjc09QbWHUDhgmZ28-A#/registration)** - Name: _DOAJ at 20: Trusted_ - Date: _7th December 2023_ - - Event Time: to be confirmed + - Event Time: 14:00 UTC ([Check the event time](https://www.timeanddate.com/worldclock/fixedtime.html?msg=DOAJ+at+20%3A+Trusted&iso=20231207T14&p1=1440&ah=1&am=30) where you are.) - Duration: 90 mins ## Open @@ -48,11 +49,11 @@ Our first of three events marking our 20th anniversary took place on the 15th Ju ## Global -Our second event will be around the theme Global, where we will have eight lighting talks from speakers from around the world. Our moderator and DOAJ Ambassador, Ivonne Lujano, will introduce speakers and manage two Q&As, where the audience can ask our speakers questions. More information about the event and all the speakers can be found on the [registration page](https://us02web.zoom.us/webinar/register/WN_fu42oi59S7GZ366rjyAUGg#/registration). +Our second event was around the theme Global and took place on the 28th September 2023. The event consisted of eight lighting talks from speakers from around the world. Our moderator and DOAJ Ambassador, Ivonne Lujano, introduced speakers and managed two Q&As, where the audience could ask our speakers questions. A recording of the event is [available on YouTube](https://www.youtube.com/watch?v=TRjtc-7tg8w). ## Trusted -Our third and last DOAJ at 20 event will be around the theme Trusted. More information about this event will be available later in the year. +Our third and last DOAJ at 20 event will be around the theme Trusted, where our four speakers, Judith Barnsby, Ivan Oransky, Ixchel Faniel and Dr Haseeb Irfanullah will discuss what trust and mistrust means in scholarly communications. The event will be moderated by Joy Owango, Founding Director of TCC Africa, and there will be a Q&A where the audience can ask questions. [Registration is open](https://us02web.zoom.us/webinar/register/WN_sePTjc09QbWHUDhgmZ28-A#/registration) for this event, and the event is free to attend. ## Timeline diff --git a/cms/pages/apply/guide.md b/cms/pages/apply/guide.md index 14bc161758..cd57b4a270 100644 --- a/cms/pages/apply/guide.md +++ b/cms/pages/apply/guide.md @@ -7,7 +7,7 @@ sticky_sidenav: true featuremap: ~~GuideToApplying:Fragment~~ --- -Before you start the application process, you will be asked to log in or register. You will be able to save your progress and review all your answers before you submit them. A [PDF version of the application form](/static/doaj/docs/2023-07-04-DOAJQuestions.pdf) is available for reference only. +Before you start the application process, you will be asked to log in or register. You can save your progress and review all your answers before you submit them. A [PDF version of the application form](/static/doaj/docs/2023-07-04-DOAJQuestions.pdf) is available for reference only. ## Basic criteria for inclusion @@ -17,11 +17,11 @@ Open access journals published in any language may apply. Journals should adhere - The journal must be actively publishing scholarly research - Any research subject area - - Publish at least 5 research articles per year + - Publish at least five research articles per year - Primary target audience of researchers or practitioners - Newly launched journals - - Before applying to DOAJ, a new or flipped journal must demonstrate a publishing history of more than one year, or have published at least 10 open access research articles. + - Before applying to DOAJ, a new or flipped journal must demonstrate a publishing history of more than one year or have published at least ten open access research articles. --- @@ -44,10 +44,10 @@ Open access journals published in any language may apply. Journals should adhere - one unique URL per article - HTML or PDF, as a minimum - Journals that include intrusive advertising will not be accepted. See best practice [recommendations](https://www.doaj.org/apply/transparency) for advertising. -- DOAJ does not approve of the use of Impact Factors or ranking metrics. However, journals are allowed to display the Journal Impact Factor calculated by Clarivate and metrics from Scopus. Journals must not display any reference (images, links, logos) to Impact Factors or ranking metrics from any other service. -- The website does not need to be in English. If the site is available in multiple languages the information provided must be the same in all languages. +- DOAJ does not approve of using Impact Factors or ranking metrics. However, journals may display the Journal Impact Factor calculated by Clarivate and metrics from Scopus. Journals must not display any reference (images, links, logos) to Impact Factors or ranking metrics from any other service. +- The website does not need to be in English. If the site is available in multiple languages, the information provided must be the same in all languages. -The following information must be available online, and easily accessible from the journal homepage: +The following information must be available online and easily accessible from the journal homepage: - Open access policy - Aims and scope @@ -57,26 +57,26 @@ The following information must be available online, and easily accessible from t - Licensing terms - Copyright terms - Author charges - - If a journal doesn’t have any charges then this must be stated + - If a journal doesn’t have any charges, then this must be stated - Must include all fees that may be charged to the author, from submission to publication, including: - submission fees - editorial processing charges - article processing charges (APCs) - page charges - colour charges - - Any fee waiver must be clearly specified with the conditions of waiver e.g. amount, time period. + - Any fee waiver must be clearly specified with the conditions of waiver, e.g. amount, time period. - If there are charges for withdrawing the article after submission, they cannot exceed the author charges. - Contact details - Include the name of a contact person and the journal's dedicated email address. - - The country in the application and on the journal website must be the country where the publisher is registered and carries out its business activities. + - The country in the application and on the journal website must be where the publisher is registered and carries out its business activities. --- ### ISSN -- A journal must have at least one ISSN (International Standard Serial Number) which is registered and confirmed at [issn.org](https://portal.issn.org/). +- A journal must have at least one ISSN (International Standard Serial Number) that is registered and confirmed at [issn.org](https://portal.issn.org/). - The ISSN(s) must be displayed on the website. -- The name of the journal in the application and on the website must match what is shown at [issn.org](https://portal.issn.org/). +- The journal's name in the application and on the website must match what is shown at [issn.org](https://portal.issn.org/). --- @@ -88,28 +88,42 @@ The following information must be available online, and easily accessible from t - Journals that display a list of reviewers must include their names and affiliations. - The editorial board for the journal should consist of at least five editors with appropriate qualifications and expertise. It is recommended that board members should not all come from the same institution. - All articles must pass through a quality control system (peer review) before publication. - - The type and details of the peer review process must be stated clearly on the website. + - The type and details of the peer review process must be stated clearly on the website, including the process for special issues, if applicable. - At least two independent reviewers should review each article. -- Use of a plagiarism checking service is highly recommended, but not required for inclusion in DOAJ. +- Use of a plagiarism checking service is highly recommended but not required for inclusion in DOAJ. - Endogeny should be minimised. - - The proportion of published research papers where at least one of the authors is an editor, editorial board member or reviewer must not exceed 25% based on either of the latest two issues. + - The proportion of published research papers where at least one of the authors is an editor, editorial board member, or reviewer must not exceed 25% in either of the latest two issues. + +--- + +### Special issues + +Journals that publish special issues or other content curated by guest editors must adhere to these additional criteria: + +- The Editor-in-Chief must be responsible for the content of the entire journal, including all special issues, which must fall within the scope of the journal +- Special issue articles must have the same editorial oversight as regular papers, including external peer review, and be clearly labelled +- Journals must ensure that guest editors’ credentials are checked and approved +- The Editor-in-Chief or dedicated board members must oversee the guest editors +- Papers submitted to a special issue by the guest editor(s) must be handled under an independent review process and make up no more than 25% of the issue's total + +DOAJ will not accept a journal if all content in the last year/volume is published as special issues. --- ### Licensing - The licensing terms for use and re-use of the published content must be clearly stated on the website. -- DOAJ recommends the use of [Creative Commons](https://creativecommons.org/) licenses for this purpose. +- DOAJ recommends using [Creative Commons](https://creativecommons.org/) licenses for this purpose. - If Creative Commons licensing is not used, similar terms and conditions should be applied. - Extra care must be taken to state these terms clearly. -- It is recommended that licensing information is displayed or embedded in full text articles, but this is not required for inclusion in DOAJ. +- It is recommended that licensing information is displayed or embedded in full-text articles, but this is not required for inclusion in DOAJ. - [More about licensing](/apply/copyright-and-licensing/) --- ### Copyright -- The copyright terms applied to the published content must be clearly stated and separate from the copyright terms applied to the website. +- The copyright terms applied to the published content must be clearly stated and separate from those applied to the website. - Copyright terms must not contradict the licensing terms or the terms of the open access policy. - “All rights reserved” is never appropriate for open access content. - [More about copyright](/apply/copyright-and-licensing/) @@ -122,15 +136,15 @@ The following information must be available online, and easily accessible from t - ### Arts and humanities journals - For these disciplines ([pages 10-16 only](http://uis.unesco.org/sites/default/files/documents/international-standard-classification-of-education-fields-of-education-and-training-2013-detailed-field-descriptions-2015-en.pdf)), DOAJ can accept journals that undertake editorial review, rather than peer review. Editorial review must be handled with a minimum of two editors. - ### Clinical case reports journals - - DOAJ only considers a case report as research if it includes the retrospective analysis of three or more clinical cases or a literature review. The journal must publish at least five articles per year that meet this definition. + - DOAJ only considers a case report research if it includes the retrospective analysis of three or more clinical cases or a literature review. The journal must publish at least five articles per year that meet this definition. - ### Conference proceedings journals - - For DOAJ to include journals that are dedicated to publishing conference proceedings, the journal must have an ISSN and a permanent editorial or advisory board providing editorial oversight. Papers from each published conference must be peer-reviewed according to DOAJ criteria. Full text of all conference papers must be available. Individual conference proceedings will not be indexed. + - For DOAJ to include journals dedicated to publishing conference proceedings, the journal must have an ISSN and a permanent editorial or advisory board providing editorial oversight. Papers from each published conference must be peer-reviewed according to DOAJ criteria. Full text of all conference papers must be available. Individual conference proceedings will not be indexed. - ### Data journals - DOAJ will accept journals publishing research articles about data or datasets, but not journals that simply link to datasets or announce their availability. - ### Overlay journals - - DOAJ will accept journals that select and peer-review articles that are hosted on a preprint server or other site. + - DOAJ will accept journals that select and peer-review articles hosted on a preprint server or other site. - ### Student-run journals - - If a journal is run by a student body, there must be an advisory board for the journal where at least two members have a PhD or equivalent qualification. + - If a student body runs a journal, there must be an advisory board for the journal where at least two members have a PhD or equivalent qualification. - ### Flipped journals - Where a journal was previously published as a subscription or hybrid journal and has now flipped to a fully open access model, this information must be clearly displayed: - The date of the change to fully open access @@ -149,14 +163,14 @@ The following information must be available online, and easily accessible from t 1. Your application is successfully submitted when you see a _Thank You_ screen and receive a confirmation email. - If you do not receive that email, contact us. -2. Your application will be reviewed by an editor. You may receive an email alerting you when this process has started. -3. The editor may need to contact you as part of the review process. Please answer any questions they send you. This helps us process your application correctly. Be aware that we will reject an application if, after one month, we haven't received answers to our questions. +2. An editor will review your application. You may receive an email alerting you when this process has started. +3. The editor may need to contact you during the review process. Please answer any questions they send you. This helps us process your application correctly. Be aware that we will reject an application if, after one month, we haven't received answers to our questions. 4. The editor reviewing your application may be a volunteer and will not have a doaj.org email address. Check your spam folder frequently just in case the emails have ended up there. 5. If your application is accepted, you will receive an email to confirm this. ### Time from submission to decision -The time from submission to decision varies greatly and is dependent on the responsiveness of the journal contact and/or applicant. Generally a decision is reached within three months. +The time from submission to decision varies greatly and depends on the responsiveness of the journal contact and/or applicant. Generally, a decision is reached within three months. We cannot provide a status update for applications which are less than three months old. @@ -164,11 +178,11 @@ We cannot provide a status update for applications which are less than three mon ## If your application is rejected -You will receive an email giving the reasons for rejection of the application. Unless otherwise advised by DOAJ staff, you may not submit another application for the same journal until 6 months after the date of the notification of rejection. +You will receive an email giving the reasons for rejection of the application. Unless otherwise advised by DOAJ staff, you may not submit another application for the same journal until six months after the date of the notification of rejection. -If your application was rejected due to an unconfirmed ISSN, we may be able to reopen the application if the ISSN is confirmed within 3 months of the DOAJ rejection. Send an email to the [DOAJ Helpdesk](mailto:helpdesk@doaj.org). +If your application was rejected due to an unconfirmed ISSN, we may be able to reopen the application if the ISSN is confirmed within three months of the DOAJ rejection. Email the [DOAJ Helpdesk](mailto:helpdesk@doaj.org). -You are responsible for providing accurate information when submitting an application. Applications that contain information that is inaccurate or wrong, or that have answers missing, are automatically rejected. +You are responsible for providing accurate information when you submit an application. Applications that contain information that is inaccurate or wrong or that have answers missing are automatically rejected. DOAJ only accepts journals that follow good publishing practice. If our review finds a publisher is not adhering to best practice, has provided information that is untrue or misleading, or has questionable publishing practices, DOAJ will not allow further applications from that publisher for a period of up to three years. @@ -176,13 +190,13 @@ Journals or publishers that have had multiple exclusions totalling six years or Journals already accepted into DOAJ may be removed if they are found to be no longer adhering to DOAJ criteria or publishing best practice. -Please ensure that, if you do apply again after the exclusion period, the journal meets the DOAJ criteria for inclusion and any necessary changes have been made to the journal website or policies. +Please ensure that, if you do apply again after the exclusion period, the journal meets the DOAJ criteria for inclusion and that any necessary changes have been made to the journal website or policies. --- ## Appeals -You may submit an appeal if your journal has been removed from DOAJ, or your application has been rejected and no new application is allowed for at least a year. Send an email to the [DOAJ Appeals Committee](mailto:appeals@doaj.org). +You may submit an appeal if your journal has been removed from DOAJ or your application has been rejected and no new application is allowed for at least a year. Email the [DOAJ Appeals Committee](mailto:appeals@doaj.org). The appeal should contain the following information: @@ -192,7 +206,7 @@ The appeal should contain the following information: - Evidence for the DOAJ Appeals Committee to consider - The original notification of rejection/removal -Appeals are considered by the DOAJ Appeals Committee, consisting of the Editor-in-Chief and members of the editorial team. Committee meetings are held monthly. After your case is discussed a member of the Committee will contact you with the result of your appeal. This will not be the same Managing Editor who handled your case originally. +Appeals are considered by the DOAJ Appeals Committee, consisting of the Editor-in-Chief and members of the editorial team. Committee meetings are held monthly. After your case is discussed, a member of the Committee will contact you with the result of your appeal. This will not be the same Managing Editor who handled your case originally. No further communication will be entered into after the appeal is heard and the Committee has made their decision. @@ -200,15 +214,15 @@ No further communication will be entered into after the appeal is heard and the ## Updating your journal record -For journals that have been accepted into DOAJ, it is important that the information we hold about them is up to date. +For journals accepted into DOAJ, it is important that the information we hold about them is up to date. -You can update the journal information via our API or by logging into your account and going to your Publisher dashboard. Under 'My journals' is a list of all the journals connected to your account. Use the black 'Update' button, next to each journal record, to submit an update request to us. +You can update the journal information via our API or by logging into your account and going to your Publisher dashboard. Under 'My journals' is a list of all the journals connected to your account. Use the black 'Update' button next to each journal record to submit an update request to us. A journal update request form will open with the current answers pre-filled. Change any answers that need to be updated. If you haven't updated the journal since before November 2020, when we revised the form, we suggest that you review every answer. Note that some questions may not have an answer, as new questions were added in November 2020. Once you have amended and reviewed the answers, submit the update request to us. -Update requests are reviewed by the DOAJ editorial team for accuracy and we aim to process them within six weeks of submission. +The DOAJ editorial team reviews update requests for accuracy and we aim to process them within six weeks of submission. --- @@ -216,7 +230,9 @@ Update requests are reviewed by the DOAJ editorial team for accuracy and we aim We are always looking for volunteers to translate our criteria. If you would like to volunteer to do this, please [contact us](mailto:helpdesk@doaj.org). -Our criteria are in: +Please note that these translations may not include the latest changes made to the Guide in November 2023. + +Our criteria are available in: - [Arabic](https://drive.google.com/file/d/1UrY8p8KV4hBaYlYuyH1MGUOsHhEABtSE/view?usp=sharing) - [Chinese (application form)](https://zenodo.org/record/4350096) @@ -234,11 +250,12 @@ Our criteria are in: --- -## Change log +## Change log -This is Version 1.3 of the Guide to applying. +This is Version 2.0 of the Guide to applying. -*Version: 1.3 (April 2023 - further clarification to endogeny, displaying journal rankings, information about author charges and reviewers, flipped journals and journals/publishers with multiple exclusions )*
+*Version 2.0 (November 2023 - addition of new criteria around special issues, smaller changes for clarity and plain English.)*
+Version: 1.3 (April 2023 - further clarification to endogeny, displaying journal rankings, information about author charges and reviewers, flipped journals and journals/publishers with multiple exclusions )
Version: 1.2 (February 2022 - extra copyright and licensing information were moved to a separate page)
Version: 1.1 (December 2021 - a clarification to endogeny criterion)
Version 1.0 (November 1.0) diff --git a/cms/pages/apply/publisher-responsibilities.md b/cms/pages/apply/publisher-responsibilities.md index 7980707816..bd3e64c223 100644 --- a/cms/pages/apply/publisher-responsibilities.md +++ b/cms/pages/apply/publisher-responsibilities.md @@ -1,6 +1,6 @@ --- layout: sidenav -title: Publisher responsibilities +title: Publisher information section: Apply toc: true sticky_sidenav: true @@ -8,12 +8,167 @@ featuremap: ~~PublisherResponsibilities:Fragment~~ --- -Lorem ipsum dolor sit amet. +## Working together -## First heading +### Our commitment to you -More content here. +- **No charge** - we will never charge a fee for reviewing your applications in DOAJ +- **Fairness** - we assess all journals using the same criteria. Our [Guide to applying](/apply/guide/) fully describes these criteria. +- **Confidentiality** - we will only share or discuss details and information collected during the review process with the applicant or an entity authorised to represent the journal. +- **Privacy** - we [will not share your personal data](https://doaj.org/privacy/) with third parties unless you have permitted us to do so. We will always ask you first. -### Subheading +### Your commitment to us -Some more content here. +- You will [respond to questions](/apply/guide/#the-application-process) from our editorial team promptly. +- You will [keep your contact details up to date](). +- You will inform us of changes to your journals and [keep your journal records up to date](). +- You will [upload article metadata](/publisher/uploadfile) to us if you are able to. + +We have [a zero-tolerance policy](/about/#zero-tolerance-policy). We will treat you with respect and courtesy. We expect you to do the same. + +### Emails from us + +When you register with us, we use your email address to send important information about your applications, updates and journals. To make sure that our emails reach you, consider the following: + +- Add helpdesk@doaj.org to your contacts. +- Check your Spam folder. If you see an email from us there, mark it as safe. + +**Using an institutional email address** +If you use an institutional email address, your institution may apply extra security measures, such as a firewall. Your IT department administers the firewall. If you aren’t receiving our emails, contact your IT department to see if they can find emails from helpdesk@doaj.org and if they will let them through. If they have concerns about doing this, ask them to contact us. + +## Applying to DOAJ + +### Before you submit an application + +1. Read our [Guide to applying](/apply/guide/) (available in [several languages](/apply/guide/#in-other-languages)) and make sure your journal meets all the basic criteria. +2. If you are applying for the first time, [register with us first](/account/register). +3. If you already have a DOAJ account, [sign in](/account/login) to submit a new application. + +A [sample of the application form](/static/doaj/docs/2023-07-04-DOAJQuestions.pdf) (Downloads a PDF) is available. Details of the [application process](/apply/guide/#the-application-process) are in the guide. + +### Rejected applications + +If we reject your application, you will receive an email explaining why and how long you must wait before applying again. [Read the information](/apply/guide/#if-your-application-is-rejected) in the guide. + +### Accepted applications + +If we accept your application, you will receive an automated email that includes: + +- The URL of your journal record in DOAJ +- Information on how to log into your publisher dashboard +- Details on why it is helpful to upload article metadata to DOAJ + +If you found your journal in DOAJ but didn’t get an email from us, there are [steps you can take]() to ensure our emails are delivered. + +During the review process, we might correct some of the information in your application. + +You can [submit an update request](/apply/guide/#updating-your-journal-record) if something about your journal has changed since you applied. + +## Your DOAJ account + +You can access your dashboard and account settings by logging into your account. + +### Your publisher dashboard + +(You must be signed in to access these links.) + +On [your dashboard](/publisher/), you can + +- See [a list of your journals](/publisher/journal) indexed in DOAJ +- [Submit an update request](/apply/guide/#updating-your-journal-record) when journal details change +- [Upload](/publisher/uploadfile) or [enter](/publisher/metadata) article metadata for your journals +- Download the Seal logo (only for journals awarded the DOAJ Seal). +- Upload your full-text content (only for journals preserved via JASPER). + +### Settings + +The Settings button allows you to: + +- Update your account details +- Change your password +- Find your account ID +- Find your API key + +### Contact details + +- You are responsible for keeping the name and email address on your DOAJ account up to date. +- If the contact person changes, log in to your account and edit the details so that DOAJ always knows how to contact you. + +### Problems logging in + +If you cannot log in, you can [reset your password](/account/forgot). We will send an email containing a reset link. The link is valid for 24 hours. + +If you don’t get the email, check your Spam folder and which address you registered with us. If you haven’t received the email after three hours, [contact us](mailto:helpdesk@doaj.org). + +## Your indexed journals + +### Displaying the DOAJ logo + +To show that your journal is indexed in DOAJ or has the DOAJ Seal: + +- You may use the DOAJ or the Seal logo on your journal's website but not on individual articles +- You may only use the DOAJ or Seal logo on a platform or portal website if all of the platform's journals are indexed or have been awarded the Seal +- You can [download the DOAJ logos](https://doaj-kit.netlify.app/building-blocks/logotype/) +- If awarded, you can download the Seal logo from [your publisher dashboard](/publisher/journal) + +We have [guidelines](https://doaj-kit.netlify.app/building-blocks/logotype/) about how you can use our logo on your website or in printed material. + +### Keeping your journal records up to date +When something changes, you must keep your journal records in DOAJ up to date. + +- [Contact our Help Desk](mailto:helpdesk@doaj.org) if you want to change the Title or ISSN of your journal, but [read this section first](). +- [Contact our Help Desk](mailto:helpdesk@doaj.org) if your journal qualifies for the Seal. +- Request a CSV file from us that allows you to update multiple journals and values at the same time and in spreadsheet format. +- For all other changes, log into your DOAJ account to [submit an update request](/publisher/journal). Use the black ‘Update’ button. + +### When should I submit an update? + +- When the URLs of your journal change +- When you change the fees/APCs +- When journal information or policies change +- When your journal is included in a digital archive, starts using DOIs, or has an entry in Sherpa/RoMEO + +If a member of the DOAJ Team has sent you a CSV file, we ask that you complete it as quickly as possible. Full details are available on our [Metadata Help](/docs/faq/) page. + +We aim to process update requests within one month. + +### Journal title changes, mergers or continuations + +- There are [specific rules](https://www.issn.org/understanding-the-issn/assignment-rules/issn-the-major-principles/) about what to do when a journal changes its title. +- You must submit a new application if your journal has a new ISSN. +- We will automatically reject the application if the new ISSN is not confirmed. +- We will review the two records alongside each other to determine if the new title meets our basic criteria. If not, we will reject the application and withdraw the previous title. + +### Ceased journals + +- If one of your indexed journals will be ceasing publication, let us know as soon as possible by [contacting our Help Desk](mailto:helpdesk@doaj.org). When you contact us, include the ISSN and the title of your journal. +- If another title is continuing the journal, you must [submit a continuation](). + +### Journal transfers + +If a journal is moving to or from another publisher, let us know as soon as possible by [contacting our Help Desk](mailto:helpdesk@doaj.org). We need the following information from you: + +- Journal title +- ISSN +- Transfer date +- Name and email address of the old publisher +- Name and email address of the new publisher + +## Journal queries + +### Journal is not listed (is withdrawn) + +When we withdraw your journal, we will send you an email. Remind yourself of the email address attached to your DOAJ account and check your Spam folder if you haven’t received anything. The email may include information on how long you must wait before you can apply again. + +- We have to withdraw your journal from DOAJ if it no longer adheres to our criteria or to publishing best practices. +- We have to withdraw the journal if it is inactive (ceased publishing) or the website is unavailable. + +You can also check our [list of withdrawn journals](https://docs.google.com/spreadsheets/d/183mRBRqs2jOyP0qZWXN8dUd02D4vL0Mov_kgYF8HORM/edit#gid=1650882189) and ask for more information by [contacting our Help Desk](mailto:helpdesk@doaj.org). + +### Journal is not listed on my dashboard + +- [Contact our Help Desk](mailto:helpdesk@doaj.org) with the journal title and ISSN; they will help you. + +### Unknown journal on my dashboard + +- [Contact our Help Desk](mailto:helpdesk@doaj.org) with the journal title and ISSN; they will help you. diff --git a/cms/pages/apply/seal.md b/cms/pages/apply/seal.md index b9c4968cca..7fc7f4d414 100644 --- a/cms/pages/apply/seal.md +++ b/cms/pages/apply/seal.md @@ -12,7 +12,7 @@ The DOAJ Seal is awarded to journals that demonstrate best practice in open acce **Journals do not need to meet the Seal criteria to be accepted into DOAJ.** -There are seven criteria which a journal must meet to be eligible for the DOAJ Seal. These relate to best practice in long term preservation, use of persistent identifiers, discoverability, reuse policies and authors' rights. +There are seven criteria which a journal must meet to be eligible for the DOAJ Seal. These relate to best practices in long-term preservation, use of persistent identifiers, discoverability, reuse policies and authors' rights. --- @@ -21,29 +21,29 @@ There are seven criteria which a journal must meet to be eligible for the DOAJ S All seven criteria must be met for a journal to be awarded the Seal. Failure to maintain the best practice and standards described in these criteria may lead to removal of the Seal. {:.tabular-list .tabular-list--ordered} -1. Digital preservation +1. Digital preservation (Archiving policy) - The journal content must be continuously deposited in one of these archives: - any archiving agency included in [Keepers Registry](https://keepers.issn.org/keepers) - Internet Archive - PubMed Central -2. Persistent article identifiers +2. Self-archiving (Repository policy) + - Authors must be permitted to deposit all versions of their paper in an institutional or subject repository. + - Preprint + - Author's Accepted Manuscript + - Published article (Version of Record) + - An embargo may not be applied. +3. Persistent article identifiers (Unique identifiers) - Articles must use persistent article identifiers. DOI, ARK or Handle are the most commonly used. - All persistent links must resolve correctly. -3. Metadata supply to DOAJ +4. Metadata supply to DOAJ - Article metadata must be uploaded to DOAJ regularly. -4. License type +5. License type - The journal must permit the use of a Creative Commons license that allows the creation of derivative products. - CC BY - CC BY-SA - CC BY-NC - CC BY-NC-SA -5. License information in articles +6. License information in articles - Creative Commons licensing information must be displayed in all full-text article formats. -6. Copyright and publishing rights +7. Copyright and publishing rights - Authors must retain unrestricted copyright and all publishing rights when publishing under any license permitted by the journal. -7. Self-archiving policy - - Authors must be permitted to deposit all versions of their paper in an institutional or subject repository. - - Preprint - - Author's Accepted Manuscript - - Published article (Version of Record) - - An embargo may not be applied. diff --git a/cms/pages/apply/transparency.md b/cms/pages/apply/transparency.md index 0bda2aae3d..913d061882 100644 --- a/cms/pages/apply/transparency.md +++ b/cms/pages/apply/transparency.md @@ -23,13 +23,14 @@ These principles also acknowledge that publishers and editors are responsible fo ### JOURNAL CONTENT -#### 1. Name of journal +**1. Name of journal** + The journal's name should: - Be unique and not be one that is easily confused with another journal. - Not mislead potential authors and readers about the journal's origin, scope, or association with other journals and organisations. -#### 2. Website +**2. Website** - Websites should be properly supported and maintained, with particular attention given to security aspects that help protect users from viruses and malware. - As a minimum, websites should use https and not http, and all traffic should be redirected through https. Those responsible for the website should apply web standards and best ethical practices to the website's content, presentation, and application. @@ -45,20 +46,22 @@ In addition to the requirements outlined above, the following items should be cl - Authorship criteria. - ISSNs (separate for print and electronic versions). -#### 3. Publishing schedule +**3. Publishing schedule** + A journal's publishing frequency should be clearly described, and the journal must keep to its publishing schedule unless there are exceptional circumstances. -#### 4. Archiving +**4. Archiving** + A journal's plan for electronic backup and long term digital preservation of the journal content, in the event that the journal and/or publisher stops operating, should be clearly indicated. Examples include PMC and those listed in [the Keepers Registry](https://keepers.issn.org/). -#### 5. Copyright +**5. Copyright** - The copyright terms for published content should be clearly stated on the website and in the content. - The copyright terms should be separate and distinct from the copyright of the website. - The copyright holder should be named on the full text of all published articles (HTML and PDF). - If the copyright terms are described in a separate form, this should be easy to find on the website and available to all. -#### 6. Licencing +**6. Licencing** - Licencing information should be clearly described on the website. - Licencing terms should be indicated on the full text of all published articles (HTML and PDF). @@ -69,7 +72,8 @@ If Creative Commons licences are used, then the terms of that licence should als ### JOURNAL PRACTICES -#### 7. Publication ethics and related editorial policies +**7. Publication ethics and related editorial policies** + A journal should have policies on publication ethics (for example, [COPE's Core Practice guidance](https://publicationethics.org/core-practices)). These should be visible on its website, and should refer to: - Journal's policies on [authorship and contributorship](https://publicationethics.org/authorship). @@ -84,7 +88,8 @@ A journal should have policies on publication ethics (for example, [COPE's Core Editors and publishers are responsible for ensuring the integrity of the scholarly literature in their journals and should ensure they outline their policies and procedures for handling such issues when they arise. These issues include plagiarism, citation manipulation, and data falsification/fabrication, among others. Neither the journal’s policies nor the statements of its editors should encourage such misconduct, or knowingly allow such misconduct to take place. In the event that a journal's editors or publisher are made aware of any allegation of research misconduct relating to a submitted or published article in their journal, the editor or publisher should follow [COPE's guidance](https://publicationethics.org/guidance) (or equivalent) in dealing with allegations. -#### 8. Peer review +**8. Peer review** + Peer review is defined as obtaining advice on manuscripts from reviewers/experts in the manuscript’s subject area. Those individuals should not be part of the journal's editorial team. However, the specific elements of peer review may differ by journal and discipline, so the following should be clearly stated on the website: - Whether or not the content is peer reviewed. @@ -105,30 +110,31 @@ Journals should not guarantee acceptance of initial manuscript submissions. Stat The date of publication should be published with all published research. Dates of submission and acceptance are preferred as well. -#### 9. Access +**9. Access** + If any of the online content is not freely accessible to everyone, the method of gaining access (for example, registration, subscription, or pay-per-view fees) should be clearly described. If offline versions (for example, print) are available, this should be clearly described along with any associated charges. ### ORGANISATION -#### 10. Ownership and management +**10. Ownership and management** - Information about the ownership and management of a journal should be clearly indicated on the journal's website. - Organisational names should not be used in a way that could mislead potential authors and editors about the nature of the journal's owner. - If a journal is affiliated with a society, institution, or sponsor, links to their website(s) should be provided where available. -#### 11. Advisory body +**11. Advisory body** Journals should have editorial boards or other advisory bodies whose members are recognised experts in the subject areas stated in the journal's aims and scope. - The full names and affiliations of the members should be provided on the journal's website. - The list should be up to date, and members must agree to serve. - To avoid being associated with predatory or deceptive journals, journals should periodically review their board to ensure it is still relevant and appropriate. -#### 12. Editorial team/contact information +**12. Editorial team/contact information** Journals should provide the full names and affiliations of their editors as well as contact information for the editorial office, including a full mailing address, on the journal’s website. ### BUSINESS PRACTICES -#### 13. Author fees +**13. Author fees** - If author fees are charged (such as article processing charges, page charges, editorial processing charges, language editing fees, colour charges, submission fees, membership fees, or other supplementary charges), then the fees should be clearly stated on the website. - If there are no such fees, this should be clearly stated. @@ -141,14 +147,16 @@ Journals should provide the full names and affiliations of their editors as well - When and how to apply for a waiver. - Author fees or waiver status should not influence editorial decision making, and this should be clearly stated. -#### 14. Other revenue +**14. Other revenue** + Business models or revenue sources should be clearly stated on the journal's website. Examples include author fees (see section 13), subscriptions, sponsorships and subsidies, advertising (see section 15), reprints, supplements, or special issues. Business models or revenue sources (for example, reprint income, supplements, special issues, sponsorships) should not influence editorial decision making. -#### 15. Advertising +**15. Advertising** + Journals should state whether they accept advertising. If they do, they should state their advertising policy, including: - Which types of advertisements will be considered. @@ -157,7 +165,8 @@ Journals should state whether they accept advertising. If they do, they should s Advertisements should not be related in any way to editorial decision making and should be kept separate from the published content. -#### 16. Direct marketing +**16. Direct marketing** + Any direct marketing activities, including solicitation of manuscripts, that are conducted on behalf of the journal should be appropriate, well targeted, and unobtrusive. Information provided about the publisher or journal should be truthful and not misleading for readers or authors. ## Version history diff --git a/cms/pages/docs/faq.md b/cms/pages/docs/faq.md index 2aee5f4006..59ee266a3c 100644 --- a/cms/pages/docs/faq.md +++ b/cms/pages/docs/faq.md @@ -1,6 +1,6 @@ --- layout: sidenav -title: FAQs +title: Metadata help section: About toc: true sticky_sidenav: true @@ -8,78 +8,153 @@ featuremap: ~~FAQ:Fragment~~ --- -## How can I contribute to DOAJ? +After your journal is indexed in DOAJ and you start to upload article metadata to us, we generate journal and article metadata. We make these publicly and freely available via different methods: + +- Our [Atom feed](https://staticdoaj.cottagelabs.com/feed) +- Our [OAI-PMH service](https://staticdoaj.cottagelabs.com/docs/oai-pmh/) +- [A journal CSV](https://staticdoaj.cottagelabs.com/csv) file (updates every 60 minutes) +- Our [API](https://staticdoaj.cottagelabs.com/docs/api/) +- On our website + +Our metadata is collected and incorporated into commercial discovery systems, library discovery portals and search engines around the world. Here are some of them: +- OCLC +- EBSCO products +- Clarivate's Proquest and Ex Libris products +- Clarivate's Web of Science +- SCOPUS +- Google Scholar +- Google +- Dimensions +- CONSER's MARC records + +## Uploading article metadata + +We are one of the most trusted and reliable providers of metadata about open access journals and articles. When publishers upload their article metadata to us, it increases the visibility of the journal and the articles. + +Choose how you want to upload article metadata to us. + +### API + +- **Speed and efficiency**: high +- **Level**: difficult +- **Formats accepted**: JSON +- **Maximum upload limit**: 50MB +- **Requirements**: + - An API that will connect to ours + - The API key from [your DOAJ account](/account/login) +- **Help available?** Yes, via one of our API groups; search for 'Google Group DOAJ API' in your browser. +- **Testing available**: on a case-by-case basis and only for [publisher supporters](/support/publisher-supporters/) +- **Documentation** [Yes](/docs/api/) +- **FAQS** [Yes](/api/v3/docs#api-faq) +- **OJS plugin available?** Yes. Refer to [PKP documentation](https://docs.pkp.sfu.ca/admin-guide/3.3/en/data-import-and-export#doaj-export-plugin)https://docs.pkp.sfu.ca/admin-guide/3.3/en/data-import-and-export#doaj-export-plugin. +- **OJS support**: for help, refer to [the OJS Technical Forum](https://forum.pkp.sfu.ca/c/questions/5)https://forum.pkp.sfu.ca/c/questions/5 +- **Troubleshooting uploads**: please [submit a bug report](https://github.com/DOAJ/doaj/issues/new/choose) (via GitHub) or [contact us](mailto:helpdesk@doaj.org) with the following details: + - The time of the file upload. (If you saw the error more than 12 hours ago, please try the upload again before you contact us.) + - Whether the error happened once or repeatedly + - The exact error message that appeared during the upload. Include a screenshot. + - The DOAJ account ID that you are logged in with. You will find this under 'Settings' in the Dashboard dropdown menu. + - The file(s) you had problems with + - The ISSN(s) of the journal + +### XML + +- **Speed and efficiency**: medium +- **Level**: medium +- **Formats accepted**: DOAJ and Crossref XML +- **Maximum upload limit**: 50MB +- **Requirements**: + - A way to generate structured XML and validate it against the required XSD file. + - The XSD files: [DOAJ](/static/doaj/doajArticles.xsd), [Crossref 5.3.1](/static/crossref/crossref5.3.1.xsd), [Crossref 4.4.2](/static/crossref/crossref4.4.2.xsd) +- **Testing available**: on a case-by-case basis and only for [publisher supporters](/support/publisher-supporters/) +- **Documentation** [DOAJ XML](/docs/xml/), [Crossref 5.3.1 XML](https://www.crossref.org/documentation/schema-library/metadata-deposit-schema-5-3-1/), [Crossref 4.4.2 XML](https://www.crossref.org/documentation/schema-library/resource-only-deposit-schema-4-4-2/) +- **FAQS** No +- **OJS plugin available?** Yes. Refer to [PKP documentation](https://docs.pkp.sfu.ca/admin-guide/3.3/en/data-import-and-export#doaj-export-plugin)https://docs.pkp.sfu.ca/admin-guide/3.3/en/data-import-and-export#doaj-export-plugin. +- **OJS support**: for help, refer to [the OJS Technical Forum](https://forum.pkp.sfu.ca/c/questions/5)https://forum.pkp.sfu.ca/c/questions/5 +- **Help available?** Yes + - For creating XML, see our [DOAJ XML](/docs/xml/) documentation + - For explanations of [specific error messages when uploading XML](/publisher/help#explanations) + - For help with Crossref XML, contact [Crossref support](mailto:support@crossref.org) +- **Troubleshooting uploads**: if our [error message definitions](/publisher/help#explanations) don't help you, please [submit a bug report](https://github.com/DOAJ/doaj/issues/new/choose) (via GitHub) or [contact us](mailto:helpdesk@doaj.org) with the following details: + - The time of the file upload. (If you saw the error more than 12 hours ago, please try the upload again before you contact us.) + - Whether the error happened once or repeatedly + - Whether you are uploading DOAJ or Crossref XML + - Whether you are uploading a file or uploading from a link + - The exact error message shown in the 'Notes' column of the [History of uploads section](/publisher/uploadfile), including the detail under the 'show error details' link. + - A screenshot of the error message with the 'show error details' link expanded. + - The DOAJ account ID that you are logged in with + - The file(s) you had problems with -1. [Support us](/support/) with **a financial contribution** - + Contributions help keep the DOAJ service running and allow us to develop it further. -2. If you have evidence that a journal in DOAJ **might be questionable**, [contact us](/contact/). - + All information shared with DOAJ is done so in confidence and is never published. - + Include the ISSN of the journal when you email us. -3. If you find **a broken link or something that is out of date** or incomplete, [contact us](/contact/). - + We apppreciate the community providing us with this type of feedback. - + For broken links in articles, include the journal’s ISSN, title and the title of the article. -4. Become **a volunteer** - + From time to time, we put out a call for volunteers. Follow us on [Twitter](https://twitter.com/doajplus) or [our blog](https://blog.doaj.org/) to find out when the next call is published. +### Enter article metadata manually -## How do I reset my password? +- **Speed and efficiency**: low +- **Level**: easy +- **Formats accepted**: text, entered via [our webform](/publisher/metadata) +- **Maximum upload limit**: N/A +- **Requirements**: + - Plain text only + - No email addresses + - The abstract metadata for the article: title, full-text URL, DOI (if applicable), author names, ORCiD (if applicable), affiliations, publication date, ISSN(s), Volume/Issue/Page (if applicable), abstract +- **Help available?** Yes. [Contact our Help Desk](mailto:helpdesk@doaj.org). +- **Testing available**: on a case-by-case basis and only for [publisher supporters](/support/publisher-supporters/) +- **Documentation** No +- **Troubleshooting**: you must be careful to enter the Print ISSN and Electronic ISSN in the right field. - If you cannot log in, you can [reset your password](/account/forgot). An email is sent to your email address with a rest link. That link is only valid for 24 hours. Check your Spam folder to make sure the link doesn't go in there. If you do not receive the email, [contact us](/contact/). +## Help with metadata uploads -## I haven't heard anything about the application or update I submitted +### My authors have multiple affiliations - Most applications are processed within three (3) months. Some applications take longer. We aim to have all applications processed within six (6) months. Please wait 3 months until you ask for a status update. Due to the volume of emails that we receive, we cannot answer status updates for applications which are less than three months old. - - We aim to process updates to journals already in DOAJ in three (3) weeks. +We are currently unable to display more than one affiliation per author. We are investigating how we can change this. More information will be posted on our blog. -## I know a journal which should be in DOAJ but isn’t. What should I do? +### My article abstracts are in more than one language - Contact the journal and ask them to submit an application. You can also send us the details of the journal—title and ISSN—and we will contact them. +Our XML format only supports one language for Article Title and Abstract. We are working on a solution that will allow multiple languages to be uploaded to us and displayed. -## The journal I am looking for isn’t in DOAJ. Why? +Metadata containing multiple languages can still be uploaded to us. However, you cannot choose which language is displayed. Please only send us one language to avoid your articles being displayed in a mixture of languages. - Maybe the journal hasn’t applied to us or its application is still in progress. Maybe the journal was [removed from DOAJ](https://docs.google.com/spreadsheets/d/183mRBRqs2jOyP0qZWXN8dUd02D4vL0Mov_kgYF8HORM/edit#gid=1650882189&range=A1). +### I am seeing a 403 forbidden error -## How do I report a bug or ask for technical help? +You may see the 403 forbidden error for different reasons. These apply to both the API and uploading XML. - If you are a registered GitHub user, or you don’t mind becoming one, you can [log a GitHub issue](https://github.com/DOAJ/doaj/issues/new/choose) directly in our repository. Alternatively, you can send the information we ask for below to [our helpdesk](mailto:helpdesk@doaj.org). +- ISSNs + - You may be sending us an extra ISSN that we don’t have in your journal record. + - You may be sending only one ISSN, but we have two in the journal record. + - We may have the journal's ISSNs in an old version of your journal record. +- Wrong account + - You may be sending us an ISSN that belongs to a journal attached to a different account. +- You are trying to update an article's Full Text URL (FTUs) or DOI + - Two articles with the same FTU or DOI are not allowed. + - Please contact us if you want to update the URLs or DOIs of your articles. We need to delete the old versions first. - When you report a bug, please include the following information. (You may not be able to provide every detail but fill in as many as you can.) - -- The URL of the page -- The time of the error -- Whether the error happened once or repeatedly -- If you are uploading XML, was it DOAJ or Crossref XML? - - Whether you are uploading a file or uploading from a link - - The file(s) you had problems with -- The exact error message as it appeared on your screen, including the detail under the 'show error details' link if there is one -- A screenshot of the error message -- The DOAJ account ID that you are logged in with - -## How do I update the information about my journal? - - Log into your account and go to your [Publisher dashboard](/publisher/). Under the '[My journals](/publisher/journal)' tab, you will see all the journals connected to your account. You can use the 'Update' button to submit an update to us. You cannot update the title or ISSN of your journal this way. - -## How do I upload article metadata? - - There are three ways to do this: - - via [our API](/docs/api/) - you will need an API key to do this. - - [uploading XML](/publisher/uploadfile) - - [entering the metadata manually](/publisher/metadata) - - You must have a publisher account to upload metadata to us. Log into your account and go to your [Publisher dashboard](/publisher/) to begin. - - [Help on creating and uploading XML](/docs/xml/) is available. - -## How do I update the title or ISSN of my journal? - - Contact us with the following details: - - old title and new title - - old ISSN and new ISSN - - date of the change - - whether the old title and ISSN have ceased to exist or not - -## How do I update the account details you have? - - You can update your account details by going to 'My Account', 'Settings'. You can update your name, email address and password. If you cannot log in, you can [reset your password](/account/forgot). - - If the account holder has changed and you need to add a new name and email address, please [contact us](/contact/). You should be able to provide the account name or the name of the previous account holder. +### I am seeing a timeout or a 'blocked' error + +If you see a timeout error, please try splitting your upload into smaller files, even if your file is under our 50MB limit. Many may be uploading content to us, and the server is taking longer than usual to collect your file. + +If you see a screen from Cloudflare that says you have been blocked, please [contact us](mailto:helpdesk@doaj.org). Include a screenshot that shows the Ray ID at the very bottom of the page. We need this to troubleshoot the problem. + +## Downloading your metadata + +You can download our metadata about your journal by [downloading our CSV](https://doaj.org/csv). + +You can download your article metadata by [using our API](https://doaj.org/docs/api/) or by using our [public data dump service](/docs/public-data-dump/). + +## Using a CSV file to update your journal metadata + +If you received a CSV file from us, please complete it as soon as possible. The file sent to you contains a cover sheet with instructions. + +Before you send us the file, you will need to [validate it](/publisher/journal-csv) first. Some changes will cause the validation to fail: + +- don't save the file in any format other than CSV +- don't change an ISSN or the Title of a journal. To do this, contact [Help Desk](mailto:helpdesk@doaj.org). +- don't add a new journal to the file. To do this, [submit a new application](/apply/). +- don't change the title of a column +- don't include anything in the column other than what is required + +Deleting a journal from the file will mean no update happens; it will not remove a journal from your account. To do this, contact [Help Desk](mailto:helpdesk@doaj.org). + +### Validating your file + +Before emailing your file to us, you will need to validate it. Do this in your Publisher Dashboard on the 'Validate your CSV' tab. If you do not see the tab, contact [Help Desk](mailto:helpdesk@doaj.org). + +As well as the five points above, here are some tips to help you produce a valid CSV file: + +- be careful not to add spaces before or after the information you put into the spreadsheet. This can cause the validation to fail. diff --git a/cms/pages/legal/contact.md b/cms/pages/legal/contact.md index 0219545d6f..39d0c914dc 100644 --- a/cms/pages/legal/contact.md +++ b/cms/pages/legal/contact.md @@ -7,11 +7,14 @@ featuremap: ~~ContactUs:Fragment~~ --- -We welcome your feedback, comments and questions about the DOAJ web site and our services, or if you have feedback on a journal in the index. +Use this page to: -All communication with us is treated as confidential. +- send feedback and questions about our website or services +- send feedback about a journal in our index +- submit a complaint about our website or services -Read our [FAQs](/docs/faq/) before you contact us. If you cannot find the answer to your question, send an email to [the DOAJ helpdesk](mailto:helpdesk@doaj.org). If your email is about a journal, please include the title and ISSN(s). - - - +Both complaints and feedback sent to us about specific journals are treated as confidential. + +If you're a publisher and need help, read our [publisher help page](/publisher/help/). + +Send an email to [the DOAJ helpdesk](mailto:helpdesk@doaj.org). If you're emailing about a journal or application, please include the title and ISSN(s). diff --git a/cms/pages/legal/privacy.md b/cms/pages/legal/privacy.md index 9bd20906e5..2495f41017 100644 --- a/cms/pages/legal/privacy.md +++ b/cms/pages/legal/privacy.md @@ -10,19 +10,19 @@ featuremap: ~~Privacy:Fragment~~ Protecting your privacy and personal data is important to us. Please take a few moments to review this Policy. -(Last reviewed and updated: November 2022) +(Last reviewed and updated: November 2023) ### Summary -The Directory of Open Access Journals ("DOAJ"), managed independently by [IS4OA](https://is4oa.org/), a CIC registered in the United Kingdom, is a free online directory that contains lists of peer-reviewed, open access journals. In order to ensure that every journal application and eventual entry in DOAJ can be vouched for by a representative of that journal, and to maintain a high level of recency and accuracy, the DOAJ collects and stores two groups of email addresses: of the person submitting an application (**the applicant**) for a journal to be indexed in DOAJ and of the person registered in the application as the journal contact (**the journal contact**). Both of those email addresses are stored in the application and, if an application is successful, a user account is created which contains the email address of the journal contact. DOAJ does not share any email addresses with anyone outside the organisation. +The Directory of Open Access Journals ("DOAJ"), managed independently by [IS4OA](https://is4oa.org/), a CIC registered in the United Kingdom, is a free online directory that contains lists of peer-reviewed, open access journals. To ensure that every journal application and eventual entry in DOAJ can be vouched for by a representative of that journal and to maintain a high level of recency and accuracy, the DOAJ collects and stores two groups of email addresses: the person submitting an application (**the applicant**) and the account holder (**the journal contact**), if different from the applicant. DOAJ does not share any email addresses with anyone outside the organisation. DOAJ collects email addresses of volunteers and Ambassadors. DOAJ also collects email addresses of its team members and partners it works with. ### Who to contact at DOAJ about protecting your privacy -The DOAJ Operations Manager, Dominic Mitchell, has assumed responsibility of the DOAJ data policy and implementing the changes required by the GDPR which came into effect on 25th May 2018. If you have any questions or concerns about any of the information laid out in this Notice, or any other question about how DOAJ protects or uses your data, please [send an email to Dominic](mailto:dominic@doaj.org). Alternatively, you can write to him: Dominic Mitchell, IS4OA Denmark, c/o Joanna Ball, c/o Joanna Ball, Bøgevej 33, DK-4000 Roskilde, DENMARK. +The DOAJ Operations Manager, Dominic Mitchell, has assumed responsibility for the DOAJ data policy and implementing the changes required by the GDPR, which came into effect on 25th May 2018. If you have any questions or concerns about the information in this Notice or any other question about how DOAJ protects or uses your data, please [email Dominic](mailto:dominic@doaj.org). Alternatively, you can write to him: Dominic Mitchell, IS4OA Denmark, c/o Joanna Ball, Bøgevej 33, DK-4000 Roskilde, DENMARK. ### The policy -This is the website for Directory of Open Access Journals, which is a website managed by [Infrastructure Services for Open Access](https://is4oa.org/), a UK-based Community Interest Company, registered in the UK. The DOAJ does not have an app. The DOAJ uses WordPress for its blogs: [News Service](https://blog.doaj.org/) and [DOAJ Best Practice Guide](https://www.doajbestpracticeguide.org/). This Privacy Information Notice pertains to this website (https://doaj.org) and its operations only. +This is the website for Directory of Open Access Journals, which is managed by [Infrastructure Services for Open Access](https://is4oa.org/), a UK-based Community Interest Company registered in the UK. The DOAJ does not have an app. The DOAJ uses WordPress for its blogs: [News Service](https://blog.doaj.org/) and [DOAJ Best Practice Guide](https://www.doajbestpracticeguide.org/). This Privacy Information Notice pertains to this website (https://doaj.org) and its operations only. ### 1) The information we collect and how we use it @@ -34,23 +34,23 @@ Your name Your email address Your password -An account is created and stored in our User database. We need these details to process your application. You are the **Account holder**. We will use the email address you provide to contact you: +An account is created and stored in our User database. We need these details to process your application. You are the **Account holder**. We will use the email address you provided to contact you: 1. to ask you about an update to the information we hold on the journal(s) 2. to inform you that we have changed the status of the journal in DOAJ 3. to inform you of any major changes that will affect how you carry out your tasks on the [Publisher dashboard](/publisher/) 4. to inform you about a new feature or piece of functionality which affects the way you do your work in DOAJ 5. to send you occasional DOAJ marketing emails\* -6. to contact you regarding a question on article metadata which is linked to the journal in DOAJ +6. to contact you regarding a question on article metadata, which is linked to the journal in DOAJ 7. to ask you questions about your application \*see section 7 below. -It is not technically possible for emails, generated as part of the review process, to be sent to Account holders from within the DOAJ system. Therefore email addresses and names are copied into emails, created in whatever email program the DOAJ Team member or Volunteer uses. DOAJ Team members use a dedicated DOAJ workspace, via Google Workspace and the email client is with Gmail. DOAJ Team members and Ambassadors all use @doaj.org email addresses. +It is not technically possible for emails generated as part of the review process to be sent to Account holders from within the DOAJ system. Therefore, email addresses and names are copied into emails created in whatever email program the DOAJ Team member or Volunteer uses. DOAJ Team members use a dedicated DOAJ workspace via Google for Business and the email client is Gmail. DOAJ Team members and Ambassadors all use @doaj.org email addresses. Volunteers use their own email addresses. -Gmail automatically saves email addresses that we send emails to. Our use of Gmail and the protection of your personal data within it is governed by [Google's Privacy Policy](https://policies.google.com/privacy/update), [Data Processing Amendment for G Suite](https://gsuite.google.com/terms/dpa_terms.html), and [Data Processing and Security Terms for Google Cloud Platform](https://cloud.google.com/terms/data-processing-term). +Gmail automatically saves copies of emails that we send. Our use of Gmail and the protection of your personal data within it is governed by [Google's Privacy Policy](https://policies.google.com/privacy/update), [Data Processing Amendment for G Suite](https://gsuite.google.com/terms/dpa_terms.html), and [Data Processing and Security Terms for Google Cloud Platform](https://cloud.google.com/terms/data-processing-term). -You may access, review and edit your account details by [logging into your DOAJ account](/account/login). We will never share your email address with any 3rd party and we never discuss the details of your account or journal applications with anyone apart from you or an authorised representative. +You may access, review and edit your account details by [logging into your DOAJ account](/account/login). We will never share your email address with any 3rd party, and we never discuss the details of your account or journal applications with anyone apart from you or an authorised representative. #### 1b Volunteer applications @@ -58,9 +58,9 @@ When someone applies to be a volunteer for DOAJ, we collect, via a Google Form: Your name Your email Address -Name and email address of the applicant's reference(s) +Name and email address of your reference(s) -We use this information to contact you or your references regarding your application. If your application is successful, we create an account for you in our User database. This allows applications to be assigned to you for you to review and for you to log in to your DOAJ workspace. You may access, review and edit these details by [logging into your DOAJ account](/account/login) at any time. We have to retain your email address so that we can keep you up to date about your volunteer work. +We use this information to contact you or your references regarding your application. If your application is successful, we will create an account for you in our User database. This allows applications to be assigned to you for review and to log in to your DOAJ workspace. You may access, review and edit these details by [logging into your DOAJ account](/account/login) at any time. We have to retain your email address so that we can keep you up to date about your volunteer work. #### 1c Via cookies @@ -68,25 +68,27 @@ We collect some information automatically via cookies in use on this site. See S #### 1d A user account -Sometimes we create a user account for individuals or organisation becaue they want to use our API or they are a service provider operating on behalf of one or a group of publishers. We collect: +Sometimes, we create a user account for individuals or organisations because they want to use our metadata services or they are a service provider operating on behalf of one or a group of publishers. We collect: A name An email address A password -An account is created in our User database. This allows journals to be assigned to that account, or for the account to generate an API key. You may access, review and edit these details by [logging into your DOAJ account](/account/login) at any time. +An account is created in our User database. This allows journals to be assigned to that account and for the Account Holder to find their API key or access special services. You may access, review and edit these details by [logging into your DOAJ account](/account/login) at any time. #### 1e A record in Less Annoying CRM -Sometimes, we store names, email addresses and the organisation where you work in Less Annoying, our customer relationship manager. +Sometimes, we store names, email addresses and the organisation where you work in Less Annoying, our customer relationship manager. Your data in Less Annoying is protected by [their privacy policy](https://www.lessannoyingcrm.com/help/gdpr). #### 1f A record in Billy -Sometimes, we store names, email addresses and the organisation where you work in Billy, our accounting software. +Sometimes, we store names, email addresses and the organisation where you work in Billy, our accounting software. Your data in Billy is protected by [their Privacy Policy](https://www.billy.dk/cookie-privatlivs-politik/). ### 2) Why we need to collect personal data -DOAJ has a duty to its stakeholder groups to maintain the quality, security and recency of its index. It also has a duty to keep its users informed of updates concerning user accounts, features and functionality, or policy changes. This is our legitimate business interest and why we need to collect a contact name and email address linked to each journal indexed in our database. We use those email addresses to keep our members informed and up-to-date. We use other platforms to keep users and stakeholder groups aware of our updates, such as Wordpress, for our blog, or send invoices to supporters. You may opt out of Marketing emails at any time by logging into your [DOAJ account](/account/login). +DOAJ has a duty to its stakeholder groups to maintain its index's quality, security and recency. It also has a duty to keep its users informed of updates concerning user accounts, features and functionality, or policy changes. This is our legitimate business interest and why we need to collect a contact name and email address linked to each journal indexed in our database. We use those email addresses to keep our members informed and up-to-date. We use other platforms to inform users and stakeholder groups of our updates, such as WordPress for our blog or to send invoices to supporters. + +We rarely send marketing emails, but you may opt out of Marketing emails at any time by logging into your [DOAJ account](/account/login). #### 2a Journals and uploading article metadata @@ -94,103 +96,104 @@ Anything to do with a journal or uploading article metadata must be linked to an #### 2b Journal applications -Journal applications must include the name and email address of the applicant. We use this information to process the application effectively: we use the email addresses to make contact as part of the standard review process and to fulfill our obligation to you to process your application in an unbiased and timely manner. +Journal applications must include the name and email address of the applicant. We use this information to process the application effectively: we use the email addresses to make contact as part of the standard review process and to fulfil our obligation to you to process your application in an unbiased and timely manner. #### 2c Volunteers -Volunteer records must include a name and an email address so that we can assign applications to you, and email you about changes to the Admin system where you do you work or other changes. You may opt out of marketing emails by logging into your [DOAJ account](/account/login). +Volunteer records must include a name and an email address so that we can assign applications to you, email you about changes to the Admin system where you do your work or other changes. + +You may opt out of marketing emails by logging into your [DOAJ account](/account/login). ### 3) Which cookies are in use on this site and why When you use DOAJ, cookies are set on your machine. The cookies we set are: -* Google Analytics - we track usage of this site using Google Analytics. We do not track individuals but general paths through the site. (Being retired as of August 2023.) * jquery - we use jquery.com to load the javascript framework. -* doaj.org - a small number of required cookies for the application to function, for example when you log in and when you click away the cookie consent banner. +* doaj.org - a small number of required cookies for the application to function, for example, when you log in and click away the cookie consent banner. * schema - if you upload article metadata to us via the Upload Article XML tab, we place a cookie on your machine which allows us to remember which XSD schema you used the last time you uploaded XML to us: doaj or Crossref -* hotjar - we use hotjar to do unmoderated monitoring of users journeys through our site. We look at where they click on a page, where they stop in a process (such as submitting an application) and we collect feedback via the Hotjar chat app. -* twitter - on our homepage is an embedded Twitter feed. This app comes with cookies so make Twiiter work, such as checking to see if you are logged in, or whether you have specific advertising preferences. +* hotjar - we sometimes use Hotjar to do unmoderated monitoring of user journeys through our site. We look at where they click on a page and where they stop in a process. This is only occasional. We always use a Hotjar widget to collect feedback on individual journal pages. +* twitter - on our homepage is an embedded X (Twitter) feed. This app comes with cookies to make X (Twitter) work, such as checking to see if you are logged in or whether you have specific advertising preferences. -You can disable these cookies at any time by using the privacy settings in your browser however the performance and some functions of the site may be affected. +You can disable these cookies at any time by using the privacy settings in your browser. However, the performance and some functions of the site may be affected. ### 4) How we store personal data #### 4a In journal applications -The journal applications are held in a database in the DOAJ Admin system which is accessible only by the DOAJ Team, the DOAJ Ambassadors, the DOAJ Volunteers (collectively **the DOAJ representatives**) and our technical partners in site development and hosting, [Cottage Labs](https://cottagelabs.com). Different groups of DOAJ representatives have different levels of access to the database. The main reason and objective for accessing the database is to review journal records and applications. +The journal applications are held in a database in the DOAJ Admin system, which is accessible only by the DOAJ Team, the DOAJ Ambassadors, the DOAJ Volunteers (collectively **the DOAJ representatives**) and our technical partners in site development and hosting, [Cottage Labs](https://cottagelabs.com). Different groups of DOAJ representatives have different levels of access to the database. The main reason and objective for accessing the database is to review journal records and applications. #### 4b In journal records Your contact details are linked to journal and application records. Journal names and other journal details are searchable and displayed on our website; journal contact details are never displayed publicly and are not searchable. #### 4c In user accounts -User details (name, email address, password) are stored in a separate database, the User database, within the DOAJ Admin system. Sometimes, the DOAJ Team and, very rarely, Cottage Labs, will access user accounts to make updates, reset passwords, reset an API key or delete the user account entirely. Neither DOAJ representatives or Cottage Labs have access to users' passwords which are encrypted. All personal data is encrypted when it is imported into a DOAJ test site or onto a developer's machine for development or testing. +User details (name, email address, password) are stored in a separate database, the User database, within the DOAJ Admin system. Sometimes, the DOAJ Team and, very rarely, Cottage Labs will access user accounts to make updates, reset passwords, reset an API key or delete the user account entirely. Neither DOAJ representatives nor Cottage Labs have access to users' passwords, which are encrypted. All personal data is encrypted when imported into a DOAJ test site or onto a developer's machine for development or testing. #### 4e In volunteer applications Volunteer applications are collected in a Google Sheet and are stored securely in the DOAJ's Google workspace. The Sheet is only accessible by the DOAJ Team. #### 4f As the DOAJ team -The DOAJ User database contains the personal information of all users of the DOAJ Admin system. This includes the DOAJ Team. The personal data of DOAJ Team members is also stored in contracts and role profiles on the Google Drive. DOAJ Team's banking details are held in Billy, our accounting software, to make payments. +The DOAJ User database contains the personal information of all users of the DOAJ Admin system. This includes the DOAJ Team. The personal data of DOAJ Team members is also stored in contracts and role profiles on Google Drive. DOAJ Team's banking details are held in Billy, our accounting software, to make payments. #### 4g in Less Annoying and Billy -If the journal contact is also the same contact to whom we issue invoices, a name, email address, and organisation will be stored in Less Annoying CRM and Billy. The protection of your data is governed by their privacy policies. [Less Annoying](https://www.lessannoyingcrm.com/help/gdpr) [Billy](https://www.billy.dk/databehandleraftale/) +If the journal contact is the same contact to whom we issue invoices, a name, email address, and organisation will be stored in Less Annoying CRM and Billy. Their privacy policies govern the protection of your data. [Less Annoying](https://www.lessannoyingcrm.com/help/gdpr) [Billy](https://www.billy.dk/databehandleraftale/) ### 5) How we process personal data #### 5a Account holders -DOAJ sometimes asks Cottage Labs to create an export of email addresses from the User database. We then import this list into Mailchimp, where it is password-secured. An example of this might be a list of all email addresses from accounts which have at least 1 journal indexed in DOAJ associated with them. We use this list to contact Account holders with important information about changes and updates to the DOAJ Publisher Area which may affect their use of it. We also send out the occasional marketing email. +DOAJ sometimes asks Cottage Labs to create an export of email addresses from the User database. We then import this list into Mailchimp, where it is password-secured. An example of this might be a list of all email addresses from accounts which have at least one journal indexed in DOAJ associated with them. We use this list to contact Account holders with important information about changes and updates to the DOAJ Publisher Area, which may affect their use. We also send out the occasional marketing email. #### 5b Volunteers -DOAJ maintains a list in Google Drive of all the active volunteers' names and email addresses. We use this list to coordinate volunteer activities, ensure that each Editorial Group has enough volunteers, and to ensure a smooth flow of applications through the DOAJ workflow. DOAJ also sends out a volunteer newsletter: the recipient list for this is manually created in Gmail using the list in Google Drive. This newsletter contains important updates which help the volunteers carry out their work effectively. +DOAJ maintains a list in Google Drive of all the active volunteers' names and email addresses. We use this list to coordinate volunteer activities, ensure that each Editorial Group has enough volunteers, and ensure a smooth flow of applications through the DOAJ workflow. DOAJ also sends out a volunteer newsletter: the recipient list for this is manually created in Gmail using the list in Google Drive. This newsletter contains important updates which help the volunteers carry out their work effectively. -### 6) How long we store personal data for and when we delete data +### 6) How long do we store personal data and when do we delete data #### 6a Journal applications -DOAJ stores the personal data associated with a journal application and a journal record for as long as the journal is in DOAJ. If/when an application is rejected or a journal is removed from DOAJ, we will usually delete the data after 7 years. However, we do not delete personal data embedded in these records, or delete the user's account from the User database if we need to refer to them to provide the history of a journal and DOAJ. We record notes in all our applications and these Notes provide valuable information over time and help us to continue to meet high levels of quality. +DOAJ stores the personal data associated with a journal application and a journal record for as long as the journal is in DOAJ. If/when an application is rejected or a journal is removed from DOAJ, we usually delete the data after seven years. However, we do not delete personal data embedded in these records or delete the user's account from the User database if we need to refer to them to provide the history of a journal and DOAJ. We record notes in all our applications, and these Notes provide valuable information over time and help us to continue to meet high levels of quality. #### 6b User accounts -Users may request at any time that we delete all their personal data from our systems by submitting a Subject Access Request to us\*\*. Every year, DOAJ asks Cottage Labs to produce a report of all the user accounts in the system that have no records associated with them and that are more than 7 years old. The last major overhaul of the DOAJ User database was when DOAJ migrated to its current platform at the end of 2013: we still have active user accounts pre-dating that time which were migrated to the new platform. +Users may request at any time that we delete all their personal data from our systems by submitting a Subject Access Request to us\*\*. Every year, DOAJ asks Cottage Labs to produce a report of all the user accounts in the system that have no records associated with them and are more than seven years old. The last major overhaul of the DOAJ User database was when DOAJ migrated to its current platform at the end of 2013: we still have active user accounts pre-dating that time, which were migrated to the new platform. #### 6c Volunteer applications -DOAJ stores the personal data of applicants in a Google Sheet until we assess if a person is a suitable candidate. Old applications are struck through and archived in a secure Google Drive folder which is only accessible by the Operations Manager. Old applications are deleted after seven years. Volunteer applicants may request at any time that we delete all their personal data from the Google Drive by submitting a Subject Access Request (SAR) to us\*\*. +DOAJ stores the personal data of applicants in a Google Sheet until we assess if a person is a suitable candidate. Old applications are struck through and archived in a secure Google Drive folder only accessible by the Operations Manager. Old applications are deleted after seven years. Volunteer applicants may request at any time that we delete all their personal data from Google Drive by submitting a Subject Access Request (SAR) to us\*\*. \*\*see section 9 below. -### 7) Who we share data with +### 7) Who do we share data with -DOAJ does not share personal data with any organisation or individual outside the immediate DOAJ organisation, neither will it grant requests from industry organisations for access to user data. +DOAJ does not share personal data with any organisation or individual outside the immediate DOAJ organisation, nor will it grant requests from industry organisations for access to user data. -### 8) How to delete your account and request that personal data is deleted +### 8) How to delete your account and request that personal data be deleted #### 8a How to delete your account -An individual may request at any time that DOAJ deletes their user account from the DOAJ Admin system. Do this by using our [Contact form](/contact). Please note that it is not possible for journals to be indexed in DOAJ without being linked to a User account. If you want us to delete your account and it is linked to a journal or journals which are indexed in DOAJ, then we will need to link the journal(s) to another individual's account. In these cases it would be helpful if, when you contact us asking us to delete your account, you can provide the name and email address of a replacement. +Individuals may request that DOAJ delete their user account from the DOAJ Admin system at any time. Do this by [emailing Dominic Mitchell](mailto:dominic@doaj.org). Please note that it is not possible for journals to be indexed in DOAJ without being linked to a User account. If you want us to delete your account and it is linked to a journal or journals indexed in DOAJ, then we will need a replacement account. In these cases, it would be helpful if, when you contact us asking us to delete your account, you can provide a replacement's name and email address. -#### 8b How to request that all personal data is deleted +#### 8b How to request that all personal data be deleted -To request that DOAJ deletes all of the personal data that we hold about you, please send an email to the DOAJ Operations Manager, Dominic Mitchell: [dominic@doaj.org](mailto:dominic@doaj.org). +To request that DOAJ delete all of the personal data we hold about you, please email the Operations Manager, Dominic Mitchell: [dominic@doaj.org](mailto:dominic@doaj.org). ### 9) Subject access request (SAR) #### 9a What is a subject access request (SAR)? -An SAR is the name given to the process by which a user can request to know details of the information that a site holds about them and how it is being used. A full explanation is given here: [https://ico.org.uk/for-organisations/guide-to-data-protection/principle-6-rights/subject-access-request/](https://ico.org.uk/for-organisations/guide-to-data-protection/principle-6-rights/subject-access-request/) but in summary: 'an individual who makes a written request and pays a fee is entitled to be: told whether any personal data is being processed; given a description of the personal data, the reasons it is being processed, and whether it will be given to any other organisations or people; given a copy of the information comprising the data; and given details of the source of the data (where this is available)'. The recipient organisation of an SAR must respond, according to UK law, within 40 calendar days. +An SAR is the name given to the process by which a user can request to know details of a site's information about them and how it is being used. A full explanation is given here: [https://ico.org.uk/for-organisations/guide-to-data-protection/principle-6-rights/subject-access-request/](https://ico.org.uk/for-organisations/guide-to-data-protection/principle-6-rights/subject-access-request/) but in summary: 'an individual who makes a written request and pays a fee is entitled to be: told whether any personal data is being processed; given a description of the personal data, the reasons it is being processed, and whether it will be given to any other organisations or people; given a copy of the information comprising the data; and given details of the source of the data (where this is available)'. According to UK law, the recipient organisation of a SAR must respond within 40 calendar days. -#### 9.b How to make an SAR to DOAJ +#### 9b How to make a SAR to DOAJ -You may submit an SAR to DOAJ by contacting the DOAJ Operations Manager, Dominic Mitchell, directly: [dominic@doaj.org](mailto:dominic@doaj.org) Any request in writing will be considered valid, whatever the format. +You may submit a SAR to DOAJ by contacting the Operations Manager, Dominic Mitchell, directly: [dominic@doaj.org](mailto:dominic@doaj.org). Any request in writing will be considered valid, whatever the format. ### 10) Withdrawing consent -As stated in Sections 7 and 8 above, you can withdraw your consent for us to store and process your personal data at any time by submitting an email to the DOAJ Operations Manager. +As stated in Sections 7 and 8 above, you can withdraw your consent for us to store and process your personal data at any time by submitting an email to the Operations Manager. -You may also explicitly indicate that you do not want DOAJ to use your email address for marketing purposes. We have added consent checkboxes to all user account where you can opt in or opt out of these emails. +You may also explicitly indicate that you do not want DOAJ to use your email address for marketing purposes. We have added consent checkboxes to all user accounts where you can opt in or out of these emails. ### 11) How to complain diff --git a/cms/pages/preservation/index.md b/cms/pages/preservation/index.md index 9601600341..d13b3bfe85 100644 --- a/cms/pages/preservation/index.md +++ b/cms/pages/preservation/index.md @@ -11,11 +11,11 @@ featuremap: ~~Preservation:Fragment~~ [Esta página está disponible en Español](https://docs.google.com/document/d/1dCxZYO0HDmFWMyazbkayZJtpCYkO9AIf0xqw-z55rU0/edit?usp=sharing). - Project JASPER (JournAlS are Preserved forevER) is an initiative to preserve open access journals. It was launched on [World Preservation Day 2020](https://www.dpconline.org/events/world-digital-preservation-day) and is in response to research* that shows that online journals—both open and closed access journals—can just disappear from the internet. This happens because of a lack of awareness amongst smaller publishers around the need for long-term digital preservation and/or the resources to enroll a journal in a long-term digital preservation scheme. + Project JASPER (JournAlS are Preserved forevER) is an initiative to preserve open access journals. It was launched on [World Preservation Day 2020](https://www.dpconline.org/events/world-digital-preservation-day) and is in response to research* that shows that online journals—both open and closed access journals—can disappear from the internet. -Long-term archiving of research resources is of paramount importance for scholarship. Authors want to ensure their contributions to the scholarly record will be permanent. Scholars must be able to access all of the published research in their fields, both now and long into the future. +Long-term archiving of scholarship is of paramount importance, but too often, publishers don't realise this or don't have the resources to do anything about it. Authors want to ensure their contributions to the scholarly record will be permanent. Scholars must be able to access all of the published research in their fields, both now and long into the future. -As a scholarly community, we are pledged to eliminate the possibility that high-value resources can disappear*. Project JASPER aims to close the gap in preservation coverage that currently exists among open access journals. +As a scholarly community, we are pledged to eliminate the possibility that high-value resources can disappear*. Project JASPER aims to close the gap in preservation coverage among open access journals. Questions? [Email us](mailto:preservation@doaj.org). @@ -24,59 +24,58 @@ _*References_ 1. M. Laakso, M. Matthias, N. Jahn. _Open is not forever: A study of vanished open access journals_. Journal of the Association for Information Science and Technology, Feb. 2021. [https://doi.org/10.1002/asi.24460](https://doi.org/10.1002/asi.24460) 2. J. Bosman et al. _OA Diamond Journals Study_. [https://zenodo.org/record/4558704](https://zenodo.org/record/4558704) -## Phase One -Phases One of Project JASPER was a pilot project between CLOCKSS, DOAJ, Internet Archive, Keepers Registry and PKP to scope out a solution that will reduce the number of unarchived open access journals. +## The project -## Phase Two -Phase One was considered a success. Phase Two will onboard more journals and the project partners will seek funding for a more sustainable solution, covering a wider journal base. +The premise of the JASPER process is that all journals are different, with different needs, different priorities and different resources. The workflow offers viable alternatives to journals so that they engage with one or more archiving services that meet their requirements and capacities. The JASPER process is simple: -JASPER's aim is to get more journals archived by establishing a simple process: +1. work out which archiving option might be the best fit for the publisher +2. establish the level where the amount of effort is manageable by the publisher, based on: + * platform + * ability to send article metadata to DOAJ + * the ability to export and zip full text, images etc +3. direct the journal to the most appropriate archiving option -- work out which archiving option might be the best fit for the publisher, -- establish the level where the amount of effort is manageable by the publisher. This is based on the following factors: - - platform - - ability to send article metadata to DOAJ - - ability to export and zip full text, images etc easily -- provide the journal's representatives with the correct information to make archiving happen. +## For publishers -The premise of the JASPER process is that all journals are different, with different needs, different priorities and different resources. The workflow needs to offer viable alternatives to journals so that they engage with one or more archiving services that meet their requirements and capacities. - -### For publishers -We hope that many open access publishers, whose journals are not currently archived, will want to take part. The current criteria for eligibility are +We hope that many DOAJ-indexed publishers will want to take part but to ensure our resources are focussed, we have eligibility criteria: - Your journal must be indexed in DOAJ -- The abstracts of your articles must be indexed in DOAJ -- Your journal may not charge any fees of any kind, or be subsidised in any way. -- Your journal is not already archived in a preservation service (although it may be archived in PKP PN). -- Your content is licensed with a CC BY licence (although sometimes, we will accept less open licenses). +- The journal's article abstracts must be indexed in DOAJ +- Your journal may not charge any fees or be subsidised in any way. +- A commercial contract with an archiving solution does not already cover your journal. +- Your content is licensed with a CC BY licence (sometimes, we will accept less open licenses). - The copyright of published content remains with the author. -Please note that the project partners are self-funding the JASPER project, which means that we will analyse the financial status of journals or publishers quite closely. We reserve the right to not include a journal or a publisher in JASPER if we think that it is or could be supported or funded by an organisation capable of covering the costs of long-term preservation. +We will analyse the financial status of journals or publishers quite closely. We reserve the right not to include a journal or a publisher in JASPER if we think it is or could be supported or funded by an organisation capable of covering the costs of long-term preservation. This is a necessary step while JASPER remains unfunded. + +## To apply + +If your journal meets the criteria above, [you may apply here](https://www.surveymonkey.com/r/JASPERPRESERVATION). If you have any questions about these criteria, don't hesitate to [get in touch](mailto:preservation@doaj.org). -If you have any questions about these criteria, don't hesitate to [get in touch](mailto:preservation@doaj.org). +## Presentar solicitud -#### Archiving options -1. *The OJS route*: OJS journals can use [the PKP PN preservation](https://docs.pkp.sfu.ca/pkp-pn/en/) service which is built on LOCKSS software. You need to enable a plugin in the journal's dashboard and agree to some simple terms. However, your journal must be on the right version of the OJS software to take part. JASPER will make sure that the journals are pointed to the guidance they need to upgrade and become compliant. Journals on OJS may also choose to have their content preserved via the upload route below. +Si su revista cumple con los criterios anteriores, [puede presentar su solicitud aquí](https://www.surveymonkey.com/r/ProyectoJASPER). Si tiene alguna pregunta sobre estos criterios, no dude en ponerse en contacto. -2. *The upload route*: journals indicate to DOAJ that they wish to preserve their content with CLOCKSS. DOAJ matches the full text sent to us with article metadata that we hold. We send everything to a remote folder. From this single location, archiving agencies, such as CLOCKSS and Internet Archive (and, later, other “Keepers”) collect the content. +## Different archiving options +1. *The OJS route*: OJS journals can use [the PKP PN preservation](https://docs.pkp.sfu.ca/pkp-pn/en/) service which is built on LOCKSS software. You must enable a plugin in the journal's dashboard and agree to some simple terms. However, your journal must be on the right version of the OJS software to take part. JASPER will ensure that the journals are pointed to the guidance they need to upgrade and become compliant. Journals on OJS may also choose to have their content preserved via the upload route below. -3. *The web-crawing route*: if you are not able to or do not want to export article metadata and full text, your journal website details will be provided to the Internet Archive for inclusion in a best-effort, automated web harvesting. +2. *The upload route*: journals indicate to DOAJ that they wish to preserve their content with CLOCKSS. DOAJ matches the full text sent to us with the article metadata that we hold. We send everything to a remote folder. From this single location, archiving agencies, such as CLOCKSS and Internet Archive (and, later, other “Keepers”) collect the content. -#### More copies, better preservation -It is always safer for a journal’s content to be archived in more than one place and, ideally, in at least three. To make this easy for smaller publishers, journals opting for Route 2 will automatically have their content preserved via Route 3. Future phases of the project will add the possibility for Route 1 journals to take part in other routes too. +3. *The web-crawling route*: if you cannot or do not want to export article metadata and full text, your journal website details will be provided to the Internet Archive for inclusion in a best-effort, automated web harvesting. -### For the Keepers -There are often costs associated with adding new journals to a preservation service. JASPER aims to significantly reduce these costs by using DOAJ as a common interface to the thousands of journals indexed in it. By delivering content to an FTP server hosted by Internet Archive, content deliveries are unified into a single format and available from a single location. +### More copies, better preservation +It is always safer for a journal’s content to be archived in more than one place, ideally, in at least three. To simplify this for smaller publishers, journals opting for Route 2 will automatically have their content preserved via Route 3. Future phases of the project will allow Route 1 journals to participate in other routes, too. -Phase One designed, implemented and tested this process, supported by money or services donated by the five project partners. Phase Two will onboard more publishers and seek funding. +## For the Keepers +There are often costs associated with adding new journals to a preservation service. JASPER aims to significantly reduce these costs by using DOAJ as a common interface to the thousands of journals indexed in it. By delivering content to an FTP server hosted by Internet Archive, content deliveries are unified into a single format and available from a single location. If you want to join JASPER, [get in touch](mailto:preservation@doaj.org). -### For libraries and universities -Long-term preservation is a profoundly important mission for libraries, universities, and other memory organisations. By supporting DOAJ, the archiving services participating in this project, and in Keepers, you are helping us preserve valuable scholarly content for the long-term and in a very cost-effective way. Thank you for your continuing support! +## For libraries and universities +Long-term digital preservation is a profoundly important mission for libraries, universities, and other memory organisations. By [supporting JASPER](https://doaj.org/support/), you are helping us preserve valuable scholarly content for the long term and in a very cost-effective way. Thank you for your continuing support! ## About the project partners ### CLOCKSS -[CLOCKSS](https://clockss.org/) is a not-for-profit collaboration of the world’s leading academic publishers and research libraries. Our mission is to ensure the long-term survival of digital scholarly content and to instill confidence in authors, scholars, policy makers, libraries and publishers around the world that their content will be safely and securely preserved for future generations. We are entrusted with the long-term preservation of more than 51 million journal articles and 400,000 scholarly books. CLOCKSS operates [12 archive nodes](https://clockss.org/archive-nodes/) at leading academic institutions worldwide. This secure, robust, and decentralized infrastructure can withstand threats from technological, economic, environmental, and political failures. A destructive event in one location doesn’t jeopardize the survival of preserved digital content because the 11 other locations serve as mirror sites to backup and repair the disrupted location’s archive. Content is triggered from our dark archive if it disappears from the web, and is made available to everyone under an Open Access license. +[CLOCKSS](https://clockss.org/) is a not-for-profit collaboration of leading academic publishers and research libraries. Our mission is to ensure the long-term survival of digital scholarly content and instil confidence in authors, scholars, policymakers, libraries and publishers worldwide that their content will be safely and securely preserved for future generations. We are entrusted with the long-term preservation of more than 51 million journal articles and 400,000 scholarly books. CLOCKSS operates [12 archive nodes](https://clockss.org/archive-nodes/) at leading academic institutions worldwide. This secure, robust, and decentralized infrastructure can withstand threats from technological, economic, environmental, and political failures. A destructive event in one location doesn’t jeopardize the survival of preserved digital content because the 11 other locations serve as mirror sites to back up and repair the disrupted location’s archive. Content is triggered from our dark archive if it disappears from the web and is made available to everyone under an Open Access license. ### DOAJ DOAJ is the largest, most diverse, free directory of open access journals in the world. DOAJ's mission is to increase the visibility, accessibility, reputation, usage and impact of quality, peer-reviewed, open access scholarly research journals globally, regardless of discipline, geography or language. By assisting journals to become archived in long-term digital preservation schemes, DOAJ fulfils its mission by ensuring accessibility, visibility and usage of these crucial scholarly records. @@ -88,4 +87,4 @@ DOAJ is the largest, most diverse, free directory of open access journals in the [Keepers Registry](https://keepers.issn.org) is the global monitor on the archiving arrangements for continuing resources managed by the ISSN International Centre and fuelled by reports from contributing archiving agencies. ### PKP -A research and development initiative within Simon Fraser University’s Core Facilities Program, the [Public Knowledge Project](https://pkp.sfu.ca/) (PKP) develops (free) open-source software. It conducts research to improve the quality and reach of scholarly publishing. As part of its mandate to make knowledge public, PKP understands that preserving and maintaining long-term access to published content is of utmost importance. To support this, PKP has launched a [Preservation Network](https://pkp.sfu.ca/pkp-pn/), a freely available private LOCKSS network that can be used by any OJS journal running an up-to-date version of OJS. PKP welcomes the opportunity to participate in Project JASPER with our colleagues at DOAJ, Keepers Registry, Internet Archive and CLOCKSS. +A research and development initiative within Simon Fraser University’s Core Facilities Program, the [Public Knowledge Project](https://pkp.sfu.ca/) (PKP) develops (free) open-source software. It carries out research to improve the quality and reach of scholarly publishing. As part of its mandate to make knowledge public, PKP understands that preserving and maintaining long-term access to published content is paramount. To support this, PKP has launched a [Preservation Network](https://pkp.sfu.ca/pkp-pn/), a freely available private LOCKSS network that can be used by any OJS journal running an up-to-date version of OJS. PKP welcomes the opportunity to participate in Project JASPER with our colleagues at DOAJ, Keepers Registry, Internet Archive and CLOCKSS. diff --git a/cms/pages/support/index.md b/cms/pages/support/index.md index cc39fbfd44..5674711ea3 100644 --- a/cms/pages/support/index.md +++ b/cms/pages/support/index.md @@ -1,17 +1,16 @@ --- layout: sidenav -sidenav_include: /includes/_sidenav_donation.html -include: /includes/contribution_rates.html title: Institutional and library supporter model section: Support sticky_sidenav: true +toc: true featuremap: - ~~Support:Fragment->DonationSidenav:Template~~ - ~~->ContributionRates:Template~~ --- -Support of DOAJ by academic organisations is vital, and we are proud to acknowledge that over 80% of our support comes to us this way. We are very grateful to all our supporting academic organisations worldwide. +Support of DOAJ by academic organisations is vital, and we are proud that we can state that over 80% of our support comes to us this way. We are very grateful to all our supporting academic organisations worldwide. ### 2024 pricing diff --git a/cms/pages/support/publisher-supporters.md b/cms/pages/support/publisher-supporters.md index 1d82e26b2b..61c3b24eef 100644 --- a/cms/pages/support/publisher-supporters.md +++ b/cms/pages/support/publisher-supporters.md @@ -1,7 +1,7 @@ --- layout: sidenav include: /data/publisher-supporters.html -title: Publisher supporters +title: Publisher supporter model section: Support sticky_sidenav: true toc: true @@ -10,27 +10,63 @@ featuremap: - ~~->PublisherSupportersData:Template~~ --- -The publishers on this page have chosen to show their commitment to quality, peer-reviewed open access by supporting DOAJ. We thank them! Without them, our work would not be possible. +DOAJ relies on the support of publishers and [libraries](/support/) to ensure that its metadata and services remain free for all. The publishers on this page have chosen to show their commitment to quality, peer-reviewed open access by supporting DOAJ. We thank them as without them, our work would not be possible. -**To become a publisher supporter**, send an email to [our Help desk](mailto:helpdesk@doaj.org) and we will provide with details on how to support us. Your organisation will be listed on this page. +## Pricing -'Premier' and 'Sustaining' publishers have committed to supporting DOAJ for a three-year period. 'Basic' publishers support us for one year. +We are introducing a revised and simplified model for publishers to support DOAJ for 2024 and publishing this openly in line with [our commitment to the Principles of Open Scholarly Infrastructure](https://blog.doaj.org/2022/10/06/doaj-commits-to-the-principles-of-open-scholarly-infrastructure-posi/). We are also relaunching the set of benefits for publishers choosing to support us. -
{% include '/data/sponsors.html' %}
+We only accept support through our publisher supporter model from publishers with journals already indexed in DOAJ. Non-commercial/institutional rates are only available to community-led, smaller publishers with limited funding. Please contact [supporters@doaj.org](mailto:supporters@doaj.org) if you are unsure which category applies. + +Please contact [supporters@doaj.org](mailto:supporters@doaj.org) if you want to contribute to DOAJ’s operating costs as a publisher supporter. + +### Commercial publishers + +| Band | Number of journals in DOAJ | GBPs (£)* | +|------|----------------------------|-----------| +| A | 600+ | 25,000 | +| B | 400-599 | 20,000 | +| C | 150-399 | 17,000 | +| D | 100-149 | 14,000 | +| E | 50-99 | 8000 | +| F | 30-49 | 6000 | +| G | 10-29 | 5000 | +| H | 1-9 | 3500 | + +### Non-commercial / institutional publishers + +| Band | Number of journals in DOAJ | GBPs (£)* | +|------|----------------------------|-----------| +| C | 150-399 | 3500 | +| D | 100-149 | 3000 | +| E | 50-99 | 2500 | +| F | 30-49 | 2000 | +| G | 10-29 | 1500 | +| H | 1-9 | 1000 | + +*A 50% discount is available for supporters in Low- and Middle-Income Countries according to the World Bank classification. -## Benefits for contributing publishers and aggregators +## Benefits -([A downloadable version](https://docs.google.com/document/d/1xTVxUvqLkh2-r53cYlWdSIHsPGSnhcE7gi7bRFCaJik/edit?usp=sharing) of these benefits is available.) +1. Your logo on the DOAJ website +2. A post from all our social media platforms (Twitter, Facebook, LinkedIn, Mastodon, Instagram) acknowledging your organisation as a Supporter +3. A blog post at the start of the year introducing our new supporters +4. Our DOAJ Supporter logo which you can use for your website +5. Access to our Public Data Dump +6. For supporters from Bands A-E, or those contributing over the suggested amounts, a personal DOAJ contact to whom all enquiries regarding your applications and updates can be directed -| Basic contribution | Sustaining contribution (25% higher than Basic) | Premier contribution (50% higher than Basic) | -|--------------------------|-------------------------------------------------|----------------------------------------------| -| Your logo on our website | Your logo on our website | Your logo on our website | -| A Tweet from the DOAJ account (21,900 followers) acknowledging your organisation as a Supporter | An interview-style blog post on [blog.doaj.org](https://blog.doaj.org/) (12,000 views per month) acknowledging your organisation as a Contributor, with questions about the state of open access and other current developments in open access scholarly publishing | An interview-style blog post on [blog.doaj.org](https://blog.doaj.org/) (12,000 views per month) acknowledging your organisation as a Contributor with questions about the state of open access and other current developments in open access scholarly publishing| -| | A one-pager for journal editors explaining, in general terms, how article metadata is ingested, and then pulled downstream by discovery services. | A one-pager for journal editors explaining, in general terms, how article metadata is ingested, and then pulled downstream by discovery services.| -| | General presentation or documentation, for raising awareness among your stakeholders, about the benefits of DOAJ, the work we do and how DOAJ benefits individual journal titles. | Dedicated presentation or documentation, tailored to your organisation, about the benefits of DOAJ, the work we do and how DOAJ benefits individual journal titles. Written as a joint document with the publisher. | -| | A report, (a Google Sheet), collecting metadata errors, as reported to us by discovery services. | A report, (a Google Sheet), collecting metadata errors, as reported to us by discovery services. | -| | | A yearly account review between the customer and DOAJ. The agenda can be determined by the publisher contact and a DOAJ representative. | -| | | A CSV file, generated annually, for recording changes in and which DOAJ updates your live records with. | -| | | Exposure across all our social media channels: Twitter, Instagram, LinkedIn, Facebook, WeChat. (Stats available.) | +## Sponsorship opportunities + +We are particularly grateful to those publishers who can contribute over and above these amounts. In these cases, we can offer sponsorship opportunities that enhance our services and support open access globally, for example: + +- Specific technical developments +- Ambassador programme +- Webinar programmes and events + +Please get in touch to discuss. + +## Publisher supporters + +
{% include '/data/sponsors.html' %}
## Other publisher supporters diff --git a/cms/sass/components/_dropdown.scss b/cms/sass/components/_dropdown.scss index 47ab4dc5e7..07524b5101 100644 --- a/cms/sass/components/_dropdown.scss +++ b/cms/sass/components/_dropdown.scss @@ -15,10 +15,6 @@ } } -.dropdown--notifications { - @extend .dropdown; -} - .dropdown__menu { display: none; padding: 0; diff --git a/doajtest/mocks/preservation.py b/doajtest/mocks/preservation.py index 3c6d610d5b..7ced1a4b85 100644 --- a/doajtest/mocks/preservation.py +++ b/doajtest/mocks/preservation.py @@ -226,3 +226,227 @@ class PreservationMock: "es_type": "article" } + ARTICLE_DATA_JOURNAL2 = { + "index": { + "issn": [ + "2673-611X" + ], + "date": "2016-10-01T00:00:00Z", + "date_toc_fv_month": "2016-10-01T00:00:00Z", + "subject": [ + "Science: Natural history (General): General. Including nature conservation, geographical distribution" + ], + "schema_subject": [ + "LCC:Neurology. Diseases of the nervous system" + ], + "classification": [ + "Neurology. Diseases of the nervous system" + ], + "publisher": [ + "Frontiers Media S.A." + ], + "license": [ + "CC BY" + ], + "language": [ + "English" + ], + "country": "United Kingdom", + "schema_code": [ + "LCC:RC346-429" + ], + "classification_paths": [ + "Medicine: Internal medicine: Neurosciences. Biological psychiatry. Neuropsychiatry: Neurology. Diseases of the nervous system" + ], + "unpunctitle": "Variation in TMEM106B in chronic traumatic encephalopathy", + "asciiunpunctitle": "Variation in TMEM106B in chronic traumatic encephalopathy", + "has_seal": "Yes", + "doi": "10.3389/fcosc.2022.1028295", + "fulltext": "https://frontiersin.org/articles/10.3389/fcosc.2022.1028295", + "schema_codes_tree": [ + "LCC:RC31-1245", + "LCC:RC346-429", + "LCC:RC321-571", + "LCC:R" + ] + }, + "last_updated": "2020-11-24T21:55:20Z", + "admin": { + "in_doaj": "true", + "seal": "true" + }, + "created_date": "2018-11-04T12:37:46Z", + "id": "00005741594643f4996e2666a01e0310", + "bibjson": { + "title": "Variation in TMEM106B in chronic traumatic encephalopathy", + "year": "2018", + "month": "11", + "start_page": "1", + "end_page": "9", + "abstract": "Abstract The genetic basis of chronic traumatic encephalopathy (CTE) is poorly understood. Variation in transmembrane protein 106B (TMEM106B) has been associated with enhanced neuroinflammation during aging and with TDP-43-related neurodegenerative disease, and rs3173615, a missense coding SNP in TMEM106B, has been implicated as a functional variant in these processes. Neuroinflammation and TDP-43 pathology are prominent features in CTE. The purpose of this study was to determine whether genetic variation in TMEM106B is associated with CTE risk, pathological features, and ante-mortem dementia. Eighty-six deceased male athletes with a history of participation in American football, informant-reported Caucasian, and a positive postmortem diagnosis of CTE without comorbid neurodegenerative disease were genotyped for rs3173615. The minor allele frequency (MAF = 0.42) in participants with CTE did not differ from previously reported neurologically normal controls (MAF = 0.43). However, in a case-only analysis among CTE cases, the minor allele was associated with reduced phosphorylated tau (ptau) pathology in the dorsolateral frontal cortex (DLFC) (AT8 density, odds ratio [OR] of increasing one quartile = 0.42, 95% confidence interval [CI] 0.22–0.79, p = 0.008), reduced neuroinflammation in the DLFC (CD68 density, OR of increasing one quartile = 0.53, 95% CI 0.29–0.98, p = 0.043), and increased synaptic protein density (β = 0.306, 95% CI 0.065–0.546, p = 0.014). Among CTE cases, TMEM106B minor allele was also associated with reduced ante-mortem dementia (OR = 0.40, 95% CI 0.16–0.99, p = 0.048), but was not associated with TDP-43 pathology. All case-only models were adjusted for age at death and duration of football play. Taken together, variation in TMEM106B may have a protective effect on CTE-related outcomes.", + "journal": { + "volume": "6", + "number": "1", + "publisher": "BMC", + "title": "Acta Neuropathologica Communications", + "country": "GB", + "license": [ + { + "title": "CC BY", + "type": "CC BY", + "url": "https://actaneurocomms.biomedcentral.com/submission-guidelines/copyright", + "open_access": "true" + } + ], + "language": [ + "EN" + ], + "issns": [ + "2673-611X" + ] + }, + "identifier": [ + { + "type": "doi", + "id": "10.1186/s40478-018-0619-9" + }, + { + "type": "eissn", + "id": "2051-5960" + } + ], + "keywords": [ + "Chronic traumatic encephalopathy", + "TMEM106B", + "Neuroinflammation", + "Football", + "Traumatic brain injury", + "Tau" + ], + "link": [ + { + "type": "fulltext", + "url": "http://link.springer.com/article/10.1186/s40478-018-0619-9", + "content_type": "HTML" + } + ], + "subject": [ + { + "scheme": "LCC", + "term": "Neurology. Diseases of the nervous system", + "code": "RC346-429" + } + ], + "author": [ + { + "name": "Jonathan D. Cherry", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Jesse Mez", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "John F. Crary", + "affiliation": "Department of Pathology, Fishberg Department of Neuroscience, Friedman Brain Institute, Ronald M. Loeb Center for Alzheimer’s Disease, Icahn School of Medicine at Mount Sinai School" + }, + { + "name": "Yorghos Tripodis", + "affiliation": "Department of Biostatistics, Boston University School of Public Health" + }, + { + "name": "Victor E. Alvarez", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Ian Mahar", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Bertrand R. Huber", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Michael L. Alosco", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Raymond Nicks", + "affiliation": "Department of Veterans Affairs Medical Center" + }, + { + "name": "Bobak Abdolmohammadi", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Patrick T. Kiernan", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Laney Evers", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Sarah Svirsky", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Katharine Babcock", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Hannah M. Gardner", + "affiliation": "VA Boston Healthcare System" + }, + { + "name": "Gaoyuan Meng", + "affiliation": "VA Boston Healthcare System" + }, + { + "name": "Christopher J. Nowinski", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Brett M. Martin", + "affiliation": "Department of Biostatistics, Boston University School of Public Health" + }, + { + "name": "Brigid Dwyer", + "affiliation": "Department of Neurology, Boston University School of Medicine" + }, + { + "name": "Neil W. Kowall", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Robert C. Cantu", + "affiliation": "Department of Anatomy and Neurobiology, Boston University School of Medicine" + }, + { + "name": "Lee E. Goldstein", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Douglas I. Katz", + "affiliation": "Department of Neurology, Boston University School of Medicine" + }, + { + "name": "Robert A. Stern", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Lindsay A. Farrer", + "affiliation": "Department of Neurology, Boston University School of Medicine" + }, + { + "name": "Ann C. McKee", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + }, + { + "name": "Thor D. Stein", + "affiliation": "Boston University Alzheimer’s Disease and CTE Center, Boston University School of Medicine" + } + ] + }, + "es_type": "article" + } diff --git a/doajtest/preservation_upload_test_package/invalid_article.zip b/doajtest/preservation_upload_test_package/invalid_article.zip new file mode 100644 index 0000000000..dbbcf59bf1 Binary files /dev/null and b/doajtest/preservation_upload_test_package/invalid_article.zip differ diff --git a/doajtest/preservation_upload_test_package/multi_journals.zip b/doajtest/preservation_upload_test_package/multi_journals.zip new file mode 100644 index 0000000000..cd160b2c46 Binary files /dev/null and b/doajtest/preservation_upload_test_package/multi_journals.zip differ diff --git a/doajtest/preservation_upload_test_package/valid_article.zip b/doajtest/preservation_upload_test_package/valid_article.zip new file mode 100644 index 0000000000..883394c578 Binary files /dev/null and b/doajtest/preservation_upload_test_package/valid_article.zip differ diff --git a/doajtest/testbook/administrative_search/journals.yml b/doajtest/testbook/administrative_search/journals.yml index 9d249aff11..03b6e4123b 100644 --- a/doajtest/testbook/administrative_search/journals.yml +++ b/doajtest/testbook/administrative_search/journals.yml @@ -134,3 +134,86 @@ tests: results: - A new browser window opens containing the edit form for the journal - step: Click "Unlock and Close" + +- title: Test notes searchable for admin and not for public + context: + role: admin + steps: + - step: In the admin search, select Journal search, search for a journal and click on the "Edit Journal" button to edit the journal + path: /admin + results: + - The journal edit form is displayed + - step: Add a note to a journal record by clicking on the "Add" button in the "Notes" section of the journal record. Add a distinctive note + whose text is unlikely to appear in a journal title or other metadata. Then click on "save" button to save the new note + results: + - The note is added to the journal record + - step: Go to the admin journal search again and search for the journal you added the note with the words given in the note + path: /admin + results: + - The journal record is displayed in the search results + - step: Search for the journal from public search page with the distinctive words from the note + path: /search/journals + results: + - The journal record is not displayed in the search results + - step: Go to the API documentation page + path: /api/docs + - step: Scroll to the section "Search" > "Search journals" and click on the row to expand it. + - step: in the section "search_query" enter the distinctive words from the note you created, then click on the "Try it Out!" button at the bottom of the section + results: + - The journal record is not displayed in the search results + +- title: Test notes not searchable for public + context: + role: anonymous + steps: + - step: go to the public journal search + path: /search/journals + - step: Search for the distinctive word you entered into a note in the previous test. + results: No results displayed + - step: Modify the search url in the following way. Add the text '%2C"default_field"%3A"admin.notes.note"' in the URL bar immediately after where it says '"default_operator"%3A"AND"'. + This is to attempt to trick the public search into allowing you to search on notes. + results: An error message displayed + +- title: Test notes searchable for Editor + context: + role: Editor + steps: + - step: Make sure the editor group is assigned for the journal / application which is under test and assigned to the user + - step: Go to the tab 'Your group’s journals' + results: Group's journals page displayed + - step: Search for the keyword which is given in a notes in a journal + results: The journal record is displayed in the search results + - step: Go to the tab 'Your group’s applications' + results: Group's applications page displayed + - step: Search for the keyword which is given in a notes in an application + results: The application is displayed in the search results + - step: Go to the tab 'Journals assigned to you' + results: Journals assigned to you page displayed + - step: Search for the keyword which is given in a notes in a journal + results: The journal record is displayed in the search results + - step: Go to the tab 'Applications assigned to you' + results: Applications assigned to you page displayed + - step: Search for the keyword which is given in a notes in an application + results: The application is displayed in the search results + +- title: Test notes searchable for Associate Editor + context: + role: AssociateEditor + steps: + - step: Go to the tab 'Journals assigned to you' + results: 'Journals assigned to you page' displayed + - step: Search for the keyword which is given in a notes in a journal + results: The journal record is displayed in the search results + - step: Go to the tab 'Applications assigned to you' + results: 'Applications assigned to you' page displayed + - step: Search for the keyword which is given in a notes in an application + results: The application is displayed in the search results + +- title: Test notes not searchable for fixed query widget (joint journal/article search) + context: + role: anonymous + steps: + - step: 'Do a url search for the keyword which is given in a notes for journal,article index. Ex: /query/journal,article/_search?&source={"query":{"query_string":{"query":"Test Note","default_operator":"AND"}},"size":"20"}&ref=fqw' + results: No results displayed + - step: Modify the search url to add the notes keyword and the default field 'admin.notes.note' and then hit the url + results: Error message displayed diff --git a/doajtest/testbook/articles_preservation/user_test_script.yml b/doajtest/testbook/articles_preservation/upload_preservation_files.yml similarity index 53% rename from doajtest/testbook/articles_preservation/user_test_script.yml rename to doajtest/testbook/articles_preservation/upload_preservation_files.yml index 051673c68d..effbe29bc5 100644 --- a/doajtest/testbook/articles_preservation/user_test_script.yml +++ b/doajtest/testbook/articles_preservation/upload_preservation_files.yml @@ -1,6 +1,6 @@ # ~~ ArticlesPreservation:FunctionalTest -> Preservation:Feature ~~ suite: Articles Preservation -testset: User Test Script +testset: Upload Preservation Files tests: - title: Publisher without preservation role context: @@ -22,7 +22,8 @@ tests: steps: - step: Ensure that the archive package has been created with correct structure - step: Go to preservation area /publisher/preservation - - step: Select 'Browse' and select the paaackage zip file + - step: Select 'Browse' and select the package zip file + resource: /preservation_upload_test_package/valid_article.zip - step: click 'Upload' results: - 'A flash message appears at the top of the screen: File uploaded and waiting @@ -40,7 +41,8 @@ tests: steps: - step: Create a incorrect or junk file that does not have articles - step: Go to preservation area /publisher/preservation - - step: Select 'Browse' and select the paaackage zip file + - step: Select 'Browse' and select the package zip file + resource: /preservation_upload_test_package/invalid_article.zip - step: click 'Upload' results: - 'A flash message appears at the top of the screen: File uploaded and waiting @@ -56,9 +58,11 @@ tests: context: role: publisher steps: - - step: Create a incorrect or junk file that does not have articles + - step: Make sure not to login as admin user as admin does not have restrictions. Login as publisher. + - step: Ensure that the archive package has been created with correct structure but the article does not own by the logged in user. - step: Go to preservation area /publisher/preservation - - step: Select 'Browse' and select the paaackage zip file + - step: Select 'Browse' and select the package zip file + resource: /preservation_upload_test_package/valid_article.zip - step: click 'Upload' results: - 'A flash message appears at the top of the screen: File uploaded and waiting @@ -75,6 +79,42 @@ tests: - if the status is 'partially success', show details link will be displayed. When clicked on the link details of how many articles successful and how many not successful will be displayed +- title: Upload correctly structured file with multiple journals and it is processed correctly + context: + role: publisher + setup: + - Have a publisher account which is the owner of 3 journals that are in DOAJ, and which have articles + uploaded for them + - Select 1 article from each journal and note its DOI + - Download the multi_journals.zip test package (see link below in second step) + - Unzip the multi_journals.zip test package + - In the identifiers.csv file in the root of the test package, put each of the DOIs from your articles + next to one of the article directory names listed (removing the existing test DOIs that are present in that + file already). For example you will have a line which reads "article1,10.1224/mydoi/3" where the DOI + is the one from your article. + - Re-zip the multi_journals.zip package. This wil be the file you will upload for testing in the below script + steps: + - step: Ensure that the archive package has been created with correct structure + - step: Ensure that the archive package contains more than one article with different journals. + A sample zip file is available for testing purpose. If there are no articles with the DOIs specified in the indentifiers.csv + file in your DSoace instance, + the results may not be as expected. In that case unzip the file and update the identifiers.csv file with appropriate identifiers for the articles. + resource: /preservation_upload_test_package/multi_journals.zip + - step: Go to preservation area /publisher/preservation + - step: Select 'Browse' and select the package zip file + - step: click 'Upload' + results: + - 'A flash message appears at the top of the screen: File uploaded and waiting + to be processed.' + - Upload status is shown in 'History of uploads' with status processing + - step: wait a short amount of time for the job to process, then reload the page + (do not re-submit the form data). If the job remains in "pending", reload the + page until the status changes. + results: + - Upload status in 'History of uploads' changes to 'successfully processed' and + the 'Notes' shows as 'uploaded to archive server' + - The 'Notes' has the label 'UPLOADED PACKAGES' + - Under the label 'UPLOADED PACKAGES', details of the packages that were uploaded are displayed in the format _.tar.gz - title: Test maintenance mode for 'Upload preservation file' tab context: role: publisher @@ -82,4 +122,4 @@ tests: - step: Set the value of 'PRESERVATION_PAGE_UNDER_MAINTENANCE' to True in the configuration file (app.cfg/dev.cfg/test.cfg) - step: Go to preservation area /publisher/preservation results: - - Maintenance page should be displayed with the content as 'This page is currently offline for maintenance' \ No newline at end of file + - Maintenance page should be displayed with the content as 'This page is currently offline for maintenance' diff --git a/doajtest/testbook/new_application_form/publishers_form.yml b/doajtest/testbook/new_application_form/publishers_form.yml index 07f1c2e40a..72b4de2316 100644 --- a/doajtest/testbook/new_application_form/publishers_form.yml +++ b/doajtest/testbook/new_application_form/publishers_form.yml @@ -114,6 +114,13 @@ tests: - step: Click the X next to it results: - It disappears + - step: Enter the same value in Publisher's Name and Publisher's Country + - step: Click Next + results: + - "You see the error message: The value of this field and the Publisher's Country field must be different." + - step: Repeat previous step checking if the validator is case insensitive (e.g. "Publisher's name" and "publisher's Name") + results: + - The error message is still displayed - step: Repeat steps X to X for the Publisher's Name, Country, Society name, Country - step: Delete the values from Society name and country, click Next results: diff --git a/doajtest/testbook/public_site/public_search.yml b/doajtest/testbook/public_site/public_search.yml index 6b47834e85..43534a3e7c 100644 --- a/doajtest/testbook/public_site/public_search.yml +++ b/doajtest/testbook/public_site/public_search.yml @@ -58,15 +58,6 @@ tests: results: - The search/embed box is shown with a URL and an HTML/JS snippet displayed in each box - - step: Click "shorten url" (Note that this may not work in test, due to configuration - requirements, you may receive an error instead) - results: - - The long search url is replaced with a bit.ly url - - '"shorten url" is replaced by "original url"' - - step: Click "original url" - results: - - The bit.ly url is replaced by the original long search url - - '"original url" is replaced by "shorten url"' - step: Close the "Share | Embed" section by clicking the Close button at the bottom of the box, or clicking the "Share | Embed" button again results: diff --git a/doajtest/testbook/user_management/user_management.yml b/doajtest/testbook/user_management/user_management.yml index a19230dbd9..55795719bd 100644 --- a/doajtest/testbook/user_management/user_management.yml +++ b/doajtest/testbook/user_management/user_management.yml @@ -40,6 +40,10 @@ fragments: - Your API Key at the top of the page is updated - You receive a flash message saying your record is updated and the API key has changed + - step: Click "Click to Copy" icon next to your API key + results: + - The API key is copied to your clipboard + - Confirmation message appears and dissapears after a moment - step: Enter mismatched passwords into the "Change your password" boxes and click "Update Account" results: @@ -205,3 +209,12 @@ tests: - step: Click on the user's journals results: - All the same journals are still associated with the user + +- title: User account without API role + context: + role: admin + steps: + - step: Find (or create) a user account which does not have the API role + - step: View the user account page at /account/[account name] + results: + - The API key is not displayed diff --git a/doajtest/unit/resources/invalid_article.zip b/doajtest/unit/resources/invalid_article.zip new file mode 120000 index 0000000000..e955a12f14 --- /dev/null +++ b/doajtest/unit/resources/invalid_article.zip @@ -0,0 +1 @@ +../../preservation_upload_test_package/invalid_article.zip \ No newline at end of file diff --git a/doajtest/unit/resources/multi_journals.zip b/doajtest/unit/resources/multi_journals.zip new file mode 120000 index 0000000000..ac64455e26 --- /dev/null +++ b/doajtest/unit/resources/multi_journals.zip @@ -0,0 +1 @@ +../../preservation_upload_test_package/multi_journals.zip \ No newline at end of file diff --git a/doajtest/unit/resources/preservation_multiple_journals.zip b/doajtest/unit/resources/preservation_multiple_journals.zip new file mode 100644 index 0000000000..a55b9decd2 Binary files /dev/null and b/doajtest/unit/resources/preservation_multiple_journals.zip differ diff --git a/doajtest/unit/resources/valid_article.zip b/doajtest/unit/resources/valid_article.zip new file mode 120000 index 0000000000..ebf17a3099 --- /dev/null +++ b/doajtest/unit/resources/valid_article.zip @@ -0,0 +1 @@ +../../preservation_upload_test_package/valid_article.zip \ No newline at end of file diff --git a/doajtest/unit/test_query.py b/doajtest/unit/test_query.py index 99bf94976a..d1717341c7 100644 --- a/doajtest/unit/test_query.py +++ b/doajtest/unit/test_query.py @@ -1,6 +1,7 @@ from portality import models -from doajtest.fixtures import AccountFixtureFactory, ArticleFixtureFactory +from doajtest.fixtures import AccountFixtureFactory, ArticleFixtureFactory, EditorGroupFixtureFactory, \ + ApplicationFixtureFactory from doajtest.helpers import DoajTestCase, deep_sort from portality.bll.services.query import QueryService, Query @@ -30,7 +31,12 @@ "auth" : True, "role" : "admin", "dao" : "portality.models.Journal" - } + }, + "suggestion" : { + "auth" : True, + "role" : "admin", + "dao" : "portality.models.Application" + }, }, "api_query" : { "article" : { @@ -53,19 +59,89 @@ "query_filters" : ["owner", "private_source"], "dao" : "portality.models.Suggestion" } + }, + "editor_query" : { + "journal" : { + "auth" : True, + "role" : "editor", + "dao" : "portality.models.Journal" + }, + "suggestion" : { + "auth" : True, + "role" : "editor", + "dao" : "portality.models.Application" + } + }, + "associate_query": { + "journal": { + "auth": True, + "role": "associate_editor", + "dao": "portality.models.Journal" + }, + "suggestion" : { + "auth" : True, + "role" : "associate_editor", + "dao" : "portality.models.Application" + } + } +} + +SEARCH_ALL_QUERY_ROUTE = { + "query" : { + "journal" : { + "auth" : False, + "role" : None, + "query_filters" : ["search_all_meta"], + "dao" : "portality.models.Journal" + } + }, + "editor_query" : { + "journal" : { + "auth" : True, + "role" : "editor", + "query_filters" : ["search_all_meta"], + "dao" : "portality.models.Journal" + }, + "suggestion" : { + "auth" : False, + "role" : "editor", + "query_filters" : ["search_all_meta"], + "dao" : "portality.models.Application" + } + }, + "associate_query": { + "journal": { + "auth": False, + "role": "associate_editor", + "query_filters" : ["search_all_meta"], + "dao": "portality.models.Journal" + }, + "suggestion" : { + "auth" : False, + "role" : "associate_editor", + "query_filters" : ["search_all_meta"], + "dao" : "portality.models.Application" + } } } QUERY_FILTERS = { + "non_public_fields_validator" : "portality.lib.query_filters.non_public_fields_validator", + # query filters "only_in_doaj" : "portality.lib.query_filters.only_in_doaj", "owner" : "portality.lib.query_filters.owner", + "associate" : "portality.lib.query_filters.associate", + "editor" : "portality.lib.query_filters.editor", # result filters "public_result_filter" : "portality.lib.query_filters.public_result_filter", # source filter - "public_source": "portality.lib.query_filters.public_source" + "public_source": "portality.lib.query_filters.public_source", + + # search on all meta field + "search_all_meta" : "portality.lib.query_filters.search_all_meta", } def without_keys(d, keys): @@ -87,6 +163,29 @@ def tearDown(self): self.app_test.config['QUERY_ROUTE'] = self.OLD_QUERY_ROUTE self.app_test.config['QUERY_FILTERS'] = self.OLD_QUERY_FILTERS + def get_application_with_notes(self): + source = ApplicationFixtureFactory.make_application_source() + app = models.Application(**source) + app.add_note("application test", "2015-01-01T00:00:00Z") + app.save() + return app + + def get_journal_with_notes(self): + j = models.Journal() + j.set_id("aabbccdd") + j.set_created("2010-01-01T00:00:00Z") + j.set_last_updated("2012-01-01T00:00:00Z") + j.set_last_manual_update("2014-01-01T00:00:00Z") + j.set_seal(True) + j.set_owner("rama") + j.set_editor_group("worldwide") + j.set_editor("eddie") + j.add_contact("rama", "rama@email.com") + j.add_note("testing", "2015-01-01T00:00:00Z") + j.set_bibjson({"title": "test"}) + j.save() + return j + def test_01_auth(self): with self.app_test.test_client() as t_client: response = t_client.get('/query/journal') # not in the settings above @@ -339,3 +438,156 @@ def test_10_scroll(self): am = models.Article(**res) assert am.publisher_record_id() is None, am.publisher_record_id() + def test_public_query_notes(self): + + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE + + self.get_journal_with_notes() + + qsvc = QueryService() + + res = qsvc.search('query', 'journal', {'query': {'query_string': {'query': 'testing', + 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': + {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, + 'track_total_hits': True}, account=None, additional_parameters={}) + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] + + def test_admin_query_notes(self): + + self.get_journal_with_notes() + + maned = models.Account(**AccountFixtureFactory.make_managing_editor_source()) + maned.save(blocking=True) + qsvc = QueryService() + + res = qsvc.search('admin_query', 'journal', {'query': {'query_string': {'query': 'testing', + 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': + {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, + 'track_total_hits': True}, account=maned, additional_parameters={}) + assert res['hits']['total']["value"] == 1, res['hits']['total']["value"] + + def test_editor_query_notes(self): + + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE + + self.get_journal_with_notes() + + editor = models.Account(**AccountFixtureFactory.make_editor_source()) + editor.save(blocking=True) + + eg_source = EditorGroupFixtureFactory.make_editor_group_source(maned=editor.id) + eg = models.EditorGroup(**eg_source) + eg.save(blocking=True) + + qsvc = QueryService() + + res = qsvc.search('editor_query', 'journal', {'query': {'query_string': {'query': 'testing', + 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': + {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, + 'track_total_hits': True}, account=editor, additional_parameters={}) + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] + + def test_associate_editor_query_notes(self): + + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE + + self.get_journal_with_notes() + + associate = models.Account(**AccountFixtureFactory.make_assed1_source()) + associate.save(blocking=True) + + eg_source = EditorGroupFixtureFactory.make_editor_group_source(maned=associate.id) + eg = models.EditorGroup(**eg_source) + eg.save(blocking=True) + + qsvc = QueryService() + + res = qsvc.search('associate_query', 'journal', {'query': {'query_string': {'query': 'testing', + 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': + {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, + 'track_total_hits': True}, account=associate, additional_parameters={}) + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] + + def test_associate_editor_application_query_notes(self): + + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE + + app = self.get_application_with_notes() + + associate = models.Account(**AccountFixtureFactory.make_assed1_source()) + associate.save(blocking=True) + + eg_source = EditorGroupFixtureFactory.make_editor_group_source(maned=associate.id) + eg = models.EditorGroup(**eg_source) + eg.set_name(app.editor_group) + eg.save(blocking=True) + + qsvc = QueryService() + + res = qsvc.search('associate_query', 'suggestion', {'query': {'query_string': {'query': 'application test', + 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': + {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, + 'track_total_hits': True}, account=associate, additional_parameters={}) + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] + + def test_editor_application_query_notes(self): + + self.app_test.config['QUERY_ROUTE'] = SEARCH_ALL_QUERY_ROUTE + + app = self.get_application_with_notes() + + editor = models.Account(**AccountFixtureFactory.make_editor_source()) + editor.save(blocking=True) + + eg_source = EditorGroupFixtureFactory.make_editor_group_source(maned=editor.id) + eg = models.EditorGroup(**eg_source) + eg.set_name(app.editor_group) + eg.save(blocking=True) + + qsvc = QueryService() + + res = qsvc.search('editor_query', 'suggestion', {'query': {'query_string': {'query': 'application test', + 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': + {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, + 'track_total_hits': True}, account=editor, additional_parameters={}) + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] + + def test_admin_application_query_notes(self): + + app = self.get_application_with_notes() + + med = models.Account(**AccountFixtureFactory.make_managing_editor_source()) + med.save(blocking=True) + + eg_source = EditorGroupFixtureFactory.make_editor_group_source(maned=med.id) + eg = models.EditorGroup(**eg_source) + eg.set_name(app.editor_group) + eg.save(blocking=True) + + qsvc = QueryService() + + res = qsvc.search('admin_query', 'suggestion', {'query': {'query_string': {'query': 'application test', + 'default_operator': 'AND'}}, 'size': 0, 'aggs': {'country_publisher': + {'terms': {'field': 'index.country.exact', 'size': 100, 'order': {'_count': 'desc'}}}}, + 'track_total_hits': True}, account=med, additional_parameters={}) + assert res['hits']['total']["value"] == 1, res['hits']['total']["value"] + + def test_journal_article_query_notes(self): + + self.app_test.config['QUERY_ROUTE'] = self.OLD_QUERY_ROUTE + + self.app_test.config['QUERY_FILTERS'] = self.OLD_QUERY_FILTERS + + self.article11 = models.Article( + **ArticleFixtureFactory.make_article_source(pissn="1111-1111", eissn="2222-2222", doi="10.0000/article-11", + fulltext="https://www.article11.com")) + self.article11.save(blocking=True) + + self.get_journal_with_notes() + + qsvc = QueryService() + + res = qsvc.search('query', 'journal,article', {'query': {'query_string': + {'query': 'application test','default_operator': 'AND'}}, + 'size': 0, 'track_total_hits': True}, account=None, additional_parameters={"ref":"fqw"}) + assert res['hits']['total']["value"] == 0, res['hits']['total']["value"] diff --git a/doajtest/unit/test_task_preservation.py b/doajtest/unit/test_task_preservation.py index 29c170f742..d8d62d87e8 100644 --- a/doajtest/unit/test_task_preservation.py +++ b/doajtest/unit/test_task_preservation.py @@ -12,34 +12,80 @@ from portality.models.article import Article -class TestPreservation(DoajTestCase): +def mock_pull_by_key(key, value): + if value == "http://link.springer.com/article/10.1186/s40478-018-0619-9": + article = Article() + article.data = PreservationMock.ARTICLE_DATA + return article + elif value == "https://www.frontiersin.org/articles/10.3389/fcosc.2022.1028295": + article = Article() + article.data = PreservationMock.ARTICLE_DATA_JOURNAL2 + return article - def setUp(self): - super(TestPreservation, self).setUp() - articles_zip_path = test_constants.PATH_RESOURCES / "articles.zip" + +def mock_requests_post(*args, **kwargs): + class MockResponse: + def __init__(self, json_data, status_code): + self.json_data = json_data + self.status_code = status_code + + def json(self): + return self.json_data + + if not args[0] == None and kwargs["data"]["org"] == "DOAJ": + return MockResponse({ + "files": [ + { + "name": "name_of_tarball.tar.gz", + "sha256": "decafbad" + } + ] + }, 200) + + return MockResponse(None, 404) + + +def mock_owner_of_article(*args, **kwargs): + return True + + +class TestPreservationSetup(DoajTestCase): + + def initial_setup(self, package_name): + super(TestPreservationSetup, self).setUp() + articles_zip_path = test_constants.PATH_RESOURCES / package_name with open(articles_zip_path, 'rb') as zf: - self.zip_file = FileStorage(BytesIO(zf.read()), filename="articles.zip") + self.zip_file = FileStorage(BytesIO(zf.read()), filename=package_name) self.upload_dir = app.config.get("UPLOAD_DIR", ".") created_time = dates.now_str("%Y-%m-%d-%H-%M-%S") - owner = "rama" - dir_name = owner + "-" + created_time + self.owner = "rama" + self.journal_dir = "2051-5960" + dir_name = self.owner + "-" + created_time self.local_dir = os.path.join(preservation.Preservation.UPLOAD_DIR, dir_name) - self.preserve = preservation.Preservation(self.local_dir, owner) - self.package = preservation.PreservationPackage(self.preserve.preservation_dir, owner) - self.local_dir = os.path.join(self.local_dir,"tmp") + self.preserve = preservation.Preservation(self.local_dir, self.owner) + self.tmp_dir = os.path.join(self.local_dir, "tmp") self.preservation_collection = app.config.get("PRESERVATION_COLLECTION") - app.config["PRESERVATION_COLLECTION"] = {"rama":["test","2"]} + app.config["PRESERVATION_COLLECTION"] = {"rama": ["test", "2"]} def tearDown(self): - super(TestPreservation, self).tearDown() + super(TestPreservationSetup, self).tearDown() preservation.Preservation.delete_local_directory(self.local_dir) app.config["PRESERVATION_COLLECTION"] = self.preservation_collection + +class TestPreservation(TestPreservationSetup): + + def setUp(self): + super(TestPreservation, self).initial_setup("articles.zip") + + def tearDown(self): + super(TestPreservation, self).tearDown() + def test_local_directory(self): - #Test creation of local directory - #TestPreservation.preserve.create_local_directories() + # Test creation of local directory + # TestPreservation.preserve.create_local_directories() job = preservation.PreservationBackgroundTask.prepare("rama", upload_file=self.zip_file) params = job.params local_dir = params["preserve__local_dir"] @@ -48,81 +94,137 @@ def test_local_directory(self): assert os.path.isdir(os.path.join(self.upload_dir, dir_name)) assert os.path.isdir(os.path.join(self.upload_dir, dir_name,dir_name)) - #Test deletion of local directory + # Test deletion of local directory preservation.Preservation.delete_local_directory(local_dir) assert not os.path.exists(os.path.join(self.upload_dir, dir_name)) - def mock_pull_by_key(key, value): - article = Article() - article.data = PreservationMock.ARTICLE_DATA - return article + @patch.object(Article, 'pull_by_key', mock_pull_by_key) + @patch.object(requests, "post", mock_requests_post) + @patch.object(preservation.Preservation, 'owner_of_article', mock_owner_of_article) + def test_preservation(self): + self.preserve.save_file(self.zip_file) + + assert os.path.exists(os.path.join(self.tmp_dir, self.zip_file.filename)) + + # Test extraction of zip file + self.preserve.extract_zip_file() + + assert os.path.exists(os.path.join(self.tmp_dir, "articles")) + assert os.path.isdir(os.path.join(self.tmp_dir, "articles")) + assert os.path.isdir(os.path.join(self.tmp_dir, "articles", "article_1")) + assert os.path.exists(os.path.join(self.tmp_dir, "articles", + "article_1", "identifier.txt")) + + reader = preservation.CSVReader(os.path.join(self.tmp_dir, + "articles", "identifiers.csv")) + data = reader.articles_info() + + assert "article_1" in data + assert "article/10.1186/s40478-018-0619-9" in data["article_1"][0] + + # Test package structure + self.preserve.create_package_structure() + package_dir = os.path.join(self.upload_dir, + self.preserve.dir_name, self.preserve.dir_name, self.journal_dir) + tag_manifest_file = os.path.join(package_dir, "00003741594643f4996e2555a01e03c7", "tagmanifest-sha256.txt") + manifest_file = os.path.join(package_dir, "00003741594643f4996e2555a01e03c7", "manifest-sha256.txt") + assert os.path.exists(package_dir) + assert os.path.exists(tag_manifest_file) + assert os.path.exists(manifest_file) + + package = preservation.PreservationPackage(self.preserve.preservation_dir, self.journal_dir, self.owner) + + # Test creation of tar file + package.create_package() + tar_file = package_dir + "_" + package.created_time + ".tar.gz" + assert os.path.exists(tar_file) + + sha256 = package.sha256(tar_file) + response = package.upload_package(sha256, tar_file) + assert response.status_code == 200 + + def test_get_article_info(self): + issn, article_id, metadata_json = self.preserve.get_article_info(PreservationMock.ARTICLE_DATA) - def mock_requests_post(*args, **kwargs): - class MockResponse: - def __init__(self, json_data, status_code): - self.json_data = json_data - self.status_code = status_code + assert issn == "2051-5960" + assert article_id == "00003741594643f4996e2555a01e03c7" + assert metadata_json["bibjson"]["identifier"][0]["id"] == "10.1186/s40478-018-0619-9" - def json(self): - return self.json_data - if not args[0] == None and kwargs["data"]["org"] == "DOAJ": - return MockResponse({ - "files": [ - { - "name": "name_of_tarball.tar.gz", - "sha256": "decafbad" - } - ] - }, 200) +class TestPreservationMultipleJournals(TestPreservationSetup): - return MockResponse(None, 404) + def setUp(self): + super(TestPreservationMultipleJournals, self).initial_setup("preservation_multiple_journals.zip") + self.another_journal_dir = "2673-611X" - def mock_owner_of_article(*args, **kwargs): - return True + def tearDown(self): + super(TestPreservationMultipleJournals, self).tearDown() @patch.object(Article, 'pull_by_key', mock_pull_by_key) - @patch.object(requests,"post", mock_requests_post) + @patch.object(requests, "post", mock_requests_post) @patch.object(preservation.Preservation, 'owner_of_article', mock_owner_of_article) - def test_preservation(self): + def test_preservation_multiple_journals(self): self.preserve.save_file(self.zip_file) - assert os.path.exists(os.path.join(self.local_dir, self.zip_file.filename)) - # Test extraction of zip file self.preserve.extract_zip_file() - assert os.path.exists(os.path.join(self.local_dir, "articles")) - assert os.path.isdir(os.path.join(self.local_dir, "articles")) - assert os.path.isdir(os.path.join(self.local_dir, "articles", "article_1")) - assert os.path.exists(os.path.join(self.local_dir, "articles", - "article_1", "identifier.txt")) + assert os.path.exists(os.path.join(self.tmp_dir, "articles")) + assert os.path.isdir(os.path.join(self.tmp_dir, "articles")) + assert os.path.isdir(os.path.join(self.tmp_dir, "articles", "article_1")) + assert os.path.exists(os.path.join(self.tmp_dir, "articles", + "article_1", "Identifier.txt")) - reader = preservation.CSVReader(os.path.join(self.local_dir, - "articles", "identifiers.csv")) + reader = preservation.CSVReader(os.path.join(self.tmp_dir, + "articles", "Identifiers.csv")) data = reader.articles_info() assert "article_1" in data assert "article/10.1186/s40478-018-0619-9" in data["article_1"][0] + assert "article_2" in data + assert "10.3389/fcosc.2022.1028295" in data["article_2"][0] + # Test package structure self.preserve.create_package_structure() package_dir = os.path.join(self.upload_dir, - self.preserve.dir_name, self.preserve.dir_name) - tag_manifest_file = os.path.join(package_dir, "2051-5960", "00003741594643f4996e2555a01e03c7", "tagmanifest-sha256.txt") - manifest_file = os.path.join(package_dir,"2051-5960", "00003741594643f4996e2555a01e03c7", "manifest-sha256.txt") + self.preserve.dir_name, self.preserve.dir_name, self.journal_dir) + tag_manifest_file = os.path.join(package_dir, "00003741594643f4996e2555a01e03c7", "tagmanifest-sha256.txt") + manifest_file = os.path.join(package_dir, "00003741594643f4996e2555a01e03c7", "manifest-sha256.txt") assert os.path.exists(package_dir) assert os.path.exists(tag_manifest_file) assert os.path.exists(manifest_file) + package = preservation.PreservationPackage(self.preserve.preservation_dir, self.journal_dir, self.owner) + # Test creation of tar file - self.package.create_package() - assert os.path.exists(package_dir + ".tar.gz") + package.create_package() + tar_file = package_dir + "_" + package.created_time + ".tar.gz" + assert os.path.exists(tar_file) - sha256 = self.package.sha256() - response = self.package.upload_package(sha256) + sha256 = package.sha256(tar_file) + response = package.upload_package(sha256, tar_file) assert response.status_code == 200 + # Test another journal package + package_dir = os.path.join(self.upload_dir, + self.preserve.dir_name, self.preserve.dir_name, self.another_journal_dir) + tag_manifest_file = os.path.join(package_dir, "00005741594643f4996e2666a01e0310", "tagmanifest-sha256.txt") + manifest_file = os.path.join(package_dir, "00005741594643f4996e2666a01e0310", "manifest-sha256.txt") + assert os.path.exists(package_dir) + assert os.path.exists(tag_manifest_file) + assert os.path.exists(manifest_file) + + package = preservation.PreservationPackage(self.preserve.preservation_dir, self.another_journal_dir, self.owner) + + # Test creation of tar file for another journal + package.create_package() + tar_file = package_dir + "_" + package.created_time + ".tar.gz" + assert os.path.exists(tar_file) + + sha256 = package.sha256(tar_file) + response = package.upload_package(sha256, tar_file) + assert response.status_code == 200 def test_get_article_info(self): issn, article_id, metadata_json = self.preserve.get_article_info(PreservationMock.ARTICLE_DATA) diff --git a/doajtest/unit/test_toc.py b/doajtest/unit/test_toc.py index bfe6564d8c..dafe64ac31 100644 --- a/doajtest/unit/test_toc.py +++ b/doajtest/unit/test_toc.py @@ -1,16 +1,57 @@ -from doajtest.helpers import DoajTestCase from doajtest.fixtures import ArticleFixtureFactory, JournalFixtureFactory +from doajtest.helpers import DoajTestCase +from portality import app as _app # noqa, make sure route is registered from portality import models +from portality.util import url_for + + +def _test_toc_uses_both_issns_when_available(app_test, url_name): + j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) + pissn = j.bibjson().first_pissn + eissn = j.bibjson().first_eissn + j.set_last_manual_update() + j.save(blocking=True) + a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=pissn, eissn=eissn, in_doaj=True)) + a.save(blocking=True) + with app_test.test_client() as t_client: + response = t_client.get(url_for(url_name, identifier=j.bibjson().get_preferred_issn())) + assert response.status_code == 200 + assert pissn in response.data.decode() + assert eissn in response.data.decode() + + +def _test_toc_correctly_uses_pissn(app_test, url_name): + j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) + pissn = j.bibjson().first_pissn + # remove eissn + del j.bibjson().eissn + j.set_last_manual_update() + j.save(blocking=True) + a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=pissn, in_doaj=True)) + a.save(blocking=True) + with app_test.test_client() as t_client: + response = t_client.get(url_for(url_name, identifier=j.bibjson().get_preferred_issn())) + assert response.status_code == 200 + assert pissn in response.data.decode() + + +def _test_toc_correctly_uses_eissn(app_test, url_name): + j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) + eissn = j.bibjson().first_eissn + # remove pissn + del j.bibjson().pissn + j.set_last_manual_update() + j.save(blocking=True) + a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=eissn, in_doaj=True)) + a.save(blocking=True) + with app_test.test_client() as t_client: + response = t_client.get(url_for(url_name, identifier=j.bibjson().get_preferred_issn())) + assert response.status_code == 200 + assert eissn in response.data.decode() class TestTOC(DoajTestCase): - def setUp(self): - super(TestTOC, self).setUp() - - def tearDown(self): - super(TestTOC, self).tearDown() - def test_01_article_index_date_parsing(self): """ The ToC date histogram needs an accurate datestamp in the article's index """ a = models.Article(**ArticleFixtureFactory.make_article_source()) @@ -43,9 +84,9 @@ def test_01_article_index_date_parsing(self): d = a.bibjson().get_publication_date() assert d == '2012-03-01T00:00:00Z' - a.bibjson().year = '86' # beware: this test will give a false negative 70 years from - a.bibjson().month = '11' # the time of writing; this gives adequate warning (24 years) - d = a.bibjson().get_publication_date() # to fix hard-coding of centuries in get_publication_date(). + a.bibjson().year = '86' # beware: this test will give a false negative 70 years from + a.bibjson().month = '11' # the time of writing; this gives adequate warning (24 years) + d = a.bibjson().get_publication_date() # to fix hard-coding of centuries in get_publication_date(). assert d == '1986-11-01T00:00:00Z' # Check we can handle numeric months @@ -90,45 +131,21 @@ def test_02_toc_requirements(self): assert a.data['index']['date_toc_fv_month'] == a.data['index']['date'] == "1991-01-01T00:00:00Z" def test_03_toc_uses_both_issns_when_available(self): - j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) - pissn = j.bibjson().first_pissn - eissn = j.bibjson().first_eissn - j.set_last_manual_update() - j.save(blocking=True) - a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=pissn, eissn=eissn, in_doaj=True)) - a.save(blocking=True) - with self.app_test.test_client() as t_client: - response = t_client.get('/toc/{}'.format(j.bibjson().get_preferred_issn())) - assert response.status_code == 200 - assert pissn in response.data.decode() - assert eissn in response.data.decode() + _test_toc_uses_both_issns_when_available(self.app_test, 'doaj.toc') + + def test_04_toc_correctly_uses_pissn(self): + _test_toc_correctly_uses_pissn(self.app_test, 'doaj.toc') + + def test_05_toc_correctly_uses_eissn(self): + _test_toc_correctly_uses_eissn(self.app_test, 'doaj.toc') + + +class TestTOCArticles(DoajTestCase): + def test_03_toc_uses_both_issns_when_available(self): + _test_toc_uses_both_issns_when_available(self.app_test, 'doaj.toc_articles') def test_04_toc_correctly_uses_pissn(self): - j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) - pissn = j.bibjson().first_pissn - # remove eissn - del j.bibjson().eissn - - j.set_last_manual_update() - j.save(blocking=True) - a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=pissn, in_doaj=True)) - a.save(blocking=True) - with self.app_test.test_client() as t_client: - response = t_client.get('/toc/{}'.format(j.bibjson().get_preferred_issn())) - assert response.status_code == 200 - assert pissn in response.data.decode() + _test_toc_correctly_uses_pissn(self.app_test, 'doaj.toc_articles') def test_05_toc_correctly_uses_eissn(self): - j = models.Journal(**JournalFixtureFactory.make_journal_source(in_doaj=True)) - eissn = j.bibjson().first_eissn - # remove pissn - del j.bibjson().pissn - - j.set_last_manual_update() - j.save(blocking=True) - a = models.Article(**ArticleFixtureFactory.make_article_source(pissn=eissn, in_doaj=True)) - a.save(blocking=True) - with self.app_test.test_client() as t_client: - response = t_client.get('/toc/{}'.format(j.bibjson().get_preferred_issn())) - assert response.status_code == 200 - assert eissn in response.data.decode() + _test_toc_correctly_uses_eissn(self.app_test, 'doaj.toc_articles') diff --git a/docs/featuremap/terminals.csv b/docs/featuremap/terminals.csv index 607d962ef9..fdf69406d4 100644 --- a/docs/featuremap/terminals.csv +++ b/docs/featuremap/terminals.csv @@ -17,7 +17,6 @@ FieldDescriptions:Documentation Flask:Technology FlaskMail:Library GeneratedDocsIndex:Script -GoogleAnalytics:ExternalService GuideToApplying:Fragment Huey:Technology Java:Technology @@ -26,6 +25,7 @@ LCC:SourceData OpenURL:Fragment Plagiarism:FieldSet PlagiarismDetection:FormField +PlausibleAnalytics:ExternalService Privacy:Fragment Promo:Data PublisherSupporters:Data diff --git a/portality/app.py b/portality/app.py index ad2b58a5af..1562f379ef 100644 --- a/portality/app.py +++ b/portality/app.py @@ -108,9 +108,8 @@ def custom_static(path): return send_from_directory(os.path.dirname(target), os.path.basename(target)) abort(404) - -# Configure the Google Analytics tracker -# ~~-> GoogleAnalytics:ExternalService~~ +# Configure Analytics +# ~~-> PlausibleAnalytics:ExternalService~~ from portality.lib import plausible plausible.create_logfile(app.config.get('PLAUSIBLE_LOG_DIR', None)) diff --git a/portality/bll/services/journal.py b/portality/bll/services/journal.py index c1d005300d..d27956d700 100644 --- a/portality/bll/services/journal.py +++ b/portality/bll/services/journal.py @@ -10,8 +10,9 @@ from portality.lib.dates import FMT_DATETIME_SHORT from portality.store import StoreFactory, prune_container, StoreException from portality.crosswalks.journal_questions import Journal2QuestionXwalk +from portality.util import no_op -from datetime import datetime +from datetime import datetime, timedelta import re, csv, random, string @@ -131,6 +132,10 @@ def csv(self, prune=True, logger=None): {"arg": logger, "allow_none": True, "arg_name": "logger"} ], exceptions.ArgumentException) + # None isn't executable, so convert logger to NO-OP + if logger is None: + logger = no_op + # ~~->FileStoreTemp:Feature~~ filename = 'journalcsv__doaj_' + dates.now_str(FMT_DATETIME_SHORT) + '_utf8.csv' container_id = app.config.get("STORE_CACHE_CONTAINER") @@ -254,42 +259,64 @@ def _get_article_kvs(journal): return kvs # ~~!JournalCSV:Feature->Journal:Model~~ - cols = {} - for j in models.Journal.all_in_doaj(page_size=1000): #Fixme: limited by ES, this may not be sufficient + csvwriter = csv.writer(file_object) + first = True + for j in models.Journal.all_in_doaj(page_size=100): + export_start = datetime.utcnow() logger("Exporting journal {x}".format(x=j.id)) + time_log = [] bj = j.bibjson() issn = bj.get_one_identifier(idtype=bj.P_ISSN) if issn is None: issn = bj.get_one_identifier(idtype=bj.E_ISSN) + time_log.append("{x} - got issn".format(x=datetime.utcnow())) + if issn is None: continue # ~~!JournalCSV:Feature->JournalQuestions:Crosswalk~~ kvs = Journal2QuestionXwalk.journal2question(j) + time_log.append("{x} - crosswalked questions".format(x=datetime.utcnow())) meta_kvs = _get_doaj_meta_kvs(j) + time_log.append("{x} - got meta kvs".format(x=datetime.utcnow())) article_kvs = _get_article_kvs(j) + time_log.append("{x} - got article kvs".format(x=datetime.utcnow())) additionals = [] if additional_columns is not None: for col in additional_columns: additionals += col(j) - cols[issn] = kvs + meta_kvs + article_kvs + additionals + time_log.append("{x} - got additionals".format(x=datetime.utcnow())) + row = kvs + meta_kvs + article_kvs + additionals # Get the toc URL separately from the meta kvs because it needs to be inserted earlier in the CSV # ~~-> ToC:WebRoute~~ toc_kv = _get_doaj_toc_kv(j) - cols[issn].insert(2, toc_kv) + row.insert(2, toc_kv) + time_log.append("{x} - got toc kvs".format(x=datetime.utcnow())) - logger("All journals exported") - issns = cols.keys() - - csvwriter = csv.writer(file_object) - qs = None - for i in sorted(issns): - if qs is None: - qs = [q for q, _ in cols[i]] + if first is True: + qs = [q for q, _ in row] csvwriter.writerow(qs) - vs = [v for _, v in cols[i]] + first = False + + vs = [v for _, v in row] csvwriter.writerow(vs) - logger("CSV Written") + time_log.append("{x} - written row to csv".format(x=datetime.utcnow())) + + export_end = datetime.utcnow() + if export_end - export_start > timedelta(seconds=10): + for l in time_log: + logger(l) + + logger("All journals exported and CSV written") + # issns = cols.keys() + # qs = None + # for i in sorted(issns): + # if qs is None: + # qs = [q for q, _ in cols[i]] + # csvwriter.writerow(qs) + # vs = [v for _, v in cols[i]] + # csvwriter.writerow(vs) + # logger("CSV Written") diff --git a/portality/bll/services/query.py b/portality/bll/services/query.py index 9d2eac87cf..9595dfc801 100644 --- a/portality/bll/services/query.py +++ b/portality/bll/services/query.py @@ -43,18 +43,20 @@ def _get_config_for_search(self, domain, index_type, account): return cfg def _validate_query(self, cfg, query): - validator = cfg.get("query_validator") - if validator is None: - return True + validators = cfg.get("query_validators") + if validators: + for validator in validators: + filters = app.config.get("QUERY_FILTERS", {}) + validator_path = filters.get(validator) + fn = plugin.load_function(validator_path) + if fn is None: + msg = "Unable to load query validator for {x}".format(x=validator) + raise exceptions.ConfigurationException(msg) - filters = app.config.get("QUERY_FILTERS", {}) - validator_path = filters.get(validator) - fn = plugin.load_function(validator_path) - if fn is None: - msg = "Unable to load query validator for {x}".format(x=validator) - raise exceptions.ConfigurationException(msg) + if not fn(query): + return False + return True - return fn(query) def _pre_filter_search_query(self, cfg, query): # now run the query through the filters @@ -197,6 +199,43 @@ def add_must(self, filter): context["must"] = [] context["must"].append(filter) + def get_field_context(self): + """Get query string context""" + context = None + if "query_string" in self.q["query"]: + context = self.q["query"]["query_string"] + + elif "bool" in self.q["query"]: + if "must" in self.q["query"]["bool"]: + context = self.q["query"]["bool"]["must"] + return context + + def add_default_field(self, value: str): + """ Add a default field to the query string, if one is not already present""" + context = self.get_field_context() + + if context: + if isinstance(context, dict): + if "default_field" not in context: + context["default_field"] = value + elif isinstance(context, list): + for item in context: + if "query_string" in item: + if "default_field" not in item["query_string"]: + item["query_string"]["default_field"] = value + break + + def add_should(self, filter, minimum_should_match=1): + self.convert_to_bool() + context = self.q["query"]["bool"] + if "should" not in context: + context["should"] = [] + if isinstance(filter, list): + context["should"].extend(filter) + else: + context["should"].append(filter) + context["minimum_should_match"] = minimum_should_match + def add_must_filter(self, filter): self.convert_to_bool() context = self.q["query"]["bool"] diff --git a/portality/forms/application_forms.py b/portality/forms/application_forms.py index 33cd4c1eb7..ebe21abebe 100644 --- a/portality/forms/application_forms.py +++ b/portality/forms/application_forms.py @@ -417,8 +417,8 @@ class FieldDefinitions: "input": "taglist", "help": { "long_help": ["Choose upto 6 keywords that describe the subject matter of the journal. " - "Keywords must be in English.", "Use single words or short phrases (2 to 3 words) " - "that describe the journal's main topic.", "Do not add acronyms, abbreviations or descriptive sentences.", + "Keywords must be in English.", "Use single words or short phrases (2 to 3 words) " + "that describe the journal's main topic.", "Do not add acronyms, abbreviations or descriptive sentences.", "Note that the keywords may be edited by DOAJ editorial staff." ], }, "validate": [ @@ -476,7 +476,8 @@ class FieldDefinitions: "label": "Publisher’s name", "input": "text", "validate": [ - {"required": {"message": "Enter the name of the journal’s publisher"}} + {"required": {"message": "Enter the name of the journal’s publisher"}}, + {"different_to": {"field": "institution_name", "message": "Publisher and Society/Institution names cannot be the same."}} # ~~^-> DifferetTo:FormValidator~~ ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ @@ -558,28 +559,42 @@ class FieldDefinitions: "a society or other type of institution, enter that here."], "placeholder": "Type or select the society or institution’s name" }, - "contexts" : { + "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type" : "journal", "field": "bibjson.institution.name.exact"}}, # ~~^-> Autocomplete:FormWidget~~ + "full_contents" # ~~^->FullContents:FormWidget~~ + ], + "validate": [ + {"different_to": {"field": "publisher_name", + "message": "Publisher and Society/Institution names cannot be the same."}} + # ~~^-> DifferetTo:FormValidator~~ + ], + "contexts": { "admin": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type": "journal", "field": "bibjson.institution.name.exact"}}, + # ~~^-> Autocomplete:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "associate_editor": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type": "journal", "field": "bibjson.institution.name.exact"}}, + # ~~^-> Autocomplete:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] }, "editor": { "widgets": [ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + {"autocomplete": {"type": "journal", "field": "bibjson.institution.name.exact"}}, + # ~~^-> Autocomplete:FormWidget~~ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] } - }, - "widgets": [ - "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - {"autocomplete": {"type" : "journal", "field": "bibjson.institution.name.exact"}}, # ~~^-> Autocomplete:FormWidget~~ - "full_contents" # ~~^->FullContents:FormWidget~~ - ] + } } # ~~->$ InstitutionCountry:FormField~~ @@ -728,7 +743,7 @@ class FieldDefinitions: "field": "license_display", "value": "y", "message": "Enter the URL for any recent article that displays or embeds a license" - } + } }, "is_url" # ~~^->IsURL:FormValidator~~ ], @@ -836,7 +851,7 @@ class FieldDefinitions: "field": "review_process", "value": "other", "message": "Enter the name of another type of peer review" - } + } } ], "widgets" : [ @@ -875,8 +890,8 @@ class FieldDefinitions: "datatype": "integer", "help": { "long_help": ["Please enter the year that the journal started to publish all content as true open access, according to DOAJ's definition.", - "For journals that have flipped to open access, enter the year that the journal flipped, not the original launch date of the journal.", - "For journals that have made digitised backfiles freely available, enter the year that the journal started publishing as a fully open access title, not the date of the earliest free content."] + "For journals that have flipped to open access, enter the year that the journal flipped, not the original launch date of the journal.", + "For journals that have made digitised backfiles freely available, enter the year that the journal started publishing as a fully open access title, not the date of the earliest free content."] }, "validate": [ {"required": {"message": "Enter the Year (YYYY)."}}, @@ -928,7 +943,7 @@ class FieldDefinitions: "field": "plagiarism_detection", "value": "y", "message": "Enter the URL for the journal’s plagiarism policy page" - } + } }, "is_url" # ~~^->IsURL:FormValidator~~ ], @@ -1175,14 +1190,14 @@ class FieldDefinitions: "help": { "short_help": "Link to the journal’s waiver information.", "doaj_criteria": "You must provide a URL", - "placeholder": "https://www.my-journal.com/about#waiver" + "placeholder": "https://www.my-journal.com/about#waiver" }, "validate": [ {"required_if": { "field": "has_waiver", "value": "y", "message": "Enter the URL for the journal’s waiver information page" - } + } }, "is_url" # ~~^->IsURL:FormValidator~~ ], @@ -1230,7 +1245,7 @@ class FieldDefinitions: "field": "has_other_charges", "value": "y", "message": "Enter the URL for the journal’s fees information page" - } + } }, "is_url" # ~~^->IsURL:FormValidator~~ ], @@ -1289,7 +1304,7 @@ class FieldDefinitions: "field": "preservation_service", "value": "national_library", "message": "Enter the name(s) of the national library or libraries where the journal is archived" - } + } } ], "asynchronous_warning": [ @@ -1315,7 +1330,7 @@ class FieldDefinitions: "field": "preservation_service", "value": "other", "message": "Enter the name of another archiving policy" - } + } } ], "asynchronous_warning": [ @@ -1414,7 +1429,7 @@ class FieldDefinitions: "field": "deposit_policy", "value": "other", "message": "Enter the name of another repository policy" - } + } } ], "asynchronous_warning": [ @@ -1501,8 +1516,8 @@ class FieldDefinitions: ], "help": { "long_help": ["A persistent article identifier (PID) is used to find the article no matter where it is " - "located. The most common type of PID is the digital object identifier (DOI). ", - "Read more about PIDs."], + "located. The most common type of PID is the digital object identifier (DOI). ", + "Read more about PIDs."], }, "validate": [ {"required": {"message": "Select at least one option"}} @@ -1520,7 +1535,7 @@ class FieldDefinitions: "field": "persistent_identifiers", "value": "other", "message": "Enter the name of another type of identifier" - } + } } ], "asynchronous_warning": [ @@ -1543,7 +1558,7 @@ class FieldDefinitions: "default" : "", "help": { "long_help": ["An ORCID (Open Researcher and Contributor) iD is an alphanumeric code to uniquely identify " - "authors."], + "authors."], }, "contexts" : { "public" : { @@ -1634,7 +1649,7 @@ class FieldDefinitions: "input": "textarea", "help": { "long_help": ["The selected reason for rejection, and any additional information you include, " - "are sent to the journal contact with the rejection email."] + "are sent to the journal contact with the rejection email."] }, "validate": [ {"required_if": {"field": "quick_reject", "value": "other"}} @@ -1684,7 +1699,7 @@ class FieldDefinitions: "help" : { "render_error_box": False, "short_help" : "Set the status to 'In Progress' to signal to the applicant that you have started your review." - "Set the status to 'Completed' to alert the Editor that you have completed your review.", + "Set the status to 'Completed' to alert the Editor that you have completed your review.", "update_requests_diff": False } }, @@ -1692,7 +1707,7 @@ class FieldDefinitions: "help" : { "render_error_box" : False, "short_help" : "Revert the status to 'In Progress' to signal to the Associate Editor that further work is needed." - "Set the status to 'Ready' to alert the Managing Editor that you have completed your review.", + "Set the status to 'Ready' to alert the Managing Editor that you have completed your review.", "update_requests_diff": False } } @@ -1770,7 +1785,7 @@ class FieldDefinitions: "input": "taglist", "validate": [ {"is_issn_list": {"message": "This is not a valid ISSN"}}, # ~~^-> IsISSN:FormValidator~~ - {"different_to": {"field": "continued_by"}}, # ~~^-> DifferetTo:FormValidator~~ + {"different_to": {"field": "continued_by", "message": "The ISSN provided in both fields must be different. Please make sure to enter the ISSN of an older journal for the first field and the ISSN of a newer journal for the second field. They cannot be the same."}}, # ~~^-> DifferetTo:FormValidator~~ { "not_if" : { "fields" : [{"field" : "discontinued_date"}], @@ -1793,7 +1808,7 @@ class FieldDefinitions: "input": "taglist", "validate": [ {"is_issn_list": {"message": "This is not a valid ISSN"}}, # ~~^-> IsISSN:FormValidator~~ - {"different_to": {"field": "continues"}}, # ~~^-> DifferetTo:FormValidator~~ + {"different_to": {"field": "continues", "message": "The ISSN provided in both fields must be different. Please make sure to enter the ISSN of an older journal for the first field and the ISSN of a newer journal for the second field. They cannot be the same."}}, # ~~^-> DifferetTo:FormValidator~~ { "not_if": { "fields": [{"field": "discontinued_date"}], @@ -1821,18 +1836,18 @@ class FieldDefinitions: "validate": [ {"required_if" : { "field" : "application_status", - "value" : [ - constants.APPLICATION_STATUS_READY, - constants.APPLICATION_STATUS_COMPLETED, - constants.APPLICATION_STATUS_ACCEPTED - ], - "message" : "This field is required when setting the Application Status to {y}, {z} or {a}".format( - y=constants.APPLICATION_STATUS_READY, - z=constants.APPLICATION_STATUS_COMPLETED, - a=constants.APPLICATION_STATUS_ACCEPTED - ) - } - } + "value" : [ + constants.APPLICATION_STATUS_READY, + constants.APPLICATION_STATUS_COMPLETED, + constants.APPLICATION_STATUS_ACCEPTED + ], + "message" : "This field is required when setting the Application Status to {y}, {z} or {a}".format( + y=constants.APPLICATION_STATUS_READY, + z=constants.APPLICATION_STATUS_COMPLETED, + a=constants.APPLICATION_STATUS_ACCEPTED + ) + } + } ], "widgets": [ "subject_tree" @@ -2813,6 +2828,7 @@ class DifferentToBuilder: @staticmethod def render(settings, html_attrs): html_attrs["data-parsley-different-to"] = settings.get("field") + html_attrs["data-parsley-different-to-message"] = "

" + settings.get("message") + "

" @staticmethod def wtforms(field, settings): @@ -3083,7 +3099,7 @@ class MultiCheckboxBuilder(WTFormsBuilder): @staticmethod def match(field): return field.get("input") == "checkbox" and \ - (len(field.get("options", [])) > 0 or field.get("options_fn") is not None) + (len(field.get("options", [])) > 0 or field.get("options_fn") is not None) @staticmethod def wtform(formulaic_context, field, wtfargs): diff --git a/portality/lib/analytics.py b/portality/lib/analytics.py deleted file mode 100644 index c2a1a69896..0000000000 --- a/portality/lib/analytics.py +++ /dev/null @@ -1,172 +0,0 @@ -# ~~ GoogleAnalytics:ExternalService~~ -import logging -import os -from functools import wraps - -# Logger specific to analytics -logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) - -# The global tracker object -tracker = None - - -class GAException(Exception): - pass - - -def create_tracker(ga_id, domain): - global tracker - if ga_id: - #tracker = UniversalAnalytics.Tracker.create(ga_id, client_id=domain) - raise GAException("GA is not running because universal-analytics is broken in Python 3") - else: - raise GAException("Invalid GA ID supplied, no tracker created.") - - -def create_logfile(log_dir=None): - filepath = __name__ + '.log' - if log_dir is not None: - if not os.path.exists(log_dir): - os.makedirs(log_dir) - filepath = os.path.join(log_dir, filepath) - fh = logging.FileHandler(filepath) - fh.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) - logger.addHandler(fh) - - -class GAEvent(object): - category = None - action = None - label = None - value = None - fieldsobject = None - - def __init__(self, category, action, label='', value=None, fieldsobject=None): - self.category = category - self.action = action - self.label = label - self.value = value - self.fieldsobject=fieldsobject - - def submit(self): - ga_send_event(self.category, self.action, self.label, self.value, self.fieldsobject) - - -def ga_send_event(category, action, label='', value=None, fieldsobject=None): - """ - Send event data to Google Analytics. - (supposedly supporting other analytics providers as well, - check https://github.com/analytics-pros/universal-analytics-python ) - - See https://support.google.com/analytics/answer/1033068?hl=en for - guidance on how to use the various event properties this decorator - takes. - - One thing to note is that event_value must be an integer. We - don't really use that, it's for things like "total downloads" or - "number of times video played". - - The others are strings and can be anything we like. They must - take into account what previous strings we've sent so that events - can be aggregated correctly. Changing the strings you use for - categories, actions and labels to describe the same event will - split your analytics reports into two: before and after the - change of the event strings you use. - - :param category: Typically the object that was interacted with (e.g. 'Video') - :param action: The type of interaction (e.g. 'play') - :param label: Useful for categorizing events (e.g. 'Fall Campaign') - :param value: A non-negative numeric value associated with the event (e.g. 42) - :param fieldsobject: Key, value pairs which don't fit into the above categories - """ - - # Write event to file even if there's no tracker configured. - analytics_args = [category, action] - - if label != '': - analytics_args.append(label) - if value is not None: - analytics_args.append(value) - if fieldsobject is not None: - analytics_args.append(fieldsobject) - - logger.debug("Event Send %s", analytics_args) - - # Send the event to GA via the tracker - if tracker is not None: - tracker.send('event', *analytics_args) - else: - # logger.error("Google Analytics tracker is not configured.") - pass # we now know it's broken, stop spamming this in our logs. - - -def sends_ga_event(event_category, event_action, event_label='', - event_value=0, record_value_of_which_arg=''): - """ - Decorator for Flask view functions, sending event data to Google - Analytics. - - :param event_category: - :param event_action: - :param event_label: - :param event_value: - - :param record_value_of_which_arg: The name of one argument that - the view function takes. During tracking, the value of that - argument will be extracted and sent as the Event Label to the - analytics servers. NOTE! If you pass both event_label and - record_value_of_which_arg to this decorator, event_label will be - ignored and overwritten by the action that - record_value_of_which_arg causes. - - For example: - @sends_ga_event('API Hit', 'Search applications', - record_value_of_which_arg='search_query') - def search_applications(search_query): - # ... - - Then we get a hit, with search_query being set to 'computer shadows'. - This will result in an event with category "API Hit", action "Search - applications" and label "computer shadows". - - A different example: - @sends_ga_event('API Hit', 'Retrieve application', - record_value_of_which_arg='application_id') - def retrieve_application(application_id): - # ... - - Then we get a hit asking for application with id '12345'. - This will result in an event with category "API Hit", action "Retrieve - application" and label "12345". - - Clashing arguments: - @sends_ga_event('API Hit', 'Retrieve application', - event_label='Special interest action', - record_value_of_which_arg='application_id') - def retrieve_application(application_id): - # ... - - Then we get a hit asking for application with id '12345' again. - This will result in an event with category "API Hit", action "Retrieve - application" and label "12345". I.e. the event_label passed in will - be ignored, because we also passed in record_value_of_which_arg which - overrides the event label sent to the analytics servers. - - On testing: this has been tested manually on DOAJ with Google - Analytics by @emanuil-tolev & @Steven-Eardley. - """ - - def decorator(fn): - @wraps(fn) - def decorated_view(*args, **kwargs): - el = event_label - if record_value_of_which_arg in kwargs: - el = kwargs[record_value_of_which_arg] - - ga_send_event(event_category, event_action, el, event_value) - - return fn(*args, **kwargs) - - return decorated_view - return decorator diff --git a/portality/lib/es_data_mapping.py b/portality/lib/es_data_mapping.py index 2f8cf51f26..9e57d929ec 100644 --- a/portality/lib/es_data_mapping.py +++ b/portality/lib/es_data_mapping.py @@ -50,4 +50,9 @@ def create_mapping(struct, mapping_opts, path=()): for struct_name, struct_body in struct.get("structs", {}).items(): result["properties"][struct_name] = create_mapping(struct_body, mapping_opts, path + (struct_name,)) + dotpath = '.'.join(path) + for field, additional_mapping in mapping_opts.get("additional_mappings", {}).items(): + if field.startswith(dotpath) and "." not in field[len(dotpath) + 1:]: + result["properties"][field[len(dotpath):]] = additional_mapping + return result diff --git a/portality/lib/query_filters.py b/portality/lib/query_filters.py index 13aed16f39..0bcaf9d916 100644 --- a/portality/lib/query_filters.py +++ b/portality/lib/query_filters.py @@ -29,18 +29,60 @@ def public_query_validator(q): return True +def non_public_fields_validator(q): + exclude_fields = app.config.get("PUBLIC_QUERY_VALIDATOR__EXCLUDED_FIELDS", {}) + context = q.get_field_context() + if context: + if "default_field" in context: + field_value = context["default_field"] + if field_value in exclude_fields: + return False + return True + # query filters ############### def remove_search_limits(query: dict): return remove_fields(query, ['size', 'from']) + def only_in_doaj(q): q.clear_match_all() q.add_must_filter({"term": {"admin.in_doaj": True}}) return q +def search_all_meta(q): + """Search by all_meta field, which is a concatenation of all the fields in the record""" + q.add_default_field("all_meta") + return q + + +def journal_article_filter(q): + """Search by all_meta field for journal and all fields for article""" + search_text = None + context = q.get_field_context() + if isinstance(context, list): + for item in context: + if "query_string" in item: + search_text = item["query_string"]["query"] + if "default_field" in item["query_string"]: + return q + elif isinstance(context, dict): + if "query_string" in context: + search_text = context["query_string"]["query"] + if "default_field" in context["query_string"]: + return q + q.convert_to_bool() + if search_text: + filter = [{"bool": {"must": [{"term": {"es_type.exact": "article"}}, + {"query_string": {"default_field": "*", "query": search_text}}]}}, + {"bool": {"must": [{"term": {"es_type.exact": "journal"}}, + {"query_string": {"default_field": "all_meta", "query": search_text}}]}}] + q.add_should(filter) + return q + + def owner(q): q.clear_match_all() q.add_must_filter({"term" : {"admin.owner.exact" : current_user.id}}) diff --git a/portality/migrate/3575_make_notes_searchable/README.md b/portality/migrate/3575_make_notes_searchable/README.md new file mode 100644 index 0000000000..37a81f071b --- /dev/null +++ b/portality/migrate/3575_make_notes_searchable/README.md @@ -0,0 +1,7 @@ +# 09 11 2023; Issue 3575 - Make notes searchable for admin + +## Execution + +Run the migration with + + python portality/scripts/es_reindex.py -u portality/migrate/3575_make_notes_searchable/migrate.json \ No newline at end of file diff --git a/portality/migrate/3575_make_notes_searchable/__init__.py b/portality/migrate/3575_make_notes_searchable/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/portality/migrate/3575_make_notes_searchable/migrate.json b/portality/migrate/3575_make_notes_searchable/migrate.json new file mode 100644 index 0000000000..f1d8c58054 --- /dev/null +++ b/portality/migrate/3575_make_notes_searchable/migrate.json @@ -0,0 +1,16 @@ +{ + "new_version": "-20231115_searchable_notes", + "old_version": "", + "types": [ + { + "type" : "application", + "migrate": true, + "set_alias": false + }, + { + "type": "journal", + "migrate": true, + "set_alias": false + } + ] +} \ No newline at end of file diff --git a/portality/models/preservation.py b/portality/models/preservation.py index c89c2f3f6f..cdaffb2708 100644 --- a/portality/models/preservation.py +++ b/portality/models/preservation.py @@ -99,6 +99,10 @@ def no_files_articles(self, articles_list): if articles_list is not None and len(articles_list) > 0: self.data["articles_info"]["no_files_articles"] = ", ".join(articles_list) + def uploaded_journals(self, uploaded_journals): + if uploaded_journals is not None and len(uploaded_journals) > 0: + self.data["articles_info"]["uploaded_journals"] = ", ".join(uploaded_journals) + @classmethod def by_owner(cls, owner, size=10): q = OwnerFileQuery(owner) diff --git a/portality/models/v2/application.py b/portality/models/v2/application.py index 2b0d523720..58fff8d19d 100644 --- a/portality/models/v2/application.py +++ b/portality/models/v2/application.py @@ -244,14 +244,9 @@ class AllPublisherApplications(DomainObject): MAPPING_OPTS = { "dynamic": None, - "coerces": app.config["DATAOBJ_TO_MAPPING_DEFAULTS"], - "exceptions": { - "admin.notes.note": { - "type": "text", - "index": False, - # "include_in_all": False # Removed in es6 fixme: do we need to look at copy_to for the mapping? - } - } + "coerces": Journal.add_mapping_extensions(app.config["DATAOBJ_TO_MAPPING_DEFAULTS"]), + "exceptions": app.config["ADMIN_NOTES_SEARCH_MAPPING"], + "additional_mappings": app.config["ADMIN_NOTES_INDEX_ONLY_FIELDS"] } diff --git a/portality/models/v2/journal.py b/portality/models/v2/journal.py index efa6aa53e4..81e28d86dd 100644 --- a/portality/models/v2/journal.py +++ b/portality/models/v2/journal.py @@ -580,6 +580,15 @@ def delete_selected(cls, query, articles=False, snapshot_journals=True, snapshot # finally issue a delete request against the journals cls.delete_by_query(query) + @classmethod + def add_mapping_extensions(cls, default_mappings: dict): + default_mappings_copy = deepcopy(default_mappings) + mapping_extensions = app.config.get("DATAOBJ_TO_MAPPING_COPY_TO_EXTENSIONS") + for key, value in mapping_extensions.items(): + if key in default_mappings_copy: + default_mappings_copy[key] = {**default_mappings_copy[key], **value} + return default_mappings_copy + def all_articles(self): from portality.models import Article return Article.find_by_issns(self.known_issns()) @@ -907,14 +916,9 @@ def _calculate_has_apc(self): MAPPING_OPTS = { "dynamic": None, - "coerces": app.config["DATAOBJ_TO_MAPPING_DEFAULTS"], - "exceptions": { - "admin.notes.note": { - "type": "text", - "index": False, - # "include_in_all": False # Removed in es6 fixme: do we need to look at copy_to for the mapping? - } - } + "coerces": Journal.add_mapping_extensions(app.config["DATAOBJ_TO_MAPPING_DEFAULTS"]), + "exceptions": app.config["ADMIN_NOTES_SEARCH_MAPPING"], + "additional_mappings": app.config["ADMIN_NOTES_INDEX_ONLY_FIELDS"] } diff --git a/portality/scripts/230609_find_articles_with_invalid_issns.py b/portality/scripts/230609_find_articles_with_invalid_issns.py index 8b857faa01..dd861ea04d 100644 --- a/portality/scripts/230609_find_articles_with_invalid_issns.py +++ b/portality/scripts/230609_find_articles_with_invalid_issns.py @@ -22,15 +22,26 @@ parser.add_argument("-o", "--out", help="output file path", required=True) args = parser.parse_args() - with open(args.out, "w", encoding="utf-8") as f: - writer = csv.writer(f) - writer.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", "Journals found with article's EISSN", "In doaj?", "Error"]) + + with open(args.out+"notfound.csv", "w", encoding="utf-8") as f_notfound, open(args.out+"-identical.csv", "w", encoding="utf-8") as f_identical, open(args.out+"-others.csv", "w", encoding="utf-8") as f_others: + writer_notfound = csv.writer(f_notfound) + writer_notfound.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", + "Journals found with article's EISSN", "In doaj?", "Error"]) + + writer_identical = csv.writer(f_identical) + writer_identical.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", + "Journals found with article's EISSN", "In doaj?", "Error"]) + + writer_others = csv.writer(f_others) + writer_others.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", + "Journals found with article's EISSN", "In doaj?", "Error"]) for a in models.Article.iterate(q=IN_DOAJ, page_size=100, keepalive='5m'): article = models.Article(_source=a) bibjson = article.bibjson() try: articlesvc.ArticleService._validate_issns(bibjson) + articlesvc.ArticleService.match_journal_with_validation(bibjson) except exceptions.ArticleNotAcceptable as e: id = article.id pissn = bibjson.get_identifiers("pissn") @@ -53,4 +64,9 @@ j_e_in_doaj.append(jobj.is_in_doaj()) else: j_e_in_doaj.append("n/a") - writer.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, str(e)]) + if (str(e) == "The Print and Online ISSNs supplied are identical. If you supply 2 ISSNs they must be different."): + writer_identical.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, "Identical ISSNs"]) + elif (str(e) == "ISSNs provided don't match any journal."): + writer_notfound.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, "No matching journal found."]) + else: + writer_others.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, str(e)]) \ No newline at end of file diff --git a/portality/scripts/es_reindex.py b/portality/scripts/es_reindex.py new file mode 100644 index 0000000000..4893b1afc4 --- /dev/null +++ b/portality/scripts/es_reindex.py @@ -0,0 +1,135 @@ +# ~~ESReindex:CLI~~ +""" +This script is useful to create new index with any new mapping changes if applicable and copy the content from old index to new index +run the script: +portality/scripts/es_reindex.py +ex: +DOAJENV=dev python portality/scripts/es_reindex.py portality/migrate/3575_make_notes_searchable/migrate.json + +example json file: +{ + "new_version": "-20231109", #new index version + "old_version": "-20230901", #old index version + "types": [ + { + "type" : "application", #model type + "migrate": true, #if migration required true or false + "set_alias": false #set to true if alias has to be set with base name ex: doaj-application + }, + { + "type": "journal", + "migrate": true, + "set_alias": false + } + ] +} +""" + +import json +import time +import elasticsearch +from elasticsearch import helpers +from elasticsearch.exceptions import NotFoundError, RequestError, ConnectionError, AuthorizationException + +from portality.core import app +from portality.lib import es_data_mapping + + +def do_import(config): + # create a connection with timeout + es_connection = elasticsearch.Elasticsearch(app.config['ELASTICSEARCH_HOSTS'], + verify_certs=app.config.get("ELASTIC_SEARCH_VERIFY_CERTS", True), + timeout=60*10) + + # get the versions + version = config.get("new_version") + previous_version = config.get("old_version") + + # get the types we are going to work with + print("\n==Reindexing the following types / indices==") + for s in config.get("types", []): + if s.get("migrate", False) is True: + print(s.get("type")) + + print("\n") + + text = input("Continue? [y/N] ") + if text.lower() != "y": + exit() + + # get all available mappings + mappings = es_data_mapping.get_mappings(app) + + # Iterate through the types then + # 1. create new index + # 2. re index with old index + # 3. set alias for new index (if requested) + for s in config.get("types", []): + import_type = s["type"] + if import_type in mappings: + + # index names + default_index_name = app.config['ELASTIC_SEARCH_DB_PREFIX'] + import_type + new_index = default_index_name + version + old_index = default_index_name + previous_version + + if not es_connection.indices.exists(new_index): + try: + # create new index + r = es_connection.indices.create(index=new_index, body=mappings[import_type]) + print("Creating ES Type + Mapping in index {0} for {1}; status: {2}".format(new_index, import_type, r)) + + # reindex from the old index + print("Reindexing from {0} to {1}".format(old_index, new_index)) + retry_count = 0 + max_retries = 5 + success = False + while not success and retry_count < max_retries: + try: + result, errors = helpers.reindex(client=es_connection, source_index=old_index, + target_index=new_index) + if errors: + print(f"Some documents failed to reindex: {import_type}", errors) + else: + success = True + print(f"Reindex completed successfully: {import_type}", result) + # add alias + if s.get("set_alias", False): + es_connection.indices.put_alias(index=new_index, name=default_index_name) + print("alias set for {0} as {1}".format(new_index, default_index_name)) + else: + print("alias not set for {0}".format(new_index)) + except ConnectionError: + retry_count += 1 + print(f"Timeout occurred, retrying {retry_count}/{max_retries}") + time.sleep(10) # Wait for 10 seconds before retrying + + if not success: + print("Failed to complete the reindexing after several retries.") + + except ConnectionError as e: + print(f"Failed to connect to Elasticsearch server. {e.info}") + except NotFoundError as e: + print(f"The specified index or alias does not exist. {e.info}") + except RequestError as e: + print(f"Bad request: {e.info}") + except AuthorizationException as e: + print(f"You do not have permission to perform this operation. {e.info}") + except Exception as e: + print(f"An unexpected error occurred: {e}") + else: + print("ES Type + Mapping already exists in index {0} for {1}".format(new_index, import_type)) + + +if __name__ == '__main__': + + import argparse + parser = argparse.ArgumentParser() + + parser.add_argument("config", help="Config file path to migrate") + args = parser.parse_args() + + with open(args.config, "r", encoding="utf-8") as f: + config = json.load(f) + + do_import(config) \ No newline at end of file diff --git a/portality/settings.py b/portality/settings.py index c8bbb99fb2..5e161e3ce0 100644 --- a/portality/settings.py +++ b/portality/settings.py @@ -9,7 +9,7 @@ # Application Version information # ~~->API:Feature~~ -DOAJ_VERSION = "6.4.3" +DOAJ_VERSION = "6.5.3" API_VERSION = "3.0.1" ###################################### @@ -425,11 +425,14 @@ # Crontab for never running a job - February 31st (use to disable tasks) CRON_NEVER = {"month": "2", "day": "31", "day_of_week": "*", "hour": "*", "minute": "*"} +# Additional Logging for scheduled JournalCSV +EXTRA_JOURNALCSV_LOGGING = False + # Crontab schedules must be for unique times to avoid delays due to perceived race conditions HUEY_SCHEDULE = { "sitemap": {"month": "*", "day": "*", "day_of_week": "*", "hour": "8", "minute": "0"}, "reporting": {"month": "*", "day": "1", "day_of_week": "*", "hour": "0", "minute": "0"}, - "journal_csv": CRON_NEVER, # {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "20"}, + "journal_csv": {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "20"}, "read_news": {"month": "*", "day": "*", "day_of_week": "*", "hour": "*", "minute": "30"}, "article_cleanup_sync": {"month": "*", "day": "2", "day_of_week": "*", "hour": "0", "minute": "0"}, "async_workflow_notifications": {"month": "*", "day": "*", "day_of_week": "1", "hour": "5", "minute": "0"}, @@ -628,6 +631,19 @@ } } +# Extension Map from dataobj coercion declarations to ES mappings. +# This is useful when some extensions required for some objects additional to defaults. +# ~~->DataObj:Library~~ +# ~~->Seamless:Library~~ +DATAOBJ_TO_MAPPING_COPY_TO_EXTENSIONS = { + "unicode": {"copy_to": ["all_meta"]}, + "str": {"copy_to": ["all_meta"]}, + "unicode_upper": {"copy_to": ["all_meta"]}, + "unicode_lower": {"copy_to": ["all_meta"]}, + "issn": {"copy_to": ["all_meta"]}, + "url": {"copy_to": ["all_meta"]} +} + # TODO: we may want a big-type and little-type setting DEFAULT_INDEX_SETTINGS = \ { @@ -698,8 +714,8 @@ "journal" : { "auth" : False, "role" : None, - "query_validator" : "public_query_validator", - "query_filters" : ["only_in_doaj", "last_update_fallback"], + "query_validators" : ["non_public_fields_validator", "public_query_validator"], + "query_filters" : ["only_in_doaj", "last_update_fallback", "search_all_meta"], "result_filters" : ["public_result_filter"], "dao" : "portality.models.Journal", # ~~->Journal:Model~~ "required_parameters" : {"ref" : ["ssw", "public_journal", "subject_page"]} @@ -708,7 +724,7 @@ "article" : { "auth" : False, "role" : None, - "query_validator" : "public_query_validator", + "query_validators" : ["non_public_fields_validator", "public_query_validator"], "query_filters" : ["only_in_doaj"], "result_filters" : ["public_result_filter"], "dao" : "portality.models.Article", # ~~->Article:Model~~ @@ -719,8 +735,8 @@ "journal,article" : { "auth" : False, "role" : None, - "query_validator" : "public_query_validator", - "query_filters" : ["only_in_doaj", "strip_facets", "es_type_fix"], + "query_validators" : ["non_public_fields_validator", "public_query_validator"], + "query_filters" : ["only_in_doaj", "strip_facets", "es_type_fix", "journal_article_filter"], "result_filters" : ["public_result_filter", "add_fqw_facets", "fqw_back_compat"], "dao" : "portality.models.JournalArticle", # ~~->JournalArticle:Model~~ "required_parameters" : {"ref" : ["fqw"]} @@ -731,7 +747,8 @@ "journal" : { "auth" : True, "role" : "publisher", - "query_filters" : ["owner", "only_in_doaj"], + "query_validators" : ["non_public_fields_validator"], + "query_filters" : ["owner", "only_in_doaj", "search_all_meta"], "result_filters" : ["publisher_result_filter"], "dao" : "portality.models.Journal" # ~~->Journal:Model~~ }, @@ -739,7 +756,8 @@ "applications" : { "auth" : True, "role" : "publisher", - "query_filters" : ["owner", "not_update_request"], + "query_validators" : ["non_public_fields_validator"], + "query_filters" : ["owner", "not_update_request", "search_all_meta"], "result_filters" : ["publisher_result_filter"], "dao" : "portality.models.AllPublisherApplications" # ~~->AllPublisherApplications:Model~~ }, @@ -747,7 +765,8 @@ "update_requests" : { "auth" : True, "role" : "publisher", - "query_filters" : ["owner", "update_request"], + "query_validators" : ["non_public_fields_validator"], + "query_filters" : ["owner", "update_request", "search_all_meta"], "result_filters" : ["publisher_result_filter"], "dao" : "portality.models.Application" # ~~->Application:Model~~ } @@ -810,14 +829,16 @@ "journal" : { "auth" : True, "role" : "associate_editor", - "query_filters" : ["associate"], + "query_validators" : ["non_public_fields_validator"], + "query_filters" : ["associate", "search_all_meta"], "dao" : "portality.models.Journal" # ~~->Journal:Model~~ }, # ~~->AssEdApplicationQuery:Endpoint~~ "suggestion" : { "auth" : True, "role" : "associate_editor", - "query_filters" : ["associate"], + "query_validators" : ["non_public_fields_validator"], + "query_filters" : ["associate", "search_all_meta"], "dao" : "portality.models.Application" # ~~->Application:Model~~ } }, @@ -826,14 +847,16 @@ "journal" : { "auth" : True, "role" : "editor", - "query_filters" : ["editor"], + "query_validators" : ["non_public_fields_validator"], + "query_filters" : ["editor", "search_all_meta"], "dao" : "portality.models.Journal" # ~~->Journal:Model~~ }, # ~~->EditorApplicationQuery:Endpoint~~ "suggestion" : { "auth" : True, "role" : "editor", - "query_filters" : ["editor"], + "query_validators" : ["non_public_fields_validator"], + "query_filters" : ["editor", "search_all_meta"], "dao" : "portality.models.Application" # ~~->Application:Model~~ } }, @@ -851,7 +874,8 @@ "journal" : { "auth" : False, "role" : None, - "query_filters" : ["only_in_doaj", "public_source"], + "query_validators": ["non_public_fields_validator"], + "query_filters" : ["only_in_doaj", "public_source", "search_all_meta"], "dao" : "portality.models.Journal", # ~~->Journal:Model~~ "required_parameters" : None }, @@ -859,7 +883,8 @@ "application" : { "auth" : True, "role" : None, - "query_filters" : ["owner", "private_source"], + "query_validators": ["non_public_fields_validator"], + "query_filters" : ["owner", "private_source", "search_all_meta"], "dao" : "portality.models.Suggestion", # ~~->Application:Model~~ "required_parameters" : None } @@ -879,6 +904,7 @@ QUERY_FILTERS = { # sanitisers "public_query_validator" : "portality.lib.query_filters.public_query_validator", + "non_public_fields_validator" : "portality.lib.query_filters.non_public_fields_validator", # query filters "only_in_doaj" : "portality.lib.query_filters.only_in_doaj", @@ -891,6 +917,8 @@ "last_update_fallback" : "portality.lib.query_filters.last_update_fallback", "not_update_request" : "portality.lib.query_filters.not_update_request", "who_current_user" : "portality.lib.query_filters.who_current_user", # ~~-> WhoCurrentUser:Query ~~ + "search_all_meta" : "portality.lib.query_filters.search_all_meta", # ~~-> SearchAllMeta:Query ~~ + "journal_article_filter" : "portality.lib.query_filters.journal_article_filter", # ~~-> JournalArticleFilter:Query ~~ # result filters "public_result_filter": "portality.lib.query_filters.public_result_filter", @@ -902,6 +930,55 @@ "private_source": "portality.lib.query_filters.private_source", "public_source": "portality.lib.query_filters.public_source", } +# Exclude the fields that doesn't want to be searched by public queries +# This is part of non_public_fields_validator. +PUBLIC_QUERY_VALIDATOR__EXCLUDED_FIELDS = [ + "admin.notes.note", + "admin.notes.id", + "admin.notes.author_id" +] + +ADMIN_NOTES_INDEX_ONLY_FIELDS = { + "all_meta" : { + "type": "text", + "fields": { + "exact": { + "type": "keyword", + "store": True + } + } + } +} + +ADMIN_NOTES_SEARCH_MAPPING = { + "admin.notes.id": { + "type": "text", + "fields": { + "exact": { + "type": "keyword", + "store": True + } + } + }, + "admin.notes.note": { + "type": "text", + "fields": { + "exact": { + "type": "keyword", + "store": True + } + } + }, + "admin.notes.author_id": { + "type": "text", + "fields": { + "exact": { + "type": "keyword", + "store": True + } + } + } +} #################################################### # Autocomplete @@ -1068,11 +1145,11 @@ # ~~->Bitly:ExternalService~~ # bit,ly api shortening service -BITLY_SHORTENING_API_URL = "https://api-ssl.bitly.com/v4/shorten" +#BITLY_SHORTENING_API_URL = "https://api-ssl.bitly.com/v4/shorten" # bitly oauth token # ENTER YOUR OWN TOKEN IN APPROPRIATE .cfg FILE -BITLY_OAUTH_TOKEN = "" +#BITLY_OAUTH_TOKEN = "" ############################################### # Date handling @@ -1143,48 +1220,51 @@ ###################################################### -# Google Analytics configuration -# specify in environment .cfg file - avoids sending live analytics -# events from test and dev environments -# ~~->GoogleAnalytics:ExternalService~~ - -GOOGLE_ANALYTICS_ID = '' +# Analytics configuration +# specify in environment .cfg file - avoids sending live analytics events from test and dev environments -# Where to put the google analytics logs -GOOGLE_ANALTYICS_LOG_DIR = None +# ~~->PlausibleAnalytics:ExternalService~~ +# Plausible analytics +# root url of plausible +PLAUSIBLE_URL = "https://plausible.io" +PLAUSIBLE_JS_URL = PLAUSIBLE_URL + "/js/script.outbound-links.file-downloads.js" +PLAUSIBLE_API_URL = PLAUSIBLE_URL + "/api/event" +# site name / domain name that used to register in plausible +PLAUSIBLE_SITE_NAME = BASE_DOMAIN +PLAUSIBLE_LOG_DIR = None -# Google Analytics custom dimensions. These are configured in the GA interface. -GA_DIMENSIONS = { - 'oai_res_id': 'dimension1', # In GA as OAI:Record +# Analytics custom dimensions. These are configured in the interface. #fixme: are these still configured since the move from GA? +ANALYTICS_DIMENSIONS = { + 'oai_res_id': 'dimension1', # In analytics as OAI:Record } -# GA for OAI-PMH +# Plausible for OAI-PMH # ~~-> OAIPMH:Feature~~ -GA_CATEGORY_OAI = 'OAI-PMH' +ANALYTICS_CATEGORY_OAI = 'OAI-PMH' -# GA for Atom +# Plausible for Atom # ~~-> Atom:Feature~~ -GA_CATEGORY_ATOM = 'Atom' -GA_ACTION_ACTION = 'Feed request' +ANALYTICS_CATEGORY_ATOM = 'Atom' +ANALYTICS_ACTION_ACTION = 'Feed request' -# GA for JournalCSV +# Plausible for JournalCSV # ~~-> JournalCSV:Feature~~ -GA_CATEGORY_JOURNALCSV = 'JournalCSV' -GA_ACTION_JOURNALCSV = 'Download' +ANALYTICS_CATEGORY_JOURNALCSV = 'JournalCSV' +ANALYTICS_ACTION_JOURNALCSV = 'Download' -# GA for OpenURL +# Plausible for OpenURL # ~~->OpenURL:Feature~~ -GA_CATEGORY_OPENURL = 'OpenURL' +ANALYTICS_CATEGORY_OPENURL = 'OpenURL' -# GA for PublicDataDump +# Plausible for PublicDataDump # ~~->PublicDataDump:Feature~~ -GA_CATEGORY_PUBLICDATADUMP = 'PublicDataDump' -GA_ACTION_PUBLICDATADUMP = 'Download' +ANALYTICS_CATEGORY_PUBLICDATADUMP = 'PublicDataDump' +ANALYTICS_ACTION_PUBLICDATADUMP = 'Download' -# GA for API +# Plausible for API # ~~-> API:Feature~~ -GA_CATEGORY_API = 'API Hit' -GA_ACTIONS_API = { +ANALYTICS_CATEGORY_API = 'API Hit' +ANALYTICS_ACTIONS_API = { 'search_applications': 'Search applications', 'search_journals': 'Search journals', 'search_articles': 'Search articles', @@ -1204,10 +1284,10 @@ } -# GA for fixed query widget +# Plausible for fixed query widget # ~~->FixedQueryWidget:Feature~~ -GA_CATEGORY_FQW = 'FQW' -GA_ACTION_FQW = 'Hit' +ANALYTICS_CATEGORY_FQW = 'FQW' +ANALYTICS_ACTION_FQW = 'Hit' ##################################################### # Anonymised data export (for dev) configuration @@ -1305,15 +1385,6 @@ # Editorial Dashboard - set to-do list size TODO_LIST_SIZE = 48 -####################################################### -# Plausible analytics -# root url of plausible -PLAUSIBLE_URL = "https://plausible.io" -PLAUSIBLE_JS_URL = PLAUSIBLE_URL + "/js/script.outbound-links.file-downloads.js" -PLAUSIBLE_API_URL = PLAUSIBLE_URL + "/api/event" -# site name / domain name that used to register in plausible -PLAUSIBLE_SITE_NAME = BASE_DOMAIN -PLAUSIBLE_LOG_DIR = None ######################################################### # Background tasks --- monitor_bgjobs diff --git a/portality/static/js/application_form.js b/portality/static/js/application_form.js index 178aef09c4..3944845669 100644 --- a/portality/static/js/application_form.js +++ b/portality/static/js/application_form.js @@ -844,8 +844,8 @@ window.Parsley.addValidator("optionalIf", { }); window.Parsley.addValidator("differentTo", { - validateString : function(value, requirement) { - return (!value || ($("[name = " + requirement + "]")).val() !== value); + validateString : function(value, requirement, message) { + return (!value || ($("[name = " + requirement + "]")).val().toLowerCase() !== value.toLowerCase()); }, messages: { en: 'Value of this field and %s field must be different' diff --git a/portality/static/js/doaj.fieldrender.edges.js b/portality/static/js/doaj.fieldrender.edges.js index 49faf4b543..fc39fb42de 100644 --- a/portality/static/js/doaj.fieldrender.edges.js +++ b/portality/static/js/doaj.fieldrender.edges.js @@ -1302,15 +1302,15 @@ $.extend(true, doaj, { var comp = this.component; - var shareButtonFrag = ""; var shareButtonClass = edges.css_classes(this.namespace, "toggle-share", this); var modalId = edges.css_id(this.namespace, "modal", this); - shareButtonFrag = ''; + let shareButtonFrag = ''; - var shorten = ""; + let shorten = ""; if (this.component.urlShortener) { var shortenClass = edges.css_classes(this.namespace, "shorten", this); - shorten = '

Share a link to this search

' + var shortenButtonClass = edges.css_classes(this.namespace, "shorten-url", this) + shorten = '

'; } var embed = ""; if (this.component.embedSnippet) { @@ -1320,11 +1320,11 @@ $.extend(true, doaj, { } var shareBoxClass = edges.css_classes(this.namespace, "share", this); var shareUrlClass = edges.css_classes(this.namespace, "share-url", this); - var shortenButtonClass = edges.css_classes(this.namespace, "shorten-url", this); + var shareFrag = '
\ - ' + shorten + '\ +

Share a link to this search

\ \ -

\ + ' + shorten + '\ ' + embed + '\
'; diff --git a/portality/static/js/doaj.js b/portality/static/js/doaj.js index 0e8d479614..0636e9d6aa 100644 --- a/portality/static/js/doaj.js +++ b/portality/static/js/doaj.js @@ -69,29 +69,29 @@ var doaj = { doaj.bindMiniSearch(); }, - bitlyShortener : function(query, success_callback, error_callback) { - // ~~-> Bitly:ExternalService ~~ - function callbackWrapper(data) { - success_callback(data.url); - } - - function errorHandler() { - alert("Sorry, we're unable to generate short urls at this time"); - error_callback(); - } - - var page = window.location.protocol + '//' + window.location.host + window.location.pathname; - - $.ajax({ - type: "POST", - contentType: "application/json", - dataType: "jsonp", - url: "/service/shorten", - data : JSON.stringify({page: page, query: query}), - success: callbackWrapper, - error: errorHandler - }); - }, + // bitlyShortener : function(query, success_callback, error_callback) { + // // ~~-> Bitly:ExternalService ~~ + // function callbackWrapper(data) { + // success_callback(data.url); + // } + // + // function errorHandler() { + // alert("Sorry, we're unable to generate short urls at this time"); + // error_callback(); + // } + // + // var page = window.location.protocol + '//' + window.location.host + window.location.pathname; + // + // $.ajax({ + // type: "POST", + // contentType: "application/json", + // dataType: "jsonp", + // url: "/service/shorten", + // data : JSON.stringify({page: page, query: query}), + // success: callbackWrapper, + // error: errorHandler + // }); + // }, journal_toc_id : function(journal) { // if e-issn is available, use that diff --git a/portality/static/js/edges/admin.applications.edge.js b/portality/static/js/edges/admin.applications.edge.js index 3fcdbc3ab7..c43bf55d5e 100644 --- a/portality/static/js/edges/admin.applications.edge.js +++ b/portality/static/js/edges/admin.applications.edge.js @@ -61,7 +61,8 @@ $.extend(true, doaj, { {'display':'Country of publisher','field':'index.country'}, {'display':'Journal language','field':'index.language'}, {'display':'Publisher','field':'bibjson.publisher.name'}, - {'display':'Journal: alternative title','field':'bibjson.alternative_title'} + {'display':'Journal: alternative title','field':'bibjson.alternative_title'}, + {'display':'Notes','field':'admin.notes.note'}, ], defaultOperator: "AND", renderer: doaj.renderers.newFullSearchControllerRenderer({ diff --git a/portality/static/js/edges/admin.journals.edge.js b/portality/static/js/edges/admin.journals.edge.js index f6d6aef096..e612a2c7f5 100644 --- a/portality/static/js/edges/admin.journals.edge.js +++ b/portality/static/js/edges/admin.journals.edge.js @@ -307,7 +307,8 @@ $.extend(true, doaj, { {'display':'ISSN', 'field':'index.issn.exact'}, {'display':'Country of publisher','field':'index.country'}, {'display':'Journal language','field':'index.language'}, - {'display':'Publisher','field':'bibjson.publisher.name'} + {'display':'Publisher','field':'bibjson.publisher.name'}, + {'display':'Notes','field':'admin.notes.note'} ], defaultOperator: "AND", renderer: doaj.renderers.newFullSearchControllerRenderer({ diff --git a/portality/static/js/edges/admin.update_requests.edge.js b/portality/static/js/edges/admin.update_requests.edge.js index 22cc532911..cea56910fe 100644 --- a/portality/static/js/edges/admin.update_requests.edge.js +++ b/portality/static/js/edges/admin.update_requests.edge.js @@ -71,7 +71,8 @@ $.extend(true, doaj, { {'display':'Country of publisher','field':'index.country'}, {'display':'Journal language','field':'index.language'}, {'display':'Publisher','field':'bibjson.publisher.name'}, - {'display':'Journal: Alternative Title','field':'bibjson.alternative_title'} + {'display':'Journal: Alternative Title','field':'bibjson.alternative_title'}, + {'display':'Notes','field':'admin.notes.note'} ], defaultOperator: "AND", renderer: doaj.renderers.newFullSearchControllerRenderer({ diff --git a/portality/static/js/edges/public.article.edge.js b/portality/static/js/edges/public.article.edge.js index f4595e9227..b520a5c474 100644 --- a/portality/static/js/edges/public.article.edge.js +++ b/portality/static/js/edges/public.article.edge.js @@ -139,7 +139,7 @@ $.extend(true, doaj, { edges.newFullSearchController({ id: "share_embed", category: "controller", - urlShortener : doaj.bitlyShortener, + // urlShortener : doaj.bitlyShortener, embedSnippet : doaj.publicSearch.embedSnippet, renderer: doaj.renderers.newShareEmbedRenderer({ shareLinkText: ' Share or embed' diff --git a/portality/static/js/edges/public.journal.edge.js b/portality/static/js/edges/public.journal.edge.js index 47d366d55d..8904e50dfb 100644 --- a/portality/static/js/edges/public.journal.edge.js +++ b/portality/static/js/edges/public.journal.edge.js @@ -224,7 +224,7 @@ $.extend(true, doaj, { edges.newFullSearchController({ id: "share_embed", category: "controller", - urlShortener : doaj.bitlyShortener, + // urlShortener : doaj.bitlyShortener, embedSnippet : doaj.publicSearch.embedSnippet, renderer: doaj.renderers.newShareEmbedRenderer({ shareLinkText: ' Share or embed' @@ -274,7 +274,7 @@ $.extend(true, doaj, { value: false }) ], - display : "Without article processing charges (APCs)" + display : "Without any fees" } ], fieldDisplays : { diff --git a/portality/static/js/notifications.js b/portality/static/js/notifications.js index c564aff81e..9492402824 100644 --- a/portality/static/js/notifications.js +++ b/portality/static/js/notifications.js @@ -58,7 +58,7 @@ doaj.notifications.notificationsReceived = function(data) { } $(".notification_action_link").on("click", doaj.notifications.notificationClicked); - $(".dropdown--notifications").hoverIntent(doaj.notifications.showDropdown, doaj.notifications.hideDropdown); + $("#notifications_nav").hoverIntent(doaj.notifications.showDropdown, doaj.notifications.hideDropdown); } doaj.notifications.showDropdown = function(e) { diff --git a/portality/static/js/tourist.js b/portality/static/js/tourist.js index cc0f375f0a..b2931f6dcf 100644 --- a/portality/static/js/tourist.js +++ b/portality/static/js/tourist.js @@ -15,6 +15,17 @@ doaj.tourist.init = function(params) { if (first) { doaj.tourist.start(first); } + + $("#dropdown--tour_nav").hoverIntent(doaj.tourist.showDropdown, doaj.tourist.hideDropdown); +} + +doaj.tourist.showDropdown = function(e) { + console.log("showDropdown` called") + $("#feature_tours").show(); +} +doaj.tourist.hideDropdown = function() { + console.log("showDropdown` called") + $("#feature_tours").hide(); } doaj.tourist.findNextTour = function() { diff --git a/portality/static/widget/fq_widget_depends_compiled.js b/portality/static/widget/fq_widget_depends_compiled.js index a913236580..5bbbc208a4 100644 --- a/portality/static/widget/fq_widget_depends_compiled.js +++ b/portality/static/widget/fq_widget_depends_compiled.js @@ -1,5 +1,7 @@ /** base namespace for all DOAJ-specific functions */ +// ~~ DOAJ:Library ~~ var doaj = { + scrollPosition: 100, init : function() { // Use Feather icons feather.replace(); @@ -8,50 +10,48 @@ var doaj = { var openMenu = document.querySelector(".secondary-nav__menu-toggle"); var nav = document.querySelector(".secondary-nav__menu"); - openMenu.addEventListener('click', function() { - nav.classList.toggle("secondary-nav__menu-toggle--active"); - }, false); + if (openMenu) { + openMenu.addEventListener('click', function () { + nav.classList.toggle("secondary-nav__menu-toggle--active"); + }, false); + } - // Display back-to-top button on scroll - var topBtn = document.getElementById("top"); + // On scroll, display back-to-top button & add class to header primary menu + var topBtn = document.getElementById("top"), + topNav = document.querySelector(".primary-nav"); - function displayTopBtn() { - if (topBtn) { - if (document.body.scrollTop > 100 || document.documentElement.scrollTop > 100) { + function displayOnScroll() { + if (topBtn && topNav) { + if (document.body.scrollTop > doaj.scrollPosition || document.documentElement.scrollTop > doaj.scrollPosition) { topBtn.style.display = "flex"; + topNav.classList.add("primary-nav--scrolled"); } else { topBtn.style.display = "none"; + topNav.classList.remove("primary-nav--scrolled"); } } } - // Hide header menu on down scroll; display on scroll up - var prevScrollPos = window.pageYOffset, - topNav = document.querySelector(".primary-nav"); - - function hideNav() { - var currentScrollPos = window.pageYOffset; - - if (prevScrollPos > currentScrollPos) { - topNav.classList.remove("primary-nav--scrolled"); - } else { - topNav.classList.add("primary-nav--scrolled"); - } - prevScrollPos = currentScrollPos; - } - - window.onscroll = function() { - displayTopBtn(); - hideNav(); - }; + window.addEventListener("scroll", function() { + displayOnScroll(); + }); // Tabs jQuery (function($) { $("[role='tab']").click(function(e) { e.preventDefault(); - $(this).attr("aria-selected", "true"); - $(this).parent().siblings().children().attr("aria-selected", "false"); - var tabpanelShow = $(this).attr("href"); + let el = $(this); + + el.attr("aria-selected", "true"); + el.siblings().children().attr("aria-selected", "false"); + + let tabpanelShow = el.attr("href"); + if (!tabpanelShow) { + let innerLink = el.find("a"); + innerLink.attr("aria-selected", "true"); + tabpanelShow = innerLink.attr("href") + } + $(tabpanelShow).attr("aria-hidden", "false"); $(tabpanelShow).siblings().attr("aria-hidden", "true"); }); @@ -69,29 +69,29 @@ var doaj = { doaj.bindMiniSearch(); }, - bitlyShortener : function(query, success_callback, error_callback) { - - function callbackWrapper(data) { - success_callback(data.url); - } - - function errorHandler() { - alert("Sorry, we're unable to generate short urls at this time"); - error_callback(); - } - - var page = window.location.protocol + '//' + window.location.host + window.location.pathname; - - $.ajax({ - type: "POST", - contentType: "application/json", - dataType: "jsonp", - url: "/service/shorten", - data : JSON.stringify({page: page, query: query}), - success: callbackWrapper, - error: errorHandler - }); - }, + // bitlyShortener : function(query, success_callback, error_callback) { + // // ~~-> Bitly:ExternalService ~~ + // function callbackWrapper(data) { + // success_callback(data.url); + // } + // + // function errorHandler() { + // alert("Sorry, we're unable to generate short urls at this time"); + // error_callback(); + // } + // + // var page = window.location.protocol + '//' + window.location.host + window.location.pathname; + // + // $.ajax({ + // type: "POST", + // contentType: "application/json", + // dataType: "jsonp", + // url: "/service/shorten", + // data : JSON.stringify({page: page, query: query}), + // success: callbackWrapper, + // error: errorHandler + // }); + // }, journal_toc_id : function(journal) { // if e-issn is available, use that @@ -244,6 +244,59 @@ var doaj = { } else { doaj.scroller.doScroll = true; } + }, + + searchQuerySource : function (params) { + // ~~-> Edges:Technology ~~ + // ~~-> Elasticsearch:Technology ~~ + // ~~-> Edges:Query ~~ + let terms = params.terms; + let term = params.term; + let queryString = params.queryString; + let sort = params.sort; + + let musts = []; + if (terms) { + for (let term of terms) { + musts.push({"terms" : term}) + } + } + + if (term) { + for (let t of term) { + musts.push({"term" : t}); + } + } + + if (queryString) { + musts.push({ + "query_string" : { + "default_operator" : "AND", + "query" : queryString + } + }) + } + + let query = {"match_all": {}} + if (musts.length > 0) { + query = { + "bool" : { + "must" : musts + } + } + } + + let obj = {"query": query} + if (sort) { + if (Array.isArray(sort)) { + obj["sort"] = sort; + } else { + obj["sort"] = [sort]; + } + } + + let source = JSON.stringify(obj) + return encodeURIComponent(source) } }; @@ -4494,6 +4547,24 @@ $.extend(edges, { }); $.extend(true, doaj, { + filters : { + noCharges : function() { + return { + id: "no_charges", + display: "Without fees", + must: [ + es.newTermFilter({ + field: "bibjson.apc.has_apc", + value: false + }), + es.newTermFilter({ + field: "bibjson.other_charges.has_other_charges", + value: false + }) + ] + } + } + }, facets : { inDOAJ : function() { return edges.newRefiningANDTermSelector({ @@ -4503,8 +4574,18 @@ $.extend(true, doaj, { display: "In DOAJ?", deactivateThreshold: 1, valueMap : { - 1 : "True", - 0 : "False" + 1 : "Yes", + 0 : "No", + true: "Yes", + false: "No" + }, + parseSelectedValueString: function(val) { + // this is needed because ES7 doesn't understand "1" or `1` to be `true`, so + // we convert the string value of the aggregation back to a boolean + return val === "1" + }, + filterToAggValue : function(val) { + return val === true ? 1 : 0; }, renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ controls: true, @@ -4514,6 +4595,223 @@ $.extend(true, doaj, { hideInactive: true }) }) + }, + + openOrClosed: function() { + return edges.newRefiningANDTermSelector({ + id: "application_type", + category: "facet", + field: "index.application_type.exact", + display: "Open or closed?", + deactivateThreshold : 1, + orderDir: "asc", + valueMap : { + "finished application/update": "Closed", + "update request": "Open", + "new application": "Open" + }, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + + applicationStatus : function() { + return edges.newRefiningANDTermSelector({ + id: "application_status", + category: "facet", + field: "admin.application_status.exact", + display: "Status", + deactivateThreshold: 1, + valueFunction: doaj.valueMaps.adminStatusMap, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + hasEditorGroup : function() { + return edges.newRefiningANDTermSelector({ + id: "has_editor_group", + category: "facet", + field: "index.has_editor_group.exact", + display: "Has editor group?", + deactivateThreshold : 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + hasEditor : function() { + return edges.newRefiningANDTermSelector({ + id: "has_editor", + category: "facet", + field: "index.has_editor.exact", + display: "Has Associate Editor?", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + editorGroup : function() { + return edges.newRefiningANDTermSelector({ + id: "editor_group", + category: "facet", + field: "admin.editor_group.exact", + display: "Editor group", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + editor : function() { + return edges.newRefiningANDTermSelector({ + id: "editor", + category: "facet", + field: "admin.editor.exact", + display: "Editor", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + hasAPC : function() { + return edges.newRefiningANDTermSelector({ + id: "author_pays", + category: "facet", + field: "index.has_apc.exact", + display: "Publication charges?", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + classification : function() { + return edges.newRefiningANDTermSelector({ + id: "classification", + category: "facet", + field: "index.classification.exact", + display: "Classification", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + language : function() { + return edges.newRefiningANDTermSelector({ + id: "language", + category: "facet", + field: "index.language.exact", + display: "Journal language", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + countryPublisher : function() { + return edges.newRefiningANDTermSelector({ + id: "country_publisher", + category: "facet", + field: "index.country.exact", + display: "Country of publisher", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + subject : function() { + return edges.newRefiningANDTermSelector({ + id: "subject", + category: "facet", + field: "index.subject.exact", + display: "Subject", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + publisher : function() { + return edges.newRefiningANDTermSelector({ + id: "publisher", + category: "facet", + field: "bibjson.publisher.name.exact", + display: "Publisher", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) + }, + journalLicence : function() { + return edges.newRefiningANDTermSelector({ + id: "journal_license", + category: "facet", + field: "index.license.exact", + display: "Journal license", + deactivateThreshold: 1, + renderer: edges.bs3.newRefiningANDTermSelectorRenderer({ + controls: true, + open: false, + togglable: true, + countFormat: doaj.valueMaps.countFormat, + hideInactive: true + }) + }) } }, @@ -4531,6 +4829,13 @@ $.extend(true, doaj, { 'accepted' : 'Accepted' }, + adminStatusMap: function(value) { + if (doaj.valueMaps.applicationStatus.hasOwnProperty(value)) { + return doaj.valueMaps.applicationStatus[value]; + } + return value; + }, + displayYearPeriod : function(params) { var from = params.from; var to = params.to; @@ -4539,6 +4844,16 @@ $.extend(true, doaj, { return {to: to, toType: "lt", from: from, fromType: "gte", display: display} }, + displayYearMonthPeriod : function(params) { + var from = params.from; + var to = params.to; + var field = params.field; + + let d = new Date(parseInt(from)) + let display = d.getUTCFullYear().toString() + "-" + doaj.valueMaps.monthPadding(d.getUTCMonth() + 1); + return {to: to, toType: "lt", from: from, fromType: "gte", display: display} + }, + schemaCodeToNameClosure : function(tree) { var nameMap = {}; function recurse(ctx) { @@ -4564,10 +4879,36 @@ $.extend(true, doaj, { countFormat : edges.numFormat({ thousandsSeparator: "," + }), + + monthPadding: edges.numFormat({ + zeroPadding: 2 }) }, - components : { + pager : function(id, category) { + return edges.newPager({ + id: id, + category: category, + renderer: edges.bs3.newPagerRenderer({ + sizeOptions: [10, 25, 50, 100], + numberFormat: doaj.valueMaps.countFormat, + scroll: false + }) + }) + }, + + searchingNotification : function() { + return edges.newSearchingNotification({ + id: "searching-notification", + category: "searching-notification", + finishedEvent: "edges:post-render", + renderer : doaj.renderers.newSearchingNotificationRenderer({ + scrollOnSearch: true + }) + }) + }, + subjectBrowser : function(params) { var tree = params.tree; var hideEmpty = edges.getParam(params.hideEmpty, false); @@ -4618,7 +4959,7 @@ $.extend(true, doaj, { showCounts: false }) }) - }, + } }, templates : { @@ -4651,7 +4992,7 @@ $.extend(true, doaj, {

\

\
\ -
\ +
\ \
\ \ -
\ +
\ \
` this.component.edge.context.before(frag); - let offset = $(this.scrollTarget).offset().top - window.scrollTo(0, offset); + + if (this.scrollOnSearch) { + let offset = $(this.scrollTarget).offset().top + window.scrollTo(0, offset); + } } else { let that = this; let idSelector = edges.css_id_selector(this.namespace, "loading", this); @@ -4841,13 +5193,13 @@ $.extend(true, doaj, { toggle = ''; } var placeholder = 'Search ' + this.component.nodeCount + ' subjects'; - var frag = '

' + this.title + toggle + '

\ - '; // substitute in the component parts frag = frag.replace(/{{FILTERS}}/g, treeFrag); @@ -5131,8 +5483,7 @@ $.extend(true, doaj, { sortOptions = '
\ \ - '; for (var i = 0; i < comp.sortOptions.length; i++) { var field = comp.sortOptions[i].field; @@ -5140,7 +5491,8 @@ $.extend(true, doaj, { sortOptions += ''; } - sortOptions += '
'; + sortOptions += '\ +
'; } // select box for fields to search on @@ -5194,7 +5546,7 @@ $.extend(true, doaj, { clearFrag = '
' + clearFrag + '
'; } - var frag = searchBox + '
' + sortOptions + clearFrag + '
'; + var frag = searchBox + '
' + sortOptions + clearFrag + '
'; comp.context.html(frag); @@ -5274,13 +5626,14 @@ $.extend(true, doaj, { }; this.setUISortField = function () { + let sb = this.component.sortBy if (!this.component.sortBy) { - return; + sb = "_score"; } // get the selector we need var sortSelector = edges.css_class_selector(this.namespace, "sortby", this); var el = this.component.jq(sortSelector); - el.val(this.component.sortBy); + el.val(sb); }; this.setUISearchField = function () { @@ -5497,15 +5850,15 @@ $.extend(true, doaj, { var comp = this.component; - var shareButtonFrag = ""; var shareButtonClass = edges.css_classes(this.namespace, "toggle-share", this); var modalId = edges.css_id(this.namespace, "modal", this); - shareButtonFrag = ''; + let shareButtonFrag = ''; - var shorten = ""; + let shorten = ""; if (this.component.urlShortener) { var shortenClass = edges.css_classes(this.namespace, "shorten", this); - shorten = '

Share a link to this search

' + var shortenButtonClass = edges.css_classes(this.namespace, "shorten-url", this) + shorten = '

'; } var embed = ""; if (this.component.embedSnippet) { @@ -5515,11 +5868,11 @@ $.extend(true, doaj, { } var shareBoxClass = edges.css_classes(this.namespace, "share", this); var shareUrlClass = edges.css_classes(this.namespace, "share-url", this); - var shortenButtonClass = edges.css_classes(this.namespace, "shorten-url", this); + var shareFrag = '
\ - ' + shorten + '\ +

Share a link to this search

\ \ -

\ + ' + shorten + '\ ' + embed + '\
'; @@ -5746,13 +6099,12 @@ $.extend(true, doaj, { var textIdSelector = edges.css_id_selector(this.namespace, "text", this); var text = this.component.jq(textIdSelector).val(); - if (text === "") { - return; - } - // if there is search text, then proceed to run the search var val = this.component.jq(element).val(); this.component.setSearchField(val, false); + if (text === "") { + return; + } this.component.setSearchText(text); }; @@ -6028,10 +6380,10 @@ $.extend(true, doaj, { if (this.togglable) { toggle = ''; } - var frag = '

' + this.component.display + toggle + '

\ - '; // substitute in the component parts frag = frag.replace(/{{FILTERS}}/g, filterFrag + results); @@ -6075,7 +6427,7 @@ $.extend(true, doaj, { //var i = toggle.find("i"); //for (var j = 0; j < openBits.length; j++) { // i.removeClass(openBits[j]); - // } + // } //for (var j = 0; j < closeBits.length; j++) { // i.addClass(closeBits[j]); //} @@ -6087,9 +6439,9 @@ $.extend(true, doaj, { //var i = toggle.find("i"); //for (var j = 0; j < closeBits.length; j++) { // i.removeClass(closeBits[j]); - // } + // } //for (var j = 0; j < openBits.length; j++) { - // i.addClass(openBits[j]); + // i.addClass(openBits[j]); //} //results.hide(); @@ -6135,6 +6487,7 @@ $.extend(true, doaj, { // whether to hide or just disable the facet if not active this.hideInactive = edges.getParam(params.hideInactive, false); + this.hideEmpty = edges.getParam(params.hideEmpty, false) // whether the facet should be open or closed // can be initialised and is then used to track internal state @@ -6199,6 +6552,9 @@ $.extend(true, doaj, { // render each value, if it is not also a filter that has been set for (var i = 0; i < ts.values.length; i++) { var val = ts.values[i]; + if (val.count === 0 && this.hideEmpty) { + continue + } if ($.inArray(val.display, filterTerms) === -1) { var ltData = ""; @@ -6275,10 +6631,10 @@ $.extend(true, doaj, { if (this.togglable) { toggle = ''; } - var frag = '

' + this.component.display + toggle + '

\ - '; // substitute in the component parts frag = frag.replace(/{{FILTERS}}/g, filterFrag + results); @@ -6333,7 +6689,7 @@ $.extend(true, doaj, { //var i = toggle.find("i"); //for (var j = 0; j < openBits.length; j++) { // i.removeClass(openBits[j]); - // } + // } //for (var j = 0; j < closeBits.length; j++) { // i.addClass(closeBits[j]); //} @@ -6345,9 +6701,9 @@ $.extend(true, doaj, { //var i = toggle.find("i"); //for (var j = 0; j < closeBits.length; j++) { // i.removeClass(closeBits[j]); - // } + // } //for (var j = 0; j < openBits.length; j++) { - // i.addClass(openBits[j]); + // i.addClass(openBits[j]); //} //results.hide(); @@ -6468,7 +6824,7 @@ $.extend(true, doaj, { // the remove block looks different, depending on the kind of filter to remove if (def.filter === "term" || def.filter === "terms") { - filters += ''; + filters += ''; filters += def.display + valDisplay; filters += ' '; filters += ""; @@ -6523,6 +6879,7 @@ $.extend(true, doaj, { this.removeFilter = function (element) { var el = this.component.jq(element); + var sf = this.component; // if this is a compound filter, remove it by id var compound = el.attr("data-compound"); @@ -6538,7 +6895,9 @@ $.extend(true, doaj, { var value = false; if (ft === "terms" || ft === "term") { - value = el.attr("data-value"); + let values = sf.mustFilters[field].values; + let idx = el.attr("data-value-idx") + value = values[idx].val } else if (ft === "range") { value = {}; @@ -6737,14 +7096,20 @@ $.extend(true, doaj, { var seal = ""; if (edges.objVal("admin.seal", resultobj, false)) { - seal = '' + seal = '' if (this.widget){ - seal += 'check-circle icon' + seal += ' DOAJ Seal' } else { - seal += '' + seal += '\ + \ + \ + \ + \ + \ +

DOAJ Seal

\ + '; } - seal += ' DOAJ Seal'; } var issn = resultobj.bibjson.pissn; if (!issn) { @@ -6775,9 +7140,9 @@ $.extend(true, doaj, { // add the subjects var subjects = ""; if (edges.hasProp(resultobj, "index.classification_paths") && resultobj.index.classification_paths.length > 0) { - subjects = '

Journal subjects

    '; - subjects += "
  • " + resultobj.index.classification_paths.join(", 
  • ") + "
  • "; - subjects += '
'; + subjects = '

Journal subjects

    '; + subjects += "
  • " + resultobj.index.classification_paths.join(", 
  • ") + "
  • "; + subjects += '
'; } var update_or_added = ""; @@ -6814,6 +7179,15 @@ $.extend(true, doaj, { } apcs += ''; + var rights = ""; + if (resultobj.bibjson.copyright) { + var copyright_url = resultobj.bibjson.copyright.url; + rights += ''; + rights += resultobj.bibjson.copyright.author_retains ? 'Author retains all rights' : 'Author doesn\'t retain all rights'; + rights += ''; + } + + var licenses = ""; if (resultobj.bibjson.license && resultobj.bibjson.license.length > 0) { var terms_url = resultobj.bibjson.ref.license_terms; @@ -6821,7 +7195,7 @@ $.extend(true, doaj, { var lic = resultobj.bibjson.license[i]; var license_url = lic.url || terms_url; licenses += '' + edges.escapeHtml(lic.type) + ''; - if (i != (resultobj.bibjson.license.length-1)) { + if (i !== (resultobj.bibjson.license.length-1)) { licenses += ', '; } } @@ -6835,15 +7209,26 @@ $.extend(true, doaj, { } var actions = ""; + var modals = ""; if (this.actions.length > 0) { actions = '

Actions

    '; for (var i = 0; i < this.actions.length; i++) { var act = this.actions[i]; var actSettings = act(resultobj); if (actSettings) { + let data = ""; + if (actSettings.data) { + let dataAttrs = Object.keys(actSettings.data); + for(let j = 0; j < dataAttrs.length; j++) { + data += " data-" + dataAttrs[j] + "=" + actSettings.data[dataAttrs[j]]; + } + } actions += '
  • \ - ' + actSettings.label + '\ + ' + actSettings.label + '\
  • '; + if (actSettings.modal) { + modals += actSettings.modal + } } } actions += '
'; @@ -6853,7 +7238,6 @@ $.extend(true, doaj, {
\ \
\ \ \ '; @@ -6960,9 +7348,9 @@ $.extend(true, doaj, { var subjects = ""; if (edges.hasProp(resultobj, "index.classification_paths") && resultobj.index.classification_paths.length > 0) { - subjects = '

Journal subjects

    '; - subjects += "
  • " + resultobj.index.classification_paths.join(", 
  • ") + "
  • "; - subjects += '
'; + subjects = '

Journal subjects

    '; + subjects += "
  • " + resultobj.index.classification_paths.join(", 
  • ") + "
  • "; + subjects += '
'; } var subjects_or_keywords = keywords === "" ? subjects : keywords; @@ -7076,11 +7464,11 @@ $.extend(true, doaj, { \ \ '; - /* -
  • \ - ' + license + '\ -
  • \ - */ + /* +
  • \ + ' + license + '\ +
  • \ + */ // close off the result and return return frag; }; @@ -7233,7 +7621,7 @@ $.extend(true, doaj, { var deleteLinkUrl = deleteLinkTemplate.replace("__application_id__", resultobj.id); var deleteClass = edges.css_classes(this.namespace, "delete", this); if (resultobj.es_type === "draft_application" || - resultobj.admin.application_status === "update_request") { + resultobj.admin.application_status === "update_request") { deleteLink = '
  • \ \ @@ -7370,20 +7758,7 @@ $.extend(true, doaj, { } var last_updated = "Last updated "; - last_updated += doaj.humanDate(resultobj.last_updated); - - /* - var icon = "edit-3"; - if (accessLink[1] === "View") { - icon = "eye"; - } - var viewOrEdit = '
  • \ - \ - \ - ' + accessLink[1] + '\ - \ -
  • '; - */ + last_updated += doaj.humanDate(resultobj.last_manual_update); var deleteLink = ""; var deleteLinkTemplate = doaj.publisherUpdatesSearchConfig.deleteLinkTemplate; @@ -7483,9 +7858,9 @@ $.extend(true, doaj, { this._renderPublicJournal = function(resultobj) { var seal = ""; if (edges.objVal("admin.seal", resultobj, false)) { - seal = '\ - \ - DOAJ Seal\ + seal = '\ + \ + DOAJ Seal\ '; } var issn = resultobj.bibjson.pissn; @@ -7517,9 +7892,9 @@ $.extend(true, doaj, { // add the subjects var subjects = ""; if (edges.hasProp(resultobj, "index.classification_paths") && resultobj.index.classification_paths.length > 0) { - subjects = '

    Journal subjects

      '; - subjects += "
    • " + resultobj.index.classification_paths.join(", 
    • ") + "
    • "; - subjects += '
    '; + subjects = '

    Journal subjects

      '; + subjects += "
    • " + resultobj.index.classification_paths.join(", 
    • ") + "
    • "; + subjects += '
    '; } var update_or_added = ""; @@ -7592,7 +7967,6 @@ $.extend(true, doaj, {
    \