8000 Request features from Review section with one call by dpordomingo · Pull Request #233 · src-d/code-annotation · GitHub
[go: up one dir, main page]

Skip to content
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
8000 41 changes: 34 additions & 7 deletions server/handler/features.go
8000
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,51 @@ package handler
import (
"net/http"

"github.com/go-chi/chi"
"github.com/src-d/code-annotation/server/model"
"github.com/src-d/code-annotation/server/repository"
"github.com/src-d/code-annotation/server/serializer"
)

// GetFeatures returns a function that returns a *serializer.Response
// with the list of features for blobId
func GetFeatures(repo *repository.Features) RequestProcessFunc {
func GetFeatures(filePairRepo *repository.FilePairs, featuresRepo *repository.Features) RequestProcessFunc {
return func(r *http.Request) (*serializer.Response, error) {
blobID := chi.URLParam(r, "blobId")
filePairID, err := urlParamInt(r, "pairId")
if err != nil {
return nil, err
}

filePair, err := filePairRepo.GetByID(filePairID)
if err != nil {
return nil, err
}

// in the future it should take file by blobID from DB
// and make API request to ML system
features, err := repo.GetAll(blobID)
featuresA, featuresB, score, err := getFeatures(featuresRepo, filePair)
if err != nil {
return nil, err
}

return serializer.NewFeaturesResponse(features), nil
return serializer.NewFeaturesResponse(featuresA, featuresB, score), nil
}
}

// TODO (dpordomingo): in the future it should take the UAST of both blobs DB
// and make a request to ML feature extractor API
func getFeatures(repo *repository.Features, pair *model.FilePair) ([]*model.Feature, []*model.Feature, *model.Feature, error) {
blobIDA := pair.Left.BlobID
blobIDB := pair.Right.BlobID

featuresA, err := repo.GetAll(blobIDA)
if err != nil {
return nil, nil, nil, err
}

featuresB, err := repo.GetAll(blobIDB)
if err != nil {
return nil, nil, nil, err
}

score := model.Feature{Name: "score", Weight: pair.Score}

return featuresA, featuresB, &score, err
}
4 changes: 2 additions & 2 deletions server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ func Router(
r.Get("/file-pairs/{pairId}", handler.APIHandlerFunc(handler.GetFilePairDetails(filePairRepo, diffService)))
})

