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

Fulfil assignment #1848

Open
wants to merge 28 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
998cff9
adjust API
tomaskikutis Apr 20, 2023
209db09
update gettext usage
tomaskikutis Apr 20, 2023
def1a61
Fix event template (#1812)
thecalcc Jun 14, 2023
629c2e8
Fix show modal (#1814)
thecalcc Jun 14, 2023
46387f7
UIF version update (#1818)
thecalcc Jun 28, 2023
a9fe557
superdesk-ui-framework@3.0.50
tomaskikutis Jul 27, 2023
63b3795
Fulfil assignment
thecalcc Aug 31, 2023
3b4db41
Sync extension bridge
thecalcc Aug 31, 2023
d309048
Fix lint
thecalcc Sep 14, 2023
6ee85a5
Merge remote-tracking branch 'origin/develop' into authoring-react-po…
petrjasek Oct 31, 2023
400ef62
fix lint
petrjasek Oct 31, 2023
9a8ec9e
fix e2e client build
petrjasek Oct 31, 2023
25ff5e5
Fix ng import
thecalcc Nov 6, 2023
8c7e9ad
Merge branch 'authoring-react-post-broadcasting' into fulfil-assignment
thecalcc Nov 6, 2023
7cc6b74
Fix CI
thecalcc Nov 6, 2023
e768699
Merge branch 'develop' into fulfil-assignment
thecalcc Aug 30, 2024
1bdb1bd
Fix lint
thecalcc Aug 30, 2024
cd6150c
Update client-core
thecalcc Sep 11, 2024
1804301
Update e2e client-core
thecalcc Sep 12, 2024
2bb3a63
Update artifact version
thecalcc Sep 12, 2024
ecc568e
Fix type
thecalcc Sep 12, 2024
e7e5715
Merge branch 'develop' into fulfil-assignment
thecalcc Sep 12, 2024
adaa48b
Merge branch 'develop' into fulfil-assignment
thecalcc Sep 16, 2024
dfc8cfb
Fix lint
thecalcc Sep 16, 2024
93da8b6
Merge branch 'develop' into fulfil-assignment
thecalcc Sep 18, 2024
950608f
Small changes
thecalcc Sep 18, 2024
ce435ba
Fix import
thecalcc Sep 18, 2024
7e4b490
Code cleanup
thecalcc Sep 19, 2024
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
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
29 changes: 26 additions & 3 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,12 @@ 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
// keep in sync with index.ts:108
if (
superdesk.privileges.hasPrivilege('archive') &&
item.assignment_id != null &&
Expand All @@ -142,6 +142,29 @@ const extension: IExtension = {
) {
const event = new CustomEvent('planning:unlinkfromcoverage', {detail: {item}});

window.dispatchEvent(event);
}
},
},
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering whether this would appear in authoring-angular as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you're asking wether there would be a conflict thus two of the same actions rendering because of being registered in two places yes. I plan to address this in another PR, together with the external-app thingy, as this is something that affects all actions so we need a generic solution.

I think doing a simple collision detection mechanism would suffice for now. If we have an action with the same id coming from extensions as well, we don't push it to the e.g. allActions array. We skip that one, while prioritising existing ones. In react we don't show the angular registered ones, so this should be enough to ignore the react side for now. Later when we remove the angular implementation we will remove the filtering

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extensions API have access to authoringReactViewEnabled variable. What about using it to conditionally push the action?

Alternativelly, what about if we dropped the old action and kept only the new one? I did that with planning details widget - dropped the angular version of a widget - and the react one works in both - angular and react based authoring.

label: superdesk.localization.gettext('Fulfil assignment'),
groupId: planningActionsGroupId,
icon: 'calendar-list',
onTrigger: () => {
const itemStates = ['killed', 'recalled', 'unpublished', 'spiked', 'correction'];
const {isContentLinkToCoverageAllowed} = extensionBridge.assignments.utils;

// keep in sync with index.ts:79
if (
!item.assignment_id &&
isContentLinkToCoverageAllowed(item) &&
!superdesk.entities.article.isPersonal(item) &&
superdesk.privileges.hasPrivilege('archive') &&
!superdesk.entities.article.isLockedInOtherSession(item) &&
!itemStates.includes(item.state)
) {
const event = new CustomEvent('planning:fulfilassignment', {detail: item});

window.dispatchEvent(event);
}
},
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
29 changes: 29 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,35 @@ function configurePlanning(superdesk) {
});
}

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}};

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