...
 
Commits (2)
<m-dropdown class="m-nsfwSelector__dropdown" #dropdown>
<m-dropdown class="m-nsfwSelector__dropdown" [expanded]="expanded" #dropdown>
<label
class="m-nsfwSelector__label m-posterActionBar__IconAndLabel"
[class.selected]="hasSelections()">
......
......@@ -2,7 +2,7 @@ import {
Component,
EventEmitter,
Input,
Output,
Output,
} from '@angular/core';
import {
NSFWSelectorCreatorService,
......@@ -28,6 +28,7 @@ export class NSFWSelectorComponent {
@Input('service') serviceRef: string = 'consumer';
@Input('consumer') consumer: false;
@Input('expanded') expanded: false;
@Output('selected') onSelected: EventEmitter<any> = new EventEmitter();
constructor(
......@@ -77,5 +78,4 @@ export class NSFWSelectorComponent {
return true;
}
}
}
......@@ -19,6 +19,12 @@
>
<span i18n="@@M__ADMIN_NAV__BOOSTS">Boosts</span>
</a>
<a class="m-topbar--navigation--item"
routerLink="/admin/firehose"
routerLinkActive="m-topbar--navigation--item-active"
>
<span i18n="@@M__ADMIN_NAV__FIREHOSE">Firehose</span>
</a>
<a class="m-topbar--navigation--item"
routerLink="/admin/pages"
routerLinkActive="m-topbar--navigation--item-active"
......@@ -84,6 +90,7 @@
</div>
<m-admin--interactions *ngIf="filter == 'interactions'"></m-admin--interactions>
<minds-admin-boosts *ngIf="filter == 'boosts'"></minds-admin-boosts>
<minds-admin-firehose *ngIf="filter == 'firehose'"></minds-admin-firehose>
<minds-admin-pages *ngIf="filter == 'pages'"></minds-admin-pages>
<minds-admin-reports *ngIf="filter == 'reports' || filter == 'appeals'"></minds-admin-reports>
<minds-admin-monetization *ngIf="filter == 'monetization'"></minds-admin-monetization>
......
<div class="m-firehose m-page mdl-grid">
<div class="m-firehose__moderatorAction m-firehose__moderatorAction--leftButton mdl-color--white m-border mdl-cell mdl-cell--1-col"
(click)="reject()"
tabindex=0
>
<i class="material-icons">highlight_off</i>
</div>
<div class="mdl-cell mdl-cell--10-col">
<div class="m-firehose__sort-container m-border">
<m-sort-selector
[algorithm]="algorithm"
[period]="period"
[customType]="customType"
(onChange)="setSort($event.algorithm, $event.period, $event.customType)"
></m-sort-selector>
</div>
<minds-activity *ngIf="entity" [object]="entity" class="mdl-card m-border item"></minds-activity>
</div>
<div class="m-firehose__moderatorAction m-firehose__moderatorAction--rightButton mdl-color--white m-border mdl-cell mdl-cell--1-col"
(click)="accept()"
tabindex=1
>
<i class="material-icons">check_circle</i>
</div>
<ng-container *ngIf="inProgress">
<div class="m-firehose__spinner mdl-cell mdl-cell--12-col">
<div class="mdl-spinner mdl-spinner--single-color mdl-js-spinner is-active"></div>
</div>
</ng-container>
</div>
.m-firehose {
max-width: 1280px;
.m-firehose__moderatorAction {
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
margin-bottom: 25px;
max-width: 270px;
&.m-firehose__moderatorAction--leftButton {
justify-content: right;
}
&.m-firehose__moderatorAction--rightButton {
justify-content: left;
}
}
.m-firehose__sort-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 12px;
margin-bottom: 16px;
@include m-theme(){
background-color: themed($m-white);
}
m-sort-selector {
flex-grow: 1;
}
}
.m-firehose__spinner {
text-align: center;
}
}
\ No newline at end of file
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { Component, Input, Output } from '@angular/core';
import { sessionMock } from '../../../../tests/session-mock.spec';;
import { Client } from '../../../services/api/client';
import { By } from '@angular/platform-browser';
import { clientMock } from '../../../../tests/client-mock.spec';
import { AdminFirehoseComponent } from './firehose.component';
import { Session } from '../../../services/session';
import { RouterTestingModule } from '@angular/router/testing';
import { NewsfeedHashtagSelectorService } from '../../../modules/newsfeed/services/newsfeed-hashtag-selector.service';
import { newsfeedHashtagSelectorServiceMock } from '../../../../tests/newsfeed-hashtag-selector-service-mock.spec';
import { overlayModalServiceMock } from '../../../../tests/overlay-modal-service-mock.spec';
import { OverlayModalService } from '../../../services/ux/overlay-modal';
import { EventEmitter } from '@angular/core';
@Component({
selector: 'minds-activity',
template: ''
})
class MindsActivityMockComponent {
@Input() object: any;
}
@Component({
selector: 'm-sort-selector',
template: ''
})
class MindsSortSelectorMockComponent {
@Input() algorithm: string;
@Input() period: string;
@Input() customType: string;
@Output() onChange: EventEmitter<any> = new EventEmitter<any>();
}
describe('AdminFirehose', () => {
let comp: AdminFirehoseComponent;
let fixture: ComponentFixture<AdminFirehoseComponent>;
function getMockActivities() {
return [
{
guid: 1
},
{
guid: 2
},
{
guid: 3
}
];
}
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
MindsActivityMockComponent,
AdminFirehoseComponent,
MindsSortSelectorMockComponent,
],
imports: [RouterTestingModule],
providers: [
{ provide: Session, useValue: sessionMock },
{ provide: Client, useValue: clientMock },
{ provide: NewsfeedHashtagSelectorService, useValue: newsfeedHashtagSelectorServiceMock },
{ provide: OverlayModalService, useValue: overlayModalServiceMock },
]
})
.compileComponents();
}));
beforeEach((done) => {
fixture = TestBed.createComponent(AdminFirehoseComponent);
comp = fixture.componentInstance;
comp.entities = getMockActivities();
fixture.detectChanges();
clientMock.response = {};
clientMock.response[`api/v2/admin/firehose/latest/activities?hashtags=&period=12h&all=`] = {
'status': 'success',
'entities': getMockActivities()
}
if (fixture.isStable()) {
done();
} else {
fixture.whenStable().then(() => {
done();
});
}
});
it('should have a loading screen', () => {
comp.inProgress = true;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.m-firehose__spinner')))
.not.toBeNull();
});
it('should hide a loading screen', () => {
comp.inProgress = false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.m-firehose__spinner')))
.toBeNull();
});
it('should initialize entities', () => {
comp.entities = getMockActivities();
expect(comp.entities.length).toEqual(3);
expect(comp.entities).not.toBeNull();
comp.initializeEntity();
expect(comp.entity).toEqual(getMockActivities()[0]);
expect(comp.entities.length).toEqual(2);
});
it('should save an accept activity', fakeAsync(() => {
clientMock.response['api/v2/admin/firehose/1'] = { 'status': 'success' };
comp.save(getMockActivities()[0].guid);
fixture.detectChanges();
tick();
expect(clientMock.post).toHaveBeenCalled();
expect(clientMock.post.calls.mostRecent().args[0]).toContain('api/v2/admin/firehose/1');
}));
it('should save a reported activity', fakeAsync(() => {
clientMock.response['api/v2/admin/firehose/1'] = { 'status': 'success' };
comp.save(getMockActivities()[0].guid, 1, 1);
fixture.detectChanges();
tick();
expect(clientMock.post).toHaveBeenCalled();
expect(clientMock.post.calls.mostRecent().args[0]).toContain('api/v2/admin/firehose/1');
expect(clientMock.post.calls.mostRecent().args[1]).toEqual({ 'reason': 1, 'subreason': 1});
}));
it('should accept an activity', fakeAsync(() => {
spyOn(comp, 'save');
spyOn(comp, 'initializeEntity');
comp.accept();
expect(clientMock.post).toHaveBeenCalled();
expect(clientMock.post.calls.mostRecent().args[0]).toContain('api/v2/admin/firehose/1');
expect(comp.save).toHaveBeenCalled();
expect(comp.initializeEntity).toHaveBeenCalled();
}));
it('should swipe left', fakeAsync(() => {
spyOn(comp, 'reject').and.callThrough();
comp.onKeyPress({ key: 'ArrowLeft'});
expect(comp.reject).toHaveBeenCalled();
}));
it('should swipe right', fakeAsync(() => {
spyOn(comp, 'accept').and.callThrough();
comp.onKeyPress({ key: 'ArrowRight'});
expect(comp.accept).toHaveBeenCalled();
}));
});
import { Component, HostListener, OnInit, OnDestroy} from '@angular/core';
import { Client } from '../../../services/api';
import { Session } from '../../../services/session';
import { OverlayModalService } from '../../../services/ux/overlay-modal';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { NewsfeedHashtagSelectorService } from '../../../modules/newsfeed/services/newsfeed-hashtag-selector.service';
import { ReportCreatorComponent } from '../../../modules/report/creator/creator.component';
@Component({
moduleId: module.id,
selector: 'minds-admin-firehose',
templateUrl: 'firehose.component.html',
})
export class AdminFirehoseComponent implements OnInit, OnDestroy {
entities: Array<any> = [];
entity: any = null;
inProgress = true;
algorithm = 'latest';
period = '12h';
customType = 'activities';
hashtag: string | null = null;
all = false;
paramsSubscription: Subscription;
timeout: any = null;
constructor(
private session: Session,
public client: Client,
public router: Router,
public route: ActivatedRoute,
protected newsfeedHashtagSelectorService: NewsfeedHashtagSelectorService,
private overlayModal: OverlayModalService,
) {
this.paramsSubscription = this.route.params.subscribe(params => {
this.algorithm = params['algorithm'] || 'latest';
this.period = params['period'] || '12h';
this.customType = params['type'] || 'activities';
if (typeof params['hashtag'] !== 'undefined') {
this.hashtag = params['hashtag'] || null;
this.all = false;
} else if (typeof params['all'] !== 'undefined') {
this.hashtag = null;
this.all = true;
} else if (params['query']) {
this.all = true;
this.updateSortRoute();
} else {
this.hashtag = null;
this.all = false;
}
if (this.algorithm !== 'top'
&& (this.customType === 'channels' || this.customType === 'groups')
) {
this.algorithm = 'top';
this.updateSortRoute();
}
this.load();
});
}
ngOnInit() {}
ngOnDestroy() {
if (this.paramsSubscription) {
this.paramsSubscription.unsubscribe();
}
this.timeout = null;
}
public async load() {
this.inProgress = true;
const hashtags = this.hashtag ? encodeURIComponent(this.hashtag) : '';
const period = this.period || '';
const all = this.all ? '1' : '';
try {
const url = `api/v2/admin/firehose/${this.algorithm}/${this.customType}?hashtags=${hashtags}&period=${period}&all=${all}`;
console.log(url);
const response: any = await this.client.get(url);
this.entities = response.entities;
if (this.entities.length > 0) {
this.initializeEntity();
this.timeout = setTimeout(() => this.load(), 3600000);
}
} catch (exception) {
console.error(exception);
}
this.inProgress = false;
}
public initializeEntity() {
this.entity = null;
if (this.entities.length > 0) {
this.entity = this.entities.shift();
} else {
this.load();
}
}
public save(guid: number, reason: number = null, subreason: number = null) {
const data = {
'reason': reason,
'subreason': subreason
};
return this.client.post('api/v2/admin/firehose/' + guid, data);
}
public reject() {
const options = {
onReported: (guid, reason, subreason) => {
this.save(guid, reason, subreason);
this.initializeEntity();
}
};
this.overlayModal.create(
ReportCreatorComponent,
this.entity,
options
).present();
}
public accept() {
this.save(this.entity.guid);
this.initializeEntity();
}
@HostListener('document:keydown', ['$event'])
public onKeyPress(e) {
if (this.entity) {
switch (e.key) {
case 'ArrowLeft':
this.reject();
break;
case 'ArrowRight':
this.accept();
break;
}
}
}
public setSort(algorithm: string, period: string | null, customType: string | null) {
this.algorithm = algorithm;
this.period = period;
this.customType = customType;
this.updateSortRoute();
}
updateSortRoute() {
const route: any[] = ['admin/firehose', this.algorithm];
const params: any = {};
params.algorithm = this.algorithm;
if (this.period) {
params.period = this.period;
}
if (this.customType) {
params.type = this.customType;
}
if (this.hashtag) {
params.hashtag = this.hashtag;
} else if (this.all) {
params.all = 1;
}
route.push(params);
this.router.navigate(route);
}
}
\ No newline at end of file
import {AdminReportsDownload} from './controllers/admin/reports-download/reports-download';
import {AdminBoosts} from './controllers/admin/boosts/boosts';
import {AdminFirehoseComponent} from './controllers/admin/firehose/firehose.component';
import {AdminPages} from './controllers/admin/pages/pages';
import {AdminReports} from './controllers/admin/reports/reports';
import {AdminMonetization} from './controllers/admin/monetization/monetization';
......@@ -22,6 +23,7 @@ export const MINDS_DECLARATIONS: any[] = [
AdminInteractions,
RejectionReasonModalComponent,
AdminBoosts,
AdminFirehoseComponent,
AdminPages,
AdminReports,
AdminMonetization,
......
......@@ -3,6 +3,7 @@ import { OverlayModalService } from '../../../services/ux/overlay-modal';
import { Client } from '../../../services/api';
import { Session } from '../../../services/session';
import { REASONS } from '../../../services/list-options';
import { EventEmitter } from "@angular/core";
@Component({
moduleId: module.id,
......@@ -36,6 +37,12 @@ export class ReportCreatorComponent implements AfterViewInit {
this.guid = object ? object.guid : null;
}
_opts: any;
set opts(opts: any) {
this._opts = opts;
}
constructor(
public session: Session,
private _changeDetectorRef: ChangeDetectorRef,
......@@ -112,7 +119,7 @@ export class ReportCreatorComponent implements AfterViewInit {
try {
let response: any = await this.client.post(`api/v2/moderation/report`, {
entity_guid: this.guid,
entity_guid: this.guid,
reason_code: this.subject.value,
note: this.note,
sub_reason_code: this.subReason.value,
......@@ -120,6 +127,14 @@ export class ReportCreatorComponent implements AfterViewInit {
this.inProgress = false;
this.success = true;
if (this.session.isAdmin()) {
this.close();
}
if (this._opts && this._opts.onReported) {
this._opts.onReported(this.guid, this.subject.value, this.subReason.value);
}
} catch (e) {
this.inProgress = false;
//this.overlayModal.dismiss();\
......
......@@ -23,7 +23,8 @@
"import-spacing": true,
"indent": [
true,
"spaces"
"spaces",
2
],
"interface-over-type-literal": true,
"label-position": true,
......@@ -125,7 +126,7 @@
"component-selector": [
true,
"element",
"app",
"minds",
"kebab-case"
],
"no-output-on-prefix": true,
......