r.Route("/features", func(r chi.Router) {
r.Route("/file-pair", func(r chi.Router) {
r.Use(requesterACL.Middleware)

r.Get("/{blobId}", handler.APIHandlerFunc(handler.GetFeatures(featureRepo)))
8000 r.Get("/{pairId}/features", handler.APIHandlerFunc(handler.GetFeatures(filePairRepo, featureRepo)))
})

r.Route("/exports", func(r chi.Router) {
Expand Down
28 changes: 22 additions & 6 deletions server/serializer/serializers.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,29 @@ type featureResponse struct {
Weight float64 `json:"weight"`
}

// NewFeaturesResponse returns a Response for the passed Features
func NewFeaturesResponse(fs []*model.Feature) *Response {
features := make([]featureResponse, len(fs))
for i, f := range fs {
features[i] = featureResponse(*f)
type featuresResponse struct {
Object1 []featureResponse `json:"featuresA"`
Object2 []featureResponse `json:"featuresB"`
Pair featureResponse `json:"score"`
}

// NewFeaturesResponse returns a Response for the passed Features and score
func NewFeaturesResponse(fsA []*model.Feature, fsB []*model.Feature, s *model.Feature) *Response {
featuresA := make([]featureResponse, len(fsA))
for i, f := range fsA {
featuresA[i] = featureResponse(*f)
}

featuresB := make([]featureResponse, len(fsB))
for i, f := range fsB {
featuresB[i] = featureResponse(*f)
}
return newResponse(features)

return newResponse(featuresResponse{
Object1: featuresA,
Object2: featuresB,
Pair: featureResponse(*s),
})
}

type countResponse struct {
Expand Down
4 changes: 2 additions & 2 deletions src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ function getFilePairAnnotations(experimentId, id) {
);
}

function getFeatures(blobId) {
return apiCall(`/api/features/${blobId}`);
function getFeatures(filePairId) {
return apiCall(`/api/file-pair/${filePairId}/features`);
}

function exportList() {
Expand Down
23 changes: 16 additions & 7 deletions src/pages/Review.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Grid, Row, Col } from 'react-bootstrap';
import { push } from 'redux-little-router';
import SplitPane from 'react-split-pane';
import Page from './Page';
import Loader from '../components/Loader';
import Breadcrumbs from '../components/Breadcrumbs';
import Selector from '../components/Experiment/Selector';
import Diff from '../components/Experiment/Diff';
import Results from '../components/Review/Results';
import { makeUrl } from '../state/routes';
import { getCurrentFilePair, loadFilePair } from '../state/filePairs';
import {
getCurrentFilePair,
selectPair,
loadFilePair,
} from '../state/filePairs';
import { getFeatures, mostSimilar, leastSimilar } from '../state/features';
getFeatures,
mostSimilar,
leastSimilar,
getScore,
} from '../state/features';
import { toggleInvisible } from '../state/user';
import './Review.less';

Expand Down Expand Up @@ -64,6 +67,7 @@ class Review extends Component {
mostSimilarFeatures,
leastSimilarFeatures,
features,
score,
} = this.props;

if (fileLoading || !filePair) {
Expand Down Expand Up @@ -102,7 +106,7 @@ class Review extends Component {
/>
<Results
className="results"
score={filePair.score}
score={score}
annotations={annotations}
features={features}
mostSimilarFeatures={mostSimilarFeatures}
Expand All @@ -127,6 +131,7 @@ const mapStateToProps = state => {
name: `(${i + 1})`,
}));

const score = getScore(state);
const features = getFeatures(state);
// keep only 100 results
// it will be improved (most probably with pagination) later
Expand All @@ -146,13 +151,17 @@ const mapStateToProps = state => {
mostSimilarFeatures,
leastSimilarFeatures,
features,
score,
showInvisible,
};
};

const experimentId = 1;
const mapDispatchToProps = dispatch => ({
onSelect: pairId => dispatch(selectPair(experimentId, pairId)),
onSelect: pairId =>
dispatch(
push(makeUrl('reviewPair', { experiment: experimentId, pair: pairId }))
),
toggleInvisible: (expId, id) => {
dispatch(toggleInvisible());
return dispatch(loadFilePair(expId, id));
Expand Down
22 changes: 17 additions & 5 deletions src/state/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@ const reducer = (state = initialState, action) => {

// Actions

export const load = (blobIdA, blobIdB) => dispatch => {
export const load = filePairId => dispatch => {
dispatch({ type: LOAD });

return Promise.all([api.getFeatures(blobIdA), api.getFeatures(blobIdB)])
.then(([featuresA, featuresB]) => {
return api
.getFeatures(filePairId)
.then(res => {
const { featuresA, featuresB, score } = res;

// collect names from both responses
const names = featuresA.map(f => f.name);
featuresB.forEach(f => {
Expand All @@ -57,12 +60,17 @@ export const load = (blobIdA, blobIdB) => dispatch => {
}, {});

// merge features by name
const features = names.map(name => ({
const individualFeatures = names.map(name => ({
name,
weightA: mapA[name] || 0,
weightB: mapB[name] || 0,
}));

const features = {
features: individualFeatures,
score: score.weight,
};

return dispatch({ type: LOAD_SUCCESS, features });
})
.catch(err => {
Expand All @@ -72,7 +80,11 @@ export const load = (blobIdA, blobIdB) => dispatch => {

// Selectors

export const getFeatures = state => state.features.features;
export const getFeatures = state =>
(state.features.features && state.features.features.features) || [];

export const getScore = state =>
(state.features.features && state.features.features.score) || 0;

export const mostSimilar = createSelector(getFeatures, features => {
// copy because sort mutates array
Expand Down
58 changes: 29 additions & 29 deletions src/state/features.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,47 +27,44 @@ describe('features/reducer', () => {
describe('features/actions', () => {
describe('load', () => {
it('success', () => {
const blobIdA = 1;
const blobIdB = 2;
const filePairId = 1;
const store = mockStore({
features: {
...initialState,
},
});

fetch.mockResponses(
// blobA response
[
JSON.stringify({
data: [
fetch.mockResponses([
JSON.stringify({
data: {
featuresA: [
{ name: 'feature1', weight: 0.9 },
{ name: 'feature2', weight: 0.8 },
],
}),
],
// blobB response
[
JSON.stringify({
data: [
featuresB: [
{ name: 'feature1', weight: 0.8 },
{ name: 'feature3', weight: 0.1 },
],
}),
]
);
score: { name: 'score', weight: 0.5 },
},
}),
]);

return store.dispatch(load(blobIdA, blobIdB)).then(() => {
return store.dispatch(load(filePairId)).then(() => {
expect(store.getActions()).toEqual([
{
type: LOAD,
},
{
type: LOAD_SUCCESS,
features: [
{ name: 'feature1', weightA: 0.9, weightB: 0.8 },
{ name: 'feature2', weightA: 0.8, weightB: 0 },
{ name: 'feature3', weightA: 0, weightB: 0.1 },
],
features: {
features: [
{ name: 'feature1', weightA: 0.9, weightB: 0.8 },
{ name: 'feature2', weightA: 0.8, weightB: 0 },
{ name: 'feature3', weightA: 0, weightB: 0.1 },
],
score: 0.5,
},
},
]);
});
Expand Down Expand Up @@ -103,13 +100,16 @@ describe('features/actions', () => {
describe('features/selectors', () => {
const stateForSort = {
features: {
features: [
{ name: 'a', weightA: 5, weightB: 10 },
{ name: 'b', weightA: 10, weightB: 10 },
{ name: 'c', weightA: 10, weightB: 1 },
{ name: 'b', weightA: 2, weightB: 2 },
{ name: 'd', weightA: 10, weightB: 10 },
],
features: {
features: [
{ name: 'a', weightA: 5, weightB: 10 },
{ name: 'b', weightA: 10, weightB: 10 },
{ name: 'c', weightA: 10, weightB: 1 },
{ name: 'b', weightA: 2, weightB: 2 },
{ name: 'd', weightA: 10, weightB: 10 },
],
score: 0.5,
},
},
};

Expand Down
2 changes: 1 addition & 1 deletion src/state/filePairs.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export const middleware = store => next => action => {
.then(() => next(loadAnnotations(expIdParam, +payload.params.pair)))
.then(() => {
const pair = getCurrentFilePair(store.getState());
return pair && next(featuresLoad(pair.leftBlobId, pair.rightBlobId));
return pair && next(featuresLoad(pair.id));
});
default:
return result;
Expand Down
15 changes: 6 additions & 9 deletions src/state/routes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,17 +288,14 @@ describe('routers', () => {
}),
{ status: 200 },
],
// features left
// features
[
JSON.stringify({
data: [],
}),
{ status: 200 },
],
// features right
[
JSON.stringify({
data: [],
data: {
featuresA: [],
featuresB: [],
score: {},
},
}),
{ status: 200 },
]
Expand Down
0