Skip to content

Commit

Permalink
Fulfil assignment (#1848)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecalcc authored Oct 9, 2024
1 parent 5d8a729 commit 486497b
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 158 deletions.
118 changes: 63 additions & 55 deletions client/controllers/FulfilAssignmentController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,31 @@ import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {ModalsContainer} from '../components';
import {get} from 'lodash';
import {registerNotifications} from '../utils';
import {WORKSPACE, MODALS, ASSIGNMENTS} from '../constants';
import {getErrorMessage} from '../utils/index';
import {IArticle} from 'superdesk-api';

export class FulFilAssignmentController {
item: IArticle;
notify: {error: (message: string) => void};
gettext: (
value: string,
params?: {[placeholder: string]: string | number | React.ComponentType},
) => string;
$scope: any;
store: any;
newsItem: any;
rendered: boolean;
$timeout: any;
lock: any;
$element: any;
session: any;
superdeskFlags: any;
userList: any;
api: any;
desks: any;

constructor(
$element,
$scope,
Expand Down Expand Up @@ -43,20 +62,18 @@ export class FulFilAssignmentController {

this.store = null;
this.newsItem = null;
this.item = get($scope, 'locals.data.item', {});
this.item = $scope.locals?.data.item ?? {};
this.rendered = false;

$scope.$on('$destroy', this.onDestroy);
$scope.$on('item:unlock', this.onItemUnlock);

if (get(this.item, 'archive_item')) { // use archive item for published
if (this.item?.archive_item) { // use archive item for published
this.item = this.item.archive_item;
}

if (get(this.item, 'slugline', '') === '') {
this.notify.error(
this.gettext('[SLUGLINE] is a required field')
);
if (!this.item?.slugline) {
this.notify.error(this.gettext('[SLUGLINE] is a required field'));
this.$scope.resolve();
return;
}
Expand Down Expand Up @@ -92,7 +109,7 @@ export class FulFilAssignmentController {
return Promise.resolve();
}

loadWorkspace(store, workspaceChanged) {
loadWorkspace(store) {
this.store = store;

return this.loadArchiveItem()
Expand Down Expand Up @@ -120,15 +137,15 @@ export class FulFilAssignmentController {
}, 1000);

// Only unlock the item if it was locked when launching this modal
if (get(this.newsItem, 'lock_session', null) !== null &&
get(this.newsItem, 'lock_action', 'edit') === 'fulfil_assignment' &&
this.lock.isLockedInCurrentSession(this.newsItem)
if (this.newsItem?.lock_session != null
&& (this.newsItem.lock_action ?? 'edit') === 'fulfil_assignment'
&& this.lock.isLockedInCurrentSession(this.newsItem)
) {
this.lock.unlock(this.newsItem);
}

// update the scope item.
if (this.item && get(this.newsItem, 'assignment_id')) {
if (this.item && this.newsItem?.assignment_id) {
this.item.assignment_id = this.newsItem.assignment_id;
}
}
Expand Down Expand Up @@ -170,51 +187,42 @@ export class FulFilAssignmentController {
}

loadArchiveItem() {
return this.api.find('archive', this.item._id)
.then((newsItem) => {
if (get(newsItem, 'assignment_id')) {
this.notify.error(
this.gettext('Item already linked to a Planning item')
);
this.$scope.resolve();
return Promise.reject();
}
return this.api.find('archive', this.item._id).then((newsItem) => {
if (newsItem?.assignment_id) {
this.notify.error(this.gettext('Item already linked to a Planning item'));
this.$scope.resolve();
return Promise.reject();
}

if (this.lock.isLocked(newsItem)) {
this.notify.error(
this.gettext('Item already locked.')
if (this.lock.isLocked(newsItem)) {
this.notify.error(this.gettext('Item already locked.'));
this.$scope.resolve();
return Promise.reject();
}

if (!this.lock.isLockedInCurrentSession(newsItem)) {
newsItem._editable = true;
return this.lock.lock(newsItem, false, 'fulfil_assignment')
.then(
(lockedItem) => Promise.resolve(lockedItem),
(error) => {
this.notify.error(
this.gettext(getErrorMessage(error, 'Failed to lock the item.'))
);
this.$scope.resolve(error);
return Promise.reject(error);
}
);
this.$scope.resolve();
return Promise.reject();
}

if (!this.lock.isLockedInCurrentSession(newsItem)) {
newsItem._editable = true;
return this.lock.lock(newsItem, false, 'fulfil_assignment')
.then(
(lockedItem) => Promise.resolve(lockedItem),
(error) => {
this.notify.error(
this.gettext(
getErrorMessage(error, 'Failed to lock the item.')
)
);
this.$scope.resolve(error);
return Promise.reject(error);
}
);
}

return Promise.resolve(newsItem);
}, (error) => {
this.notify.error(
this.gettext(
getErrorMessage(error, 'Failed to load the item.')
)
);
this.$scope.resolve(error);
return Promise.reject(error);
});
}

return Promise.resolve(newsItem);
}, (error) => {
this.notify.error(
this.gettext(getErrorMessage(error, 'Failed to load the item.'))
);
this.$scope.resolve(error);
return Promise.reject(error);
});
}
}

Expand Down
5 changes: 4 additions & 1 deletion client/extension_bridge.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';

import {IVocabularyItem} from 'superdesk-api';
import {IArticle, IVocabularyItem} from 'superdesk-api';

import {getAssignmentTypeInfo} from './utils/assignments';
import {SluglineComponent} from './components/Assignments/AssignmentItem/fields/Slugline';
Expand All @@ -10,6 +10,7 @@ import {EditorFieldVocabulary, IEditorFieldVocabularyProps} from './components/f

import {getVocabularyItemFieldTranslated} from './utils/vocabularies';
import {getUserInterfaceLanguageFromCV} from './utils/users';
import {isContentLinkToCoverageAllowed} from './utils/archive';

import {registerEditorField} from './components/fields/resources/registerEditorFields';
import {IAssignmentItem, IEditorFieldProps, IPlanningAppState, IPlanningItem} from 'interfaces';
Expand All @@ -20,6 +21,7 @@ import PlanningDetailsWidget, {getItemPlanningInfo} from './components/PlanningD
interface IExtensionBridge {
assignments: {
utils: {
isContentLinkToCoverageAllowed(item: IArticle): boolean;
getAssignmentTypeInfo(
assignment: IAssignmentItem,
contentTypes: Array<IVocabularyItem>,
Expand Down Expand Up @@ -74,6 +76,7 @@ export const extensionBridge: IExtensionBridge = {
assignments: {
utils: {
getAssignmentTypeInfo,
isContentLinkToCoverageAllowed,
},
components: {
SluglineComponent,
Expand Down
39 changes: 34 additions & 5 deletions client/planning-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const extension: IExtension = {
const displayTopbarWidget = superdesk.privileges.hasPrivilege('planning_assignments_view')
&& extensionConfig?.assignmentsTopBarWidget === true;
const {gettext} = superdesk.localization;

const planningActionsGroupId = 'planning-actions';
const {getItemPlanningInfo} = extensionBridge.planning;

const result: IExtensionActivationResult = {
Expand All @@ -123,12 +123,11 @@ const extension: IExtension = {
getActions: (item) => [
{
label: gettext('Unlink as Coverage'),
groupId: 'planning-actions',
groupId: planningActionsGroupId,
icon: 'cut',
onTrigger: () => {
const superdeskArticle = superdesk.entities.article;

// keep in sync with client/planning-extension/src/extension.ts:123
if (
superdesk.privileges.hasPrivilege('archive') &&
item.assignment_id != null &&
Expand All @@ -140,9 +139,39 @@ const extension: IExtension = {
superdeskArticle.itemAction(item).deschedule
)
) {
const event = new CustomEvent('planning:unlinkfromcoverage', {detail: {item}});
superdeskArticle.get(item._id).then((_item) => {
window.dispatchEvent(new CustomEvent(
'planning:unlinkfromcoverage',
{detail: {item: _item}},
));
});
}
},
},
{
label: superdesk.localization.gettext('Fulfil assignment'),
groupId: planningActionsGroupId,
icon: 'calendar-list',
onTrigger: () => {
const itemStates = ['killed', 'recalled', 'unpublished', 'spiked', 'correction'];
const {isContentLinkToCoverageAllowed} = extensionBridge.assignments.utils;

window.dispatchEvent(event);
if (
!item.assignment_id &&
!superdesk.entities.article.isPersonal(item) &&
isContentLinkToCoverageAllowed(item) &&
!superdesk.entities.article.isLockedInOtherSession(item) &&
!itemStates.includes(item.state) &&
superdesk.privileges.hasPrivilege('archive')
) {
superdesk.entities.article.get(item._id).then((_item) => {
window.dispatchEvent(new CustomEvent(
'planning:fulfilassignment',
{detail: {item: _item}},
));
});
} else {
superdesk.ui.notify.error('This action is not permitted');
}
},
}
Expand Down
2 changes: 2 additions & 0 deletions client/planning-extension/src/extension_bridge.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import {IVocabularyItem} from 'superdesk-api';
import {IAssignmentItem, IEditorFieldProps, IPlanningAppState, IPlanningItem} from '../../interfaces';
import {IArticle} from 'superdesk-api';

interface IEditorFieldVocabularyProps extends IEditorFieldProps {
options: Array<any>;
Expand All @@ -16,6 +17,7 @@ interface IEditorFieldVocabularyProps extends IEditorFieldProps {
interface IExtensionBridge {
assignments: {
utils: {
isContentLinkToCoverageAllowed(item: IArticle): boolean;
getAssignmentTypeInfo(
assignment: IAssignmentItem,
contentTypes: Array<IVocabularyItem>,
Expand Down
89 changes: 29 additions & 60 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,69 +75,38 @@ function configurePlanning(superdesk) {
authoring.itemActions(item).deschedule
);
}],
})
.activity('planning.fulfil', {
label: gettext('Fulfil Assignment'),
icon: 'calendar-list',
modal: true,
priority: 2000,
controller: ctrl.FulFilAssignmentController,
filters: [
{
action: 'list',
type: 'archive',
},
{
action: 'external-app',
type: 'fulfill-assignment',
},
],
group: gettext('Planning'),
privileges: {archive: 1},
additionalCondition: ['archiveService', 'item',
function(archiveService, item: IArticle) {
return !item.assignment_id &&
!archiveService.isPersonal(item) &&
!superdeskApi.entities.article.isLockedInOtherSession(item) &&
isContentLinkToCoverageAllowed(item) &&
!['killed', 'recalled', 'unpublished', 'spiked', 'correction'].includes(item.state);
}],
})

// TAG: AUTHORING-ANGULAR
.activity('planning.unlink', {
label: gettext('Unlink as Coverage'),
icon: 'cut',
priority: 1000,
controller: ctrl.UnlinkAssignmentController,
filters: [
{
action: 'list',
type: 'archive',
},
{
action: 'external-app',
type: 'unlink-assignment',
},
],
group: gettext('Planning'),
privileges: {archive: 1},

// keep in sync with client/planning-extension/src/extension.ts:126
additionalCondition: ['archiveService', 'item', 'authoring',
function(archiveService, item, authoring) {
return item.assignment_id &&
!archiveService.isPersonal(item) &&
!superdeskApi.entities.article.isLockedInOtherSession(item) &&
(
authoring.itemActions(item).edit ||
authoring.itemActions(item).correct ||
authoring.itemActions(item).deschedule
);
}],
});
}

window.addEventListener('planning:fulfilassignment', (event: CustomEvent) => {
const element = window.$(document.createElement('div'));
const localScope = ng.get('$rootScope').$new(true);
const handleDestroy = () => {
localScope.$broadcast('$destroy');
element[0].remove();
};

localScope.resolve = handleDestroy;
localScope.reject = handleDestroy;
localScope.locals = {data: {item: event.detail.item}};

new ctrl.FulFilAssignmentController(
element,
localScope,
ng.get('sdPlanningStore'),
ng.get('notify'),
ng.get('gettext'),
ng.get('lock'),
ng.get('session'),
ng.get('userList'),
ng.get('api'),
ng.get('$timeout'),
ng.get('superdeskFlags'),
ng.get('desks')
);
});


window.addEventListener('planning:unlinkfromcoverage', (event: CustomEvent) => {
ctrl.UnlinkAssignmentController(
event.detail,
Expand Down
Loading

0 comments on commit 486497b

Please sign in to comment.