Skip to content
GitLab
Menu
Why GitLab
Pricing
Contact Sales
Explore
Why GitLab
Pricing
Contact Sales
Explore
Sign in
Get free trial
Commits on Source (2)
Admin firehose
· ea29be81
Brian Hatchet
authored
Jun 12, 2019
and
Mark Harding
committed
Jun 12, 2019
ea29be81
Merge branch 'admin_firehose' into 'master'
· 65b5bbb3
Mark Harding
authored
Jun 12, 2019
Admin firehose See merge request
!295
65b5bbb3
Hide whitespace changes
Inline
Side-by-side
src/app/common/components/nsfw-selector/nsfw-selector.component.html
View file @
65b5bbb3
<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()"
>
...
...
src/app/common/components/nsfw-selector/nsfw-selector.component.ts
View file @
65b5bbb3
...
...
@@ -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
;
}
}
}
src/app/controllers/admin/admin.html
View file @
65b5bbb3
...
...
@@ -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>
...
...
src/app/controllers/admin/firehose/firehose.component.html
0 → 100644
View file @
65b5bbb3
<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>
src/app/controllers/admin/firehose/firehose.component.scss
0 → 100644
View file @
65b5bbb3
.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
src/app/controllers/admin/firehose/firehose.component.spec.ts
0 → 100644
View file @
65b5bbb3
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
();
}));
});
src/app/controllers/admin/firehose/firehose.component.ts
0 → 100644
View file @
65b5bbb3
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
src/app/declarations.ts
View file @
65b5bbb3
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
,
...
...
src/app/modules/report/creator/creator.component.ts
View file @
65b5bbb3
...
...
@@ -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();\
...
...
tslint.json
View file @
65b5bbb3
...
...
@@ -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
,
...
...