MarkLogic - Custom Search Snippet - xquery

I am using Roxy to manage my project. Also using MarkLogic 8.0-6.1
I am trying to submit a searchTerm, and return a custom formatted search:snippet
Here are the complete steps that I am taking:
./../roxy/ml new test-app --server-version=8 --app-type=rest
Configure my build.properties
cd test-app/
./ml local bootstrap
Now I have my project Structure.
Create File - test-app/rest-api/ext/show-search.xqy
xquery version "1.0-ml";
module namespace ss = "http://marklogic.com/rest-api/resource/show-search";
import module namespace search = "http://marklogic.com/appservices/search" at "/MarkLogic/appservices/search/search.xqy";
import module namespace json = "http://marklogic.com/xdmp/json" at "/MarkLogic/json/json.xqy";
declare
function ss:get(
$context as map:map,
$params as map:map
) as document-node()*
{
map:put($context, "output-types", "application/json"),
map:put($context, "output-status", (200, "OK")),
let $search-term := map:get($params, "searchTerm")
let $query := search:search($search-term,
<options xmlns="http://marklogic.com/appservices/search">
<transform-results apply="raw"/>
</options>
)
return document {$query}
};
(:
:)
declare
function ss:put(
$context as map:map,
$params as map:map,
$input as document-node()*
) as document-node()?
{
map:put($context, "output-types", "application/xml"),
map:put($context, "output-status", (201, "Created")),
document { "PUT called on the ext service extension" }
};
(:
:)
declare
function ss:post(
$context as map:map,
$params as map:map,
$input as document-node()*
) as document-node()*
{
map:put($context, "output-types", "application/xml"),
map:put($context, "output-status", (201, "Created")),
document { "POST called on the ext service extension" }
};
(:
:)
declare
function ss:delete(
$context as map:map,
$params as map:map
) as document-node()?
{
map:put($context, "output-types", "application/xml"),
map:put($context, "output-status", (200, "OK")),
document { "DELETE called on the ext service extension" }
};
The above GET request uses the transform-results apply=raw option, deploys, and functions properly (I have some test documents).
However I do not want to return the whole document, I want to return a whole section of the json that had a match, no matter where in that seciton the match happened (lower levels)
So I try to write my own snipper
Create File - test-app/rest-api/ext/show-search-snipper.xqy
xquery version "1.0-ml";
module namespace sss = "http://marklogic.com/rest-api/resource/show-search-snipper";
import module namespace search = "http://marklogic.com/appservices/search" at "/MarkLogic/appservices/search/search.xqy";
import module namespace json = "http://marklogic.com/xdmp/json" at "/MarkLogic/json/json.xqy";
declare
function sss:my-snippet(
$result as node(),
$ctsquery as schema-element(cts:query),
$options as element(search:transform-results)?
) as element(search:snippet)
{
<search:snippet>
</search:snippet>
};
I then update the search:search call to the following
let $query := search:search($search-term,
<options xmlns="http://marklogic.com/appservices/search">
<transform-results apply="my-snippet" ns="http://marklogic.com/rest-api/resource/show-search-snipper" at="show-search-snipper.xqy"/>
</options>
)
Now I should have everything I need (I think)
I run the deploy ./ml local deploy rest
and get the following
Minty-linux test-app # ./ml local deploy rest Loading REST properties
in /opt/this-is-a-test/test-app/rest-api/config/properties.xml Loading
REST options in /opt/this-is-a-test/test-app/rest-api/config/options
Loading REST extensions from /opt/this-is-a-test/test-app/rest-api/ext
ERROR: 400 "Bad Request" ERROR: {"errorResponse":{"statusCode":400,
"status":"Bad Request", "messageCode":"RESTAPI-INVALIDCONTENT",
"message":"RESTAPI-INVALIDCONTENT: (err:FOER0000) Invalid content:
invalid show-search-snipper extension: show-search-snipper either is
not a valid module or does not provide extension functions (delete,
get, put, post) in the
http://marklogic.com/rest-api/resource/show-search-snipper
namespace"}}
So I tried moving the show-search-snipper.xqy file up 1 level (to test-app/rest-api/show-search-snipper.xqy`
Run the deployment
Deployment Works No errors
Hit the URL and receive the following
500 Internal Server Error
INTERNAL ERROR
RESTAPI-INVALIDREQ: (err:FOER0000) Invalid request: reason: Extension
show-search does not exist. . See the MarkLogic server error log for
further detail.
Although I know the extension was created properly, since it worked fine before the introduction of the custom snip function. (with apply="raw")
Any thoughts how I can apply my custom snip function or what I am doing wrong in deployment?

Should you decide to stick with a custom snippeter:
It looks like Roxy is trying to treat it your snippeter module as a resource extension, which it is not. Your snippeter should be just a vanilla module in the modules db.
IDK how to configure Roxy, unfortunately, but what you're aiming for is to get Roxy to either install it using PUT /v1/ext/directories/asset or a straight up insert (`PUT /v1/documents) on your modules db. See http://docs.marklogic.com/REST/PUT/v1/ext/[directories]/[asset].
Assuming Roxy uses /ext, then the path to your snippeter would NOT be the unqualified path you have in your options. It would be an absolute path rooted at /ext/. See http://docs.marklogic.com/guide/rest-dev/search#id_72390.

There's a simpler way -- you can do this with the extract-document-data search option.
I set up some sample data to work with like this:
xquery version "1.0-ml";
for $i in (1 to 10)
return
xdmp:document-insert(
'/test-content-' || $i || '.json',
xdmp:unquote('{ "important": { "foo": 1, "bar": 2 }, "not-important": { "stuff": 3, "blah": 4 } }')
)
Having bootstrapped and deployed modules, I can get search results at http://localhost:8040/v1/search. However, I can start taking more control of the search results by using stored search options. Take a look in your project at rest-api/config/options/all.xml. They were already deployed for you when you run ml local deploy modules, so you now can search using http://localhost:8040/v1/search?options=all. Since you're using JSON data, I took it one step further and ran with: http://localhost:8040/v1/search?format=json&options=all.
I added this to rest-api/config/options/all.xml:
<extract-document-data selected="include">
<extract-path>/important</extract-path>
</extract-document-data>
This tells the search options to include the specified path with all search results. After deploying and running the search again, one result looks like this:
{
"index":1,
"uri":"/test-content-6.json",
"path":"fn:doc(\"/test-content-6.json\")",
"score":0,
"confidence":0,
"fitness":0,
"href":"/v1/documents?uri=%2Ftest-content-6.json",
"mimetype":"application/json",
"format":"json",
"matches":[{"path":"fn:doc(\"/test-content-6.json\")/object-node()", "match-text":[]}],
"extracted":{
"kind":"array",
"content":[
{"important":{"foo":1, "bar":2}}
]
}
},
Notice the "extracted" part at the end there -- I get the "important" JSON property, as specified in the options.
For a full list of options you can set to control search, see the Query Options Reference appendix.

Related

How to pass a parameter from the Jupyter backend to a frontend extension

I currently have a value that is stored as an environment variable the environment where a jupyter server is running. I would like to somehow pass that value to a frontend extension. It does not have to read the environment variable in real time, I am fine with just using the value of the variable at startup. Is there a canonical way to pass parameters a frontend extension on startup? Would appreciate an examples of both setting the parameter from the backend and accessing it from the frontend.
[update]
I have posted a solution that works for nbextentions, but I can't seem to find the equivalent pattern for labextensions (typescript), any help there would be much appreciated.
I was able to do this by adding the following code to my jupter_notebook_config.py
from notebook.services.config import ConfigManager
cm = ConfigManager()
cm.update('notebook', {'variable_being_set': value})
Then I had the parameters defined in my extension in my main.js
// define default values for config parameters
var params = {
variable_being_set : 'default'
};
// to be called once config is loaded, this updates default config vals
// with the ones specified by the server's config file
var update_params = function() {
var config = Jupyter.notebook.config;
for (var key in params) {
if (config.data.hasOwnProperty(key) ){
params[key] = config.data[key];
}
}
};
I also have the parameters declared in my main.yaml
Parameters:
- name: variable_being_set
description: ...
input_type: text
default: `default_value`
This took some trial and error to find out because there is very little documentation on the ConfigManager class and none of it has an end-to-end example.

Virtual Filesystem for PHPUnit tests in Laravel 5.4

i'm having a bit of a problem with my PHPUnit integration tests, i have a method which handles a form upload for a video file as well as a preview image for that video.
public function store($request)
{
/** #var Video $resource */
$resource = new $this->model;
// Create a new Content before creating the related Photo
$contentRepo = new ContentRepository();
$content = $contentRepo->store($request);
if($content->isValid()) {
$resource->content_id = $content->id;
$directory = 'frontend/videos/assets/'.date("Y").'/'.date('m').'/'.time();
\File::makeDirectory($directory, 0755, true);
$request->video->move($directory.'/', $request->video->getClientOriginalName());
$resource->video = '/'.$directory.'/'.$request->video->getClientOriginalName();
$request->preview_image->move($directory.'/', $request->preview_image->getClientOriginalName());
$resource->preview_image = '/'.$directory.'/'.$request->preview_image->getClientOriginalName();
$resource->highlighted = intval($request->input('highlighted') == 'on');
$resource->save();
return $resource;
}
else {
return $content;
}
}
The important part to keep is the $request->video->move() call which i probably need to replace in order to use Virtual Filesystem.
and then the test
public function testVideoUpload(){
File::put(__DIR__.'/frontend/videos/assets/image.mp4', 'test');
$file = new UploadedFile(__DIR__.'/frontend/videos/assets/image.mp4', 'foofile.mp4', 'video/mp4', 100023, null, $test=true);
File::put(__DIR__.'/frontend/images/assets/image.jpg', 'test');
$preview = new UploadedFile(__DIR__.'/frontend/images/assets/image.jpg', 'foofile.jpg', 'image/jpeg', 100023, null, $test=true);
$this->post('/admin/videos', [
'title' => 'My Video #12',
'description' => 'This is a description',
'actors' => [$this->actor->id, $this->actor2->id],
'scenes' => [$this->scene->id, $this->scene2->id],
'payment_methods' => [$this->paymentMethod->id],
'video' => $file,
'preview_image' => $preview
])->seeInDatabase('contents', [
'title' => 'My Video #12',
'description' => 'This is a description'
]);
}
As you can see, i need to create a dummy file in some local directory and then use that in the HTTP request to the form's endpoint, then after that, that file would be moved and i need to delete the created folder and the new moved file... it's an authentic mess.
As such i want to use Virtual Filesystem instead, but i have no idea how to set it up in this particular case, i've already downloaded a package and set it up, but the questions are, first, which package have you used/recommend and how would you tweak the class and the test to support the Virtual Filesystem? Would i need to switch over to using the Storage facade instead of the $request->video->move() call? If so how would that be done exactly?
Thank you in advance for your help
I couldn't figure out the VFS system, however i do have somewhat of an alternative that's still kinda messy but gets the job done.
Basically i set up two methods on my PHPUnit base class to setup and teardown the temp folders i need on any test that requires them, because i'm using Database Transactions the files get deleted on every test run and i need to create new dummy files every time i run the test.
So i have two methods setupTempDirectories and teardownTempDirectories which i will call at the beginning and at the end of each test that requires those temporary directories.
I put my temp files in the Storage directory because sometimes i run my tests individually through PHPStorm and the __DIR__ command gets messed up and points to different directories when i do that, i also tried __FILE__ with the same result, so i just resorted to using Laravel's storage_path instead and that works fine.
Then that leaves the problem of my concrete class which tries to move files around and create directories in the public folder for them... so in order to fix that i changed the code to use the Storage facade, then i Mock the Storage facade in my tests
So in my concrete class
$directory = 'frontend/videos/assets/'.date("Y").'/'.date('m').'/'.time();
Storage::makeDirectory($directory, 0755, true);
Storage::move($request->video, $directory . '/' . $request->video->getClientOriginalName());
$resource->video = '/'.$directory.'/'.$request->video->getClientOriginalName();
Storage::move($request->preview_image, $directory . '/' . $request->preview_image->getClientOriginalName());
$resource->preview_image = '/'.$directory.'/'.$request->preview_image->getClientOriginalName();
And then in my test i mock both the makeDirectory and the move methods like such
// Override the Storage facade with a Mock version so that we don't actually try to move files around...
Storage::shouldReceive('makeDirectory')->once()->andReturn(true);
Storage::shouldReceive('move')->twice()->andReturn(true);
That makes my tests work and does not actually leave files behind after it's done...i hope someone has a better solution but for the time being this is what i came up with.
I was actually trying to use VFS but it never worked out... i keep getting errors that the original file in the storage directory is not found even though it's right there...
I'm not even sure the Storage facade was using VFS in the background to begin with even though it should...

How can a part of url be used as variables a controller in Laravel 5.3

Working with Laravel 5.3, I'm trying to pass part of my url into a controller function dynamically with no luck so far. When a link is clicked on http://127.0.0.1:8000 page, the url becomes http://127.0.0.1:8000/politics. How can I pass the politics part into my controller function? Below is what I have in my
web.php routes file
Route::get('/{$category}', 'PostController#category');
PostController.php file
public function category($category)
{
$tag = $category;
$posts = Post::where('tag', '=', '{tag}');
return view('post', compact('posts'));
}
php artisan tinker
Psy Shell v0.8.1 (PHP 5.6.16 ΓÇö cli) by Justin Hileman
>>> $tag = 'politics'
=> "politics"
>>> $post = App\Post::where('tag', '=', '{tag}')->get();
=> Illuminate\Database\Eloquent\Collection {#671
all: [],
}
>>>
So after reviewing the code and putting a small demo together locally there is only one minor reason as to why this is failing.
{varName} variables are already recognised as a variable so you don't need to define it as $varName like you are currently ({$category})
The following code should give you a working example:
Create a controller called DemoController.
Inside this file put the following:
public function demo($category)
{
dd($category);
}
Inside your routes\web.php route file add the following line"
Route::get('/mena/{category}', 'DemoController#demo');
Now, when you go to www.website.com/mena/politics you should see it print politics to the screen.

Meteor : "console" variable undefined inside require call

I'm facing a strange problem on Meteor and I can't resolve it :
I'm developping a WebRTC app using Meteor, PeerJS and AdapterJS (which give an WebRTC plugin for unsupported browser like Safari or IE). These two libs are downloaded using NPM : meteor npm install peerjs/adapterjs
So in my view's controller I have :
view.js
//import Peer from 'peerjs'; => same error with "import"
//import AdapterJS from 'adapterjs';
Template.view.onRendered(function(){
AdapterJS = require("adapterjs");
Peer = require("peerjs");
//var peerkey="..."
var peer = new Peer({
key: peerkey, // get a free key at http://peerjs.com/peerserver
debug: 3,
config: {'iceServers': [
{ url: 'stun:stun.l.google.com:19302' },
{ url: 'stun:stun1.l.google.com:19302' },
]}
});
But when I run my controller, I get an exception because "console" is undefined inside peerjs/util.js function when calling the peerjs constructor :
Uncaught TypeError: Cannot read property 'log' of undefined
Strangly, when I only require "peerjs", there is no exeption...
I tried to change the order of require functions but it won't work.
Other variable like "alert", "window.console" work and are defined inside the module but "console" not.. :/
Any suggestion can help ^^
Thanks in advance.
EDIT : If I add a breakpoint on the first line of node_module/peerjs/lib/util.js, I see that the "console" variable is "undefined" inside util.js but .... it is defined inside the caller function (fileEvaluate) !
EDIT2 : I tried something else to check if the code inside adapterjs redefine or change something : I put 'require("adapterjs")' inside a timeout function with a long delay (10 seconds) and .... console is still undefined inside peer module ! But when I comment require("adapterjs"), no error, console is defined ! I think that Meteor do something special before running the controller script depending on require functions...
EDIT3 : Here is a git repo to test the project : gitlab.com
If you display the dev console, you will see exceptions.
I found a solution, although I don't fully understand it myself. It's something to do with the way that Meteor imports modules, and Peerjs runs afoul of that.
Basically I copied node_modules/peerjs/dist/peer.js into the client directory, so that Meteor will load it "as is".
A small change to main.js as follows:
import './main.html';
// I placed peer.js from the node_modules/peerjs/dist/peer.js into the client folder and it works fine
// import {Peer} from 'peerjs';
import {AdapterJS as Adapter} from 'adapterjs';
Template.hello.onCreated(function helloOnCreated() {
// counter starts at 0
window.peer = new Peer({
and it works fine :)
I see in line 860+ of adapter.js that console is being defined (part of the shim) from https://github.com/Temasys/AdapterJS/blob/master/source/adapter.js
// IE 9 is not offering an implementation of console.log until you open a console
if (typeof console !== 'object' || typeof console.log !== 'function') {
/* jshint -W020 */
console = {} || console;
// Implemented based on console specs from MDN
// You may override these functions
console.log = function (arg) {};
console.info = function (arg) {};
console.error = function (arg) {};
This code defines the console if it doesn't find one to it's liking? Does this mean you are using IE9 or some other incompatible browser?
Try pasting this into your console and see what it tells you:
if (typeof console !== 'object' || typeof console.log !== 'function') alert("Console not present, needs to be shimmed?"); else console.log("console is ok");
Presumably the reason you are using adapter.js is for compatibility purposes - this will help you trouble shoot it. Please let me know what you find, as I will be following you down this path :)

How to list files in folder

How can I list all files inside a folder with Meteor.I have FS collection and cfs:filesystem installed on my app. I didn't find it in the doc.
Another way of doing this is by adding the shelljs npm module.
To add npm modules see: https://github.com/meteorhacks/npm
Then you just need to do something like:
var shell = Meteor.npmRequire('shelljs');
var list = shell.ls('/yourfolder');
Shelljs docs:
https://github.com/arturadib/shelljs
The short answer is that FS.Collection creates a Mongo collection that you can treat like any other, i.e., you can list entries using find().
The long answer...
Using cfs:filesystem, you can create a mongo database that mirrors a given folder on the server, like so:
// in lib/files.js
files = new FS.Collection("my_files", {
stores: [new FS.Store.FileSystem("my_files", {"~/test"})] // creates a ~/test folder at the home directory of your server and will put files there on insert
});
You can then access this collection on the client to upload files to the server to the ~test/ directory:
files.insert(new File(['Test file contents'], 'my_test_file'));
And then you can list the files on the server like so:
files.find(); // returns [ { createdByTransform: true,
_id: 't6NoXZZdx6hmJDEQh',
original:
{ name: 'my_test_file',
updatedAt: (Date)
size: (N),
type: '' },
uploadedAt: (Date),
copies: { my_files: [Object] },
collectionName: 'my_files'
}
The copies object appears to contain the actual names of the files created, e.g.,
files.findOne().copies
{
"my_files" : {
"name" : "testy1",
"type" : "",
"size" : 6,
"key" : "my_files-t6NoXZZdx6hmJDEQh-my_test_file", // This is the name of the file on the server at ~/test/
"updatedAt" : ISODate("2015-03-29T16:53:33Z"),
"createdAt" : ISODate("2015-03-29T16:53:33Z")
}
}
The problem with this approach is that it only tracks the changes made through the Collection; if you add something manually to the ~/test directory, it won't get mirrored into the Collection. For instance, if on the server I run something like...
mkfile 1k ~/test/my_files-aaaaaaaaaa-manually-created
Then I look for it in the collection, it won't be there:
files.findOne({"original.name": {$regex: ".*manually.*"}}) // returns undefined
If you just want a straightforward list of files on the server, you might consider just running an ls. From https://gentlenode.com/journal/meteor-14-execute-a-unix-command/33 you can execute any arbitrary UNIX command using Node's child_process.exec(). You can access the app root directory with process.env.PWD (from this question). So in the end if you wanted to list all the files in your public directory, for instance, you might do something like this:
exec = Npm.require('child_process').exec;
console.log("This is the root dir:");
console.log(process.env.PWD); // running from localhost returns: /Users/me/meteor_apps/test
child = exec('ls -la ' + process.env.PWD + '/public', function(error, stdout, stderr) {
// Fill in this callback with whatever you actually want to do with the information
console.log('stdout: ' + stdout);
console.log('stderr: ' + stderr);
if(error !== null) {
console.log('exec error: ' + error);
}
});
This will have to run on the server, so if you want the information on the client, you'll have to put it in a method. This is also pretty insecure, depending on how you structure it, so you'd want to think about how to stop people from listing all the files and folders on your server, or worse -- running arbitrary execs.
Which method you choose probably depends on what you're really trying to accomplish.

Resources