Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for signed/published-resource contents in files #640

Merged
merged 5 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fresh-queens-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"frontend-gelinkt-notuleren": patch
---

Add support for signed-resources and published-resources which have their content in files instead of directly as text
5 changes: 5 additions & 0 deletions .changeset/mean-apricots-brush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"frontend-gelinkt-notuleren": patch
---

Fix some bugs around versioned-notulen with content stored as files
2 changes: 1 addition & 1 deletion app/components/signatures/notulen/behandelingen-list.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
{{#let (includes preview.behandeling @publicBehandelingUris) as |isPublicBehandeling|}}
<div class="au-c-editor-preview-treatment {{if isPublicBehandeling "au-c-editor-preview-treatment--public" "au-c-editor-preview-treatment--private"}}">
<div class="au-o-box au-c-editor-preview-treatment__info {{if isPublicBehandeling "au-c-editor-preview-treatment__info--public" "au-c-editor-preview-treatment__info--private"}} ">
{{#if this.isPublished}}
{{#if @isPublished}}
{{#if isPublicBehandeling}}
<p><strong>{{t "behandeling-list.public-past"}}</strong></p>
<p class="au-c-help-text au-c-help-text--normal">{{t "behandeling-list.public-description"}}</p>
Expand Down
7 changes: 0 additions & 7 deletions app/components/signatures/notulen/behandelingen-list.js

This file was deleted.

42 changes: 24 additions & 18 deletions app/controllers/meetings/publish/notulen.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { task } from 'ember-concurrency';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { isEmpty } from '@ember/utils';
import { getResourceContent } from 'frontend-gelinkt-notuleren/utils/get-resource-content';

export default class MeetingsPublishNotulenController extends Controller {
@service store;
Expand All @@ -14,7 +15,10 @@ export default class MeetingsPublishNotulenController extends Controller {

behandelingContainerId = 'behandeling-van-agendapunten-container';
@tracked notulen;
@tracked fullNotulen;
@tracked fullNotulenContent;
// Since content can be in a file or in the triplestore, handle content independently from the
// notulen itself
@tracked notulenContent;
@tracked errors;
@tracked validationErrors;
@tracked signedResources = [];
Expand All @@ -33,6 +37,7 @@ export default class MeetingsPublishNotulenController extends Controller {

resetController() {
this.notulen = null;
this.notulenContent = null;
this.errors = null;
this.validationErrors = null;
this.signedResources = [];
Expand Down Expand Up @@ -149,13 +154,14 @@ export default class MeetingsPublishNotulenController extends Controller {
this.publishedResource = publishedResource;
}
this.publicBehandelingUris = publicNotulen.publicBehandelingen || [];
if (isEmpty(publicNotulen.content)) {
const fileMeta = await publicNotulen.file;
publicNotulen.content = await (
await fetch(fileMeta.downloadLink)
).text();
}
this.notulen = publicNotulen;
// Fetching the file could take time or fail, so clear the content first
this.notulenContent = null;
this.notulenContent = !isEmpty(publicNotulen.content)
? publicNotulen.content
: await getResourceContent(publicNotulen, (statusText) => {
this.errors = [`Error fetching file contents: ${statusText}`];
});
} else {
try {
// generate a rendered document
Expand All @@ -173,6 +179,7 @@ export default class MeetingsPublishNotulenController extends Controller {
});

this.notulen = rslt;
this.notulenContent = content;
this.validationErrors = errors;
} catch (e) {
console.error(e);
Expand Down Expand Up @@ -200,7 +207,12 @@ export default class MeetingsPublishNotulenController extends Controller {
this.signedResources = signedNonDeletedResources;
this.hasDeletedSignedResources =
!!signedDeletedResources.toArray().length;
this.fullNotulen = fullNotulen;
this.fullNotulenContent = await getResourceContent(
fullNotulen,
(statusText) => {
this.errors = [`Error fetching file contents: ${statusText}`];
},
);
} else {
// this means there are no signatures
try {
Expand All @@ -211,12 +223,7 @@ export default class MeetingsPublishNotulenController extends Controller {
// -> this will still show all the contents as they always sign everything
const { content, errors } =
await this.createPrePublishedResource.perform();
const rslt = await this.store.createRecord('versioned-notulen', {
zitting: this.model,
content: content,
kind: 'full',
});
this.fullNotulen = rslt;
this.fullNotulenContent = content;
this.validationErrors = errors;
} catch (e) {
console.error(e);
Expand Down Expand Up @@ -358,9 +365,9 @@ export default class MeetingsPublishNotulenController extends Controller {
});

get zittingWrapper() {
if (this.notulen?.content) {
if (this.notulenContent) {
const div = document.createElement('div');
div.innerHTML = this.notulen.content;
div.innerHTML = this.notulenContent;

const bvapContainer = div.querySelector(
"[property='http://mu.semte.ch/vocabularies/ext/behandelingVanAgendapuntenContainer']",
Expand All @@ -386,7 +393,7 @@ export default class MeetingsPublishNotulenController extends Controller {

updateNotulenPreview() {
const div = document.createElement('div');
div.innerHTML = this.notulen.content;
div.innerHTML = this.notulenContent;

const behandelingNodes = div.querySelectorAll(
"[typeof='besluit:BehandelingVanAgendapunt']",
Expand All @@ -401,7 +408,6 @@ export default class MeetingsPublishNotulenController extends Controller {
}
});

this.notulen.set('body', div.innerHTML);
this.allBehandelingPublic =
this.treatments.length === this.publicBehandelingUris.length;
}
Expand Down
1 change: 1 addition & 0 deletions app/controllers/meetings/publish/uittreksels/show.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export default class MeetingsPublishUittrekselsShowController extends Controller
@action
async refreshRoute() {
await this.router.refresh();
this.error = undefined;
}

signDocumentTask = task(async () => {
Expand Down
21 changes: 15 additions & 6 deletions app/models/published-resource.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import Model, { attr, belongsTo } from '@ember-data/model';

export default class PublishedResourceModel extends Model {
/** Optional, content might be in .file instead **/
@attr content;
@attr hashValue;
@attr('datetime') createdOn;
@attr submissionStatus;

@belongsTo('blockchain-status', { inverse: null }) status;
@belongsTo('gebruiker', { inverse: null }) gebruiker;
@belongsTo('agenda', { inverse: 'publishedResource' }) agenda;
@belongsTo('versioned-besluiten-lijst', { inverse: 'publishedResource' })
@belongsTo('blockchain-status', { async: true, inverse: null }) status;
@belongsTo('gebruiker', { async: true, inverse: null }) gebruiker;
@belongsTo('agenda', { async: true, inverse: 'publishedResource' }) agenda;
@belongsTo('versioned-besluiten-lijst', {
async: true,
inverse: 'publishedResource',
})
versionedBesluitenLijst;
@belongsTo('versioned-behandeling', { inverse: 'publishedResource' })
@belongsTo('versioned-behandeling', {
async: true,
inverse: 'publishedResource',
})
versionedBehandeling;
@belongsTo('versioned-notulen', { inverse: 'publishedResource' })
@belongsTo('versioned-notulen', { async: true, inverse: 'publishedResource' })
versionedNotulen;
/** Optional, content might be in .content instead **/
@belongsTo('file', { async: true, inverse: null }) file;
}
21 changes: 15 additions & 6 deletions app/models/signed-resource.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import Model, { attr, belongsTo } from '@ember-data/model';

export default class SignedResourceModel extends Model {
/** Optional, content might be in .file instead **/
@attr content;
@attr hashValue;
@attr('boolean', { defaultValue: false }) deleted;
@attr('datetime') createdOn;

@belongsTo('blockchain-status', { inverse: null }) status;
@belongsTo('gebruiker', { inverse: null }) gebruiker;
@belongsTo('agenda', { inverse: 'signedResources' }) agenda;
@belongsTo('versioned-besluiten-lijst', { inverse: 'signedResources' })
@belongsTo('blockchain-status', { async: true, inverse: null }) status;
@belongsTo('gebruiker', { async: true, inverse: null }) gebruiker;
@belongsTo('agenda', { async: true, inverse: 'signedResources' }) agenda;
@belongsTo('versioned-besluiten-lijst', {
async: true,
inverse: 'signedResources',
})
versionedBesluitenLijst;
@belongsTo('versioned-behandeling', { inverse: 'signedResources' })
@belongsTo('versioned-behandeling', {
async: true,
inverse: 'signedResources',
})
versionedBehandeling;
@belongsTo('versioned-notulen', { inverse: 'signedResources' })
@belongsTo('versioned-notulen', { async: true, inverse: 'signedResources' })
versionedNotulen;
/** Optional, content might be in .content instead **/
@belongsTo('file', { async: true, inverse: null }) file;
}
2 changes: 1 addition & 1 deletion app/models/versioned-notulen.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Model, { attr, hasMany, belongsTo } from '@ember-data/model';

export default class VersionedNotulesModel extends Model {
export default class VersionedNotulenModel extends Model {
@attr state;
@attr content;
@attr publicContent;
Expand Down
10 changes: 8 additions & 2 deletions app/routes/meetings/publish/publication-actions/detail.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import Route from '@ember/routing/route';
import { service } from '@ember/service';
import { getResourceContent } from 'frontend-gelinkt-notuleren/utils/get-resource-content';

function onError(statusText) {
return `<div class="au-c-alert au-c-alert--warning"><p>Error fetching file contents: ${statusText}</p></div>`;
}

export default class MeetingsPublishPublicationActionsDetailRoute extends Route {
@service store;

Expand All @@ -13,10 +19,10 @@ export default class MeetingsPublishPublicationActionsDetailRoute extends Route
);
const signedResource = await log.signedResource;
if (signedResource) {
return signedResource.content;
return getResourceContent(signedResource, onError);
} else {
const publishedResource = await log.publishedResource;
return publishedResource.content;
return getResourceContent(publishedResource, onError);
}
}
}
4 changes: 2 additions & 2 deletions app/routes/meetings/publish/uittreksels/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default class MeetingsPublishUittrekselsRoute extends Route {
query['filter[titel]'] = params.title;
}
const result = await this.store.query('agendapunt', query);
const agendapoints = result.toArray();
const agendapoints = result.slice();
const agendapointsToDisplay = [];
agendapointsToDisplay.meta = result.meta;

Expand All @@ -48,7 +48,7 @@ export default class MeetingsPublishUittrekselsRoute extends Route {
'filter[:or:][deleted]': false,
'filter[:or:][:has-no:deleted]': 'yes',
})
).toArray()[0];
)[0];
agendapointsToDisplay.push({
titel: agendapoint.titel,
behandeling: agendapoint.behandeling,
Expand Down
2 changes: 1 addition & 1 deletion app/routes/meetings/publish/uittreksels/show.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export default class MeetingsPublishUittrekselsShowRoute extends Route {
versionedTreatment,
meeting,
signedResources: signedResources.toArray(),
publishedResource: publishedResource.firstObject,
publishedResource: publishedResource[0],
// if a versionedTreatment exists, that means some signature or publication
// has happened, which means that there are no errors, so we can safely do this
validationErrors: [],
Expand Down
6 changes: 3 additions & 3 deletions app/templates/meetings/publish/notulen.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
@status={{if (eq this.status "published") "published" "concept"}}
>
{{#if (eq this.status 'published')}}
{{html-safe this.notulen.content}}
{{html-safe this.notulenContent}}
{{else}}
{{html-safe this.zittingWrapper}}
{{/if}}
{{#if this.showPublicToggles}}
{{#in-element this.containerElement}}
<Signatures::Notulen::BehandelingenList
@notulen={{this.notulen}}
@isPublished={{this.isPublished}}
@prepublishedBehandelingen={{this.treatments}}
@publicBehandelingUris={{this.publicBehandelingUris}}
@toggle={{this.togglePublicationStatus}}
Expand Down Expand Up @@ -64,7 +64,7 @@
@confirm={{perform this.createSignedResource}}
@cancel={{fn (mut this.showSigningModal) false}}
>
{{html-safe this.fullNotulen.content}}
{{html-safe this.fullNotulenContent}}
</Signatures::SignatureConfirmation>
{{/if}}
{{!-- publish --}}
Expand Down
4 changes: 2 additions & 2 deletions app/templates/meetings/publish/uittreksels/show.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<AuAlert @icon="cross" @sitle={{t "publish.excerpt-error-message"}} @skin="error" @size="small">
<code>{{this.error}}</code>
</AuAlert>
<AuButton {{on "click" (perform this.loadExtract)}}>{{t "publish.retry"}}</AuButton>
<AuButton {{on "click" this.refreshRoute}}>{{t "publish.retry"}}</AuButton>
{{else}}
<div class="au-u-margin-bottom-small au-u-hide-on-print">
<AuLink @route="meetings.publish.uittreksels" @skin="secondary" @icon="arrow-left" @iconAlignment="left">{{t
Expand Down Expand Up @@ -186,4 +186,4 @@
@mockDocument={{this.previewDocument}}
@confirm={{perform this.publishDocumentTask}}
@cancel={{this.closePublishingModal}} />
{{/if}}
{{/if}}
22 changes: 22 additions & 0 deletions app/utils/get-resource-content.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Get the content of a resource from the `content` member if possible,
* otherwise by fetching the file from the `file` member
*
* @param {SignedResourceModel | PublishedResourceModel | VersionedNotulenModel} resource - resource to get the content of
* @param {(statusText: string) => void} - Optional on-error callback to handle the status code
**/
export async function getResourceContent(resource, onError) {
if (resource?.content) {
return resource.content;
} else {
const fileMeta = await resource.file;
if (fileMeta && fileMeta.downloadLink) {
const fileReq = await fetch(fileMeta.downloadLink);
if (fileReq.ok) {
return fileReq.text();
} else {
onError?.(fileReq.statusText);
}
}
}
}