where('partial', false) ->get(); return view('results.index', ['results' => $results]); } public function indexPartial() { $results = Result::select(self::ROWS_FOR_INDEX) ->where('partial', true) ->get(); return view('results.index', ['results' => $results]); } /** * Show the form for creating a new resource. * * @return \Illuminate\Http\Response */ public function create() { // } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ public function store(Request $request) { // } /** * Display the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function show(Result $result) { $header = collect($result->data ?? []) ->map(function($entry) { return array_keys($entry); }) ->flatten() ->unique(); $rows = collect($result->data ?? []) ->map(function($row) use ($header) { foreach ($header as $key) { // Fill empty columns with N/A $row[$key] = $row[$key] ?? config('experiment.n-a-text'); // Replace arrays with json if (is_array($row[$key])) $row[$key] = json_encode($row[$key]); } return $row; }); return view('results.show', [ 'header' => $header, 'rows' => $rows, ]); } // Returns object obj with obj->header: array with header info // obj->rows: arrays with data. // // @param $results Collection of Result private function getResults($results) { $header = collect($results->first()->data ?? []) ->map(function($entry) { return array_keys($entry); }) ->flatten() ->unique(); $all_rows = collect(); $id = config('experiment.csv-id-header-start'); $id_format = config('experiment.csv-id-header-format'); $id_header = config('experiment.csv-id-header'); foreach ($results as $result) { $rows = collect($result->data ?? []) ->map(function($row) use ($header, $id_header, $id_format, $id) { foreach ($header as $key) { // Fill empty columns with N/A $row[$key] = $row[$key] ?? config('experiment.n-a-text'); // Replace arrays with json if (is_array($row[$key])) $row[$key] = json_encode($row[$key]); } if ($id_header) return array_merge( [$id_header => sprintf($id_format, $id)], $row); else return $row; }); $all_rows = $all_rows->concat($rows); $id += 1; } if ($id_header) { $header->prepend($id_header); } return (object) [ 'header' => $header->toArray(), 'rows' => $all_rows->toArray(), ]; } public function showAllCSV() { header('Content-Type: text/csv; charset=utf-8'); header('Content-Type: application/force-download'); header('Content-Disposition: attachment; filename=results.csv'); $data = $this->getResults(Result::all()); $csv = Writer::createFromString(''); $csv->insertOne($data->header); $csv->insertAll($data->rows); return $csv->getContent(); } public function downloadZip() { $options = new ZipOptions(); $options->setSendHttpHeaders(true); $zip = new ZipStream('results.zip', $options); Result::chunk(5, function($results) use ($zip) { foreach ($results as $result) { $filename = $result->created_at . ".json"; $zip->addFile($filename, $result->getOriginal("data")); } }); $zip->finish(); } /** * Display the result as json. */ public function showJson(Result $result) { return $result->data; } /** * Show the form for editing the specified resource. * * @param int $id * @return \Illuminate\Http\Response */ public function edit($id) { // } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * @return \Illuminate\Http\Response */ public function update(Request $request, $id) { // } /** * Remove the specified resource from storage. * * @param int $id * @return \Illuminate\Http\Response */ public function destroy(Result $result) { $result->delete(); return back(); } /** * API route for submitting results for the experiment. */ public function submit(Request $request) { try { $data = $request->data; $run_id = $request->id; $isPartial = $request->partial ? true : false; // In case the client didn't provide a id, we add a new one. $run_id = $run_id ?? Str::random(60); if ($data === null) return [ 'code' => 3, 'message' => 'Invalid data format. Please provide valid JSON.' ]; // Mark the experiment as done, if this is final data. $experiment = Experiment::firstWhere('run_id', $run_id); if ($experiment && ! $isPartial) { $experiment->status = Experiment::STATUS_DONE; if (! config('experiment.store-session-id')) $experiment->session_id = null; $experiment->save(); } // Check, if we already have partial data lying around. // NOTE: Result::firstOrNew() somehow doesn't work here... $result = Result::where('run_id', $run_id) ->where('partial', true) ->first(); $result = $result ?? Result::make([ 'run_id' => $run_id, ]); $result->partial = $isPartial; $result->data = $data; if (config('experiment.store-ip')) { $result->ip = IPAnonymizer::anonymize($request->ip()); Log::info("Storing data from (anonymized) ip '{$result->ip}'."); } else $result->ip = null; if ($experiment && $isPartial) { $result->session_id = $experiment->session_id; } else if (! config('experiment.store-session-id')) { $result->session_id = null; } // Note: even though results can reference there Experiments // via the experiment_id key, we don't set it here, because // results should probably keep existing, when the Experiment // is deleted, but we configured the database to cascade delete. // I think, the 'experiment_id' key maybe should be removed. $result->save(); return [ 'code' => 0, 'message' => 'Result submitted.', 'session' => $run_id, ]; } catch (\Throwable $e) { Log::error("Submit request failed:\n{$request}\nError was:\n{$e}"); return [ 'code' => 500, 'message' => 'Your request caused a server error. It has been logged.' ]; } } public function results() { return Result::all()->values(); } }